使用 Gorm 插入和选择 PostGIS 几何
Posted
技术标签:
【中文标题】使用 Gorm 插入和选择 PostGIS 几何【英文标题】:Inserting and selecting PostGIS Geometry with Gorm 【发布时间】:2019-07-03 07:04:09 【问题描述】:我一直在尝试找到一种使用 Golang 插入和检索几何类型的方法,特别是库 gorm。我也在尝试使用库orb,它为几何定义了不同的类型,并提供了不同格式之间的编码/解码。
Orb 已经为每种类型实现了 Scan()
和 Value()
方法。这允许 go 的 Insert()
和 Scan()
函数使用除原语以外的类型。然而,Orb 希望使用以众所周知的二进制 (WKB) 格式表示的几何图形。
orb 文档显示,要完成此操作,您只需将字段包装在 PostGIS 函数 ST_AsBinary()
和 ST_GeomFromWKB()
中,分别用于查询和插入。例如,一个表定义为:
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS orbtest (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
geom geometry(POLYGON, 4326) NOT NULL
);
`)
你可以这样做:
rows, err := db.Query("SELECT id, name, ST_AsBinary(geom) FROM orbtest LIMIT 1")
对于插入(其中 p 是一个 orb.Point):
db.Exec("INSERT INTO orbtest (id, name, geom) VALUES ($1, $2, ST_GeomFromWKB($3))", 1, "Test", wkb.Value(p))
这是我的问题:通过使用 GORM,我无法使用这些函数构建这些查询。 GORM 会自动将值插入到给定结构的数据库中,并将数据扫描到结构的整个层次结构中。那些Scan()
和Value()
方法在幕后被调用,不受我的控制。
尝试直接将二进制数据插入几何列是行不通的,直接查询几何列会得到十六进制的结果。
我尝试了多种数据库方法来解决这个问题。我尝试创建在几何列上自动调用所需函数的视图。这适用于查询,但不适用于插入。
是否可以制作某种触发器或规则来自动调用传入/传出数据所需的函数?
我还应该注意,我正在开发的库完全独立于数据和模式,因此我没有对任何类型的查询进行硬编码的奢侈。我当然可以编写一个扫描整个数据模型并从头开始生成查询的函数,但如果有更好的选择,我会更喜欢。
有没有人知道在 SQL 中进行这项工作的方法?仅通过查询列本身就能够自动调用列上的函数吗?
任何建议将不胜感激。
【问题讨论】:
【参考方案1】:我将@robbieperry22 的答案与不同的编码库一起使用,发现我根本不需要修改字节。
包含的要点供参考。
import "github.com/twpayne/go-geom/encoding/geojson"
type EWKBGeomPoint geom.Point
func (g *EWKBGeomPoint) Scan(input interface) error
gt, err := ewkb.Unmarshal(input.([]byte))
if err != nil
return err
g = gt.(*EWKBGeomPoint)
return nil
func (g EWKBGeomPoint) Value() (driver.Value, error)
b := geom.Point(g)
bp := &b
ewkbPt := ewkb.PointPoint: bp.SetSRID(4326)
return ewkbPt.Value()
type Track struct
gorm.Model
GeometryPoint EWKBGeomPoint `gorm:"column:geom"`
然后在表设置/迁移部分使用了一些自定义:
err = db.Exec(`CREATE TABLE IF NOT EXISTS tracks (
id SERIAL PRIMARY KEY,
geom geometry(POINT, 4326) NOT NULL
);`).Error
if err != nil
return err
err = gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration
ID: "init",
Migrate: func(tx *gorm.DB) error
return tx.CreateTable(
Tables...,
).Error
,
,
ID: "tracks_except_geom",
Migrate: func(tx *gorm.DB) error
return db.AutoMigrate(Track).Error
,
,
).Migrate()
【讨论】:
【参考方案2】:是否有可能制作某种触发器或规则来自动调用传入/传出数据所需的函数?
曾经尝试过 gorm 钩子,例如:
type Example struct
ID int
Name string
Geom ...
func (e *Example) AfterFind() (err error)
e.Geom = ... // Do whatever you like here
return
您可以使用一些hooks。我发现它们非常整洁和有用。
【讨论】:
【参考方案3】:我最终使用的解决方案如下:
首先我创建了包含所有球体类型的新类型,例如:
type Polygon4326 orb.Polygon
type Point4326 orb.Point
然后我在每种类型上实现了Scan()
、Value()
方法。但是,我必须编辑字节并转换为十六进制。当您在 PostGIS 中直接查询空间列时,它将返回 EWKB 的十六进制表示,本质上是 WKB,但包括 4 个字节来表示投影 ID(在我的情况下为 4326)。
在插入之前,我必须添加代表 4326 投影的字节。
在阅读之前,我必须删除这些字节,因为 orb 内置扫描预期的 WKB 格式。
【讨论】:
【参考方案4】:我最终使用的另一个解决方案是go-geos,因为我发现我需要使用 GEOS C 库。这样,我就可以将结构转换为 WKT
以进行插入(因为 postgis 将其作为常规文本接受)并在扫描时从 WKB
转换。
type Geometry4326 *geos.Geometry
// Value converts the given Geometry4326 struct into WKT such that it can be stored in a
// database. Implements Valuer interface for use with database operations.
func (g Geometry4326) Value() (driver.Value, error)
str, err := g.ToWKT()
if err != nil
return nil, err
return "SRID=4326;" + str, nil
// Scan converts the hexadecimal representation of geometry into the given Geometry4326
// struct. Implements Scanner interface for use with database operations.
func (g *Geometry4326) Scan(value interface) error
bytes, ok := value.([]byte)
if !ok
return errors.New("cannot convert database value to geometry")
str := string(bytes)
geom, err := geos.FromHex(str)
if err != nil
return errors.Wrap(err, "cannot get geometry from hex")
geometry := Geometry4326(geom)
*g = geometry
return nil
此解决方案可能并不适合所有人,因为并非所有人都需要使用 GEOS C 库,在 Windows 上工作可能会很痛苦。我确信使用不同的库可以完成同样的事情。
我还在结构上实现了UnmarshalJSON()
和MarshalJSON()
,以便它可以自动Marshal/Unmarshal GeoJSON,然后无缝地从数据库中保存/获取。我使用geojson-go 将GeoJSON 转换为结构体,然后使用geojson-geos-go 将所述结构体转换为我正在使用的go-geos 结构体。是的,有点令人费解,但它确实有效。
【讨论】:
【参考方案5】:对于 gomigrate/v2,上面的代码更新版本有点新,这是我使用的:
func customMigrateTables() error
sqlStatements := []string
`CREATE SCHEMA IF NOT EXISTS "your custom schema"`,
`CREATE EXTENSION IF NOT EXISTS postgis`,
`CREATE TABLE IF NOT EXISTS "your custom table" (
id SERIAL PRIMARY KEY,
geom geometry(GEOMETRY, 4326) NOT NULL
);`,
// needed for some postgres id issue with gorm.
`ALTER TABLE IF EXISTS dook.findings ALTER COLUMN "id" TYPE bigint`,
for _, stm := range sqlStatements
err := DB.Exec(stm).Error
if err != nil
log.Fatal(err)
err := gormigrate.New(DB, gormigrate.DefaultOptions, []*gormigrate.Migration
ID: "init",
Migrate: func(tx *gorm.DB) error
// your normal tables to be migrated.
return tx.AutoMigrate(&Note)
,
,
ID: "findings_except_geom",
Migrate: func(tx *gorm.DB) error
return tx.AutoMigrate(Finding)
,
,
).Migrate()
return err
【讨论】:
以上是关于使用 Gorm 插入和选择 PostGIS 几何的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Sequelize ORM 中插入 PostGIS GEOMETRY 点?