使用Gorm和MySQL处理空间数据
我参考了关于支持PostGIS的空间数据类型的内容。我正在使用MySQL并尝试为自定义数据类型使用Gorm和MySQL处理空间数据,mysql,go,spatial,go-gorm,Mysql,Go,Spatial,Go Gorm,我参考了关于支持PostGIS的空间数据类型的内容。我正在使用MySQL并尝试为自定义数据类型EWKBGeomPoint实现Value() 我的Gorm模型: import ( "github.com/twpayne/go-geom" "github.com/twpayne/go-geom/encoding/ewkb" ) type EWKBGeomPoint geom.Point type Tag struct { Name string `json:"name"`
EWKBGeomPoint
实现Value()
我的Gorm模型:
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)'))
或
如果我使用自己的Value()
来满足database/sql
的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_geomefromtext()
在内的整个值在Gorm的单引号中引用,因此它不起作用:
INSERT INTO `tag` (`name`,`loc`) VALUES ('tag name','ST_GeomFromText('POINT(10.000000 20.000000)')');
我如何让它工作
编辑1:
我追踪Gorm代码,最终得到了callback\u create.go
的createCallback
函数。在它内部检查,如果primaryField==nil
且为真,则它进入调用scope.SQLDB().Exec
,然后我无法进一步跟踪
scope.SQL是字符串插入标记(
name,
loc)值(?,)
和scope.SQLVars
打印[标记名{{12[10 20]0}]
。看起来插值发生在这个调用中
这是对数据库/sql
代码的调用吗
编辑2:
发现了一个类似的问题。但我不理解解决方案。更新:这种方法不起作用。
可以在Gorm生成sql之前将列设置为a
例如,插入之前的类似内容:
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
}
这是另一种方法;使用二进制编码
根据这一点,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).Error
if 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) |
+--------+------------------------+
- (4326是存储全球范围内参考数据的最常用空间参考。它是PostGIS空间数据库和GeoJSON标准的默认值。大多数web地图库默认也使用它。)
你能用吗?@Mark我可以,但我绝对不想用。我有一个已经定义了CRUD的REST服务。如果这是我认为应该的方式,那么一旦我定义了模式,就不需要为每个表复制所有端点处理程序,从JSON到JSON的编组/解编组只是一点工作。为什么原始选择LAST\u INSERT\u ID()
不能满足在原始查询中使用auto\u inc的要求?它与连接绑定,因此不会受到其他连接的干扰。GORM是否无法“转义”引号以便您可以使用嵌套引号?无论如何,在大多数(如果不是全部的话)MySQL语法情况下,单引号和双引号是可互换的。这就提供了两层,而不需要任何转义。@我认为嵌套引号不起作用。db函数不应该被引用。关于last\u insert\u id()。谢谢你的提示。:)奇怪的是,在本例中,我的自定义扫描程序是用参数*gorm.SqlExpr调用的。我不知道该怎么办。在查询过程中需要此扫描仪。注释也无助于创建数据。我收到错误1364:字段“loc”没有默认值
。我在添加钩子之前添加了跟踪结果。字段“loc”没有默认值
如果列在db中不为null,但未设置为插入(例如,忽略/未导出),则会发生该错误,并且Gorm似乎需要在模型上设置一个默认值作为属性。在为insert准备值时调用Scan,我不知道为什么,但假设这是为了双重检查。尽管可以调用SetColumn来设置*SqlExpr,但在插入时不使用它。我认为如果字段的原始类型是*SqlExpr,则会使用它。很抱歉,这种方法似乎行不通。另一个想法可能是创造你自己的方言。马克,你知道133行在哪里吗?我正在尝试查找scope.SQLDB().Exec()的定义。我正试图找到那个额外的单引号是在哪里构建的。如果我能弄明白,我希望我能定制它。这是方言的作品吗?非常感谢你的详细解释。结果很好。没有你的帮助,我不可能弄明白。还有一个编码/wekb
,但是值非常接近0,我不知道为什么。我还尝试使用类型转换而不是封装,但是来自扫描接口的缓冲区(src)的长度似乎是错误的。但是你的方法很管用。别担心,希望能有帮助。
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) |
+--------+------------------------+