Inserting and selecting PostGIS Geometry with Gorm

后端 未结 4 1768
星月不相逢
星月不相逢 2021-02-07 15:56

I\'ve been trying to find a way to insert and retrieve geometric types using Golang, and specifically the library gorm. I\'m also attempting to use the library orb that defines

相关标签:
4条回答
  • 2021-02-07 16:29

    The solution I ended up using was as follows:

    First I created new types that wrapped all of the orb types, for example:

    type Polygon4326 orb.Polygon
    type Point4326 orb.Point
    

    Then I implemented the Scan(), Value() methods on each type. I had to however edit the bytes and convert to/from hexadecimal. When you directly query on a spatial column in PostGIS, it will return a hexadecimal representation of EWKB, essentially WKB, but including 4 bytes to represent the projection ID (in my case 4326).

    Before inserting, I had to add the bytes that represent the projection of 4326.

    Before reading, I had to strip those bytes, since orb's built in scanning expected WKB format.

    0 讨论(0)
  • 2021-02-07 16:32

    I used @robbieperry22's answer with a different encoding library and found I didn't need to tinker with bytes at all.

    Included gist for reference.

    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"`
    }
    

    And then used a little customization on the table set up/migrate part:

    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()
    
    
    0 讨论(0)
  • 2021-02-07 16:34

    Is it possible to make some sort of trigger or rule that would automatically call the needed functions on the data coming in/out?

    Ever tried gorm hooks, example:

    type Example struct {
        ID   int
        Name string
        Geom ...
    }
    
    func (e *Example) AfterFind() (err error) {
        e.Geom = ... // Do whatever you like here
        return
    }
    

    There is a handful of hooks that you can use. I find them pretty neat and useful.

    0 讨论(0)
  • 2021-02-07 16:40

    Another solution, which I ended up using was with go-geos, as I discovered I needed to use the GEOS C library. With that, I am able to convert the struct into WKT for inserting (as postgis accepts it as regular text) and convert from WKB when scanning.

    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
    }
    

    This solution might not be ideal for everyone as not everyone needs to use the GEOS C library, which can be a pain to get working on windows. I'm sure though that the same thing can be accomplished using different libraries.

    I additionally implemented UnmarshalJSON() and MarshalJSON() on the struct so that it can automatically Marshal/Unmarshal GeoJSON, and then save/get from the database seamlessly. I accomplished this using geojson-go to convert GeoJSON to/from a struct, and then geojson-geos-go to convert said struct into the go-geos struct I was using. A little convoluted, yes, but it works.

    0 讨论(0)
提交回复
热议问题