twpayne / go-geom

Package geom implements efficient geometry types for geospatial applications.
BSD 2-Clause "Simplified" License
852 stars 105 forks source link

Question: dealing with postgis geometry type #177

Closed tbruyelle closed 4 years ago

tbruyelle commented 4 years ago

My geo data are stored in a column of type GEOMETRY in postgis, because it can be a point, a line or a polygon.

Thus I wonder which encoding/wkb type should I use to decode its value when I fetch the data. Indeed for instance it can't be a wkb.Point because it will work only for a geometry of kind point, and I will have an error for other kinds.

Any help would be appreciated :)

twpayne commented 4 years ago

For generic geometry (or geography) types you need to call wkb.Unmarshal directly, which returns a geom.T.

Here is most of a worked example, working on a database called geogtest:

create table test (id int primary key, geog geography);
insert into test (id, geog) values (1, null);
insert into test (id, geog) values (2, 'POINT(1 2)'::geography);
insert into test (id, geog) values (3, 'LINESTRING(3 4, 5 6)'::geography);
    db, err := sql.Open("postgres", "postgres://localhost/geogtest?binary_parameters=yes&sslmode=disable")
    if err != nil {
        return err
    }

    rows, err := db.Query(`
        SELECT id, ST_AsBinary(geog) FROM test ORDER BY id;
    `)
    if err != nil {
        return err
    }
    defer rows.Close()
    for rows.Next() {
        var id int
        var geog []byte
        if err := rows.Scan(&id, &geog); err != nil {
            return err
        }
        var geometry geom.T
        if len(geog) != 0 {
            var err error
            geometry, err = wkb.Unmarshal(geog)
            if err != nil {
                return err
            }
        }
        fmt.Printf("%d\t%+v\n", id, geometry)
    }
    return rows.Err()

Note that we call ST_AsBinary on the GEOGRAPHY column and scan into a []byte which we then unmarshal using wkb.Unmarshal. The len(geog) != 0 is a test against NULL values (which wkb.Unmarshal should probably handle better).

Hope this helps - please follow-up with any questions.

tbruyelle commented 4 years ago

Thank you for the hint, this is helpful.

I was able to create a custom Scanner for that purpose :

type Geom struct {
   geom.T
}

func (g *Geom) Scan(src interface{}) error {
  b, ok := src.([]byte)
  if !ok {
    return errors.Errorf("expected []byte, got %T", src)
  }
  if len(b) == 0 {
    return nil
  }
  var err error
  g.T, err = wkb.Unmarshal(b)
  return err
}

Maybe this is something that could be added in wkb package ?

twpayne commented 4 years ago

Maybe this is something that could be added in wkb package ?

Yes, please do open a PR. It's a very useful addition.

tbruyelle commented 4 years ago

Sure, how should I name this new type ? wkb.Geom or wkb.T ? I think I prefer wkb.Geom since wkb.T won't be an interface like geom.T and this could be confusing.

twpayne commented 4 years ago

I agree with you: wkb.Geom is nicer. It should probably have a Geom() method that returns the wrapped geom.T.

twpayne commented 4 years ago

Fixed by @tbruyelle in #178.