猿问

使用 Gorm 和 MySQL 处理空间数据

我参考了 irbanana关于支持 PostGIS 的空间数据类型的回答。我正在使用 MySQL 并尝试实现Value()自定义数据类型EWKBGeomPoint。


我的戈姆模型:


import (

    "github.com/twpayne/go-geom"

    "github.com/twpayne/go-geom/encoding/ewkb"

)


type EWKBGeomPoint geom.Point


type Tag struct {

    Name string `json:"name"`

json:"siteID"` // forign key

    Loc EWKBGeomPoint `json:"loc"`

}

据我所知,MySQL 支持这样的插入:


INSERT INTO `tag` (`name`,`loc`) VALUES ('tag name',ST_GeomFromText('POINT(10.000000 20.000000)'))

或者


INSERT INTO `tag` (`name`,`loc`) VALUES ('tag name', ST_GeomFromWKB(X'0101000000000000000000F03F000000000000F03F'))

如果我自己做一个Value()来满足database/sql's 的Valuer界面:


func (g EWKBGeomPoint) Value() (driver.Value, error) {

    log.Println("EWKBGeomPoint value called")

    b := geom.Point(g)

    bp := &b


    floatArr := bp.Coords()

    return fmt.Sprintf("ST_GeomFromText('POINT(%f %f)')", floatArr[0], floatArr[1]), nil

}

包括在内的整个值ST_GeomFromText()在 Gorm 的单引号中引用,因此它不起作用:


INSERT INTO `tag` (`name`,`loc`) VALUES ('tag name','ST_GeomFromText('POINT(10.000000 20.000000)')');

我如何使它工作?


编辑1:


我追踪到 Gorm 代码,最终它得到了 tocallback_create.go的createCallback功能。在里面检查if primaryField == nil它是真的,它进入调用scope.SQLDB().Exec然后我没有进一步追踪。


scope.SQL 是字符串INSERT INTO标记(名称,loc) VALUES (?,?)并scope.SQLVars打印[tag name {{1 2 [10 20] 0}}]. 看起来插值发生在这个调用中。


这是调用database/sql代码吗?




炎炎设计
浏览 124回答 2
2回答

叮当猫咪

这是另一种方法;使用二进制编码。根据此文档,MySQL 使用 4 个字节存储几何值以指示 SRID(空间参考 ID),然后是值的 WKB(众所周知的二进制)表示。因此,一个类型可以使用 WKB 编码并在 Value() 和 Scan() 函数中添加和删除四字节前缀。在其他答案中找到的 go-geom 库有一个 WKB 编码包,github.com/twpayne/go-geom/encoding/wkb。例如:type MyPoint struct {    Point wkb.Point}func (m *MyPoint) Value() (driver.Value, error) {    value, err := m.Point.Value()    if err != nil {        return nil, err    }    buf, ok := value.([]byte)    if !ok {        return nil, fmt.Errorf("did not convert value: expected []byte, but was %T", value)    }    mysqlEncoding := make([]byte, 4)    binary.LittleEndian.PutUint32(mysqlEncoding, 4326)    mysqlEncoding = append(mysqlEncoding, buf...)    return mysqlEncoding, err}func (m *MyPoint) Scan(src interface{}) error {    if src == nil {        return nil    }    mysqlEncoding, ok := src.([]byte)    if !ok {        return fmt.Errorf("did not scan: expected []byte but was %T", src)    }    var srid uint32 = binary.LittleEndian.Uint32(mysqlEncoding[0:4])    err := m.Point.Scan(mysqlEncoding[4:])    m.Point.SetSRID(int(srid))    return err}使用 MyPoint 类型定义标签:type Tag struct {    Name string   `gorm:"type:varchar(50);primary_key"`    Loc  *MyPoint `gorm:"column:loc"`}func (t Tag) String() string {    return fmt.Sprintf("%s @ Point(%f, %f)", t.Name, t.Loc.Point.Coords().X(), t.Loc.Point.Coords().Y())}使用以下类型创建标签:tag := &Tag{    Name: "London",    Loc: &MyPoint{        wkb.Point{            geom.NewPoint(geom.XY).MustSetCoords([]float64{0.1275, 51.50722}).SetSRID(4326),        },    },}err = db.Create(&tag).Errorif err != nil {    log.Fatalf("create: %v", err)}MySQL结果:mysql> describe tag;+-------+-------------+------+-----+---------+-------+| Field | Type        | Null | Key | Default | Extra |+-------+-------------+------+-----+---------+-------+| name  | varchar(50) | NO   | PRI | NULL    |       || loc   | geometry    | YES  |     | NULL    |       |+-------+-------------+------+-----+---------+-------+mysql> select name, st_astext(loc) from tag;+--------+------------------------+| name   | st_astext(loc)         |+--------+------------------------+| London | POINT(0.1275 51.50722) |+--------+------------------------+(ArcGIS 说4326 是用于在全世界存储参考数据的最常见的空间参考。它是 PostGIS 空间数据库和 GeoJSON 标准的默认值。它也被默认用于大多数 Web 制图库。)

慕桂英3389331

Hooks可以让你在 Gorm 的 sql 生成之前将列设置为gorm.Expr 。例如,在插入之前是这样的:func (t *Tag) BeforeCreate(scope *gorm.Scope) error {  x, y := .... // tag.Loc coordinates  text := fmt.Sprintf("POINT(%f %f)", x, y)  expr := gorm.Expr("ST_GeomFromText(?)", text)  scope.SetColumn("loc", expr)  return nil}
随时随地看视频慕课网APP

相关分类

Go
我要回答