使用Gorm插入和选择PostGIS几何

jchrr9hc  于 2023-09-28  发布在  Go
关注(0)|答案(6)|浏览(200)

我一直在尝试找到一种使用Golang插入和检索几何类型的方法,特别是gorm库。我还尝试使用orb库,它为几何定义了不同的类型,并提供了不同格式之间的编码/解码。
Orb已经为每种类型实现了Scan()Value()方法。这使得go的Insert()Scan()函数可以处理原语以外的类型。然而,Orb期望使用以众所周知的二进制(WKB)格式表示的几何体。
orb文档显示,要实现这一点,只需将字段 Package 在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")

对于insert(其中p是一个球形点):

db.Exec("INSERT INTO orbtest (id, name, geom) VALUES ($1, $2, ST_GeomFromWKB($3))", 1, "Test", wkb.Value(p))

这是我的问题:通过使用GORM,我没有奢侈的能力用这些函数构建那些查询。GORM会自动将值插入到给定结构的数据库中,并将数据扫描到结构的整个层次结构中。那些Scan()Value()方法是在幕后调用的,不受我的控制。
尝试直接将二进制数据插入几何列是行不通的,直接查询几何列将给予十六进制的结果。
我尝试了多种数据库方法来解决这个问题。我尝试创建自动调用几何列所需函数的视图。这对查询有效,但对插入无效。
有没有可能制定某种触发器或规则,自动调用进出数据所需的函数?
我还应该注意到,我正在使用的库完全独立于数据和模式,所以我没有硬编码任何类型的查询的奢侈。当然,我可以编写一个函数来扫描整个数据模型,并从头开始生成查询,但我更希望有更好的选择。
有没有人知道如何在SQL中实现这一点?能够通过查询列本身来自动调用列上的函数?
如有任何建议,将不胜感激。

kuarbcqp

kuarbcqp1#

我使用了@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"`
}

然后在表的设置/迁移部分使用了一点自定义:

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()
9w11ddsr

9w11ddsr2#

有没有可能制定某种触发器或规则,自动调用进出数据所需的函数?
尝试过gorm hooks,例如:

type Example struct {
    ID   int
    Name string
    Geom ...
}

func (e *Example) AfterFind() (err error) {
    e.Geom = ... // Do whatever you like here
    return
}

有几个hooks可以使用。我发现它们非常整洁和有用。

zqdjd7g9

zqdjd7g93#

我最终使用的解决方案如下:
首先,我创建了一些新的类型来 Package 所有的orb类型,例如:

type Polygon4326 orb.Polygon
type Point4326 orb.Point

然后我在每个类型上实现了Scan()Value()方法。然而,我不得不编辑字节并转换为十六进制。当您直接在PostGIS中查询空间列时,它将返回EWKB的十六进制表示,本质上是WKB,但包括4个字节来表示投影ID(在我的示例中为4326)。
在插入之前,我必须添加表示4326投影的字节。
在阅读之前,我必须剥离这些字节,因为orb内置了扫描预期的WKB格式。

yqkkidmi

yqkkidmi4#

我最终使用的另一个解决方案是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(),以便它可以自动编组/解编组GeoJSON,然后无缝地保存/获取数据库。我使用geojson-go将GeoJSON转换为结构体,然后使用geojson-geos-go将所述结构体转换为我正在使用的go-geos结构体。有点复杂,是的,但它工作。

nukf8bse

nukf8bse5#

你可以这样做:

package utils

import (
    "context"
    "encoding/json"
    "fmt"

    geojson "github.com/paulmach/go.geojson"
    "gorm.io/gorm"
    "gorm.io/gorm/clause"
)

type GeoPoint geojson.Geometry

func (g GeoPoint) GormDataType() string {
    return "geography(Point, 4326)"
}

func (g GeoPoint) GormDBDataType() string {
    return "geometry(Point, 4326)"
}
func (g GeoPoint) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
    if len(g.Point) == 0 { // Sprawdzanie, czy punkt jest pusty
        return clause.Expr{
            SQL: "NULL",
        }
    }

    geoJSONBytes, err := json.Marshal(g)
    if err != nil {
        return clause.Expr{SQL: "NULL"} // Obsłuż błąd
    }

    return clause.Expr{
        SQL:  "ST_SetSRID(ST_GeomFromGeoJSON(?),4326)",
        Vars: []interface{}{string(geoJSONBytes)},
    }
}

func (g *GeoPoint) Scan(input interface{}) error {
    switch value := input.(type) {
    case []byte:
        // Stworzenie pustego obiektu GeoPoint
        *g = GeoPoint{}
    case string:
        geom, err := geojson.UnmarshalGeometry([]byte(value))
        if err != nil {
            return fmt.Errorf("can't unmarshal GeoJSON: %w", err)
        }

        if geom.Type != geojson.GeometryPoint {
            return fmt.Errorf("expected point geometry, got %s", geom.Type)
        }

        // Przypisanie wartości do *g
        *g = GeoPoint(*geom)
    default:
        return fmt.Errorf("can't convert %T to GeoJSON", value)
    }

    return nil
}

在模型描述中:

Position     utils.GeoPoint `gorm:"column:position"`

接下来,设置数据-它将像这样工作(*s.Geometry来自geojson包):

utils.GeoPoint(*s.Geometry)

并且为了接收数据:

result := store.db.Raw(`SELECT id, uuid, ST_AsGeoJSON(position) as position FROM table WHERE deleted_at IS NULL AND something_id = ?`, somethingID).Scan(&models)

(我没有找到通过GORM在特定字段上自动执行ST_AsGeoJSON函数的方法)

7hiiyaii

7hiiyaii6#

对于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

}

相关问题