paulmach / orb

Types and utilities for working with 2d geometry in Golang
MIT License
892 stars 104 forks source link

Add ewkb encoding/decoding support #88

Closed paulmach closed 2 years ago

paulmach commented 2 years ago

This PR adds support for encoding and decoding of extended WKB data.

The API interface looks like, please provide feedback

func Marshal(geom orb.Geometry, srid int, byteOrder ...binary.ByteOrder) ([]byte, error)
func MarshalToHex(geom orb.Geometry, srid int, byteOrder ...binary.ByteOrder) (string, error)
func MustMarshal(geom orb.Geometry, srid int, byteOrder ...binary.ByteOrder) []byte
func MustMarshalToHex(geom orb.Geometry, srid int, byteOrder ...binary.ByteOrder) string
func NewEncoder(w io.Writer) *Encoder
func (e *Encoder) SetByteOrder(bo binary.ByteOrder) *Encoder
func (e *Encoder) SetSRID(srid int) *Encoder
func (e *Encoder) Encode(geom orb.Geometry) error
func Unmarshal(b []byte) (orb.Geometry, int, error)
func NewDecoder(r io.Reader) *Decoder
func (d *Decoder) Decode() (orb.Geometry, int, error)

Inserting geometry into a database

Depending on the database different formats and functions are supported.

PostgreSQL and PostGIS

PostGIS stores geometry as EWKB internally. As a result it can be inserted without a wrapper function.

db.Exec("INSERT INTO geodata(geom) VALUES (ST_GeomFromEWKB($1))", ewkb.Value(coord, 4326))
db.Exec("INSERT INTO geodata(geom) VALUES ($1)", ewkb.Value(coord, 4326))

MySQL/MariaDB

MySQL and MariaDB store geometry data in WKB format with a 4 byte SRID prefix.

coord := orb.Point{1, 2}
// as WKB in hex format
data := wkb.MustMarshalToHex(coord)
db.Exec("INSERT INTO geodata(geom) VALUES (ST_GeomFromWKB(UNHEX(?), 4326))", data)
// relying on the raw encoding
db.Exec("INSERT INTO geodata(geom) VALUES (?)", ewkb.ValuePrefixSRID(coord, 4326))

Reading geometry from a database query

As stated above, different databases supported different formats and functions.

PostgreSQL and PostGIS

When working with PostGIS the raw format is EWKB so the wrapper function is not necessary

// both of these queries return the same data
row := db.QueryRow("SELECT ST_AsEWKB(geom) FROM geodata")
row := db.QueryRow("SELECT geom FROM geodata")
// if you don't need the SRID
p := orb.Point{}
err := row.Scan(ewkb.Scanner(&p))
log.Printf("geom: %v", p)
// if you need the SRID
p := orb.Point{}
gs := ewkb.Scanner(&p)
err := row.Scan(gs)
log.Printf("srid: %v", gs.SRID)
log.Printf("geom: %v", gs.Geometry)
log.Printf("also geom: %v", p)

MySQL/MariaDB

// using the ST_AsBinary function
row := db.QueryRow("SELECT st_srid(geom), ST_AsBinary(geom) FROM geodata")
row.Scan(&srid, ewkb.Scanner(&data))
// relying on the raw encoding
row := db.QueryRow("SELECT geom FROM geodata")
// if you don't need the SRID
p := orb.Point{}
err := row.Scan(ewkb.ScannerPrefixSRID(&p))
log.Printf("geom: %v", p)
// if you need the SRID
p := orb.Point{}
gs := ewkb.ScannerPrefixSRID(&p)
err := row.Scan(gs)
log.Printf("srid: %v", gs.SRID)
log.Printf("geom: %v", gs.Geometry)
paulmach commented 2 years ago

@asmeikal Thank you for testing https://github.com/paulmach/orb/issues/68#issuecomment-1112956837

paulmach commented 2 years ago

This change is included in v0.6.0 https://github.com/paulmach/orb/releases/tag/v0.6.0