【问题标题】:Inserting and selecting PostGIS Geometry with Gorm使用 Gorm 插入和选择 PostGIS 几何
【发布时间】: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 中进行这项工作的方法?仅通过查询列本身就能够自动调用列上的函数吗?

任何建议将不胜感激。

【问题讨论】:

    标签: postgresql go postgis go-gorm wkb


    【解决方案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.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()
    
    

    【讨论】:

      【解决方案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
            

            }

            【讨论】:

              猜你喜欢
              • 2019-08-23
              • 2020-09-25
              • 2018-05-27
              • 1970-01-01
              • 2021-05-04
              • 2020-03-21
              • 2018-07-24
              • 2023-03-11
              • 2013-12-07
              相关资源
              最近更新 更多