使用 Gorm 插入和选择 PostGIS 几何
Inserting and selecting PostGIS Geometry with Gorm
我一直在尝试寻找一种使用 Golang 插入和检索几何类型的方法,特别是定义不同几何类型并提供不同格式之间 encoding/decoding 的库 gorm. I'm also attempting to use the library orb。
Orb 已经为每种类型实现了 Scan()
和 Value()
方法。这允许 go 的 Insert()
和 Scan()
函数可以处理基元以外的类型。然而,Orb 希望使用以众所周知的二进制 (WKB) 格式表示的几何图形。
orb 文档表明,要实现此目的,您应该简单地将字段包装在 PostGIS 函数 ST_AsBinary()
和 ST_GeomFromWKB()
中,以分别进行查询和插入。例如,table 定义为:
_, 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 (, , ST_GeomFromWKB())", 1, "Test", wkb.Value(p))
这是我的问题:通过使用 GORM,我无法使用这些函数构建那些查询。 GORM 会自动将值插入给定结构的数据库,并将数据扫描到结构的整个层次结构中。那些 Scan()
和 Value()
方法是在幕后调用的,不受我的控制。
尝试将二进制数据直接插入几何列是行不通的,直接查询几何列将给出十六进制的结果。
我尝试了多种数据库方法来解决这个问题。我试图创建自动调用几何列上所需函数的视图。这适用于查询,但不适用于插入。
是否可以创建某种触发器或规则来自动调用来自 in/out 的数据所需的函数?
我还应该注意,我正在使用的库完全独立于数据和模式工作,因此我没有硬编码任何查询的奢侈。我当然可以编写一个函数来扫描整个数据模型,并从头开始生成查询,但如果有更好的选择,我会更喜欢。
有谁知道在 SQL 中实现此功能的方法吗?能够通过查询列本身自动调用列上的函数吗?
如有任何建议,我们将不胜感激。
Is it possible to make some sort of trigger or rule that would automatically call the needed functions on the data coming in/out?
曾经尝试过 gorm 钩子,例如:
type Example struct {
ID int
Name string
Geom ...
}
func (e *Example) AfterFind() (err error) {
e.Geom = ... // Do whatever you like here
return
}
您可以使用一些 hooks。我发现它们非常简洁实用。
我最终使用的解决方案如下:
首先,我创建了包含所有 orb 类型的新类型,例如:
type Polygon4326 orb.Polygon
type Point4326 orb.Point
然后我在每个类型上实现了Scan()
、Value()
方法。但是我不得不编辑字节并将 to/from 转换为十六进制。当您直接查询 PostGIS 中的空间列时,它将 return EWKB 的十六进制表示,本质上是 WKB,但包括 4 个字节来表示投影 ID(在我的例子中是 4326)。
在插入之前,我必须添加代表 4326 投影的字节。
在阅读之前,我不得不剥离那些字节,因为 orb 内置扫描预期的 WKB 格式。
我将@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.Point{Point: bp.SetSRID(4326)}
return ewkbPt.Value()
}
type Track struct {
gorm.Model
GeometryPoint EWKBGeomPoint `gorm:"column:geom"`
}
然后对 table 集 up/migrate 部分进行了一些自定义:
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()
我最终使用的另一个解决方案是 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,然后从数据库无缝地 save/get 。我使用 geojson-go 将 GeoJSON to/from 转换为一个结构,然后 geojson-geos-go 将所述结构转换为我使用的 go-geos 结构。有点复杂,是的,但它有效。
for 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
}
我一直在尝试寻找一种使用 Golang 插入和检索几何类型的方法,特别是定义不同几何类型并提供不同格式之间 encoding/decoding 的库 gorm. I'm also attempting to use the library orb。
Orb 已经为每种类型实现了 Scan()
和 Value()
方法。这允许 go 的 Insert()
和 Scan()
函数可以处理基元以外的类型。然而,Orb 希望使用以众所周知的二进制 (WKB) 格式表示的几何图形。
orb 文档表明,要实现此目的,您应该简单地将字段包装在 PostGIS 函数 ST_AsBinary()
和 ST_GeomFromWKB()
中,以分别进行查询和插入。例如,table 定义为:
_, 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 (, , ST_GeomFromWKB())", 1, "Test", wkb.Value(p))
这是我的问题:通过使用 GORM,我无法使用这些函数构建那些查询。 GORM 会自动将值插入给定结构的数据库,并将数据扫描到结构的整个层次结构中。那些 Scan()
和 Value()
方法是在幕后调用的,不受我的控制。
尝试将二进制数据直接插入几何列是行不通的,直接查询几何列将给出十六进制的结果。
我尝试了多种数据库方法来解决这个问题。我试图创建自动调用几何列上所需函数的视图。这适用于查询,但不适用于插入。
是否可以创建某种触发器或规则来自动调用来自 in/out 的数据所需的函数?
我还应该注意,我正在使用的库完全独立于数据和模式工作,因此我没有硬编码任何查询的奢侈。我当然可以编写一个函数来扫描整个数据模型,并从头开始生成查询,但如果有更好的选择,我会更喜欢。
有谁知道在 SQL 中实现此功能的方法吗?能够通过查询列本身自动调用列上的函数吗?
如有任何建议,我们将不胜感激。
Is it possible to make some sort of trigger or rule that would automatically call the needed functions on the data coming in/out?
曾经尝试过 gorm 钩子,例如:
type Example struct {
ID int
Name string
Geom ...
}
func (e *Example) AfterFind() (err error) {
e.Geom = ... // Do whatever you like here
return
}
您可以使用一些 hooks。我发现它们非常简洁实用。
我最终使用的解决方案如下:
首先,我创建了包含所有 orb 类型的新类型,例如:
type Polygon4326 orb.Polygon
type Point4326 orb.Point
然后我在每个类型上实现了Scan()
、Value()
方法。但是我不得不编辑字节并将 to/from 转换为十六进制。当您直接查询 PostGIS 中的空间列时,它将 return EWKB 的十六进制表示,本质上是 WKB,但包括 4 个字节来表示投影 ID(在我的例子中是 4326)。
在插入之前,我必须添加代表 4326 投影的字节。
在阅读之前,我不得不剥离那些字节,因为 orb 内置扫描预期的 WKB 格式。
我将@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.Point{Point: bp.SetSRID(4326)}
return ewkbPt.Value()
}
type Track struct {
gorm.Model
GeometryPoint EWKBGeomPoint `gorm:"column:geom"`
}
然后对 table 集 up/migrate 部分进行了一些自定义:
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()
我最终使用的另一个解决方案是 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,然后从数据库无缝地 save/get 。我使用 geojson-go 将 GeoJSON to/from 转换为一个结构,然后 geojson-geos-go 将所述结构转换为我使用的 go-geos 结构。有点复杂,是的,但它有效。
for 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
}