├── .gitignore ├── AUTHORS ├── Makefile ├── geos ├── geos.h ├── Makefile ├── example_test.go ├── geos_test.go ├── helper.go ├── geos.c ├── wkt_test.go ├── coord.go ├── geos.go ├── wkt.go ├── coordseq_test.go ├── wkb.go ├── types.go ├── wkb_test.go ├── coordseq.go ├── prepared.go ├── geoscapi.py ├── examples.go ├── geom_test.go ├── cwrappers.go └── geom.go ├── .travis.yml ├── CHANGELOG ├── COPYING └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /_site 3 | /tags 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Paul Smith 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | check: 2 | $(MAKE) -C geos check 3 | 4 | fmt: 5 | $(MAKE) -C geos fmt 6 | 7 | install: 8 | go install ./... 9 | -------------------------------------------------------------------------------- /geos/geos.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void gogeos_notice_handler(const char *fmt, ...); 8 | void gogeos_error_handler(const char *fmt, ...); 9 | char *gogeos_get_last_error(void); 10 | GEOSContextHandle_t gogeos_initGEOS(); 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | before_install: 3 | - sudo apt-add-repository -y ppa:ubuntugis/ppa 4 | - sudo apt-get update -qq 5 | - sudo apt-get install -qq pkg-config libgeos-dev 6 | script: go test -v ./... 7 | go: 1.1 8 | notifications: 9 | irc: "chat.freenode.net#gogeos" 10 | email: 11 | - paulsmith@pobox.com 12 | -------------------------------------------------------------------------------- /geos/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: fmt check 2 | 3 | SRCS = coord.go coordseq.go cwrappers.go geom.go geos.c geos.go geos.h helper.go prepared.go types.go wkb.go wkt.go 4 | 5 | all: build 6 | 7 | cwrappers.go: geoscapi.py 8 | python3.3 $< /usr/local/include/geos_c.h > $@ 9 | gofmt -w $@ 10 | 11 | build: $(SRCS) 12 | go build 13 | 14 | check: 15 | go test -v 16 | 17 | fmt: 18 | go fmt 19 | -------------------------------------------------------------------------------- /geos/example_test.go: -------------------------------------------------------------------------------- 1 | package geos_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/paulsmith/gogeos/geos" 7 | ) 8 | 9 | func ExampleGeometry_LineInterpolatePoint() { 10 | line := geos.Must(geos.FromWKT("LINESTRING(25 50, 100 125, 150 190)")) 11 | pt := geos.Must(line.LineInterpolatePoint(0.20)) 12 | fmt.Println(pt) 13 | // Output: POINT (51.5974135047432014 76.5974135047432014) 14 | } 15 | -------------------------------------------------------------------------------- /geos/geos_test.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | ) 7 | 8 | func TestVersion(t *testing.T) { 9 | const re = `3\.3\.\d+-CAPI-1\.7\.\d+$` 10 | version := Version() 11 | matched, err := regexp.MatchString(re, version) 12 | if err != nil { 13 | t.Fatal("Version regex:", err) 14 | } 15 | if !matched { 16 | t.Errorf("Version(): %q didn't match regex \"%s\"", version, re) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /geos/helper.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | // Must is a helper that wraps a call to a function returning (*Geometry, error) 4 | // and panics if the error is non-nil. 5 | func Must(g *Geometry, err error) *Geometry { 6 | if err != nil { 7 | panic(err) 8 | } 9 | return g 10 | } 11 | 12 | // MustCoords is a helper that wraps a call to a function returning ([]Coord, error) 13 | // and panics if the error is non-nil. 14 | func MustCoords(c []Coord, err error) []Coord { 15 | if err != nil { 16 | panic(err) 17 | } 18 | return c 19 | } 20 | -------------------------------------------------------------------------------- /geos/geos.c: -------------------------------------------------------------------------------- 1 | #include "geos.h" 2 | 3 | void gogeos_notice_handler(const char *fmt, ...) { 4 | va_list ap; 5 | va_start(ap, fmt); 6 | fprintf(stderr, "NOTICE: "); 7 | vfprintf(stderr, fmt, ap); 8 | va_end(ap); 9 | } 10 | 11 | #define ERRLEN 256 12 | 13 | char gogeos_last_err[ERRLEN]; 14 | 15 | void gogeos_error_handler(const char *fmt, ...) { 16 | va_list ap; 17 | va_start(ap, fmt); 18 | vsnprintf(gogeos_last_err, (size_t) ERRLEN, fmt, ap); 19 | va_end(ap); 20 | } 21 | 22 | char *gogeos_get_last_error(void) { 23 | return gogeos_last_err; 24 | } 25 | 26 | GEOSContextHandle_t gogeos_initGEOS() { 27 | return initGEOS_r(gogeos_notice_handler, gogeos_error_handler); 28 | } 29 | -------------------------------------------------------------------------------- /geos/wkt_test.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var wktEncoderTests = []struct{ in, out string }{ 8 | {"POINT(-117 33)", "POINT (-117.0000000000000000 33.0000000000000000)"}, 9 | } 10 | 11 | func TestWktEncoder(t *testing.T) { 12 | encoder := newWktEncoder() 13 | decoder := newWktDecoder() 14 | var geom *Geometry 15 | var err error 16 | for _, test := range wktEncoderTests { 17 | geom, err = decoder.decode(test.in) 18 | if err != nil { 19 | t.Errorf("wktDecoder.decode(): %v", err) 20 | } 21 | actual, err := encoder.encode(geom) 22 | if err != nil { 23 | t.Errorf("wktEncoder.encode(): %v", err) 24 | } 25 | if actual != test.out { 26 | t.Errorf("wktEncoder.encode(): want %v, got %v", test.out, actual) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /geos/coord.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Coord represents a coordinate in 3-dimensional space. 8 | type Coord struct { 9 | X, Y, Z float64 10 | } 11 | 12 | // NewCoord is the constructor for a Coord object. 13 | func NewCoord(x, y float64) Coord { 14 | return Coord{x, y, 0} 15 | } 16 | 17 | // String returns a (2d) string representation of a Coord. 18 | func (c Coord) String() string { 19 | return fmt.Sprintf("%f %f", c.X, c.Y) 20 | } 21 | 22 | // coordSlice constructs a slice of Coord objects from a coordinate sequence. 23 | func coordSlice(cs *coordSeq) ([]Coord, error) { 24 | size, err := cs.size() 25 | if err != nil { 26 | return nil, err 27 | } 28 | coords := make([]Coord, size) 29 | for i := 0; i < size; i++ { 30 | x, err := cs.x(i) 31 | if err != nil { 32 | return nil, err 33 | } 34 | y, err := cs.y(i) 35 | if err != nil { 36 | return nil, err 37 | } 38 | coords[i] = Coord{X: x, Y: y} 39 | } 40 | return coords, nil 41 | } 42 | -------------------------------------------------------------------------------- /geos/geos.go: -------------------------------------------------------------------------------- 1 | // Package geos provides support for creating and manipulating spatial data. 2 | // At its core, it relies on the GEOS C library for the implementation of 3 | // spatial operations and geometric algorithms. 4 | package geos 5 | 6 | /* 7 | #cgo LDFLAGS: -lgeos_c 8 | #include "geos.h" 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "fmt" 14 | "sync" 15 | ) 16 | 17 | var ( 18 | // Required for the thread-safe GEOS C API (the "*_r" functions). 19 | handle = C.gogeos_initGEOS() 20 | // Protects the handle from being used concurrently in multiple C threads. 21 | handlemu sync.Mutex 22 | ) 23 | 24 | // XXX: store last error message from handler in a global var (chan?) 25 | 26 | // Version returns the version of the GEOS C API in use. 27 | func Version() string { 28 | return C.GoString(cGEOSversion()) 29 | } 30 | 31 | // Error gets the last error that occured in the GEOS C API as a Go error type. 32 | func Error() error { 33 | return fmt.Errorf("geos: %s", C.GoString(C.gogeos_get_last_error())) 34 | } 35 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | 3 | 0.1.2 / 2014-05-29 4 | * Add LineInterpolatePoint -- returns a point interpolated along a line, ala 5 | similar functionality in PostGIS, useful for geocoding, for example. 6 | 7 | * Allow GEOS version 3.3.9 (in addition to 3.3.8) -- support for GEOS version >= 8 | 3.4.x may be pending in 0.1.3. 9 | 10 | * Add ProjectNormalized -- linear referencing function (thanks, bdon@bdon.org!) 11 | 12 | * Fix race condition -- library should be thread-safe with respect to underlying 13 | GEOS C library. 14 | 15 | * Fix bug with join and cap style constants. 16 | 17 | 0.1.1 / 2013-06-14 18 | * Add BufferWithOpts -- provides user more control over buffering options, 19 | including # of quadrant segments, end cap & join styles, mitre limit, and 20 | single-sidedness. 21 | 22 | * Add OffsetCurve -- computes a new LineString offset from the input LineString 23 | by a given distance. 24 | 25 | * Add WKB encoding/decoding -- geometries can now be constructed from Well-Known 26 | Binary (both raw bytes and hex-encoded), and conversely be output as WKB. 27 | 28 | 0.1.0 / 2013-06-12 29 | * Initial release 30 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Paul Smith 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation files 5 | (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /geos/wkt.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | /* 4 | #include "geos.h" 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "runtime" 10 | "unsafe" 11 | ) 12 | 13 | // Reads the WKT serialization and produces geometries 14 | type wktDecoder struct { 15 | r *C.GEOSWKTReader 16 | } 17 | 18 | // Creates a new WKT decoder, can be nil if initialization in the C API fails 19 | func newWktDecoder() *wktDecoder { 20 | r := cGEOSWKTReader_create() 21 | if r == nil { 22 | return nil 23 | } 24 | d := &wktDecoder{r} 25 | runtime.SetFinalizer(d, (*wktDecoder).destroy) 26 | return d 27 | } 28 | 29 | // decode decodes the WKT string and returns a geometry 30 | func (d *wktDecoder) decode(wkt string) (*Geometry, error) { 31 | cstr := C.CString(wkt) 32 | defer C.free(unsafe.Pointer(cstr)) 33 | g := cGEOSWKTReader_read(d.r, cstr) 34 | if g == nil { 35 | return nil, Error() 36 | } 37 | return geomFromPtr(g), nil 38 | } 39 | 40 | func (d *wktDecoder) destroy() { 41 | // XXX: mutex 42 | cGEOSWKTReader_destroy(d.r) 43 | d.r = nil 44 | } 45 | 46 | type wktEncoder struct { 47 | w *C.GEOSWKTWriter 48 | } 49 | 50 | func newWktEncoder() *wktEncoder { 51 | w := cGEOSWKTWriter_create() 52 | if w == nil { 53 | return nil 54 | } 55 | e := &wktEncoder{w} 56 | runtime.SetFinalizer(e, (*wktEncoder).destroy) 57 | return e 58 | } 59 | 60 | // Encode returns a string that is the geometry encoded as WKT 61 | func (e *wktEncoder) encode(g *Geometry) (string, error) { 62 | cstr := cGEOSWKTWriter_write(e.w, g.g) 63 | if cstr == nil { 64 | return "", Error() 65 | } 66 | return C.GoString(cstr), nil 67 | } 68 | 69 | func (e *wktEncoder) destroy() { 70 | // XXX: mutex 71 | cGEOSWKTWriter_destroy(e.w) 72 | e.w = nil 73 | } 74 | -------------------------------------------------------------------------------- /geos/coordseq_test.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCoordSeq(t *testing.T) { 8 | var test = struct { 9 | size, dims, idx int 10 | x, y, z float64 11 | }{ 12 | 3, 2, 1, 3.14, 0.0, -1000.1, 13 | } 14 | cs := newCoordSeq(test.size, test.dims) 15 | if cs == nil { 16 | t.Errorf("newCoordSeq(): got nil from C API") 17 | } 18 | var err error 19 | if err = cs.setX(test.idx, test.x); err != nil { 20 | t.Errorf("CoordSeq.setX(): %v", err) 21 | } 22 | if err = cs.setY(test.idx, test.y); err != nil { 23 | t.Errorf("CoordSeq.setY(): %v", err) 24 | } 25 | if err = cs.setZ(test.idx, test.z); err != nil { 26 | t.Errorf("CoordSeq.setZ(): %v", err) 27 | } 28 | var val float64 29 | if val, err = cs.x(test.idx); err != nil { 30 | t.Errorf("CoordSeq.x(%v): %v", test.idx, err) 31 | } 32 | if val != test.x { 33 | t.Errorf("CoordSeq.x(%v): want %v, got %v", test.idx, test.x, val) 34 | } 35 | if val, err = cs.y(test.idx); err != nil { 36 | t.Errorf("CoordSeq.y(%v): %v", test.idx, err) 37 | } 38 | if val != test.y { 39 | t.Errorf("CoordSeq.y(%v): want %v, got %v", test.idx, test.y, val) 40 | } 41 | if val, err = cs.z(test.idx); err != nil { 42 | t.Errorf("CoordSeq.z(%v): %v", test.idx, err) 43 | } 44 | if val != test.z { 45 | t.Errorf("CoordSeq.z(%v): want %v, got %v", test.idx, test.z, val) 46 | } 47 | size, err := cs.size() 48 | if err != nil { 49 | t.Fatalf("size(): error: %v", err) 50 | } 51 | if size != test.size { 52 | t.Errorf("CoordSeq.size(): want %v, got %v", test.size, size) 53 | } 54 | dims, err := cs.dims() 55 | if err != nil { 56 | t.Fatalf("dims(): error: %v", err) 57 | } 58 | if dims != test.dims { 59 | t.Errorf("CoordSeq.dims(): want %v, got %v", test.dims, dims) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /geos/wkb.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | /* 4 | #include "geos.h" 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "encoding/hex" 10 | "runtime" 11 | "unsafe" 12 | ) 13 | 14 | type wkbDecoder struct { 15 | r *C.GEOSWKBReader 16 | } 17 | 18 | func newWkbDecoder() *wkbDecoder { 19 | r := cGEOSWKBReader_create() 20 | d := &wkbDecoder{r} 21 | runtime.SetFinalizer(d, (*wkbDecoder).destroy) 22 | return d 23 | } 24 | 25 | func (d *wkbDecoder) destroy() { 26 | // XXX: mutex 27 | cGEOSWKBReader_destroy(d.r) 28 | d.r = nil 29 | } 30 | 31 | func (d *wkbDecoder) decode(wkb []byte) (*Geometry, error) { 32 | var cwkb []C.uchar 33 | for i := range wkb { 34 | cwkb = append(cwkb, C.uchar(wkb[i])) 35 | } 36 | g := cGEOSWKBReader_read(d.r, &cwkb[0], C.size_t(len(wkb))) 37 | if g == nil { 38 | return nil, Error() 39 | } 40 | return geomFromPtr(g), nil 41 | } 42 | 43 | func (d *wkbDecoder) decodeHex(wkbHex string) (*Geometry, error) { 44 | wkb, err := hex.DecodeString(wkbHex) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return d.decode(wkb) 49 | } 50 | 51 | type wkbEncoder struct { 52 | w *C.GEOSWKBWriter 53 | } 54 | 55 | func newWkbEncoder() *wkbEncoder { 56 | w := cGEOSWKBWriter_create() 57 | if w == nil { 58 | return nil 59 | } 60 | e := &wkbEncoder{w} 61 | runtime.SetFinalizer(e, (*wkbEncoder).destroy) 62 | return e 63 | } 64 | 65 | func encodeWkb(e *wkbEncoder, g *Geometry, fn func(*C.GEOSWKBWriter, *C.GEOSGeometry, *C.size_t) *C.uchar) ([]byte, error) { 66 | var size C.size_t 67 | bytes := fn(e.w, g.g, &size) 68 | if bytes == nil { 69 | return nil, Error() 70 | } 71 | ptr := unsafe.Pointer(bytes) 72 | defer C.free(ptr) 73 | l := int(size) 74 | var out []byte 75 | for i := 0; i < l; i++ { 76 | el := unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(C.uchar(0))*uintptr(i)) 77 | out = append(out, byte(*(*C.uchar)(el))) 78 | } 79 | return out, nil 80 | } 81 | 82 | func (e *wkbEncoder) encode(g *Geometry) ([]byte, error) { 83 | return encodeWkb(e, g, cGEOSWKBWriter_write) 84 | } 85 | 86 | func (e *wkbEncoder) encodeHex(g *Geometry) ([]byte, error) { 87 | return encodeWkb(e, g, cGEOSWKBWriter_writeHEX) 88 | } 89 | 90 | func (e *wkbEncoder) destroy() { 91 | // XXX: mutex 92 | cGEOSWKBWriter_destroy(e.w) 93 | e.w = nil 94 | } 95 | -------------------------------------------------------------------------------- /geos/types.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | /* 4 | #include "geos.h" 5 | */ 6 | import "C" 7 | 8 | // GeometryType represents the various geometry types supported by GEOS, and 9 | // correspond to OGC Simple Features geometry types. 10 | type GeometryType C.int 11 | 12 | const ( 13 | // POINT is a 0-dimensional geometric object, a single location is geometric 14 | // space. 15 | POINT GeometryType = C.GEOS_POINT 16 | // LINESTRING is a curve with linear interpolation between points. 17 | LINESTRING GeometryType = C.GEOS_LINESTRING 18 | // LINEARRING is a linestring that is both closed and simple. 19 | LINEARRING GeometryType = C.GEOS_LINEARRING 20 | // POLYGON is a planar surface with 1 exterior boundary and 0 or more 21 | // interior boundaries. 22 | POLYGON GeometryType = C.GEOS_POLYGON 23 | // MULTIPOINT is a 0-dimensional geometry collection, the elements of which 24 | // are restricted to points. 25 | MULTIPOINT GeometryType = C.GEOS_MULTIPOINT 26 | // MULTILINESTRING is a 1-dimensional geometry collection, the elements of 27 | // which are restricted to linestrings. 28 | MULTILINESTRING GeometryType = C.GEOS_MULTILINESTRING 29 | // MULTIPOLYGON is a 2-dimensional geometry collection, the elements of 30 | // which are restricted to polygons. 31 | MULTIPOLYGON GeometryType = C.GEOS_MULTIPOLYGON 32 | // GEOMETRYCOLLECTION is a geometric object that is a collection of some 33 | // number of geometric objects. 34 | GEOMETRYCOLLECTION GeometryType = C.GEOS_GEOMETRYCOLLECTION 35 | ) 36 | 37 | var cGeomTypeIds = map[C.int]GeometryType{ 38 | C.GEOS_POINT: POINT, 39 | C.GEOS_LINESTRING: LINESTRING, 40 | C.GEOS_LINEARRING: LINEARRING, 41 | C.GEOS_POLYGON: POLYGON, 42 | C.GEOS_MULTIPOINT: MULTIPOINT, 43 | C.GEOS_MULTILINESTRING: MULTILINESTRING, 44 | C.GEOS_MULTIPOLYGON: MULTIPOLYGON, 45 | C.GEOS_GEOMETRYCOLLECTION: GEOMETRYCOLLECTION, 46 | } 47 | 48 | var geometryTypes = map[GeometryType]string{ 49 | POINT: "Point", 50 | LINESTRING: "LineString", 51 | LINEARRING: "LinearRing", 52 | POLYGON: "Polygon", 53 | MULTIPOINT: "MultiPoint", 54 | MULTILINESTRING: "MultiLineString", 55 | MULTIPOLYGON: "MultiPolygon", 56 | GEOMETRYCOLLECTION: "GeometryCollection", 57 | } 58 | 59 | func (t GeometryType) String() string { 60 | return geometryTypes[t] 61 | } 62 | -------------------------------------------------------------------------------- /geos/wkb_test.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | var wkbDecoderTests = []struct { 9 | wkb []byte 10 | wkt string 11 | }{ 12 | {[]byte{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 64, 93, 192, 0, 0, 0, 0, 0, 128, 65, 64}, "POINT(-117 35)"}, 13 | } 14 | 15 | func TestWkbDecoderRead(t *testing.T) { 16 | wktDecoder := newWktDecoder() 17 | wkbDecoder := newWkbDecoder() 18 | for i, test := range wkbDecoderTests { 19 | g1 := Must(wkbDecoder.decode(test.wkb)) 20 | g2 := Must(wktDecoder.decode(test.wkt)) 21 | if !mustEqual(g1.Equals(g2)) { 22 | t.Errorf("#%d: should equal! got %v want %v", i, g1, g2) 23 | } 24 | } 25 | } 26 | 27 | var wkbDecoderHexTests = []struct { 28 | hex string 29 | wkt string 30 | }{ 31 | {"01010000000000000000405DC00000000000804140", "POINT(-117 35)"}, 32 | } 33 | 34 | func TestWkbDecoderHexRead(t *testing.T) { 35 | wktDecoder := newWktDecoder() 36 | wkbDecoder := newWkbDecoder() 37 | for i, test := range wkbDecoderHexTests { 38 | g1 := Must(wkbDecoder.decodeHex(test.hex)) 39 | g2 := Must(wktDecoder.decode(test.wkt)) 40 | if !mustEqual(g1.Equals(g2)) { 41 | t.Errorf("#%d: should equal! got %v want %v", i, g1, g2) 42 | } 43 | } 44 | } 45 | 46 | var wkbEncoderTests = []struct { 47 | wkt string 48 | wkb []byte 49 | }{ 50 | {"POINT(-117 35)", []byte{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 64, 93, 192, 0, 0, 0, 0, 0, 128, 65, 64}}, 51 | } 52 | 53 | func TestWkbEncoderEncode(t *testing.T) { 54 | wktDecoder := newWktDecoder() 55 | wkbEncoder := newWkbEncoder() 56 | for i, test := range wkbEncoderTests { 57 | g1 := Must(wktDecoder.decode(test.wkt)) 58 | actual, err := wkbEncoder.encode(g1) 59 | if err != nil { 60 | panic(err) 61 | } 62 | if !bytes.Equal(actual, test.wkb) { 63 | t.Errorf("#%d: want %v got %v", i, test.wkb, actual) 64 | } 65 | } 66 | } 67 | 68 | var wkbEncoderHexTests = []struct { 69 | wkt string 70 | wkb []byte 71 | }{ 72 | {"POINT(-117 35)", []byte("01010000000000000000405DC00000000000804140")}, 73 | } 74 | 75 | func TestWkbEncoderEncodeHex(t *testing.T) { 76 | wktDecoder := newWktDecoder() 77 | wkbEncoder := newWkbEncoder() 78 | for i, test := range wkbEncoderHexTests { 79 | g1 := Must(wktDecoder.decode(test.wkt)) 80 | actual, err := wkbEncoder.encodeHex(g1) 81 | if err != nil { 82 | panic(err) 83 | } 84 | if !bytes.Equal(actual, test.wkb) { 85 | t.Errorf("#%d: want %v got %v", i, string(test.wkb), string(actual)) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /geos/coordseq.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | /* 4 | #include "geos.h" 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "runtime" 10 | ) 11 | 12 | type coordSeq struct { 13 | c *C.GEOSCoordSequence 14 | } 15 | 16 | func newCoordSeq(size, dims int) *coordSeq { 17 | p := cGEOSCoordSeq_create(C.uint(size), C.uint(dims)) 18 | if p == nil { 19 | return nil 20 | } 21 | return coordSeqFromPtr(p) 22 | } 23 | 24 | func coordSeqFromPtr(ptr *C.GEOSCoordSequence) *coordSeq { 25 | cs := &coordSeq{c: ptr} 26 | runtime.SetFinalizer(cs, func(*coordSeq) { 27 | cGEOSCoordSeq_destroy(ptr) 28 | }) 29 | return cs 30 | } 31 | 32 | func coordSeqFromSlice(coords []Coord) (*coordSeq, error) { 33 | // XXX: handle 3-dim 34 | ptr := cGEOSCoordSeq_create(C.uint(len(coords)), C.uint(2)) 35 | if ptr == nil { 36 | return nil, Error() 37 | } 38 | cs := &coordSeq{c: ptr} 39 | for i, c := range coords { 40 | if err := cs.setX(i, c.X); err != nil { 41 | return nil, err 42 | } 43 | if err := cs.setY(i, c.Y); err != nil { 44 | return nil, err 45 | } 46 | } 47 | return cs, nil 48 | } 49 | 50 | func (c *coordSeq) Clone() (*coordSeq, error) { 51 | p := cGEOSCoordSeq_clone(c.c) 52 | if p == nil { 53 | return nil, Error() 54 | } 55 | return coordSeqFromPtr(p), nil 56 | } 57 | 58 | func (c *coordSeq) setX(idx int, val float64) error { 59 | i := cGEOSCoordSeq_setX(c.c, C.uint(idx), C.double(val)) 60 | if i == 0 { 61 | return Error() 62 | } 63 | return nil 64 | } 65 | 66 | func (c *coordSeq) setY(idx int, val float64) error { 67 | i := cGEOSCoordSeq_setY(c.c, C.uint(idx), C.double(val)) 68 | if i == 0 { 69 | return Error() 70 | } 71 | return nil 72 | } 73 | 74 | func (c *coordSeq) setZ(idx int, val float64) error { 75 | i := cGEOSCoordSeq_setZ(c.c, C.uint(idx), C.double(val)) 76 | if i == 0 { 77 | return Error() 78 | } 79 | return nil 80 | } 81 | 82 | func (c *coordSeq) x(idx int) (float64, error) { 83 | var val C.double 84 | i := cGEOSCoordSeq_getX(c.c, C.uint(idx), &val) 85 | if i == 0 { 86 | return 0.0, Error() 87 | } 88 | return float64(val), nil 89 | } 90 | 91 | func (c *coordSeq) y(idx int) (float64, error) { 92 | var val C.double 93 | i := cGEOSCoordSeq_getY(c.c, C.uint(idx), &val) 94 | if i == 0 { 95 | return 0.0, Error() 96 | } 97 | return float64(val), nil 98 | } 99 | 100 | func (c *coordSeq) z(idx int) (float64, error) { 101 | var val C.double 102 | i := cGEOSCoordSeq_getZ(c.c, C.uint(idx), &val) 103 | if i == 0 { 104 | return 0.0, Error() 105 | } 106 | return float64(val), nil 107 | } 108 | 109 | func (c *coordSeq) size() (int, error) { 110 | var val C.uint 111 | i := cGEOSCoordSeq_getSize(c.c, &val) 112 | if i == 0 { 113 | return 0, Error() 114 | } 115 | return int(val), nil 116 | } 117 | 118 | func (c *coordSeq) dims() (int, error) { 119 | var val C.uint 120 | i := cGEOSCoordSeq_getDimensions(c.c, &val) 121 | if i == 0 { 122 | return 0, Error() 123 | } 124 | return int(val), nil 125 | } 126 | -------------------------------------------------------------------------------- /geos/prepared.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | /* 4 | #include "geos.h" 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "errors" 10 | "runtime" 11 | ) 12 | 13 | // PGeometry represents a "prepared geometry", a type of geometry object that is 14 | // optimized for a limited set of operations. 15 | type PGeometry struct { 16 | p *C.GEOSPreparedGeometry 17 | } 18 | 19 | // PrepareGeometry constructs a prepared geometry from a normal geometry object. 20 | func PrepareGeometry(g *Geometry) *PGeometry { 21 | ptr := cGEOSPrepare(g.g) 22 | p := &PGeometry{ptr} 23 | runtime.SetFinalizer(p, (*PGeometry).destroy) 24 | return p 25 | } 26 | 27 | func (p *PGeometry) destroy() { 28 | cGEOSPreparedGeom_destroy(p.p) 29 | p.p = nil 30 | } 31 | 32 | // Prepared geometry binary predicates 33 | 34 | // Contains computes whether the prepared geometry contains the other prepared 35 | // geometry. 36 | func (p *PGeometry) Contains(other *Geometry) (bool, error) { 37 | return p.predicate("contains", cGEOSPreparedContains, other) 38 | } 39 | 40 | // ContainsP computes whether the prepared geometry properly contains the other 41 | // prepared geometry. 42 | func (p *PGeometry) ContainsP(other *Geometry) (bool, error) { 43 | return p.predicate("contains", cGEOSPreparedContainsProperly, other) 44 | } 45 | 46 | // CoveredBy computes whether the prepared geometry is covered by the other 47 | // prepared geometry. 48 | func (p *PGeometry) CoveredBy(other *Geometry) (bool, error) { 49 | return p.predicate("covered by", cGEOSPreparedCoveredBy, other) 50 | } 51 | 52 | // Covers computes whether the prepared geometry covers the other prepared 53 | // geometry. 54 | func (p *PGeometry) Covers(other *Geometry) (bool, error) { 55 | return p.predicate("covers", cGEOSPreparedCovers, other) 56 | } 57 | 58 | // Crosses computes whether the prepared geometry crosses the other prepared 59 | // geometry. 60 | func (p *PGeometry) Crosses(other *Geometry) (bool, error) { 61 | return p.predicate("crosses", cGEOSPreparedCrosses, other) 62 | } 63 | 64 | // Disjoint computes whether the prepared geometry is disjoint from the other 65 | // prepared geometry. 66 | func (p *PGeometry) Disjoint(other *Geometry) (bool, error) { 67 | return p.predicate("disjoint", cGEOSPreparedDisjoint, other) 68 | } 69 | 70 | // Intersects computes whether the prepared geometry intersects the other 71 | // prepared geometry. 72 | func (p *PGeometry) Intersects(other *Geometry) (bool, error) { 73 | return p.predicate("intersects", cGEOSPreparedIntersects, other) 74 | } 75 | 76 | // Overlaps computes whether the prepared geometry overlaps the other 77 | // prepared geometry. 78 | func (p *PGeometry) Overlaps(other *Geometry) (bool, error) { 79 | return p.predicate("overlaps", cGEOSPreparedOverlaps, other) 80 | } 81 | 82 | // Touches computes whether the prepared geometry touches the other 83 | // prepared geometry. 84 | func (p *PGeometry) Touches(other *Geometry) (bool, error) { 85 | return p.predicate("touches", cGEOSPreparedTouches, other) 86 | } 87 | 88 | // Within computes whether the prepared geometry is within the other 89 | // prepared geometry. 90 | func (p *PGeometry) Within(other *Geometry) (bool, error) { 91 | return p.predicate("within", cGEOSPreparedWithin, other) 92 | } 93 | 94 | func (p *PGeometry) predicate(name string, fn func(*C.GEOSPreparedGeometry, *C.GEOSGeometry) C.char, other *Geometry) (bool, error) { 95 | i := fn(p.p, other.g) 96 | if i == 2 { 97 | return false, errors.New("geos: prepared " + name) 98 | } 99 | return i == 1, nil 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gogeos - Go library for spatial data operations and geometric algorithms 2 | ======================================================================== 3 | 4 | [![Build Status](https://travis-ci.org/paulsmith/gogeos.png?branch=master)](https://travis-ci.org/paulsmith/gogeos) 5 | 6 | gogeos is a library for Go that provides operations on spatial data and 7 | geometric algorithms. 8 | 9 | It provides bindings to the [GEOS](http://trac.osgeo.org/geos/) C library. 10 | 11 | Quick start 12 | ----------- 13 | 14 | ```go 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/paulsmith/gogeos/geos" 21 | ) 22 | 23 | func main() { 24 | line, _ := geos.FromWKT("LINESTRING (0 0, 10 10, 20 20)") 25 | buf, _ := line.Buffer(2.5) 26 | fmt.Println(buf) 27 | // Output: POLYGON ((18.2322330470336311 21.7677669529663689, 18.61… 28 | } 29 | ``` 30 | 31 | Overview 32 | -------- 33 | 34 | ### Functionality 35 | 36 | * Binary predicates - intersects, disjoint, etc. 37 | * Topology operations - difference, union, etc. 38 | * Polygonization, line merging, and simplification 39 | * Prepared geometries (for better performance for common binary predicates) 40 | * Validity checking 41 | * DE-9IM 42 | * Geometry info - area, length, distance, etc. 43 | * IO - WKT & WKB read/write 44 | 45 | gogeos is an open source project. 46 | 47 | ### Community 48 | 49 | * [Source code: GitHub project](https://github.com/paulsmith/gogeos) 50 | * [Issues tracker](https://github.com/paulsmith/gogeos/issues) 51 | * [Mailing list: gogeos@googlegroups.com](https://groups.google.com/forum/?fromgroups#!forum/gogeos) 52 | * [IRC: #gogeos on freenode](irc://irc.freenode.net/gogeos) 53 | 54 | Installation 55 | ------------ 56 | 57 | ### Requirements 58 | 59 | * GEOS 3.3.8 or 3.3.9 60 | 61 | GEOS must be installed on your system to build gogeos. 62 | 63 | #### Ubuntu 64 | 65 | ```bash 66 | $ apt-get install libgeos-dev 67 | ``` 68 | 69 | #### OS X - homebrew 70 | 71 | ```bash 72 | $ brew install geos 73 | ``` 74 | 75 | #### From source (all OSes) 76 | 77 | ```bash 78 | $ wget http://download.osgeo.org/geos/geos-3.3.8.tar.bz2 79 | $ tar xvfj geos-3.3.8.tar.bz2 80 | $ cd geos-3.3.8 81 | $ ./configure 82 | $ make 83 | $ sudo make install 84 | ``` 85 | 86 | ### Installing gogeos 87 | 88 | ```bash 89 | $ go get github.com/paulsmith/gogeos/geos 90 | ``` 91 | 92 | Documentation 93 | ------------- 94 | 95 | * [Main gogeos documentation](http://paulsmith.github.io/gogeos/) 96 | * [godoc](http://godoc.org/github.com/paulsmith/gogeos/geos) 97 | 98 | Example 99 | ------- 100 | 101 | Let’s say you have two polygons, A (blue) and B (orange). 102 | 103 | ![](http://paulsmith.github.io/gogeos/img/example2-a-b.png) 104 | 105 | One of the most common things to do with a spatial data library like gogeos is 106 | compute the intersection of two or more geometries. Intersection is just 107 | a method on geometry objects in gogeos, which takes one argument, the other 108 | geometry, and computes the intersection with the receiver. The result is a new 109 | geometry, C (magenta): 110 | 111 | ```go 112 | C := geos.Must(A.Intersection(B)) 113 | ``` 114 | 115 | ![](http://paulsmith.github.io/gogeos/img/example3-intersection.png) 116 | 117 | `geos.Must` is just a convenience function that takes the output of any gogeos 118 | function or method that returns a geometry and an error. It panics if the 119 | error is non-null, otherwise returning the geometry, making it more convenient 120 | to use in single-value contexts. In production code, though, you’ll want to 121 | check the error value. 122 | 123 | *(NB: these graphics weren't produced by gogeos directly - I used the 124 | excellent [draw2d](http://code.google.com/p/draw2d/draw2d) package to render 125 | the output of gogeos functions.)* 126 | 127 | License 128 | ------- 129 | 130 | MIT. See `COPYING`. 131 | 132 | Copyright (c) 2013 Paul Smith 133 | -------------------------------------------------------------------------------- /geos/geoscapi.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from string import Template 3 | from collections import namedtuple 4 | from pycparser import c_parser, c_ast, parse_file 5 | 6 | Func = namedtuple('Func', ('name', 'type', 'args')) 7 | Arg = namedtuple('Arg', ('name', 'type')) 8 | Type = namedtuple('Type', ('ptr', 'name', 'array')) 9 | 10 | class FuncDeclVisitor(c_ast.NodeVisitor): 11 | def __init__(self): 12 | self.funcs = [] 13 | self.reset() 14 | 15 | def reset(self): 16 | self.name = None 17 | self.ptr = '' 18 | self.type = None 19 | self.inargs = False 20 | self.args = [] 21 | self.argname = None 22 | self.array = False 23 | 24 | def visit_Typedef(self, node): 25 | # Prevent func decls in typedefs from being visited 26 | pass 27 | 28 | def visit_FuncDecl(self, node): 29 | self.visit(node.type) 30 | if node.args: 31 | self.inargs = True 32 | self.visit(node.args) 33 | self.funcs.append(Func(self.name, self.type, self.args)) 34 | self.reset() 35 | 36 | def visit_PtrDecl(self, node): 37 | self.ptr += '*' 38 | self.visit(node.type) 39 | 40 | def visit_TypeDecl(self, node): 41 | if node.type.__class__.__name__ == 'Struct': 42 | return 43 | if self.inargs: 44 | self.argname = node.declname 45 | else: 46 | self.name = node.declname 47 | self.visit(node.type) 48 | 49 | def visit_ArrayDecl(self, node): 50 | self.array = True 51 | self.visit(node.type) 52 | 53 | def visit_IdentifierType(self, node): 54 | type_ = Type(self.ptr, ' '.join(node.names), self.array) 55 | if self.inargs: 56 | self.args.append(Arg(self.argname, type_)) 57 | else: 58 | self.type = type_ 59 | self.ptr = '' 60 | self.array = False 61 | 62 | def cgo_func_wrappers(filename): 63 | ast = parse_file(filename, use_cpp=True) 64 | v = FuncDeclVisitor() 65 | v.visit(ast) 66 | 67 | funcnames = {} 68 | threadsafe = [] 69 | 70 | for func in v.funcs: 71 | funcnames[func.name] = func 72 | 73 | for func in v.funcs: 74 | if not func.name.endswith('_r'): 75 | if func.name + '_r' in funcnames: 76 | threadsafe.append(funcnames[func.name + '_r']) 77 | else: 78 | threadsafe.append(func) 79 | 80 | print(""" 81 | package geos 82 | 83 | // Created mechanically from C API header - DO NOT EDIT 84 | 85 | /* 86 | #include 87 | */ 88 | import "C" 89 | 90 | import ( 91 | "unsafe" 92 | )\ 93 | """) 94 | 95 | typemap = { 96 | "unsigned char": "uchar", 97 | "unsigned int": "uint", 98 | } 99 | 100 | identmap = { 101 | "type": "_type", 102 | } 103 | 104 | for func in threadsafe: 105 | def gotype(ctype): 106 | type_ = "C." + typemap.get(ctype.name, ctype.name) 107 | if ctype.ptr: 108 | type_ = ctype.ptr + type_ 109 | if ctype.array: 110 | type_ = '[]' + type_ 111 | return type_ 112 | 113 | def goident(arg, inbody=True): 114 | def voidptr(ctype): 115 | return ctype.ptr and ctype.name == 'void' 116 | 117 | ident = identmap.get(arg.name, arg.name) 118 | if arg.type.array and inbody: 119 | ident = '&' + ident + '[0]' 120 | if voidptr(arg.type) and inbody: 121 | ident = 'unsafe.Pointer(' + ident + ')' 122 | return ident 123 | 124 | # Go function signature 125 | gosig = "func $name($parameters)" 126 | if func.type.name != "void": 127 | gosig += " $result" 128 | gosig += " {" 129 | t = Template(gosig) 130 | params = ", ".join([goident(p, inbody=False) + " " + gotype(p.type) for p in func.args if p.type.name != 'GEOSContextHandle_t']) 131 | result = gotype(func.type) 132 | func_name = "c" + func.name 133 | if func_name.endswith('_r'): 134 | func_name = func_name[:-2] 135 | print(t.substitute(name=func_name, parameters=params, result=result)) 136 | 137 | # Go function body 138 | gobody = """\ 139 | \t${return_stmt}C.$name($args) 140 | } 141 | """ 142 | if func.name.endswith("_r") and func.name != "initGEOS_r": 143 | gobody = """\ 144 | \t${handle_lock}.Lock() 145 | \tdefer ${handle_lock}.Unlock() 146 | """ + gobody 147 | 148 | t = Template(gobody) 149 | args = ", ".join([goident(p) for p in func.args]) 150 | return_stmt = 'return ' if func.type.name != 'void' else '' 151 | print(t.substitute(return_stmt=return_stmt, name=func.name, args=args, handle_lock='handlemu')) 152 | 153 | if __name__ == "__main__": 154 | cgo_func_wrappers(sys.argv[1]) 155 | #from pycparser.c_generator import CGenerator 156 | #ast = parse_file(sys.argv[1], use_cpp=True) 157 | #print(CGenerator().visit(ast)) 158 | -------------------------------------------------------------------------------- /geos/examples.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "image" 8 | "image/color" 9 | "image/png" 10 | "log" 11 | "math" 12 | "os" 13 | 14 | "code.google.com/p/draw2d/draw2d" 15 | 16 | "github.com/paulsmith/gogeos/geos" 17 | ) 18 | 19 | const ( 20 | WIDTH = 512 21 | HEIGHT = 512 22 | PAD = 20 23 | ) 24 | 25 | func main() { 26 | ex1() 27 | ex2() 28 | ex3() 29 | ex4() 30 | ex5() 31 | ex6() 32 | ex7() 33 | ex8() 34 | ex9() 35 | ex10() 36 | } 37 | 38 | func ex1() { 39 | example("example1.png", geos.Must(geos.FromWKT("LINESTRING (0 0, 10 10, 20 20)"))) 40 | } 41 | 42 | var ( 43 | a = geos.Must(geos.FromWKT("POLYGON ((0 3, 2 3, 3 1, 1 0, 0 1.5, 0 3))")) 44 | b = geos.Must(geos.FromWKT("POLYGON ((1 2, 1.5 4, 3.5 4, 4.5 2.5, 1 2))")) 45 | ) 46 | 47 | func ex2() { 48 | a := geos.Must(geos.FromWKT("LINEARRING (0 3, 2 3, 3 1, 1 0, 0 1.5, 0 3)")) 49 | b := geos.Must(geos.FromWKT("LINEARRING (1 2, 1.5 4, 3.5 4, 4.5 2.5, 1 2)")) 50 | example("example2-a-b.png", a, b) 51 | } 52 | 53 | func ex3() { 54 | example("example3-intersection.png", a, b, geos.Must(a.Intersection(b))) 55 | } 56 | 57 | func ex4() { 58 | example("example4-union.png", a, b, geos.Must(a.Union(b))) 59 | } 60 | 61 | func ex5() { 62 | example("example5-difference.png", a, b, geos.Must(a.Difference(b))) 63 | } 64 | 65 | func ex6() { 66 | example("example6-difference.png", a, b, geos.Must(b.Difference(a))) 67 | } 68 | 69 | func ex7() { 70 | example("example7-symdifference.png", geos.Must(a.SymDifference(b))) 71 | } 72 | 73 | func ex8() { 74 | l := geos.Must(geos.FromWKT("LINESTRING (0 2, 6 2, 3 4, 4.5 8, 1 8, 3 0)")) 75 | b := geos.Must(l.Buffer(0.5)) 76 | example("example8-buffer.png", b, l) 77 | } 78 | 79 | func ex9() { 80 | wkt := []string{ 81 | "LINESTRING (2 10, 3 10, 4 9, 5 8)", 82 | "LINESTRING (5 8, 6 6, 6 5)", 83 | "LINESTRING (6 5, 9 6, 10 7, 11 9)", 84 | "LINESTRING (11 9, 11 10, 10 11)", 85 | "LINESTRING (6 5, 3 4, 2 3)", 86 | "LINESTRING (2 3, 1.5 2.5, 1 1, 1 0)", 87 | "LINESTRING (1 0, 0 0, 0 -1, 1 -2)", 88 | "LINESTRING (1 -2, 2 -2, 3 -1, 3 0, 1 0)", 89 | "LINESTRING (6 5, 6 3, 6.5 2.5)", 90 | "LINESTRING (6.5 2.5, 7.5 2, 8.5 1.5)", 91 | "LINESTRING (8.5 1.5, 9 0.5, 10 0.5, 11 1.5)", 92 | } 93 | var linestrings []*geos.Geometry 94 | for i := range wkt { 95 | linestrings = append(linestrings, geos.Must(geos.FromWKT(wkt[i]))) 96 | } 97 | example("example9-unmerged-linestrings.png", linestrings...) 98 | merged := geos.Must(geos.Must(geos.NewCollection(geos.MULTILINESTRING, linestrings...)).LineMerge()) 99 | example("example9-merged-linestrings.png", merged) 100 | } 101 | 102 | func ex10() { 103 | l := geos.Must(geos.FromWKT("LINESTRING (0 2, 6 2, 3 4, 4.5 8, 1 8, 3 0)")) 104 | b := geos.Must(l.ConvexHull()) 105 | example("example10-convex-hull.png", b, l) 106 | } 107 | 108 | func example(filename string, geoms ...*geos.Geometry) { 109 | m, ctxt := newContext(WIDTH, HEIGHT) 110 | ctxt.Clear() 111 | drawGeoms(m, ctxt, geoms...) 112 | saveImageToFile(m, filename) 113 | } 114 | 115 | func test() { 116 | g1 := geos.Must(geos.FromWKT("LINESTRING(0 0, 3 3, 3 2, 4 2, 7 5)")) 117 | g2 := geos.Must(geos.FromWKT("LINESTRING(0 10, 1 0, 0 10, 10 10)")) 118 | g3 := geos.Must(geos.FromWKT("POINT(5 5)")) 119 | g4 := geos.Must(g3.Buffer(1)) 120 | g5 := geos.Must(geos.FromWKT("MULTIPOINT(5 6, 6 6, 3 8, 7 8)")) 121 | example("test.png", g1, g2, g4, g3, g5) 122 | } 123 | 124 | var ( 125 | blue = color.RGBA{0x4C, 0x94, 0xFF, 0xFF} 126 | orange = color.RGBA{0xFF, 0xB8, 0x4C, 0xFF} 127 | magenta = color.RGBA{0xFF, 0x4C, 0xEE, 0xFF} 128 | green = color.RGBA{0x4C, 0xFF, 0x5E, 0xFF} 129 | purple = color.RGBA{0x8B, 0x4C, 0xFF, 0xFF} 130 | ygreen = color.RGBA{0xC1, 0xFF, 0x4C, 0xFF} 131 | red = color.RGBA{0xFF, 0x4C, 0x67, 0xFF} 132 | cyan = color.RGBA{0x4C, 0xFF, 0xE5, 0xFF} 133 | ) 134 | 135 | var colors = []color.Color{ 136 | blue, 137 | orange, 138 | magenta, 139 | green, 140 | purple, 141 | ygreen, 142 | red, 143 | cyan, 144 | } 145 | 146 | func drawGeoms(m image.Image, ctxt draw2d.GraphicContext, geoms ...*geos.Geometry) { 147 | // get envelope and calculate scale fn 148 | var env Envelope 149 | if len(geoms) > 1 { 150 | coll, err := geos.NewCollection(geos.GEOMETRYCOLLECTION, geoms...) 151 | if err != nil { 152 | log.Fatal(err) 153 | } 154 | union, err := coll.UnaryUnion() 155 | if err != nil { 156 | log.Fatal(err) 157 | } 158 | env = envelope(union) 159 | } else { 160 | env = envelope(geoms[0]) 161 | } 162 | scale := func(x, y float64) (float64, float64) { 163 | x = env.Px(x)*(WIDTH-2*PAD) + PAD 164 | y = HEIGHT - (env.Py(y)*(HEIGHT-2*PAD) + PAD) 165 | return x, y 166 | } 167 | 168 | var draw func(geoms ...*geos.Geometry) 169 | 170 | draw = func(geoms ...*geos.Geometry) { 171 | for i, g := range geoms { 172 | // pick color 173 | c := colors[i%len(colors)] 174 | // switch type 175 | _type, err := g.Type() 176 | if err != nil { 177 | log.Fatal(err) 178 | } 179 | switch _type { 180 | case geos.POINT: 181 | drawPoint(ctxt, g, c, 4.0, scale) 182 | case geos.LINESTRING, geos.LINEARRING: 183 | drawLine(ctxt, g, c, 4.0, scale) 184 | case geos.POLYGON: 185 | drawPolygon(ctxt, g, c, darken(c), 3.0, scale) 186 | case geos.MULTIPOINT, geos.MULTILINESTRING, geos.MULTIPOLYGON, geos.GEOMETRYCOLLECTION: 187 | n, err := g.NGeometry() 188 | if err != nil { 189 | log.Fatal(err) 190 | } 191 | var subgeoms []*geos.Geometry 192 | for i := 0; i < n; i++ { 193 | subgeoms = append(subgeoms, geos.Must(g.Geometry(i))) 194 | } 195 | draw(subgeoms...) 196 | default: 197 | log.Fatalf("unknown geometry type %v", _type) 198 | } 199 | } 200 | } 201 | 202 | draw(geoms...) 203 | } 204 | 205 | func darken(c color.Color) color.Color { 206 | return color.RGBA{ 207 | R: (c.(color.RGBA).R & 0xfe) >> 1, 208 | G: (c.(color.RGBA).G & 0xfe) >> 1, 209 | B: (c.(color.RGBA).B & 0xfe) >> 1, 210 | A: c.(color.RGBA).A, 211 | } 212 | } 213 | 214 | type Envelope struct { 215 | Min, Max Point 216 | } 217 | 218 | type Point struct { 219 | X, Y float64 220 | } 221 | 222 | func Env(minx, miny, maxx, maxy float64) Envelope { 223 | return Envelope{Point{minx, miny}, Point{maxx, maxy}} 224 | } 225 | 226 | func (e Envelope) Dx() float64 { 227 | return e.Max.X - e.Min.X 228 | } 229 | 230 | func (e Envelope) Dy() float64 { 231 | return e.Max.Y - e.Min.Y 232 | } 233 | 234 | func (e Envelope) Px(x float64) float64 { 235 | return (x - e.Min.X) / e.Dx() 236 | } 237 | 238 | func (e Envelope) Py(y float64) float64 { 239 | return (y - e.Min.Y) / e.Dy() 240 | } 241 | 242 | func envelope(g *geos.Geometry) Envelope { 243 | env, err := g.Envelope() 244 | if err != nil { 245 | log.Fatal(err) 246 | } 247 | ring, err := env.ExteriorRing() 248 | if err != nil { 249 | log.Fatal(err) 250 | } 251 | cs, err := ring.coordSeq() 252 | if err != nil { 253 | log.Fatal(err) 254 | } 255 | getX := getOrd(cs, (*geos.CoordSeq).GetX) 256 | getY := getOrd(cs, (*geos.CoordSeq).GetY) 257 | return Env(getX(0), getY(0), getX(2), getY(2)) 258 | } 259 | 260 | func getOrd(cs *geos.CoordSeq, fn func(*geos.CoordSeq, int) (float64, error)) func(int) float64 { 261 | return func(idx int) float64 { 262 | ord, err := fn(cs, idx) 263 | if err != nil { 264 | log.Fatal(err) 265 | } 266 | return ord 267 | } 268 | } 269 | 270 | func drawPoint(ctxt draw2d.GraphicContext, g *geos.Geometry, c color.Color, radius float64, scale func(x, y float64) (float64, float64)) { 271 | if c != nil { 272 | ctxt.SetFillColor(c) 273 | } 274 | x, err := g.X() 275 | if err != nil { 276 | log.Fatal(err) 277 | } 278 | y, err := g.Y() 279 | if err != nil { 280 | log.Fatal(err) 281 | } 282 | x, y = scale(x, y) 283 | ctxt.MoveTo(x, y) 284 | ctxt.ArcTo(x, y, radius, radius, 0, 2*math.Pi) 285 | ctxt.Fill() 286 | } 287 | 288 | func drawLine(ctxt draw2d.GraphicContext, g *geos.Geometry, c color.Color, width float64, scale func(x, y float64) (float64, float64)) { 289 | if c != nil { 290 | ctxt.SetStrokeColor(c) 291 | } 292 | if width != 0.0 { 293 | ctxt.SetLineWidth(width) 294 | } 295 | // XXX: should get a [] of points 296 | cs, err := g.coordSeq() 297 | if err != nil { 298 | log.Fatal(err) 299 | } 300 | lineCoordSeq(ctxt, cs, scale) 301 | ctxt.Stroke() 302 | } 303 | 304 | func lineCoordSeq(ctxt draw2d.GraphicContext, cs *geos.CoordSeq, scale func(x, y float64) (float64, float64)) { 305 | n := cs.Size() 306 | if n == 0 { 307 | return 308 | } 309 | // XXX: interface like sql.Scan() and .Error() 310 | getX := getOrd(cs, (*geos.CoordSeq).GetX) 311 | getY := getOrd(cs, (*geos.CoordSeq).GetY) 312 | x, y := getX(0), getY(0) 313 | ctxt.MoveTo(scale(x, y)) 314 | for i := 1; i < n; i++ { 315 | x, y = getX(i), getY(i) 316 | x, y = scale(x, y) 317 | ctxt.LineTo(x, y) 318 | } 319 | } 320 | 321 | func drawPolygon(ctxt draw2d.GraphicContext, g *geos.Geometry, fillColor color.Color, strokeColor color.Color, width float64, scale func(x, y float64) (float64, float64)) { 322 | ctxt.SetFillColor(fillColor) 323 | ctxt.SetStrokeColor(strokeColor) 324 | ctxt.SetLineWidth(width) 325 | // exterior ring 326 | ring := geos.Must(g.ExteriorRing()) 327 | cs, err := ring.coordSeq() 328 | if err != nil { 329 | log.Fatal(err) 330 | } 331 | lineCoordSeq(ctxt, cs, scale) 332 | ctxt.FillStroke() 333 | // interior rings... 334 | } 335 | 336 | func newContext(w, h int) (image.Image, draw2d.GraphicContext) { 337 | m := image.NewRGBA(image.Rect(0, 0, w, h)) 338 | ctxt := draw2d.NewGraphicContext(m) 339 | ctxt.SetFillColor(image.White) 340 | ctxt.SetStrokeColor(image.Black) 341 | return m, ctxt 342 | } 343 | 344 | func saveImageToFile(m image.Image, filename string) { 345 | f, err := os.Create(filename) 346 | if err != nil { 347 | log.Fatal(err) 348 | } 349 | w := bufio.NewWriter(f) 350 | err = png.Encode(w, m) 351 | if err != nil { 352 | log.Fatal(err) 353 | } 354 | err = w.Flush() 355 | if err != nil { 356 | log.Fatal(err) 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /geos/geom_test.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "math" 7 | "testing" 8 | ) 9 | 10 | var geomTypeMethodTests = []struct{ wkt, _type string }{ 11 | {"POINT(-117 33)", "Point"}, 12 | {"LINESTRING(10 10, 20 20)", "LineString"}, 13 | {"POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", "Polygon"}, 14 | } 15 | 16 | func TestGeometryTypeMethod(t *testing.T) { 17 | for _, test := range geomTypeMethodTests { 18 | geom := Must(FromWKT(test.wkt)) 19 | actual, err := geom.Type() 20 | if err != nil { 21 | panic(err) 22 | } 23 | if actual.String() != test._type { 24 | t.Errorf("Geometry.Type(): want %v, got %v", test._type, actual) 25 | } 26 | } 27 | } 28 | 29 | var geomTypeTests = []struct { 30 | _type GeometryType 31 | out string 32 | }{ 33 | {POINT, "Point"}, 34 | {MULTILINESTRING, "MultiLineString"}, 35 | } 36 | 37 | func TestGeometryTypeString(t *testing.T) { 38 | for _, test := range geomTypeTests { 39 | if actual := test._type.String(); actual != test.out { 40 | t.Errorf("GeometryType.String(): want %v, actual %v", test.out, actual) 41 | } 42 | } 43 | } 44 | 45 | func TestGeometryType(t *testing.T) { 46 | g := Must(FromWKT("POINT(10 10)")) 47 | typeID, err := g.Type() 48 | if err != nil { 49 | panic(err) 50 | } 51 | if typeID != POINT { 52 | t.Errorf("Geometry.Type(): wanted %v, got %v", POINT, typeID) 53 | } 54 | } 55 | 56 | func TestGeometryProject(t *testing.T) { 57 | ls := Must(FromWKT("LINESTRING(0 0, 1 1)")) 58 | pt := Must(FromWKT("POINT(0 1)")) 59 | expected := 0.7071067811865476 60 | actual := ls.Project(pt) 61 | if expected != actual { 62 | t.Errorf("Geometry.Project(): want %v, got %v", expected, actual) 63 | } 64 | } 65 | 66 | func TestGeometryInterpolate(t *testing.T) { 67 | g := Must(FromWKT("LINESTRING(0 0, 1 1)")) 68 | pt := Must(g.Interpolate(0.7071067811865476)) 69 | expected := Must(FromWKT("POINT (0.5 0.5)")) 70 | ok, err := pt.Equals(expected) 71 | if err != nil { 72 | panic(err) 73 | } 74 | if !ok { 75 | t.Errorf("Geometry.Interpolate(): want %v, got %v", expected, pt) 76 | } 77 | } 78 | 79 | func mustEqual(ok bool, err error) bool { 80 | if err != nil { 81 | panic(err) 82 | } 83 | return ok 84 | } 85 | 86 | func TestGeometryBuffer(t *testing.T) { 87 | g := Must(FromWKT("POINT(0 0)")) 88 | b := Must(g.Buffer(1.0)) 89 | expected := Must(FromWKT(bufferPoly)) 90 | if !mustEqual(b.EqualsExact(expected, 0.0000001)) { 91 | t.Errorf("Geometry.Buffer(): want %v, got %v", expected, b) 92 | } 93 | } 94 | 95 | const bufferPoly = `POLYGON ((1.0000000000000000 0.0000000000000000, 0.9807852804032304 -0.1950903220161281, 0.9238795325112870 -0.3826834323650894, 0.8314696123025456 -0.5555702330196017, 0.7071067811865481 -0.7071067811865470, 0.5555702330196032 -0.8314696123025447, 0.3826834323650908 -0.9238795325112863, 0.1950903220161296 -0.9807852804032302, 0.0000000000000016 -1.0000000000000000, -0.1950903220161265 -0.9807852804032308, -0.3826834323650878 -0.9238795325112875, -0.5555702330196004 -0.8314696123025465, -0.7071067811865459 -0.7071067811865492, -0.8314696123025438 -0.5555702330196043, -0.9238795325112857 -0.3826834323650923, -0.9807852804032299 -0.1950903220161312, -1.0000000000000000 -0.0000000000000032, -0.9807852804032311 0.1950903220161249, -0.9238795325112882 0.3826834323650864, -0.8314696123025475 0.5555702330195990, -0.7071067811865505 0.7071067811865446, -0.5555702330196060 0.8314696123025428, -0.3826834323650936 0.9238795325112852, -0.1950903220161322 0.9807852804032297, -0.0000000000000037 1.0000000000000000, 0.1950903220161248 0.9807852804032311, 0.3826834323650867 0.9238795325112881, 0.5555702330195996 0.8314696123025469, 0.7071067811865455 0.7071067811865496, 0.8314696123025438 0.5555702330196044, 0.9238795325112859 0.3826834323650920, 0.9807852804032300 0.1950903220161305, 1.0000000000000000 0.0000000000000000))` 96 | 97 | func TestGeometryBufferWithOpts(t *testing.T) { 98 | g := Must(FromWKT("POINT (0 0)")) 99 | opts := BufferOpts{QuadSegs: 8, CapStyle: CapRound, JoinStyle: JoinRound, MitreLimit: 5.0} 100 | b := Must(g.BufferWithOpts(1.0, opts)) 101 | expected := Must(FromWKT(bufferPoly)) 102 | if !mustEqual(b.EqualsExact(expected, 0.000001)) { 103 | t.Errorf("want %v, got %v", expected, b) 104 | } 105 | } 106 | 107 | func TestOffsetCurve(t *testing.T) { 108 | g := Must(FromWKT("LINESTRING (0 10, 5 0, 10 10)")) 109 | opts := BufferOpts{QuadSegs: 8, JoinStyle: JoinRound, MitreLimit: 5.0} 110 | curve := Must(g.OffsetCurve(1.0, opts)) 111 | expected := Must(FromWKT(offsetCurve)) 112 | if !mustEqual(curve.EqualsExact(expected, 0.000001)) { 113 | t.Errorf("want %v, got %v", expected, curve) 114 | } 115 | } 116 | 117 | const offsetCurve = `LINESTRING (0.8944271909999159 10.4472135954999583, 5.0000000000000000 2.2360679774997907, 9.1055728090000834 10.4472135954999583)` 118 | 119 | func reconstructGeom(g *Geometry) *Geometry { 120 | typeID, err := g.Type() 121 | if err != nil { 122 | panic(err) 123 | } 124 | switch typeID { 125 | case POINT: 126 | coords := MustCoords(g.Coords()) 127 | return Must(NewPoint(coords...)) 128 | case LINESTRING: 129 | coords := MustCoords(g.Coords()) 130 | return Must(NewLineString(coords...)) 131 | case LINEARRING: 132 | coords := MustCoords(g.Coords()) 133 | return Must(NewLinearRing(coords...)) 134 | case POLYGON: 135 | shell := Must(g.Shell()) 136 | shellCoords := MustCoords(shell.Coords()) 137 | holes, err := g.Holes() 138 | if err != nil { 139 | panic(err) 140 | } 141 | holesCoords := make([][]Coord, len(holes)) 142 | for i, ring := range holes { 143 | holesCoords[i] = MustCoords(ring.Coords()) 144 | } 145 | return Must(NewPolygon(shellCoords, holesCoords...)) 146 | case MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRYCOLLECTION: 147 | n, err := g.NGeometry() 148 | if err != nil { 149 | panic(err) 150 | } 151 | var geoms []*Geometry 152 | for i := 0; i < n; i++ { 153 | geom := Must(g.Geometry(i)) 154 | geoms = append(geoms, reconstructGeom(geom)) 155 | } 156 | return Must(NewCollection(typeID, geoms...)) 157 | } 158 | return nil 159 | } 160 | 161 | func TestGeomConstructors(t *testing.T) { 162 | const filename = `./testdata/test.wkt` 163 | wkt, err := ioutil.ReadFile(filename) 164 | if err != nil { 165 | panic(err) 166 | } 167 | g1 := Must(FromWKT(string(wkt))) 168 | g2 := reconstructGeom(g1) 169 | if !mustEqual(g1.Equals(g2)) { 170 | t.Errorf("Fine-grained geometry reconstruction failed") 171 | } 172 | } 173 | 174 | func TestArea(t *testing.T) { 175 | g1 := Must(FromWKT("POLYGON((-1 -1, 1 -1, 1 1, -1 1, -1 -1))")) 176 | expected := 4.0 177 | area, err := g1.Area() 178 | if err != nil { 179 | panic(err) 180 | } 181 | if area != expected { 182 | t.Errorf("Area(): want %v got %v", expected, area) 183 | } 184 | } 185 | 186 | func TestLength(t *testing.T) { 187 | g1 := Must(FromWKT("LINESTRING(0 0, 1 1)")) 188 | expected := 1.4142135623730951 189 | l, err := g1.Length() 190 | if err != nil { 191 | panic(err) 192 | } 193 | if l != expected { 194 | t.Errorf("Length(): want %v got %v", expected, l) 195 | } 196 | } 197 | 198 | func TestDistance(t *testing.T) { 199 | g1 := Must(FromWKT("POINT(0 0)")) 200 | g2 := Must(FromWKT("POINT(1 1)")) 201 | expected := math.Sqrt(2) 202 | d, err := g1.Distance(g2) 203 | if err != nil { 204 | panic(err) 205 | } 206 | if d != expected { 207 | t.Errorf("Distance(): want %v got %v", expected, d) 208 | } 209 | } 210 | 211 | func TestLineStringPointFns(t *testing.T) { 212 | ls := Must(FromWKT(`LINESTRING(-117 35, 42 -12, 55 3, 100 100, 0 715)`)) 213 | g1 := Must(FromWKT("POINT(-117 35)")) 214 | g2 := Must(FromWKT("POINT(55 3)")) 215 | g3 := Must(FromWKT("POINT(0 715)")) 216 | if pt := Must(ls.StartPoint()); !mustEqual(pt.Equals(g1)) { 217 | t.Errorf("StartPoint(): should equal %v! got %v", g1, pt) 218 | } 219 | if pt := Must(ls.EndPoint()); !mustEqual(pt.Equals(g3)) { 220 | t.Errorf("EndPoint(): should equal %v! got %v", g3, pt) 221 | } 222 | if pt := Must(ls.Point(2)); !mustEqual(pt.Equals(g2)) { 223 | t.Errorf("Point(n): should equal %v! got %v", g2, pt) 224 | } 225 | } 226 | 227 | func TestCoordDim(t *testing.T) { 228 | tests := []struct { 229 | wkt string 230 | dim int 231 | }{ 232 | {"POINT(1 3)", 2}, 233 | {"POINT(2 4 6)", 3}, 234 | } 235 | for _, test := range tests { 236 | if dim := Must(FromWKT(test.wkt)).CoordDimension(); dim != test.dim { 237 | t.Errorf("CoordDimension(): want %v, got %v", test.dim, dim) 238 | } 239 | } 240 | } 241 | 242 | func TestDimension(t *testing.T) { 243 | tests := []struct { 244 | wkt string 245 | dim int 246 | }{ 247 | {"POINT(-117 35)", 0}, 248 | {"LINESTRING(63 -41, 87 -40)", 1}, 249 | {"POLYGON((0 0, 0 1, 1 1, 0 0))", 2}, 250 | } 251 | 252 | for _, test := range tests { 253 | g := Must(FromWKT(test.wkt)) 254 | if dim := g.Dimension(); dim != test.dim { 255 | t.Errorf("Dimension(): want %v got %v", test.dim, dim) 256 | } 257 | } 258 | } 259 | 260 | func TestNCoordinate(t *testing.T) { 261 | tests := []struct { 262 | wkt string 263 | n int 264 | }{ 265 | {"POINT EMPTY", 0}, 266 | {"POINT(-117 35)", 1}, 267 | {"LINESTRING(-117 35, 0 0, 1 1)", 3}, 268 | {"POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", 5}, 269 | {"POLYGON((0 0, 0 2, 2 2, 2 0, 0 0), (1 1, 1 1.5, 1.5 1.5, 1.5 1, 1 1))", 10}, 270 | } 271 | for _, test := range tests { 272 | n, err := Must(FromWKT(test.wkt)).NCoordinate() 273 | if err != nil { 274 | panic(err) 275 | } 276 | if n != test.n { 277 | t.Errorf("NCoordinate(): want %v, got %v", test.n, n) 278 | } 279 | } 280 | } 281 | 282 | func TestShell(t *testing.T) { 283 | g := Must(FromWKT("POLYGON((0 0, 0 2, 2 2, 2 0, 0 0), (1 1, 1 1.5, 1.5 1.5, 1.5 1, 1 1))")) 284 | ext := Must(g.Shell()) 285 | expected := Must(FromWKT("LINEARRING(0 0, 0 2, 2 2, 2 0, 0 0)")) 286 | if !mustEqual(ext.Equals(expected)) { 287 | t.Errorf("Shell(): should equal! got %v", ext) 288 | } 289 | } 290 | 291 | func TestHoles(t *testing.T) { 292 | poly := Must(FromWKT(`POLYGON((0 0, 0 6, 6 6, 6 0, 0 0), 293 | (1 1, 2 1, 2 2, 1 2, 1 1), 294 | (1 3, 2 3, 2 4, 1 4, 1 3), 295 | (3 2, 4 2, 4 3, 3 3, 3 2))`)) 296 | tests := [][]string{ 297 | { 298 | "LINEARRING(1 1, 2 1, 2 2, 1 2, 1 1)", 299 | "LINEARRING(1 3, 2 3, 2 4, 1 4, 1 3)", 300 | "LINEARRING(3 2, 4 2, 4 3, 3 3, 3 2)", 301 | }, 302 | } 303 | for i, holeWkts := range tests { 304 | holes, err := poly.Holes() 305 | if err != nil { 306 | t.Fatalf("#%d: %v", i, err) 307 | } 308 | if len(holes) != len(holeWkts) { 309 | t.Errorf("#%d: want %d holes, got %d", i, len(holeWkts), len(holes)) 310 | } 311 | for j, wkt := range holeWkts { 312 | ring := Must(FromWKT(wkt)) 313 | if !mustEqual(holes[j].Equals(ring)) { 314 | t.Errorf("#%d: want int ring to equal! got %v", i, holes[j]) 315 | } 316 | } 317 | } 318 | } 319 | 320 | func TestX(t *testing.T) { 321 | g := Must(FromWKT("POINT(-117 35)")) 322 | x, err := g.X() 323 | if err != nil { 324 | panic(err) 325 | } 326 | var expected float64 = -117 327 | if x != expected { 328 | t.Errorf("X(): want %v got %v", expected, x) 329 | } 330 | } 331 | 332 | func TestY(t *testing.T) { 333 | g := Must(FromWKT("POINT(-117 35)")) 334 | y, err := g.Y() 335 | if err != nil { 336 | panic(err) 337 | } 338 | var expected float64 = 35 339 | if y != expected { 340 | t.Errorf("Y(): want %v got %v", expected, y) 341 | } 342 | } 343 | 344 | func TestNPoint(t *testing.T) { 345 | g := Must(FromWKT("LINESTRING(0 0, 1 1)")) 346 | n, err := g.NPoint() 347 | if err != nil { 348 | panic(err) 349 | } 350 | if n != 2 { 351 | t.Errorf("NPoint(): want 2 got %v", n) 352 | } 353 | } 354 | 355 | func mustInt(i int, err error) int { 356 | if err != nil { 357 | panic(err) 358 | } 359 | return i 360 | } 361 | 362 | func TestNormalize(t *testing.T) { 363 | g1 := Must(FromWKT("MULTIPOINT((46 27), (61 79), (92 8), (17 7), (33 44))")) 364 | if err := g1.Normalize(); err != nil { 365 | t.Errorf("Normalize(): error: %v", err) 366 | } 367 | g2 := Must(FromWKT("MULTIPOINT (92 8, 61 79, 46 27, 33 44, 17 7)")) 368 | if !mustEqual(g1.EqualsExact(g2, 0.0)) { 369 | t.Errorf("Normalize(): want %v got %v", g2, g1) 370 | } 371 | } 372 | 373 | func TestGeometry(t *testing.T) { 374 | tests := []struct { 375 | multi, geom string 376 | n int 377 | }{ 378 | {"MULTIPOINT((0 0), (1 1), (2 2))", "POINT(0 0)", 0}, 379 | {"MULTIPOINT((0 0), (1 1), (2 2))", "POINT(1 1)", 1}, 380 | } 381 | for _, test := range tests { 382 | g1 := Must(FromWKT(test.multi)) 383 | g2 := Must(FromWKT(test.geom)) 384 | if g3 := Must(g1.Geometry(test.n)); !mustEqual(g3.Equals(g2)) { 385 | t.Errorf("Geometry(%v): should equal! got %v", test.n, g3) 386 | } 387 | } 388 | } 389 | 390 | func TestNGeometry(t *testing.T) { 391 | tests := []struct { 392 | wkt string 393 | n int 394 | }{ 395 | {"MULTIPOINT((0 0), (1 1), (2 2))", 3}, 396 | } 397 | for _, test := range tests { 398 | g := Must(FromWKT(test.wkt)) 399 | if n := mustInt(g.NGeometry()); n != test.n { 400 | t.Errorf("NGeometry(): want %v got %v", test.n, n) 401 | } 402 | } 403 | } 404 | 405 | func TestSRID(t *testing.T) { 406 | g := Must(FromWKT("POINT(-117 35)")) 407 | if _, err := g.SRID(); err == nil { 408 | t.Errorf("SRID(): should be error on unset SRID") 409 | } 410 | srid := 4326 411 | g.SetSRID(srid) 412 | if actual := mustInt(g.SRID()); actual != srid { 413 | t.Errorf("SRID(): want %v got %v", srid, actual) 414 | } 415 | } 416 | 417 | func TestIsClosed(t *testing.T) { 418 | tests := []struct { 419 | wkt string 420 | _closed bool 421 | }{ 422 | {"LINEARRING(0 0, 1 1, 0 1, 0 0)", true}, 423 | {"LINESTRING(0 0, 1 1, 0 1, 0 0)", true}, 424 | {"LINESTRING(0 0, 1 1, 0 1)", false}, 425 | } 426 | for _, test := range tests { 427 | g := Must(FromWKT(test.wkt)) 428 | if _closed := mustBool(g.IsClosed()); _closed != test._closed { 429 | t.Errorf("IsClosed(): %v - want %v got %v", g, test._closed, _closed) 430 | } 431 | } 432 | } 433 | 434 | func mustBool(b bool, err error) bool { 435 | if err != nil { 436 | panic(err) 437 | } 438 | return b 439 | } 440 | 441 | func TestHasZ(t *testing.T) { 442 | tests := []struct { 443 | wkt string 444 | z bool 445 | }{ 446 | {"POINT(0 0)", false}, 447 | {"POINT(0 0 0)", true}, 448 | } 449 | for _, test := range tests { 450 | g := Must(FromWKT(test.wkt)) 451 | if z := mustBool(g.HasZ()); z != test.z { 452 | t.Errorf("HasZ(): %v - want %v got %v", g, test.z, z) 453 | } 454 | } 455 | } 456 | 457 | //func TestIsRing ... 458 | 459 | //func TestIsSimple ... 460 | 461 | func TestIsEmpty(t *testing.T) { 462 | tests := []struct { 463 | wkt string 464 | empty bool 465 | }{ 466 | {"POINT EMPTY", true}, 467 | {"POINT(-117 35)", false}, 468 | } 469 | for _, test := range tests { 470 | g := Must(FromWKT(test.wkt)) 471 | if empty := mustBool(g.IsEmpty()); empty != test.empty { 472 | t.Errorf("IsEmpty(): %v - want %v got %v", g, test.empty, empty) 473 | } 474 | } 475 | } 476 | 477 | var binaryTopoTests = []struct { 478 | g1, g2, out string 479 | method func(*Geometry, *Geometry) (*Geometry, error) 480 | }{ 481 | { 482 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 483 | "POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))", 484 | "POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))", 485 | (*Geometry).Intersection, 486 | }, 487 | { 488 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 489 | "POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))", 490 | "POLYGON((2 1, 2 0, 0 0, 0 2, 1 2, 1 1, 2 1))", 491 | (*Geometry).Difference, 492 | }, 493 | { 494 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 495 | "POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))", 496 | "MULTIPOLYGON(((2 1, 2 0, 0 0, 0 2, 1 2, 1 1, 2 1)), ((2 1, 2 2, 1 2, 1 3, 3 3, 3 1, 2 1)))", 497 | (*Geometry).SymDifference, 498 | }, 499 | { 500 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 501 | "POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))", 502 | "POLYGON((2 1, 2 0, 0 0, 0 2, 1 2, 1 3, 3 3, 3 1, 2 1))", 503 | (*Geometry).Union, 504 | }, 505 | { 506 | "LINESTRING(0 1, 1 1, 2 2, 3 3, 4 4, 4 5)", 507 | "LINESTRING(1 0, 1 1, 4 4, 5 4)", 508 | "GEOMETRYCOLLECTION (MULTILINESTRING ((1 1, 2 2), (2 2, 3 3), (3 3, 4 4)), MULTILINESTRING EMPTY)", 509 | (*Geometry).SharedPaths, 510 | }, 511 | } 512 | 513 | func TestBinaryTopo(t *testing.T) { 514 | for _, test := range binaryTopoTests { 515 | g1 := Must(FromWKT(test.g1)) 516 | g2 := Must(FromWKT(test.g2)) 517 | expected := Must(FromWKT(test.out)) 518 | if actual := Must(test.method(g1, g2)); !mustEqual(expected.Equals(actual)) { 519 | t.Errorf("%+V(): want %v got %v", test.method, expected, actual) 520 | } 521 | } 522 | } 523 | 524 | func TestSnap(t *testing.T) { 525 | tests := []struct { 526 | g1, g2, out string 527 | tol float64 528 | }{ 529 | { 530 | "POINT(0.05 0.05)", 531 | "POINT(0 0)", 532 | "POINT(0 0)", 533 | 0.1, 534 | }, 535 | } 536 | for _, test := range tests { 537 | g1 := Must(FromWKT(test.g1)) 538 | g2 := Must(FromWKT(test.g2)) 539 | expected := Must(FromWKT(test.out)) 540 | if actual := Must(g1.Snap(g2, test.tol)); !mustEqual(expected.Equals(actual)) { 541 | t.Errorf("Snap(%v): want %v got %v", test.tol, expected, actual) 542 | } 543 | } 544 | } 545 | 546 | var unaryTopoTests = []struct { 547 | g1, out string 548 | method func(*Geometry) (*Geometry, error) 549 | }{ 550 | { 551 | "MULTIPOINT((3 1.5), (3.5 1), (4 1), (5 2), (4 1.5), (3.5 1.5))", 552 | "POLYGON ((3 1, 5 1, 5 2, 3 2, 3 1))", 553 | (*Geometry).Envelope, 554 | }, 555 | { 556 | "POLYGON((1 1, 3 1, 2 2, 3 3, 1 3, 1 1))", 557 | "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))", 558 | (*Geometry).ConvexHull, 559 | }, 560 | /* 561 | { 562 | "POINT(-117 35)", 563 | "GEOMETRYCOLLECTION EMPTY", // XXX can't compare empty geoms for equality 564 | (*Geometry).Boundary, 565 | }, 566 | */ 567 | { 568 | "LINESTRING(0 0, 5 5, 10 0)", 569 | "MULTIPOINT (0 0, 10 0)", 570 | (*Geometry).Boundary, 571 | }, 572 | { 573 | "POLYGON((1 1, 3 1, 2 2, 3 3, 1 3, 1 1))", 574 | "LINESTRING (1 1, 3 1, 2 2, 3 3, 1 3, 1 1)", 575 | (*Geometry).Boundary, 576 | }, 577 | { 578 | "MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)), ((5 5, 15 5, 15 15, 5 15, 5 5)))", 579 | "POLYGON ((10 5, 10 0, 0 0, 0 10, 5 10, 5 15, 15 15, 15 5, 10 5))", 580 | (*Geometry).UnaryUnion, 581 | }, 582 | { 583 | "MULTIPOINT((0 0), (1 1), (0 0), (2 2), (-117 35), (2 2))", 584 | "MULTIPOINT (-117 35, 0 0, 1 1, 2 2)", 585 | (*Geometry).UnaryUnion, 586 | }, 587 | { 588 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 589 | "POINT (0.5 0.5)", 590 | (*Geometry).PointOnSurface, 591 | }, 592 | { 593 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 594 | "POINT (0.5 0.5)", 595 | (*Geometry).Centroid, 596 | }, 597 | { 598 | "MULTILINESTRING((1 5, 3 4, 1 1), (1 1, 2 0, 3 1))", 599 | "LINESTRING (1 5, 3 4, 1 1, 2 0, 3 1)", 600 | (*Geometry).LineMerge, 601 | }, 602 | { 603 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 604 | "MULTIPOINT (0 0, 1 0, 1 1, 0 1)", 605 | (*Geometry).UniquePoints, 606 | }, 607 | } 608 | 609 | func TestUnaryTopo(t *testing.T) { 610 | for _, test := range unaryTopoTests { 611 | g1 := Must(FromWKT(test.g1)) 612 | expected := Must(FromWKT(test.out)) 613 | if actual := Must(test.method(g1)); !mustEqual(actual.EqualsExact(expected, 0.0)) { 614 | t.Errorf("%+V(): want %v got %v", test.method, expected, actual) 615 | } 616 | } 617 | } 618 | 619 | func TestSimplifyMethods(t *testing.T) { 620 | tests := []struct { 621 | g1, out string 622 | tol float64 623 | method func(*Geometry, float64) (*Geometry, error) 624 | }{ 625 | { 626 | "LINESTRING(0 0, 1 1, 0 2, 1 3, 0 4, 1 5)", 627 | "LINESTRING (0 0, 1 5)", 628 | 1.0, 629 | (*Geometry).Simplify, 630 | }, 631 | { 632 | "LINESTRING(0 0, 1 1, 0 2, 1 3, 0 4, 1 5)", 633 | "LINESTRING (0 0, 1 5)", 634 | 1.0, 635 | (*Geometry).SimplifyP, 636 | }, 637 | // XXX: geom that would collapse and testing for validity/simplicity 638 | } 639 | for _, test := range tests { 640 | g1 := Must(FromWKT(test.g1)) 641 | expected := Must(FromWKT(test.out)) 642 | if actual := Must(test.method(g1, test.tol)); !mustEqual(actual.EqualsExact(expected, 0.0)) { 643 | t.Errorf("%+V(): want %v got %v", test.method, expected, actual) 644 | } 645 | } 646 | } 647 | 648 | var binaryPredTests = []struct { 649 | g1, g2 string 650 | pred bool 651 | method func(*Geometry, *Geometry) (bool, error) 652 | }{ 653 | { 654 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 655 | "POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))", 656 | false, 657 | (*Geometry).Disjoint, 658 | }, 659 | { 660 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 661 | "POLYGON((3 3, 5 3, 5 5, 3 5, 3 3))", 662 | true, 663 | (*Geometry).Disjoint, 664 | }, 665 | { 666 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 667 | "LINESTRING(1 2, 3 2)", 668 | true, 669 | (*Geometry).Touches, 670 | }, 671 | { 672 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 673 | "LINESTRING(5 2, 6 2)", 674 | false, 675 | (*Geometry).Touches, 676 | }, 677 | { 678 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 679 | "POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))", 680 | true, 681 | (*Geometry).Intersects, 682 | }, 683 | { 684 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 685 | "POLYGON((3 3, 5 3, 5 5, 3 5, 3 3))", 686 | false, 687 | (*Geometry).Intersects, 688 | }, 689 | { 690 | "LINESTRING(0 0, 10 10)", 691 | "LINESTRING(10 0, 0 10)", 692 | true, 693 | (*Geometry).Crosses, 694 | }, 695 | { 696 | "LINESTRING(0 0, 10 10)", 697 | "LINESTRING(11 0, 11 10)", 698 | false, 699 | (*Geometry).Crosses, 700 | }, 701 | { 702 | "LINESTRING(0 0, 10 10)", 703 | "POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))", 704 | true, 705 | (*Geometry).Crosses, 706 | }, 707 | { 708 | "POINT(3 3)", 709 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 710 | true, 711 | (*Geometry).Within, 712 | }, 713 | { 714 | "POINT(-1 35)", 715 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 716 | false, 717 | (*Geometry).Within, 718 | }, 719 | { 720 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 721 | "POINT(3 3)", 722 | true, 723 | (*Geometry).Contains, 724 | }, 725 | { 726 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 727 | "POINT(-1 35)", 728 | false, 729 | (*Geometry).Contains, 730 | }, 731 | { 732 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 733 | "POLYGON((1 1, 3 1, 3 3, 1 3, 1 1))", 734 | true, 735 | (*Geometry).Overlaps, 736 | }, 737 | { 738 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 739 | "POLYGON((3 3, 5 3, 5 5, 3 5, 3 3))", 740 | false, 741 | (*Geometry).Overlaps, 742 | }, 743 | { 744 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 745 | "POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))", 746 | true, 747 | (*Geometry).Equals, 748 | }, 749 | { 750 | "POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))", 751 | "POLYGON((0 0, 0 2, 2 2.1, 2 0, 0 0))", 752 | false, 753 | (*Geometry).Equals, 754 | }, 755 | { 756 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 757 | "POINT(3 3)", 758 | true, 759 | (*Geometry).Covers, 760 | }, 761 | { 762 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 763 | "POINT(-1 35)", 764 | false, 765 | (*Geometry).Covers, 766 | }, 767 | { 768 | "POINT(3 3)", 769 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 770 | true, 771 | (*Geometry).CoveredBy, 772 | }, 773 | { 774 | "POINT(-1 35)", 775 | "POLYGON((0 0, 6 0, 6 6, 0 6, 0 0))", 776 | false, 777 | (*Geometry).CoveredBy, 778 | }, 779 | } 780 | 781 | func TestBinaryPred(t *testing.T) { 782 | for _, test := range binaryPredTests { 783 | g1 := Must(FromWKT(test.g1)) 784 | g2 := Must(FromWKT(test.g2)) 785 | if actual := mustBool(test.method(g1, g2)); actual != test.pred { 786 | t.Errorf("%+V(): want %v got %v", test.method, test.pred, actual) 787 | } 788 | } 789 | } 790 | 791 | func TestEqualsExact(t *testing.T) { 792 | tests := []struct { 793 | g1, g2 string 794 | tol float64 795 | pred bool 796 | }{ 797 | { 798 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 799 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 800 | 0.0, 801 | true, 802 | }, 803 | { 804 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 805 | "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", 806 | 0.0, 807 | false, 808 | }, 809 | { 810 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 811 | "POLYGON((0.05 0.05, 1.05 0.05, 1.05 1.05, 0.05 1.05, 0.05 0.05))", 812 | 0.1, 813 | true, 814 | }, 815 | { 816 | "POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", 817 | "POLYGON((0.05 0.05, 0.05 1.05, 1.05 1.05, 1.05 0.05, 0.05 0.05))", 818 | 0.1, 819 | false, 820 | }, 821 | } 822 | for _, test := range tests { 823 | g1 := Must(FromWKT(test.g1)) 824 | g2 := Must(FromWKT(test.g2)) 825 | if actual := mustBool(g1.EqualsExact(g2, test.tol)); actual != test.pred { 826 | t.Errorf("EqualsExact(): want %v got %v", test.pred, actual) 827 | } 828 | } 829 | } 830 | 831 | func TestClone(t *testing.T) { 832 | g1 := Must(FromWKT("POINT(-117 35)")) 833 | g2 := Must(g1.Clone()) 834 | if !mustEqual(g1.EqualsExact(g2, 0.0)) { 835 | t.Errorf("Cloned geom should equal! %v != %v", g1, g2) 836 | } 837 | if g1.g == g2.g { 838 | t.Errorf("Cloned geom's C ptrs should not equal!") 839 | } 840 | } 841 | 842 | // Constructors 843 | 844 | var basicConstructorTests = []struct { 845 | coords []Coord 846 | ctor func(...Coord) (*Geometry, error) 847 | err bool 848 | empty bool 849 | }{ 850 | {nil, NewPoint, false, true}, 851 | {[]Coord{NewCoord(-117, 35)}, NewPoint, false, false}, 852 | {[]Coord{NewCoord(-117, 35), NewCoord(0, 0)}, NewPoint, true, false}, 853 | {nil, NewLineString, false, true}, 854 | {[]Coord{NewCoord(0, 0), NewCoord(10, 10), NewCoord(20, 20)}, NewLineString, false, false}, 855 | {nil, NewLinearRing, false, true}, 856 | {[]Coord{NewCoord(0, 0), NewCoord(10, 10), NewCoord(10, 0), NewCoord(0, 0)}, NewLinearRing, false, false}, 857 | {[]Coord{NewCoord(0, 0)}, NewLinearRing, true, false}, 858 | {[]Coord{NewCoord(0, 0), NewCoord(10, 10), NewCoord(0, 0)}, NewLinearRing, true, false}, 859 | } 860 | 861 | func TestConstructors(t *testing.T) { 862 | for i, test := range basicConstructorTests { 863 | geom, err := test.ctor(test.coords...) 864 | if err != nil { 865 | if !test.err { 866 | t.Errorf("#%d: ctor: want no error, got: %v", i, err) 867 | } 868 | continue 869 | } 870 | empty, err := geom.IsEmpty() 871 | if err != nil { 872 | t.Errorf("#%d: empty error: %v", i, err) 873 | continue 874 | } 875 | if empty != test.empty { 876 | t.Errorf("#%d: empty: want %v, got %v", i, test.empty, empty) 877 | } 878 | } 879 | } 880 | 881 | var polygonConstructorTests = []struct { 882 | shell []Coord 883 | holes [][]Coord 884 | err bool 885 | empty bool 886 | }{ 887 | {nil, nil, false, true}, 888 | {[]Coord{NewCoord(0, 0), NewCoord(10, 10), NewCoord(10, 0), NewCoord(0, 0)}, nil, false, false}, 889 | { 890 | []Coord{NewCoord(0, 0), NewCoord(10, 10), NewCoord(10, 0), NewCoord(0, 0)}, 891 | [][]Coord{[]Coord{NewCoord(2, 1), NewCoord(2, 2), NewCoord(3, 1), NewCoord(2, 1)}}, 892 | false, 893 | false, 894 | }, 895 | {[]Coord{NewCoord(0, 0), NewCoord(10, 10), NewCoord(10, 0)}, nil, true, false}, 896 | } 897 | 898 | func TestPolygonConstructor(t *testing.T) { 899 | for i, test := range polygonConstructorTests { 900 | geom, err := NewPolygon(test.shell, test.holes...) 901 | if err != nil { 902 | if !test.err { 903 | t.Errorf("#%d: ctor: want no error, got: %v", i, err) 904 | } 905 | continue 906 | } 907 | empty, err := geom.IsEmpty() 908 | if err != nil { 909 | t.Errorf("#%d: empty error: %v", i, err) 910 | continue 911 | } 912 | if empty != test.empty { 913 | t.Errorf("#%d: empty: want %v, got %v", i, test.empty, empty) 914 | } 915 | } 916 | } 917 | 918 | func TestLineStringLinearRingEqual(t *testing.T) { 919 | line := Must(FromWKT("LINESTRING (0 0, 10 10, 10 0, 0 0)")) 920 | ring := Must(FromWKT("LINEARRING (0 0, 10 10, 10 0, 0 0)")) 921 | if !mustEqual(line.Equals(ring)) { 922 | t.Errorf("expected equal!") 923 | } 924 | } 925 | 926 | var relateTests = []struct { 927 | g1, g2 string 928 | pat string 929 | }{ 930 | { 931 | "POLYGON ((60 160, 220 160, 220 20, 60 20, 60 160))", 932 | "POLYGON ((60 160, 20 200, 260 200, 140 80, 60 160))", 933 | "212101212", 934 | }, 935 | } 936 | 937 | func TestRelate(t *testing.T) { 938 | for i, test := range relateTests { 939 | g1 := Must(FromWKT(test.g1)) 940 | g2 := Must(FromWKT(test.g2)) 941 | pat, err := g1.Relate(g2) 942 | if err != nil { 943 | t.Fatalf("#%d %v", i, err) 944 | } 945 | if pat != test.pat { 946 | t.Errorf("#%d want %v got %v", i, test.pat, pat) 947 | } 948 | } 949 | } 950 | 951 | var relatePatTests = []struct { 952 | g1, g2 string 953 | pat string 954 | relate bool 955 | }{ 956 | { 957 | "POLYGON ((60 160, 220 160, 220 20, 60 20, 60 160))", 958 | "POLYGON ((60 160, 20 200, 260 200, 140 80, 60 160))", 959 | "212101212", 960 | true, 961 | }, 962 | } 963 | 964 | func TestRelatePat(t *testing.T) { 965 | for i, test := range relatePatTests { 966 | g1 := Must(FromWKT(test.g1)) 967 | g2 := Must(FromWKT(test.g2)) 968 | ok, err := g1.RelatePat(g2, test.pat) 969 | if err != nil { 970 | t.Fatalf("#%d %v", i, err) 971 | } 972 | if ok != test.relate { 973 | t.Errorf("#%d want %v got %v", i, test.relate, ok) 974 | } 975 | } 976 | } 977 | 978 | func TestFromWKB(t *testing.T) { 979 | for i, test := range wkbDecoderTests { 980 | g1 := Must(FromWKB(test.wkb)) 981 | g2 := Must(FromWKT(test.wkt)) 982 | if !mustEqual(g1.Equals(g2)) { 983 | t.Errorf("#%d want %v got %v", i, test.wkt, g1.String()) 984 | } 985 | } 986 | } 987 | 988 | func TestFromHex(t *testing.T) { 989 | for i, test := range wkbDecoderHexTests { 990 | g1 := Must(FromHex(test.hex)) 991 | g2 := Must(FromWKT(test.wkt)) 992 | if !mustEqual(g1.Equals(g2)) { 993 | t.Errorf("#%d want %v got %v", i, test.wkt, g1.String()) 994 | } 995 | } 996 | } 997 | 998 | func TestWKB(t *testing.T) { 999 | for i, test := range wkbEncoderTests { 1000 | g := Must(FromWKT(test.wkt)) 1001 | wkb, err := g.WKB() 1002 | if err != nil { 1003 | t.Fatalf("#%d %v", i, err) 1004 | } 1005 | if !bytes.Equal(wkb, test.wkb) { 1006 | t.Errorf("#%d want %v got %v", test.wkb, wkb) 1007 | } 1008 | } 1009 | } 1010 | 1011 | func TestHex(t *testing.T) { 1012 | for i, test := range wkbEncoderHexTests { 1013 | g := Must(FromWKT(test.wkt)) 1014 | hex, err := g.Hex() 1015 | if err != nil { 1016 | t.Fatalf("#%d %v", i, err) 1017 | } 1018 | if !bytes.Equal(hex, test.wkb) { 1019 | t.Errorf("#%d want %v got %v", string(test.wkb), string(hex)) 1020 | } 1021 | } 1022 | } 1023 | 1024 | func TestLineInterpolatePointDistError(t *testing.T) { 1025 | line := Must(FromWKT("LINESTRING(0 0, 10 10)")) 1026 | _, err := line.LineInterpolatePoint(-0.1) 1027 | if err != ErrLineInterpolatePointDist { 1028 | t.Errorf("must not allow negative distance") 1029 | } 1030 | _, err = line.LineInterpolatePoint(1.1) 1031 | if err != ErrLineInterpolatePointDist { 1032 | t.Errorf("must not allow distance greater than 1.0") 1033 | } 1034 | } 1035 | 1036 | func TestLineInterpolatePointTypeError(t *testing.T) { 1037 | pt := Must(FromWKT("POINT(0 0)")) 1038 | _, err := pt.LineInterpolatePoint(0.0) 1039 | if err != ErrLineInterpolatePointType { 1040 | t.Errorf("only permitted on linestrings") 1041 | } 1042 | } 1043 | 1044 | func TestLineInterpolatePoint(t *testing.T) { 1045 | var tests = []struct { 1046 | line string 1047 | dist float64 1048 | pt string 1049 | }{ 1050 | {"LINESTRING(25 50, 75 75, 100 35)", 0.0, "POINT(25 50)"}, 1051 | {"LINESTRING(25 50, 75 75, 100 35)", 1.0, "POINT(100 35)"}, 1052 | {"LINESTRING(25 50, 100 125, 150 190)", 0.2, "POINT (51.5974135047432014 76.5974135047432014)"}, 1053 | } 1054 | for i, test := range tests { 1055 | line := Must(FromWKT(test.line)) 1056 | actual, err := line.LineInterpolatePoint(test.dist) 1057 | if err != nil { 1058 | t.Fatalf("#%d %s", i, err) 1059 | } 1060 | expected := Must(FromWKT(test.pt)) 1061 | if !mustEqual(actual.Equals(expected)) { 1062 | t.Errorf("#%d want %v got %v", i, test.pt, actual.String()) 1063 | } 1064 | } 1065 | } 1066 | -------------------------------------------------------------------------------- /geos/cwrappers.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | // Created mechanically from C API header - DO NOT EDIT 4 | 5 | /* 6 | #include 7 | */ 8 | import "C" 9 | 10 | import ( 11 | "unsafe" 12 | ) 13 | 14 | func cinitGEOS(notice_function C.GEOSMessageHandler, error_function C.GEOSMessageHandler) C.GEOSContextHandle_t { 15 | return C.initGEOS_r(notice_function, error_function) 16 | } 17 | 18 | func cfinishGEOS() { 19 | handlemu.Lock() 20 | defer handlemu.Unlock() 21 | C.finishGEOS_r(handle) 22 | } 23 | 24 | func cGEOSversion() *C.char { 25 | return C.GEOSversion() 26 | } 27 | 28 | func cGEOSGeomFromWKT(wkt *C.char) *C.GEOSGeometry { 29 | handlemu.Lock() 30 | defer handlemu.Unlock() 31 | return C.GEOSGeomFromWKT_r(handle, wkt) 32 | } 33 | 34 | func cGEOSGeomToWKT(g *C.GEOSGeometry) *C.char { 35 | handlemu.Lock() 36 | defer handlemu.Unlock() 37 | return C.GEOSGeomToWKT_r(handle, g) 38 | } 39 | 40 | func cGEOS_getWKBOutputDims() C.int { 41 | handlemu.Lock() 42 | defer handlemu.Unlock() 43 | return C.GEOS_getWKBOutputDims_r(handle) 44 | } 45 | 46 | func cGEOS_setWKBOutputDims(newDims C.int) C.int { 47 | handlemu.Lock() 48 | defer handlemu.Unlock() 49 | return C.GEOS_setWKBOutputDims_r(handle, newDims) 50 | } 51 | 52 | func cGEOS_getWKBByteOrder() C.int { 53 | handlemu.Lock() 54 | defer handlemu.Unlock() 55 | return C.GEOS_getWKBByteOrder_r(handle) 56 | } 57 | 58 | func cGEOS_setWKBByteOrder(byteOrder C.int) C.int { 59 | handlemu.Lock() 60 | defer handlemu.Unlock() 61 | return C.GEOS_setWKBByteOrder_r(handle, byteOrder) 62 | } 63 | 64 | func cGEOSGeomFromWKB_buf(wkb *C.uchar, size C.size_t) *C.GEOSGeometry { 65 | handlemu.Lock() 66 | defer handlemu.Unlock() 67 | return C.GEOSGeomFromWKB_buf_r(handle, wkb, size) 68 | } 69 | 70 | func cGEOSGeomToWKB_buf(g *C.GEOSGeometry, size *C.size_t) *C.uchar { 71 | handlemu.Lock() 72 | defer handlemu.Unlock() 73 | return C.GEOSGeomToWKB_buf_r(handle, g, size) 74 | } 75 | 76 | func cGEOSGeomFromHEX_buf(hex *C.uchar, size C.size_t) *C.GEOSGeometry { 77 | handlemu.Lock() 78 | defer handlemu.Unlock() 79 | return C.GEOSGeomFromHEX_buf_r(handle, hex, size) 80 | } 81 | 82 | func cGEOSGeomToHEX_buf(g *C.GEOSGeometry, size *C.size_t) *C.uchar { 83 | handlemu.Lock() 84 | defer handlemu.Unlock() 85 | return C.GEOSGeomToHEX_buf_r(handle, g, size) 86 | } 87 | 88 | func cGEOSCoordSeq_create(size C.uint, dims C.uint) *C.GEOSCoordSequence { 89 | handlemu.Lock() 90 | defer handlemu.Unlock() 91 | return C.GEOSCoordSeq_create_r(handle, size, dims) 92 | } 93 | 94 | func cGEOSCoordSeq_clone(s *C.GEOSCoordSequence) *C.GEOSCoordSequence { 95 | handlemu.Lock() 96 | defer handlemu.Unlock() 97 | return C.GEOSCoordSeq_clone_r(handle, s) 98 | } 99 | 100 | func cGEOSCoordSeq_destroy(s *C.GEOSCoordSequence) { 101 | handlemu.Lock() 102 | defer handlemu.Unlock() 103 | C.GEOSCoordSeq_destroy_r(handle, s) 104 | } 105 | 106 | func cGEOSCoordSeq_setX(s *C.GEOSCoordSequence, idx C.uint, val C.double) C.int { 107 | handlemu.Lock() 108 | defer handlemu.Unlock() 109 | return C.GEOSCoordSeq_setX_r(handle, s, idx, val) 110 | } 111 | 112 | func cGEOSCoordSeq_setY(s *C.GEOSCoordSequence, idx C.uint, val C.double) C.int { 113 | handlemu.Lock() 114 | defer handlemu.Unlock() 115 | return C.GEOSCoordSeq_setY_r(handle, s, idx, val) 116 | } 117 | 118 | func cGEOSCoordSeq_setZ(s *C.GEOSCoordSequence, idx C.uint, val C.double) C.int { 119 | handlemu.Lock() 120 | defer handlemu.Unlock() 121 | return C.GEOSCoordSeq_setZ_r(handle, s, idx, val) 122 | } 123 | 124 | func cGEOSCoordSeq_setOrdinate(s *C.GEOSCoordSequence, idx C.uint, dim C.uint, val C.double) C.int { 125 | handlemu.Lock() 126 | defer handlemu.Unlock() 127 | return C.GEOSCoordSeq_setOrdinate_r(handle, s, idx, dim, val) 128 | } 129 | 130 | func cGEOSCoordSeq_getX(s *C.GEOSCoordSequence, idx C.uint, val *C.double) C.int { 131 | handlemu.Lock() 132 | defer handlemu.Unlock() 133 | return C.GEOSCoordSeq_getX_r(handle, s, idx, val) 134 | } 135 | 136 | func cGEOSCoordSeq_getY(s *C.GEOSCoordSequence, idx C.uint, val *C.double) C.int { 137 | handlemu.Lock() 138 | defer handlemu.Unlock() 139 | return C.GEOSCoordSeq_getY_r(handle, s, idx, val) 140 | } 141 | 142 | func cGEOSCoordSeq_getZ(s *C.GEOSCoordSequence, idx C.uint, val *C.double) C.int { 143 | handlemu.Lock() 144 | defer handlemu.Unlock() 145 | return C.GEOSCoordSeq_getZ_r(handle, s, idx, val) 146 | } 147 | 148 | func cGEOSCoordSeq_getOrdinate(s *C.GEOSCoordSequence, idx C.uint, dim C.uint, val *C.double) C.int { 149 | handlemu.Lock() 150 | defer handlemu.Unlock() 151 | return C.GEOSCoordSeq_getOrdinate_r(handle, s, idx, dim, val) 152 | } 153 | 154 | func cGEOSCoordSeq_getSize(s *C.GEOSCoordSequence, size *C.uint) C.int { 155 | handlemu.Lock() 156 | defer handlemu.Unlock() 157 | return C.GEOSCoordSeq_getSize_r(handle, s, size) 158 | } 159 | 160 | func cGEOSCoordSeq_getDimensions(s *C.GEOSCoordSequence, dims *C.uint) C.int { 161 | handlemu.Lock() 162 | defer handlemu.Unlock() 163 | return C.GEOSCoordSeq_getDimensions_r(handle, s, dims) 164 | } 165 | 166 | func cGEOSProject(g *C.GEOSGeometry, p *C.GEOSGeometry) C.double { 167 | handlemu.Lock() 168 | defer handlemu.Unlock() 169 | return C.GEOSProject_r(handle, g, p) 170 | } 171 | 172 | func cGEOSInterpolate(g *C.GEOSGeometry, d C.double) *C.GEOSGeometry { 173 | handlemu.Lock() 174 | defer handlemu.Unlock() 175 | return C.GEOSInterpolate_r(handle, g, d) 176 | } 177 | 178 | func cGEOSProjectNormalized(g *C.GEOSGeometry, p *C.GEOSGeometry) C.double { 179 | handlemu.Lock() 180 | defer handlemu.Unlock() 181 | return C.GEOSProjectNormalized_r(handle, g, p) 182 | } 183 | 184 | func cGEOSInterpolateNormalized(g *C.GEOSGeometry, d C.double) *C.GEOSGeometry { 185 | handlemu.Lock() 186 | defer handlemu.Unlock() 187 | return C.GEOSInterpolateNormalized_r(handle, g, d) 188 | } 189 | 190 | func cGEOSBufferParams_create() *C.GEOSBufferParams { 191 | handlemu.Lock() 192 | defer handlemu.Unlock() 193 | return C.GEOSBufferParams_create_r(handle) 194 | } 195 | 196 | func cGEOSBufferParams_destroy(parms *C.GEOSBufferParams) { 197 | handlemu.Lock() 198 | defer handlemu.Unlock() 199 | C.GEOSBufferParams_destroy_r(handle, parms) 200 | } 201 | 202 | func cGEOSBufferParams_setEndCapStyle(p *C.GEOSBufferParams, style C.int) C.int { 203 | handlemu.Lock() 204 | defer handlemu.Unlock() 205 | return C.GEOSBufferParams_setEndCapStyle_r(handle, p, style) 206 | } 207 | 208 | func cGEOSBufferParams_setJoinStyle(p *C.GEOSBufferParams, joinStyle C.int) C.int { 209 | handlemu.Lock() 210 | defer handlemu.Unlock() 211 | return C.GEOSBufferParams_setJoinStyle_r(handle, p, joinStyle) 212 | } 213 | 214 | func cGEOSBufferParams_setMitreLimit(p *C.GEOSBufferParams, mitreLimit C.double) C.int { 215 | handlemu.Lock() 216 | defer handlemu.Unlock() 217 | return C.GEOSBufferParams_setMitreLimit_r(handle, p, mitreLimit) 218 | } 219 | 220 | func cGEOSBufferParams_setQuadrantSegments(p *C.GEOSBufferParams, quadSegs C.int) C.int { 221 | handlemu.Lock() 222 | defer handlemu.Unlock() 223 | return C.GEOSBufferParams_setQuadrantSegments_r(handle, p, quadSegs) 224 | } 225 | 226 | func cGEOSBufferParams_setSingleSided(p *C.GEOSBufferParams, singleSided C.int) C.int { 227 | handlemu.Lock() 228 | defer handlemu.Unlock() 229 | return C.GEOSBufferParams_setSingleSided_r(handle, p, singleSided) 230 | } 231 | 232 | func cGEOSBufferWithParams(g1 *C.GEOSGeometry, p *C.GEOSBufferParams, width C.double) *C.GEOSGeometry { 233 | handlemu.Lock() 234 | defer handlemu.Unlock() 235 | return C.GEOSBufferWithParams_r(handle, g1, p, width) 236 | } 237 | 238 | func cGEOSBuffer(g1 *C.GEOSGeometry, width C.double, quadsegs C.int) *C.GEOSGeometry { 239 | handlemu.Lock() 240 | defer handlemu.Unlock() 241 | return C.GEOSBuffer_r(handle, g1, width, quadsegs) 242 | } 243 | 244 | func cGEOSBufferWithStyle(g1 *C.GEOSGeometry, width C.double, quadsegs C.int, endCapStyle C.int, joinStyle C.int, mitreLimit C.double) *C.GEOSGeometry { 245 | handlemu.Lock() 246 | defer handlemu.Unlock() 247 | return C.GEOSBufferWithStyle_r(handle, g1, width, quadsegs, endCapStyle, joinStyle, mitreLimit) 248 | } 249 | 250 | func cGEOSSingleSidedBuffer(g1 *C.GEOSGeometry, width C.double, quadsegs C.int, joinStyle C.int, mitreLimit C.double, leftSide C.int) *C.GEOSGeometry { 251 | handlemu.Lock() 252 | defer handlemu.Unlock() 253 | return C.GEOSSingleSidedBuffer_r(handle, g1, width, quadsegs, joinStyle, mitreLimit, leftSide) 254 | } 255 | 256 | func cGEOSOffsetCurve(g1 *C.GEOSGeometry, width C.double, quadsegs C.int, joinStyle C.int, mitreLimit C.double) *C.GEOSGeometry { 257 | handlemu.Lock() 258 | defer handlemu.Unlock() 259 | return C.GEOSOffsetCurve_r(handle, g1, width, quadsegs, joinStyle, mitreLimit) 260 | } 261 | 262 | func cGEOSGeom_createPoint(s *C.GEOSCoordSequence) *C.GEOSGeometry { 263 | handlemu.Lock() 264 | defer handlemu.Unlock() 265 | return C.GEOSGeom_createPoint_r(handle, s) 266 | } 267 | 268 | func cGEOSGeom_createEmptyPoint() *C.GEOSGeometry { 269 | handlemu.Lock() 270 | defer handlemu.Unlock() 271 | return C.GEOSGeom_createEmptyPoint_r(handle) 272 | } 273 | 274 | func cGEOSGeom_createLinearRing(s *C.GEOSCoordSequence) *C.GEOSGeometry { 275 | handlemu.Lock() 276 | defer handlemu.Unlock() 277 | return C.GEOSGeom_createLinearRing_r(handle, s) 278 | } 279 | 280 | func cGEOSGeom_createLineString(s *C.GEOSCoordSequence) *C.GEOSGeometry { 281 | handlemu.Lock() 282 | defer handlemu.Unlock() 283 | return C.GEOSGeom_createLineString_r(handle, s) 284 | } 285 | 286 | func cGEOSGeom_createEmptyLineString() *C.GEOSGeometry { 287 | handlemu.Lock() 288 | defer handlemu.Unlock() 289 | return C.GEOSGeom_createEmptyLineString_r(handle) 290 | } 291 | 292 | func cGEOSGeom_createEmptyPolygon() *C.GEOSGeometry { 293 | handlemu.Lock() 294 | defer handlemu.Unlock() 295 | return C.GEOSGeom_createEmptyPolygon_r(handle) 296 | } 297 | 298 | func cGEOSGeom_createPolygon(shell *C.GEOSGeometry, holes **C.GEOSGeometry, nholes C.uint) *C.GEOSGeometry { 299 | handlemu.Lock() 300 | defer handlemu.Unlock() 301 | return C.GEOSGeom_createPolygon_r(handle, shell, holes, nholes) 302 | } 303 | 304 | func cGEOSGeom_createCollection(_type C.int, geoms **C.GEOSGeometry, ngeoms C.uint) *C.GEOSGeometry { 305 | handlemu.Lock() 306 | defer handlemu.Unlock() 307 | return C.GEOSGeom_createCollection_r(handle, _type, geoms, ngeoms) 308 | } 309 | 310 | func cGEOSGeom_createEmptyCollection(_type C.int) *C.GEOSGeometry { 311 | handlemu.Lock() 312 | defer handlemu.Unlock() 313 | return C.GEOSGeom_createEmptyCollection_r(handle, _type) 314 | } 315 | 316 | func cGEOSGeom_clone(g *C.GEOSGeometry) *C.GEOSGeometry { 317 | handlemu.Lock() 318 | defer handlemu.Unlock() 319 | return C.GEOSGeom_clone_r(handle, g) 320 | } 321 | 322 | func cGEOSGeom_destroy(g *C.GEOSGeometry) { 323 | handlemu.Lock() 324 | defer handlemu.Unlock() 325 | C.GEOSGeom_destroy_r(handle, g) 326 | } 327 | 328 | func cGEOSEnvelope(g1 *C.GEOSGeometry) *C.GEOSGeometry { 329 | handlemu.Lock() 330 | defer handlemu.Unlock() 331 | return C.GEOSEnvelope_r(handle, g1) 332 | } 333 | 334 | func cGEOSIntersection(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) *C.GEOSGeometry { 335 | handlemu.Lock() 336 | defer handlemu.Unlock() 337 | return C.GEOSIntersection_r(handle, g1, g2) 338 | } 339 | 340 | func cGEOSConvexHull(g1 *C.GEOSGeometry) *C.GEOSGeometry { 341 | handlemu.Lock() 342 | defer handlemu.Unlock() 343 | return C.GEOSConvexHull_r(handle, g1) 344 | } 345 | 346 | func cGEOSDifference(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) *C.GEOSGeometry { 347 | handlemu.Lock() 348 | defer handlemu.Unlock() 349 | return C.GEOSDifference_r(handle, g1, g2) 350 | } 351 | 352 | func cGEOSSymDifference(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) *C.GEOSGeometry { 353 | handlemu.Lock() 354 | defer handlemu.Unlock() 355 | return C.GEOSSymDifference_r(handle, g1, g2) 356 | } 357 | 358 | func cGEOSBoundary(g1 *C.GEOSGeometry) *C.GEOSGeometry { 359 | handlemu.Lock() 360 | defer handlemu.Unlock() 361 | return C.GEOSBoundary_r(handle, g1) 362 | } 363 | 364 | func cGEOSUnion(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) *C.GEOSGeometry { 365 | handlemu.Lock() 366 | defer handlemu.Unlock() 367 | return C.GEOSUnion_r(handle, g1, g2) 368 | } 369 | 370 | func cGEOSUnaryUnion(g *C.GEOSGeometry) *C.GEOSGeometry { 371 | handlemu.Lock() 372 | defer handlemu.Unlock() 373 | return C.GEOSUnaryUnion_r(handle, g) 374 | } 375 | 376 | func cGEOSUnionCascaded(g1 *C.GEOSGeometry) *C.GEOSGeometry { 377 | handlemu.Lock() 378 | defer handlemu.Unlock() 379 | return C.GEOSUnionCascaded_r(handle, g1) 380 | } 381 | 382 | func cGEOSPointOnSurface(g1 *C.GEOSGeometry) *C.GEOSGeometry { 383 | handlemu.Lock() 384 | defer handlemu.Unlock() 385 | return C.GEOSPointOnSurface_r(handle, g1) 386 | } 387 | 388 | func cGEOSGetCentroid(g *C.GEOSGeometry) *C.GEOSGeometry { 389 | handlemu.Lock() 390 | defer handlemu.Unlock() 391 | return C.GEOSGetCentroid_r(handle, g) 392 | } 393 | 394 | func cGEOSPolygonize(geoms []*C.GEOSGeometry, ngeoms C.uint) *C.GEOSGeometry { 395 | handlemu.Lock() 396 | defer handlemu.Unlock() 397 | return C.GEOSPolygonize_r(handle, &geoms[0], ngeoms) 398 | } 399 | 400 | func cGEOSPolygonizer_getCutEdges(geoms []*C.GEOSGeometry, ngeoms C.uint) *C.GEOSGeometry { 401 | handlemu.Lock() 402 | defer handlemu.Unlock() 403 | return C.GEOSPolygonizer_getCutEdges_r(handle, &geoms[0], ngeoms) 404 | } 405 | 406 | func cGEOSPolygonize_full(input *C.GEOSGeometry, cuts **C.GEOSGeometry, dangles **C.GEOSGeometry, invalidRings **C.GEOSGeometry) *C.GEOSGeometry { 407 | handlemu.Lock() 408 | defer handlemu.Unlock() 409 | return C.GEOSPolygonize_full_r(handle, input, cuts, dangles, invalidRings) 410 | } 411 | 412 | func cGEOSLineMerge(g *C.GEOSGeometry) *C.GEOSGeometry { 413 | handlemu.Lock() 414 | defer handlemu.Unlock() 415 | return C.GEOSLineMerge_r(handle, g) 416 | } 417 | 418 | func cGEOSSimplify(g1 *C.GEOSGeometry, tolerance C.double) *C.GEOSGeometry { 419 | handlemu.Lock() 420 | defer handlemu.Unlock() 421 | return C.GEOSSimplify_r(handle, g1, tolerance) 422 | } 423 | 424 | func cGEOSTopologyPreserveSimplify(g1 *C.GEOSGeometry, tolerance C.double) *C.GEOSGeometry { 425 | handlemu.Lock() 426 | defer handlemu.Unlock() 427 | return C.GEOSTopologyPreserveSimplify_r(handle, g1, tolerance) 428 | } 429 | 430 | func cGEOSGeom_extractUniquePoints(g *C.GEOSGeometry) *C.GEOSGeometry { 431 | handlemu.Lock() 432 | defer handlemu.Unlock() 433 | return C.GEOSGeom_extractUniquePoints_r(handle, g) 434 | } 435 | 436 | func cGEOSSharedPaths(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) *C.GEOSGeometry { 437 | handlemu.Lock() 438 | defer handlemu.Unlock() 439 | return C.GEOSSharedPaths_r(handle, g1, g2) 440 | } 441 | 442 | func cGEOSSnap(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry, tolerance C.double) *C.GEOSGeometry { 443 | handlemu.Lock() 444 | defer handlemu.Unlock() 445 | return C.GEOSSnap_r(handle, g1, g2, tolerance) 446 | } 447 | 448 | func cGEOSDisjoint(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 449 | handlemu.Lock() 450 | defer handlemu.Unlock() 451 | return C.GEOSDisjoint_r(handle, g1, g2) 452 | } 453 | 454 | func cGEOSTouches(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 455 | handlemu.Lock() 456 | defer handlemu.Unlock() 457 | return C.GEOSTouches_r(handle, g1, g2) 458 | } 459 | 460 | func cGEOSIntersects(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 461 | handlemu.Lock() 462 | defer handlemu.Unlock() 463 | return C.GEOSIntersects_r(handle, g1, g2) 464 | } 465 | 466 | func cGEOSCrosses(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 467 | handlemu.Lock() 468 | defer handlemu.Unlock() 469 | return C.GEOSCrosses_r(handle, g1, g2) 470 | } 471 | 472 | func cGEOSWithin(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 473 | handlemu.Lock() 474 | defer handlemu.Unlock() 475 | return C.GEOSWithin_r(handle, g1, g2) 476 | } 477 | 478 | func cGEOSContains(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 479 | handlemu.Lock() 480 | defer handlemu.Unlock() 481 | return C.GEOSContains_r(handle, g1, g2) 482 | } 483 | 484 | func cGEOSOverlaps(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 485 | handlemu.Lock() 486 | defer handlemu.Unlock() 487 | return C.GEOSOverlaps_r(handle, g1, g2) 488 | } 489 | 490 | func cGEOSEquals(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 491 | handlemu.Lock() 492 | defer handlemu.Unlock() 493 | return C.GEOSEquals_r(handle, g1, g2) 494 | } 495 | 496 | func cGEOSEqualsExact(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry, tolerance C.double) C.char { 497 | handlemu.Lock() 498 | defer handlemu.Unlock() 499 | return C.GEOSEqualsExact_r(handle, g1, g2, tolerance) 500 | } 501 | 502 | func cGEOSCovers(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 503 | handlemu.Lock() 504 | defer handlemu.Unlock() 505 | return C.GEOSCovers_r(handle, g1, g2) 506 | } 507 | 508 | func cGEOSCoveredBy(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) C.char { 509 | handlemu.Lock() 510 | defer handlemu.Unlock() 511 | return C.GEOSCoveredBy_r(handle, g1, g2) 512 | } 513 | 514 | func cGEOSPrepare(g *C.GEOSGeometry) *C.GEOSPreparedGeometry { 515 | handlemu.Lock() 516 | defer handlemu.Unlock() 517 | return C.GEOSPrepare_r(handle, g) 518 | } 519 | 520 | func cGEOSPreparedGeom_destroy(g *C.GEOSPreparedGeometry) { 521 | handlemu.Lock() 522 | defer handlemu.Unlock() 523 | C.GEOSPreparedGeom_destroy_r(handle, g) 524 | } 525 | 526 | func cGEOSPreparedContains(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 527 | handlemu.Lock() 528 | defer handlemu.Unlock() 529 | return C.GEOSPreparedContains_r(handle, pg1, g2) 530 | } 531 | 532 | func cGEOSPreparedContainsProperly(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 533 | handlemu.Lock() 534 | defer handlemu.Unlock() 535 | return C.GEOSPreparedContainsProperly_r(handle, pg1, g2) 536 | } 537 | 538 | func cGEOSPreparedCoveredBy(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 539 | handlemu.Lock() 540 | defer handlemu.Unlock() 541 | return C.GEOSPreparedCoveredBy_r(handle, pg1, g2) 542 | } 543 | 544 | func cGEOSPreparedCovers(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 545 | handlemu.Lock() 546 | defer handlemu.Unlock() 547 | return C.GEOSPreparedCovers_r(handle, pg1, g2) 548 | } 549 | 550 | func cGEOSPreparedCrosses(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 551 | handlemu.Lock() 552 | defer handlemu.Unlock() 553 | return C.GEOSPreparedCrosses_r(handle, pg1, g2) 554 | } 555 | 556 | func cGEOSPreparedDisjoint(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 557 | handlemu.Lock() 558 | defer handlemu.Unlock() 559 | return C.GEOSPreparedDisjoint_r(handle, pg1, g2) 560 | } 561 | 562 | func cGEOSPreparedIntersects(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 563 | handlemu.Lock() 564 | defer handlemu.Unlock() 565 | return C.GEOSPreparedIntersects_r(handle, pg1, g2) 566 | } 567 | 568 | func cGEOSPreparedOverlaps(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 569 | handlemu.Lock() 570 | defer handlemu.Unlock() 571 | return C.GEOSPreparedOverlaps_r(handle, pg1, g2) 572 | } 573 | 574 | func cGEOSPreparedTouches(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 575 | handlemu.Lock() 576 | defer handlemu.Unlock() 577 | return C.GEOSPreparedTouches_r(handle, pg1, g2) 578 | } 579 | 580 | func cGEOSPreparedWithin(pg1 *C.GEOSPreparedGeometry, g2 *C.GEOSGeometry) C.char { 581 | handlemu.Lock() 582 | defer handlemu.Unlock() 583 | return C.GEOSPreparedWithin_r(handle, pg1, g2) 584 | } 585 | 586 | func cGEOSSTRtree_create(nodeCapacity C.size_t) *C.GEOSSTRtree { 587 | handlemu.Lock() 588 | defer handlemu.Unlock() 589 | return C.GEOSSTRtree_create_r(handle, nodeCapacity) 590 | } 591 | 592 | func cGEOSSTRtree_insert(tree *C.GEOSSTRtree, g *C.GEOSGeometry, item *C.void) { 593 | handlemu.Lock() 594 | defer handlemu.Unlock() 595 | C.GEOSSTRtree_insert_r(handle, tree, g, unsafe.Pointer(item)) 596 | } 597 | 598 | func cGEOSSTRtree_query(tree *C.GEOSSTRtree, g *C.GEOSGeometry, callback C.GEOSQueryCallback, userdata *C.void) { 599 | handlemu.Lock() 600 | defer handlemu.Unlock() 601 | C.GEOSSTRtree_query_r(handle, tree, g, callback, unsafe.Pointer(userdata)) 602 | } 603 | 604 | func cGEOSSTRtree_iterate(tree *C.GEOSSTRtree, callback C.GEOSQueryCallback, userdata *C.void) { 605 | handlemu.Lock() 606 | defer handlemu.Unlock() 607 | C.GEOSSTRtree_iterate_r(handle, tree, callback, unsafe.Pointer(userdata)) 608 | } 609 | 610 | func cGEOSSTRtree_remove(tree *C.GEOSSTRtree, g *C.GEOSGeometry, item *C.void) C.char { 611 | handlemu.Lock() 612 | defer handlemu.Unlock() 613 | return C.GEOSSTRtree_remove_r(handle, tree, g, unsafe.Pointer(item)) 614 | } 615 | 616 | func cGEOSSTRtree_destroy(tree *C.GEOSSTRtree) { 617 | handlemu.Lock() 618 | defer handlemu.Unlock() 619 | C.GEOSSTRtree_destroy_r(handle, tree) 620 | } 621 | 622 | func cGEOSisEmpty(g1 *C.GEOSGeometry) C.char { 623 | handlemu.Lock() 624 | defer handlemu.Unlock() 625 | return C.GEOSisEmpty_r(handle, g1) 626 | } 627 | 628 | func cGEOSisSimple(g1 *C.GEOSGeometry) C.char { 629 | handlemu.Lock() 630 | defer handlemu.Unlock() 631 | return C.GEOSisSimple_r(handle, g1) 632 | } 633 | 634 | func cGEOSisRing(g1 *C.GEOSGeometry) C.char { 635 | handlemu.Lock() 636 | defer handlemu.Unlock() 637 | return C.GEOSisRing_r(handle, g1) 638 | } 639 | 640 | func cGEOSHasZ(g1 *C.GEOSGeometry) C.char { 641 | handlemu.Lock() 642 | defer handlemu.Unlock() 643 | return C.GEOSHasZ_r(handle, g1) 644 | } 645 | 646 | func cGEOSisClosed(g1 *C.GEOSGeometry) C.char { 647 | handlemu.Lock() 648 | defer handlemu.Unlock() 649 | return C.GEOSisClosed_r(handle, g1) 650 | } 651 | 652 | func cGEOSRelatePattern(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry, pat *C.char) C.char { 653 | handlemu.Lock() 654 | defer handlemu.Unlock() 655 | return C.GEOSRelatePattern_r(handle, g1, g2, pat) 656 | } 657 | 658 | func cGEOSRelate(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry) *C.char { 659 | handlemu.Lock() 660 | defer handlemu.Unlock() 661 | return C.GEOSRelate_r(handle, g1, g2) 662 | } 663 | 664 | func cGEOSRelatePatternMatch(mat *C.char, pat *C.char) C.char { 665 | handlemu.Lock() 666 | defer handlemu.Unlock() 667 | return C.GEOSRelatePatternMatch_r(handle, mat, pat) 668 | } 669 | 670 | func cGEOSRelateBoundaryNodeRule(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry, bnr C.int) *C.char { 671 | handlemu.Lock() 672 | defer handlemu.Unlock() 673 | return C.GEOSRelateBoundaryNodeRule_r(handle, g1, g2, bnr) 674 | } 675 | 676 | func cGEOSisValid(g1 *C.GEOSGeometry) C.char { 677 | handlemu.Lock() 678 | defer handlemu.Unlock() 679 | return C.GEOSisValid_r(handle, g1) 680 | } 681 | 682 | func cGEOSisValidReason(g1 *C.GEOSGeometry) *C.char { 683 | handlemu.Lock() 684 | defer handlemu.Unlock() 685 | return C.GEOSisValidReason_r(handle, g1) 686 | } 687 | 688 | func cGEOSisValidDetail(g *C.GEOSGeometry, flags C.int, reason **C.char, location **C.GEOSGeometry) C.char { 689 | handlemu.Lock() 690 | defer handlemu.Unlock() 691 | return C.GEOSisValidDetail_r(handle, g, flags, reason, location) 692 | } 693 | 694 | func cGEOSGeomType(g1 *C.GEOSGeometry) *C.char { 695 | handlemu.Lock() 696 | defer handlemu.Unlock() 697 | return C.GEOSGeomType_r(handle, g1) 698 | } 699 | 700 | func cGEOSGeomTypeId(g1 *C.GEOSGeometry) C.int { 701 | handlemu.Lock() 702 | defer handlemu.Unlock() 703 | return C.GEOSGeomTypeId_r(handle, g1) 704 | } 705 | 706 | func cGEOSGetSRID(g *C.GEOSGeometry) C.int { 707 | handlemu.Lock() 708 | defer handlemu.Unlock() 709 | return C.GEOSGetSRID_r(handle, g) 710 | } 711 | 712 | func cGEOSSetSRID(g *C.GEOSGeometry, SRID C.int) { 713 | handlemu.Lock() 714 | defer handlemu.Unlock() 715 | C.GEOSSetSRID_r(handle, g, SRID) 716 | } 717 | 718 | func cGEOSGetNumGeometries(g *C.GEOSGeometry) C.int { 719 | handlemu.Lock() 720 | defer handlemu.Unlock() 721 | return C.GEOSGetNumGeometries_r(handle, g) 722 | } 723 | 724 | func cGEOSGetGeometryN(g *C.GEOSGeometry, n C.int) *C.GEOSGeometry { 725 | handlemu.Lock() 726 | defer handlemu.Unlock() 727 | return C.GEOSGetGeometryN_r(handle, g, n) 728 | } 729 | 730 | func cGEOSNormalize(g1 *C.GEOSGeometry) C.int { 731 | handlemu.Lock() 732 | defer handlemu.Unlock() 733 | return C.GEOSNormalize_r(handle, g1) 734 | } 735 | 736 | func cGEOSGetNumInteriorRings(g1 *C.GEOSGeometry) C.int { 737 | handlemu.Lock() 738 | defer handlemu.Unlock() 739 | return C.GEOSGetNumInteriorRings_r(handle, g1) 740 | } 741 | 742 | func cGEOSGeomGetNumPoints(g1 *C.GEOSGeometry) C.int { 743 | handlemu.Lock() 744 | defer handlemu.Unlock() 745 | return C.GEOSGeomGetNumPoints_r(handle, g1) 746 | } 747 | 748 | func cGEOSGeomGetX(g1 *C.GEOSGeometry, x *C.double) C.int { 749 | handlemu.Lock() 750 | defer handlemu.Unlock() 751 | return C.GEOSGeomGetX_r(handle, g1, x) 752 | } 753 | 754 | func cGEOSGeomGetY(g1 *C.GEOSGeometry, y *C.double) C.int { 755 | handlemu.Lock() 756 | defer handlemu.Unlock() 757 | return C.GEOSGeomGetY_r(handle, g1, y) 758 | } 759 | 760 | func cGEOSGetInteriorRingN(g *C.GEOSGeometry, n C.int) *C.GEOSGeometry { 761 | handlemu.Lock() 762 | defer handlemu.Unlock() 763 | return C.GEOSGetInteriorRingN_r(handle, g, n) 764 | } 765 | 766 | func cGEOSGetExteriorRing(g *C.GEOSGeometry) *C.GEOSGeometry { 767 | handlemu.Lock() 768 | defer handlemu.Unlock() 769 | return C.GEOSGetExteriorRing_r(handle, g) 770 | } 771 | 772 | func cGEOSGetNumCoordinates(g1 *C.GEOSGeometry) C.int { 773 | handlemu.Lock() 774 | defer handlemu.Unlock() 775 | return C.GEOSGetNumCoordinates_r(handle, g1) 776 | } 777 | 778 | func cGEOSGeom_getCoordSeq(g *C.GEOSGeometry) *C.GEOSCoordSequence { 779 | handlemu.Lock() 780 | defer handlemu.Unlock() 781 | return C.GEOSGeom_getCoordSeq_r(handle, g) 782 | } 783 | 784 | func cGEOSGeom_getDimensions(g *C.GEOSGeometry) C.int { 785 | handlemu.Lock() 786 | defer handlemu.Unlock() 787 | return C.GEOSGeom_getDimensions_r(handle, g) 788 | } 789 | 790 | func cGEOSGeom_getCoordinateDimension(g *C.GEOSGeometry) C.int { 791 | handlemu.Lock() 792 | defer handlemu.Unlock() 793 | return C.GEOSGeom_getCoordinateDimension_r(handle, g) 794 | } 795 | 796 | func cGEOSGeomGetPointN(g1 *C.GEOSGeometry, n C.int) *C.GEOSGeometry { 797 | handlemu.Lock() 798 | defer handlemu.Unlock() 799 | return C.GEOSGeomGetPointN_r(handle, g1, n) 800 | } 801 | 802 | func cGEOSGeomGetStartPoint(g1 *C.GEOSGeometry) *C.GEOSGeometry { 803 | handlemu.Lock() 804 | defer handlemu.Unlock() 805 | return C.GEOSGeomGetStartPoint_r(handle, g1) 806 | } 807 | 808 | func cGEOSGeomGetEndPoint(g1 *C.GEOSGeometry) *C.GEOSGeometry { 809 | handlemu.Lock() 810 | defer handlemu.Unlock() 811 | return C.GEOSGeomGetEndPoint_r(handle, g1) 812 | } 813 | 814 | func cGEOSArea(g1 *C.GEOSGeometry, area *C.double) C.int { 815 | handlemu.Lock() 816 | defer handlemu.Unlock() 817 | return C.GEOSArea_r(handle, g1, area) 818 | } 819 | 820 | func cGEOSLength(g1 *C.GEOSGeometry, length *C.double) C.int { 821 | handlemu.Lock() 822 | defer handlemu.Unlock() 823 | return C.GEOSLength_r(handle, g1, length) 824 | } 825 | 826 | func cGEOSDistance(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry, dist *C.double) C.int { 827 | handlemu.Lock() 828 | defer handlemu.Unlock() 829 | return C.GEOSDistance_r(handle, g1, g2, dist) 830 | } 831 | 832 | func cGEOSHausdorffDistance(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry, dist *C.double) C.int { 833 | handlemu.Lock() 834 | defer handlemu.Unlock() 835 | return C.GEOSHausdorffDistance_r(handle, g1, g2, dist) 836 | } 837 | 838 | func cGEOSHausdorffDistanceDensify(g1 *C.GEOSGeometry, g2 *C.GEOSGeometry, densifyFrac C.double, dist *C.double) C.int { 839 | handlemu.Lock() 840 | defer handlemu.Unlock() 841 | return C.GEOSHausdorffDistanceDensify_r(handle, g1, g2, densifyFrac, dist) 842 | } 843 | 844 | func cGEOSGeomGetLength(g1 *C.GEOSGeometry, length *C.double) C.int { 845 | handlemu.Lock() 846 | defer handlemu.Unlock() 847 | return C.GEOSGeomGetLength_r(handle, g1, length) 848 | } 849 | 850 | func cGEOSOrientationIndex(Ax C.double, Ay C.double, Bx C.double, By C.double, Px C.double, Py C.double) C.int { 851 | handlemu.Lock() 852 | defer handlemu.Unlock() 853 | return C.GEOSOrientationIndex_r(handle, Ax, Ay, Bx, By, Px, Py) 854 | } 855 | 856 | func cGEOSWKTReader_create() *C.GEOSWKTReader { 857 | handlemu.Lock() 858 | defer handlemu.Unlock() 859 | return C.GEOSWKTReader_create_r(handle) 860 | } 861 | 862 | func cGEOSWKTReader_destroy(reader *C.GEOSWKTReader) { 863 | handlemu.Lock() 864 | defer handlemu.Unlock() 865 | C.GEOSWKTReader_destroy_r(handle, reader) 866 | } 867 | 868 | func cGEOSWKTReader_read(reader *C.GEOSWKTReader, wkt *C.char) *C.GEOSGeometry { 869 | handlemu.Lock() 870 | defer handlemu.Unlock() 871 | return C.GEOSWKTReader_read_r(handle, reader, wkt) 872 | } 873 | 874 | func cGEOSWKTWriter_create() *C.GEOSWKTWriter { 875 | handlemu.Lock() 876 | defer handlemu.Unlock() 877 | return C.GEOSWKTWriter_create_r(handle) 878 | } 879 | 880 | func cGEOSWKTWriter_destroy(writer *C.GEOSWKTWriter) { 881 | handlemu.Lock() 882 | defer handlemu.Unlock() 883 | C.GEOSWKTWriter_destroy_r(handle, writer) 884 | } 885 | 886 | func cGEOSWKTWriter_write(reader *C.GEOSWKTWriter, g *C.GEOSGeometry) *C.char { 887 | handlemu.Lock() 888 | defer handlemu.Unlock() 889 | return C.GEOSWKTWriter_write_r(handle, reader, g) 890 | } 891 | 892 | func cGEOSWKTWriter_setTrim(writer *C.GEOSWKTWriter, trim C.char) { 893 | handlemu.Lock() 894 | defer handlemu.Unlock() 895 | C.GEOSWKTWriter_setTrim_r(handle, writer, trim) 896 | } 897 | 898 | func cGEOSWKTWriter_setRoundingPrecision(writer *C.GEOSWKTWriter, precision C.int) { 899 | handlemu.Lock() 900 | defer handlemu.Unlock() 901 | C.GEOSWKTWriter_setRoundingPrecision_r(handle, writer, precision) 902 | } 903 | 904 | func cGEOSWKTWriter_setOutputDimension(writer *C.GEOSWKTWriter, dim C.int) { 905 | handlemu.Lock() 906 | defer handlemu.Unlock() 907 | C.GEOSWKTWriter_setOutputDimension_r(handle, writer, dim) 908 | } 909 | 910 | func cGEOSWKTWriter_getOutputDimension(writer *C.GEOSWKTWriter) C.int { 911 | handlemu.Lock() 912 | defer handlemu.Unlock() 913 | return C.GEOSWKTWriter_getOutputDimension_r(handle, writer) 914 | } 915 | 916 | func cGEOSWKTWriter_setOld3D(writer *C.GEOSWKTWriter, useOld3D C.int) { 917 | handlemu.Lock() 918 | defer handlemu.Unlock() 919 | C.GEOSWKTWriter_setOld3D_r(handle, writer, useOld3D) 920 | } 921 | 922 | func cGEOSWKBReader_create() *C.GEOSWKBReader { 923 | handlemu.Lock() 924 | defer handlemu.Unlock() 925 | return C.GEOSWKBReader_create_r(handle) 926 | } 927 | 928 | func cGEOSWKBReader_destroy(reader *C.GEOSWKBReader) { 929 | handlemu.Lock() 930 | defer handlemu.Unlock() 931 | C.GEOSWKBReader_destroy_r(handle, reader) 932 | } 933 | 934 | func cGEOSWKBReader_read(reader *C.GEOSWKBReader, wkb *C.uchar, size C.size_t) *C.GEOSGeometry { 935 | handlemu.Lock() 936 | defer handlemu.Unlock() 937 | return C.GEOSWKBReader_read_r(handle, reader, wkb, size) 938 | } 939 | 940 | func cGEOSWKBReader_readHEX(reader *C.GEOSWKBReader, hex *C.uchar, size C.size_t) *C.GEOSGeometry { 941 | handlemu.Lock() 942 | defer handlemu.Unlock() 943 | return C.GEOSWKBReader_readHEX_r(handle, reader, hex, size) 944 | } 945 | 946 | func cGEOSWKBWriter_create() *C.GEOSWKBWriter { 947 | handlemu.Lock() 948 | defer handlemu.Unlock() 949 | return C.GEOSWKBWriter_create_r(handle) 950 | } 951 | 952 | func cGEOSWKBWriter_destroy(writer *C.GEOSWKBWriter) { 953 | handlemu.Lock() 954 | defer handlemu.Unlock() 955 | C.GEOSWKBWriter_destroy_r(handle, writer) 956 | } 957 | 958 | func cGEOSWKBWriter_write(writer *C.GEOSWKBWriter, g *C.GEOSGeometry, size *C.size_t) *C.uchar { 959 | handlemu.Lock() 960 | defer handlemu.Unlock() 961 | return C.GEOSWKBWriter_write_r(handle, writer, g, size) 962 | } 963 | 964 | func cGEOSWKBWriter_writeHEX(writer *C.GEOSWKBWriter, g *C.GEOSGeometry, size *C.size_t) *C.uchar { 965 | handlemu.Lock() 966 | defer handlemu.Unlock() 967 | return C.GEOSWKBWriter_writeHEX_r(handle, writer, g, size) 968 | } 969 | 970 | func cGEOSWKBWriter_getOutputDimension(writer *C.GEOSWKBWriter) C.int { 971 | handlemu.Lock() 972 | defer handlemu.Unlock() 973 | return C.GEOSWKBWriter_getOutputDimension_r(handle, writer) 974 | } 975 | 976 | func cGEOSWKBWriter_setOutputDimension(writer *C.GEOSWKBWriter, newDimension C.int) { 977 | handlemu.Lock() 978 | defer handlemu.Unlock() 979 | C.GEOSWKBWriter_setOutputDimension_r(handle, writer, newDimension) 980 | } 981 | 982 | func cGEOSWKBWriter_getByteOrder(writer *C.GEOSWKBWriter) C.int { 983 | handlemu.Lock() 984 | defer handlemu.Unlock() 985 | return C.GEOSWKBWriter_getByteOrder_r(handle, writer) 986 | } 987 | 988 | func cGEOSWKBWriter_setByteOrder(writer *C.GEOSWKBWriter, byteOrder C.int) { 989 | handlemu.Lock() 990 | defer handlemu.Unlock() 991 | C.GEOSWKBWriter_setByteOrder_r(handle, writer, byteOrder) 992 | } 993 | 994 | func cGEOSWKBWriter_getIncludeSRID(writer *C.GEOSWKBWriter) C.char { 995 | handlemu.Lock() 996 | defer handlemu.Unlock() 997 | return C.GEOSWKBWriter_getIncludeSRID_r(handle, writer) 998 | } 999 | 1000 | func cGEOSWKBWriter_setIncludeSRID(writer *C.GEOSWKBWriter, writeSRID C.char) { 1001 | handlemu.Lock() 1002 | defer handlemu.Unlock() 1003 | C.GEOSWKBWriter_setIncludeSRID_r(handle, writer, writeSRID) 1004 | } 1005 | 1006 | func cGEOSFree(buffer *C.void) { 1007 | handlemu.Lock() 1008 | defer handlemu.Unlock() 1009 | C.GEOSFree_r(handle, unsafe.Pointer(buffer)) 1010 | } 1011 | -------------------------------------------------------------------------------- /geos/geom.go: -------------------------------------------------------------------------------- 1 | package geos 2 | 3 | /* 4 | #include "geos.h" 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "errors" 10 | "math" 11 | "runtime" 12 | "unsafe" 13 | ) 14 | 15 | // Geometry represents a geometry object, which can be any one of the types of 16 | // the Simple Features Specification of the Open GIS Consortium: 17 | // Point 18 | // LineString 19 | // LinearRing 20 | // Polygon 21 | // MultiPoint 22 | // MultiLineString 23 | // MultiPolygon 24 | // GeometryCollection 25 | type Geometry struct { 26 | g *C.GEOSGeometry 27 | } 28 | 29 | // geomFromPtr returns a new Geometry that's been initialized with a C pointer 30 | // to the GEOS C API object. 31 | // 32 | // This constructor should be used when the caller has ownership of the 33 | // underlying C object. 34 | func geomFromPtr(ptr *C.GEOSGeometry) *Geometry { 35 | g := &Geometry{g: ptr} 36 | runtime.SetFinalizer(g, func(g *Geometry) { 37 | cGEOSGeom_destroy(ptr) 38 | }) 39 | return g 40 | } 41 | 42 | // geomFromPtrUnowned returns a new Geometry that's been initialized with 43 | // a C pointer to the GEOS C API object. 44 | // 45 | // This constructor should be used when the caller doesn't have ownership of the 46 | // underlying C object. 47 | func geomFromPtrUnowned(ptr *C.GEOSGeometry) (*Geometry, error) { 48 | if ptr == nil { 49 | return nil, Error() 50 | } 51 | return &Geometry{g: ptr}, nil 52 | } 53 | 54 | // FromWKT is a factory function that returns a new Geometry decoded from a 55 | // Well-Known Text (WKT) string. 56 | func FromWKT(wkt string) (*Geometry, error) { 57 | decoder := newWktDecoder() 58 | return decoder.decode(wkt) 59 | } 60 | 61 | // FromWKB is a factory function that returns a new Geometry decoded from a 62 | // Well-Known Binary (WKB). 63 | func FromWKB(wkb []byte) (*Geometry, error) { 64 | decoder := newWkbDecoder() 65 | return decoder.decode(wkb) 66 | } 67 | 68 | // FromHex is a factory function that returns a new Geometry decoded from a 69 | // Well-Known Binary (WKB) hex string. 70 | func FromHex(hex string) (*Geometry, error) { 71 | decoder := newWkbDecoder() 72 | return decoder.decodeHex(hex) 73 | } 74 | 75 | // ToWKT returns a string encoding of the geometry, in Well-Known Text (WKT) 76 | // format. 77 | func (g *Geometry) ToWKT() (string, error) { 78 | encoder := newWktEncoder() 79 | return encoder.encode(g) 80 | } 81 | 82 | // String returns a string encoding of the geometry, in Well-Known Text (WKT) 83 | // format, or the empty string if there is an error creating the encoding. 84 | func (g *Geometry) String() string { 85 | str, err := g.ToWKT() 86 | if err != nil { 87 | return "" // XXX: better to panic? 88 | } 89 | return str 90 | } 91 | 92 | // WKB returns the geoemtry encoded as a Well-Known Binary (WKB). 93 | func (g *Geometry) WKB() ([]byte, error) { 94 | encoder := newWkbEncoder() 95 | return encoder.encode(g) 96 | } 97 | 98 | // Hex returns the geometry as a Well-Known Binary (WKB) hex-encoded byte slice. 99 | func (g *Geometry) Hex() ([]byte, error) { 100 | encoder := newWkbEncoder() 101 | return encoder.encodeHex(g) 102 | } 103 | 104 | // Linearref functions 105 | 106 | // Project returns distance of point projected on this geometry from origin. 107 | // This must be a lineal geometry. 108 | func (g *Geometry) Project(p *Geometry) float64 { 109 | // XXX: error if wrong geometry types 110 | return float64(cGEOSProject(g.g, p.g)) 111 | } 112 | 113 | // ProjectNormalized returns distance of point projected on this geometry from 114 | // origin, divided by its length. 115 | // This must be a lineal geometry. 116 | func (g *Geometry) ProjectNormalized(p *Geometry) float64 { 117 | // XXX: error if wrong geometry types 118 | return float64(cGEOSProjectNormalized(g.g, p.g)) 119 | } 120 | 121 | // Interpolate returns the closest point to given distance within geometry. 122 | // This geometry must be a LineString. 123 | func (g *Geometry) Interpolate(dist float64) (*Geometry, error) { 124 | return geomFromC("Interpolate", cGEOSInterpolate(g.g, C.double(dist))) 125 | } 126 | 127 | var ( 128 | // ErrLineInterpolatePointDist is an error for an invalid interpolation 129 | // distance value. 130 | ErrLineInterpolatePointDist = errors.New("distance must between 0 and 1") 131 | // ErrLineInterpolatePointType is an error for an line interpolation point 132 | // performed on a non-linestring geometry. 133 | ErrLineInterpolatePointType = errors.New("geometry must be a linestring") 134 | ) 135 | 136 | // LineInterpolatePoint interpolates a point along a line. 137 | func (g *Geometry) LineInterpolatePoint(dist float64) (*Geometry, error) { 138 | // This code ported from LWGEOM_line_interpolate_point in postgis, 139 | // by jsunday@rochgrp.com and strk@refractions.net. 140 | 141 | if dist < 0 || dist > 1 { 142 | return nil, ErrLineInterpolatePointDist 143 | } 144 | 145 | typ, err := g.Type() 146 | if err != nil { 147 | return nil, err 148 | } 149 | if typ != LINESTRING { 150 | return nil, ErrLineInterpolatePointType 151 | } 152 | 153 | empty, err := g.IsEmpty() 154 | if err != nil { 155 | return nil, err 156 | } 157 | if empty { 158 | pt, err := NewPoint() 159 | if err != nil { 160 | return nil, err 161 | } 162 | return pt, nil 163 | } 164 | 165 | // If distance is one of two extremes, return the point on that end. 166 | if dist == 0.0 || dist == 1.0 { 167 | var ( 168 | pt *Geometry 169 | err error 170 | ) 171 | if dist == 0.0 { 172 | pt, err = g.StartPoint() 173 | } else { 174 | pt, err = g.EndPoint() 175 | } 176 | if err != nil { 177 | return nil, err 178 | } 179 | return pt, nil 180 | } 181 | 182 | // Interpolate a point on the line. 183 | 184 | nsegs, err := g.NPoint() 185 | if err != nil { 186 | return nil, err 187 | } 188 | nsegs-- 189 | 190 | length, err := g.Length() 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | var tlength float64 196 | 197 | for i := 0; i < nsegs; i++ { 198 | a, err := g.Point(i) 199 | if err != nil { 200 | return nil, err 201 | } 202 | b, err := g.Point(i + 1) 203 | if err != nil { 204 | return nil, err 205 | } 206 | 207 | // Find the relative length of this segment. 208 | slength, err := a.Distance(b) 209 | if err != nil { 210 | return nil, err 211 | } 212 | slength /= length 213 | 214 | if dist < tlength+slength { 215 | dseg := (dist - tlength) / slength 216 | pt, err := interpolatePoint2D(a, b, dseg) 217 | if err != nil { 218 | return nil, err 219 | } 220 | return pt, nil 221 | } 222 | tlength += slength 223 | } 224 | 225 | // Return the last point on the line. This shouldn't happen, but could if 226 | // there's some floating point rounding errors. 227 | return g.EndPoint() 228 | } 229 | 230 | func interpolatePoint2D(a, b *Geometry, dist float64) (*Geometry, error) { 231 | absDist := math.Abs(dist) 232 | if dist < 0 || dist > 1 { 233 | return nil, errors.New("distance must be between 0 and 1") 234 | } 235 | 236 | ax, err := a.X() 237 | if err != nil { 238 | return nil, err 239 | } 240 | ay, err := a.Y() 241 | if err != nil { 242 | return nil, err 243 | } 244 | bx, err := b.X() 245 | if err != nil { 246 | return nil, err 247 | } 248 | by, err := b.Y() 249 | if err != nil { 250 | return nil, err 251 | } 252 | 253 | ix := ax + ((bx - ax) * absDist) 254 | iy := ay + ((by - ay) * absDist) 255 | 256 | coord := NewCoord(ix, iy) 257 | return NewPoint(coord) 258 | } 259 | 260 | // Buffer computes a new geometry as the dilation (position amount) or erosion 261 | // (negative amount) of the geometry -- a sum or difference, respectively, of 262 | // the geometry with a circle of radius of the absolute value of the buffer 263 | // amount. 264 | func (g *Geometry) Buffer(d float64) (*Geometry, error) { 265 | const quadsegs = 8 266 | return geomFromC("Buffer", cGEOSBuffer(g.g, C.double(d), quadsegs)) 267 | } 268 | 269 | // CapStyle is the style of the cap at the end of a line segment. 270 | type CapStyle int 271 | 272 | const ( 273 | _ CapStyle = iota 274 | // CapRound is a round end cap. 275 | CapRound 276 | // CapFlat is a flat end cap. 277 | CapFlat 278 | // CapSquare is a square end cap. 279 | CapSquare 280 | ) 281 | 282 | // JoinStyle is the style of the joint of two line segments. 283 | type JoinStyle int 284 | 285 | const ( 286 | _ JoinStyle = iota 287 | // JoinRound is a round segment join style. 288 | JoinRound 289 | // JoinMitre is a mitred segment join style. 290 | JoinMitre 291 | // JoinBevel is a beveled segment join style. 292 | JoinBevel 293 | ) 294 | 295 | // BufferOpts are options to the BufferWithOpts method. 296 | type BufferOpts struct { 297 | // QuadSegs is the number of quadrant segments. 298 | QuadSegs int 299 | // CapStyle is the end cap style. 300 | CapStyle CapStyle 301 | // JoinStyle is the line segment join style. 302 | JoinStyle JoinStyle 303 | // MitreLimit is the limit in the amount of a mitred join. 304 | MitreLimit float64 305 | } 306 | 307 | // BufferWithOpts computes a new geometry as the dilation (position amount) or erosion 308 | // (negative amount) of the geometry -- a sum or difference, respectively, of 309 | // the geometry with a circle of radius of the absolute value of the buffer 310 | // amount. 311 | // 312 | // BufferWithOpts gives the user more control than Buffer over the parameters of 313 | // the buffering, including: 314 | // 315 | // - # of quadrant segments (defaults to 8 in Buffer) 316 | // - mitre limit (defaults to 5.0 in Buffer) 317 | // - end cap style (see CapStyle consts) 318 | // - join style (see JoinStyle consts) 319 | func (g *Geometry) BufferWithOpts(width float64, opts BufferOpts) (*Geometry, error) { 320 | return geomFromC("BufferWithOpts", cGEOSBufferWithStyle(g.g, C.double(width), C.int(opts.QuadSegs), C.int(opts.CapStyle), C.int(opts.JoinStyle), C.double(opts.MitreLimit))) 321 | } 322 | 323 | // OffsetCurve computes a new linestring that is offset from the input 324 | // linestring by the given distance and buffer options. A negative distance is 325 | // offset on the right side; positive distance offset on the left side. 326 | func (g *Geometry) OffsetCurve(distance float64, opts BufferOpts) (*Geometry, error) { 327 | return geomFromC("OffsetCurve", cGEOSOffsetCurve(g.g, C.double(distance), C.int(opts.QuadSegs), C.int(opts.JoinStyle), C.double(opts.MitreLimit))) 328 | } 329 | 330 | // Geometry Constructors 331 | 332 | // NewPoint returns a new geometry of type Point, initialized with the given 333 | // coordinate(s). If no coordinates are given, it's an empty geometry (i.e., 334 | // IsEmpty() == true). It's an 335 | // error if more than one coordinate is given. 336 | func NewPoint(coords ...Coord) (*Geometry, error) { 337 | if len(coords) == 0 { 338 | return emptyGeom("EmptyPoint", cGEOSGeom_createEmptyPoint) 339 | } 340 | cs, err := coordSeqFromSlice(coords) 341 | if err != nil { 342 | return nil, err 343 | } 344 | return geomFromCoordSeq(cs, "NewPoint", cGEOSGeom_createPoint) 345 | } 346 | 347 | // NewLinearRing returns a new geometry of type LinearRing, initialized with the 348 | // given coordinates. The number of coordinates must either be zero (none 349 | // given), in which case it's an empty geometry (IsEmpty() == true), or >= 4. 350 | func NewLinearRing(coords ...Coord) (*Geometry, error) { 351 | cs, err := coordSeqFromSlice(coords) 352 | if err != nil { 353 | return nil, err 354 | } 355 | return geomFromCoordSeq(cs, "NewLinearRing", cGEOSGeom_createLinearRing) 356 | } 357 | 358 | // NewLineString returns a new geometry of type LineString, initialized with the 359 | // given coordinates. If no coordinates are given, it's an empty geometry 360 | // (IsEmpty() == true). 361 | func NewLineString(coords ...Coord) (*Geometry, error) { 362 | cs, err := coordSeqFromSlice(coords) 363 | if err != nil { 364 | return nil, err 365 | } 366 | return geomFromCoordSeq(cs, "NewLineString", cGEOSGeom_createLineString) 367 | } 368 | 369 | // EmptyPolygon returns a new geometry of type Polygon that's empty (i.e., 370 | // IsEmpty() == true). 371 | func EmptyPolygon() (*Geometry, error) { 372 | return emptyGeom("EmptyPoint", cGEOSGeom_createEmptyPolygon) 373 | } 374 | 375 | // NewPolygon returns a new geometry of type Polygon, initialized with the given 376 | // shell (exterior ring) and slice of holes (interior rings). The shell and holes 377 | // slice are themselves slices of coordinates. A shell is required, and a 378 | // variadic number of holes (therefore are optional). 379 | // 380 | // To create a new polygon from existing linear ring Geometry objects, use 381 | // PolygonFromGeom. 382 | func NewPolygon(shell []Coord, holes ...[]Coord) (*Geometry, error) { 383 | ext, err := NewLinearRing(shell...) 384 | if err != nil { 385 | return nil, err 386 | } 387 | var ints []*Geometry 388 | for i := range holes { 389 | g, err := NewLinearRing(holes[i]...) 390 | if err != nil { 391 | return nil, err 392 | } 393 | ints = append(ints, g) 394 | runtime.SetFinalizer(g, nil) 395 | } 396 | runtime.SetFinalizer(ext, nil) 397 | return PolygonFromGeom(ext, ints...) 398 | } 399 | 400 | // PolygonFromGeom returns a new geometry of type Polygon, initialized with the 401 | // given shell (exterior ring) and slice of holes (interior rings). The shell 402 | // and slice of holes are geometry objects, and expected to be LinearRings. 403 | func PolygonFromGeom(shell *Geometry, holes ...*Geometry) (*Geometry, error) { 404 | var ptrHoles **C.GEOSGeometry 405 | // build c array of geom ptrs 406 | var holeCPtrs []*C.GEOSGeometry 407 | for i := range holes { 408 | holeCPtrs = append(holeCPtrs, holes[i].g) 409 | // The ownership of the holes becomes that of the new polygon 410 | runtime.SetFinalizer(holes[i], nil) 411 | } 412 | if len(holeCPtrs) > 0 { 413 | ptrHoles = &holeCPtrs[0] 414 | } 415 | // The ownership of the shell becomes that of the new polygon 416 | runtime.SetFinalizer(shell, nil) 417 | return geomFromC("NewPolygon", cGEOSGeom_createPolygon(shell.g, ptrHoles, C.uint(len(holeCPtrs)))) 418 | } 419 | 420 | // NewCollection returns a new geometry that is a collection containing multiple 421 | // geometries given as variadic arguments. The type of the collection (in the 422 | // SFS sense of type -- MultiPoint, MultiLineString, etc.) is determined by the 423 | // first argument. If no geometries are given, the geometry is an empty version 424 | // of the given collection type. 425 | func NewCollection(_type GeometryType, geoms ...*Geometry) (*Geometry, error) { 426 | if len(geoms) == 0 { 427 | return geomFromC("EmptyCollection", cGEOSGeom_createEmptyCollection(C.int(_type))) 428 | } 429 | var ptrGeoms **C.GEOSGeometry 430 | // build c array of geom ptrs 431 | var geomCPtrs []*C.GEOSGeometry 432 | for i := range geoms { 433 | geomCPtrs = append(geomCPtrs, geoms[i].g) 434 | // The ownership of the component geometries becomes that of the new 435 | // collection geometry 436 | runtime.SetFinalizer(geoms[i], nil) 437 | } 438 | ptrGeoms = &geomCPtrs[0] 439 | return geomFromC("NewCollection", cGEOSGeom_createCollection(C.int(_type), ptrGeoms, C.uint(len(geomCPtrs)))) 440 | } 441 | 442 | // Clone performs a deep copy on the geometry. 443 | func (g *Geometry) Clone() (*Geometry, error) { 444 | return geomFromC("Clone", cGEOSGeom_clone(g.g)) 445 | } 446 | 447 | // Unary topology functions 448 | 449 | // Envelope is the bounding box of a geometry, as a polygon. 450 | func (g *Geometry) Envelope() (*Geometry, error) { 451 | return g.unaryTopo("Envelope", cGEOSEnvelope) 452 | } 453 | 454 | // ConvexHull computes the smallest convex geometry that contains all the points 455 | // of the geometry. 456 | func (g *Geometry) ConvexHull() (*Geometry, error) { 457 | return g.unaryTopo("ConvexHull", cGEOSConvexHull) 458 | } 459 | 460 | // Boundary is the boundary of the geometry. 461 | func (g *Geometry) Boundary() (*Geometry, error) { 462 | return g.unaryTopo("Boundary", cGEOSBoundary) 463 | } 464 | 465 | // UnaryUnion computes the union of all the constituent geometries of the 466 | // geometry. 467 | func (g *Geometry) UnaryUnion() (*Geometry, error) { 468 | return g.unaryTopo("UnaryUnion", cGEOSUnaryUnion) 469 | } 470 | 471 | // PointOnSurface computes a point geometry guaranteed to be on the surface of 472 | // the geometry. 473 | func (g *Geometry) PointOnSurface() (*Geometry, error) { 474 | return g.unaryTopo("PointOnSurface", cGEOSPointOnSurface) 475 | } 476 | 477 | // Centroid is the center point of the geometry. 478 | func (g *Geometry) Centroid() (*Geometry, error) { 479 | return g.unaryTopo("Centroid", cGEOSGetCentroid) 480 | } 481 | 482 | // LineMerge will merge together a collection of LineStrings where they touch 483 | // only at their start and end points. The LineStrings must be fully noded. The 484 | // resulting geometry is a new collection. 485 | func (g *Geometry) LineMerge() (*Geometry, error) { 486 | return g.unaryTopo("LineMerge", cGEOSLineMerge) 487 | } 488 | 489 | // Simplify returns a geometry simplified by amount given by tolerance. 490 | // May not preserve topology -- see SimplifyP. 491 | func (g *Geometry) Simplify(tolerance float64) (*Geometry, error) { 492 | return g.simplify("simplify", cGEOSSimplify, tolerance) 493 | } 494 | 495 | // SimplifyP returns a geometry simplified by amount given by tolerance. 496 | // Unlike Simplify, SimplifyP guarantees it will preserve topology. 497 | func (g *Geometry) SimplifyP(tolerance float64) (*Geometry, error) { 498 | return g.simplify("simplify", cGEOSTopologyPreserveSimplify, tolerance) 499 | } 500 | 501 | // UniquePoints return all distinct vertices of input geometry as a MultiPoint. 502 | func (g *Geometry) UniquePoints() (*Geometry, error) { 503 | return g.unaryTopo("UniquePoints", cGEOSGeom_extractUniquePoints) 504 | } 505 | 506 | // SharedPaths finds paths shared between the two given lineal geometries. 507 | // Returns a GeometryCollection having two elements: 508 | // - first element is a MultiLineString containing shared paths having the _same_ direction on both inputs 509 | // - second element is a MultiLineString containing shared paths having the _opposite_ direction on the two inputs 510 | func (g *Geometry) SharedPaths(other *Geometry) (*Geometry, error) { 511 | return g.binaryTopo("SharedPaths", cGEOSSharedPaths, other) 512 | } 513 | 514 | // Snap returns a new geometry where the geometry is snapped to the given 515 | // geometry by given tolerance. 516 | func (g *Geometry) Snap(other *Geometry, tolerance float64) (*Geometry, error) { 517 | return geomFromC("Snap", cGEOSSnap(g.g, other.g, C.double(tolerance))) 518 | } 519 | 520 | // Prepare returns a new prepared geometry from the geometry -- see PGeometry 521 | func (g *Geometry) Prepare() *PGeometry { 522 | return PrepareGeometry(g) 523 | } 524 | 525 | // Binary topology functions 526 | 527 | // Intersection returns a new geometry representing the points shared by this 528 | // geometry and the other. 529 | func (g *Geometry) Intersection(other *Geometry) (*Geometry, error) { 530 | return g.binaryTopo("Intersection", cGEOSIntersection, other) 531 | } 532 | 533 | // Difference returns a new geometry representing the points making up this 534 | // geometry that do not make up the other. 535 | func (g *Geometry) Difference(other *Geometry) (*Geometry, error) { 536 | return g.binaryTopo("Difference", cGEOSDifference, other) 537 | } 538 | 539 | // SymDifference returns a new geometry representing the set combining the 540 | // points in this geometry not in the other, and the points in the other 541 | // geometry and not in this. 542 | func (g *Geometry) SymDifference(other *Geometry) (*Geometry, error) { 543 | return g.binaryTopo("SymDifference", cGEOSSymDifference, other) 544 | } 545 | 546 | // Union returns a new geometry representing all points in this geometry and the 547 | // other. 548 | func (g *Geometry) Union(other *Geometry) (*Geometry, error) { 549 | return g.binaryTopo("Union", cGEOSUnion, other) 550 | } 551 | 552 | // Binary predicate functions 553 | 554 | // Disjoint returns true if the two geometries have no point in common. 555 | func (g *Geometry) Disjoint(other *Geometry) (bool, error) { 556 | return g.binaryPred("Disjoint", cGEOSDisjoint, other) 557 | } 558 | 559 | // Touches returns true if the two geometries have at least one point in common, 560 | // but their interiors do not intersect. 561 | func (g *Geometry) Touches(other *Geometry) (bool, error) { 562 | return g.binaryPred("Touches", cGEOSTouches, other) 563 | } 564 | 565 | // Intersects returns true if the two geometries have at least one point in 566 | // common. 567 | func (g *Geometry) Intersects(other *Geometry) (bool, error) { 568 | return g.binaryPred("Intersects", cGEOSIntersects, other) 569 | } 570 | 571 | // Crosses returns true if the two geometries have some but not all interior 572 | // points in common. 573 | func (g *Geometry) Crosses(other *Geometry) (bool, error) { 574 | return g.binaryPred("Crosses", cGEOSCrosses, other) 575 | } 576 | 577 | // Within returns true if every point of this geometry is a point of the other, 578 | // and the interiors of the two geometries have at least one point in common. 579 | func (g *Geometry) Within(other *Geometry) (bool, error) { 580 | return g.binaryPred("Within", cGEOSWithin, other) 581 | } 582 | 583 | // Contains returns true if every point of the other is a point of this geometry, 584 | // and the interiors of the two geometries have at least one point in common. 585 | func (g *Geometry) Contains(other *Geometry) (bool, error) { 586 | return g.binaryPred("Contains", cGEOSContains, other) 587 | } 588 | 589 | // Overlaps returns true if the geometries have some but not all points in 590 | // common, they have the same dimension, and the intersection of the interiors 591 | // of the two geometries has the same dimension as the geometries themselves. 592 | func (g *Geometry) Overlaps(other *Geometry) (bool, error) { 593 | return g.binaryPred("Overlaps", cGEOSOverlaps, other) 594 | } 595 | 596 | // Equals returns true if the two geometries have at least one point in common, 597 | // and no point of either geometry lies in the exterior of the other geometry. 598 | func (g *Geometry) Equals(other *Geometry) (bool, error) { 599 | return g.binaryPred("Equals", cGEOSEquals, other) 600 | } 601 | 602 | // Covers returns true if every point of the other geometry is a point of this 603 | // geometry. 604 | func (g *Geometry) Covers(other *Geometry) (bool, error) { 605 | return g.binaryPred("Covers", cGEOSCovers, other) 606 | } 607 | 608 | // CoveredBy returns true if every point of this geometry is a point of the 609 | // other geometry. 610 | func (g *Geometry) CoveredBy(other *Geometry) (bool, error) { 611 | return g.binaryPred("CoveredBy", cGEOSCoveredBy, other) 612 | } 613 | 614 | // EqualsExact returns true if both geometries are Equal, as evaluated by their 615 | // points being within the given tolerance. 616 | func (g *Geometry) EqualsExact(other *Geometry, tolerance float64) (bool, error) { 617 | return boolFromC("EqualsExact", cGEOSEqualsExact(g.g, other.g, C.double(tolerance))) 618 | } 619 | 620 | // Unary predicate functions 621 | 622 | // IsEmpty returns true if the set of points of this geometry is empty (i.e., 623 | // the empty geometry). 624 | func (g *Geometry) IsEmpty() (bool, error) { 625 | return g.unaryPred("IsEmpty", cGEOSisEmpty) 626 | } 627 | 628 | // IsSimple returns true iff the only self-intersections are at boundary points. 629 | func (g *Geometry) IsSimple() (bool, error) { 630 | return g.unaryPred("IsSimple", cGEOSisSimple) 631 | } 632 | 633 | // IsRing returns true if the lineal geometry has the ring property. 634 | func (g *Geometry) IsRing() (bool, error) { 635 | return g.unaryPred("IsRing", cGEOSisRing) 636 | } 637 | 638 | // HasZ returns true if the geometry is 3D. 639 | func (g *Geometry) HasZ() (bool, error) { 640 | return g.unaryPred("HasZ", cGEOSHasZ) 641 | } 642 | 643 | // IsClosed returns true if the geometry is closed (i.e., start & end points 644 | // equal). 645 | func (g *Geometry) IsClosed() (bool, error) { 646 | return g.unaryPred("IsClosed", cGEOSisClosed) 647 | } 648 | 649 | // Geometry info functions 650 | 651 | // Type returns the SFS type of the geometry. 652 | func (g *Geometry) Type() (GeometryType, error) { 653 | i := cGEOSGeomTypeId(g.g) 654 | if i == -1 { 655 | // XXX: error 656 | return -1, Error() 657 | } 658 | return cGeomTypeIds[i], nil 659 | } 660 | 661 | // SRID returns the geometry's SRID, if set. 662 | func (g *Geometry) SRID() (int, error) { 663 | return intFromC("SRID", cGEOSGetSRID(g.g), 0) 664 | } 665 | 666 | // SetSRID sets the geometry's SRID. 667 | func (g *Geometry) SetSRID(srid int) { 668 | cGEOSSetSRID(g.g, C.int(srid)) 669 | } 670 | 671 | // NGeometry returns the number of component geometries (eg., for 672 | // a collection). 673 | func (g *Geometry) NGeometry() (int, error) { 674 | return intFromC("NGeometry", cGEOSGetNumGeometries(g.g), -1) 675 | } 676 | 677 | // XXX: method to return a slice of geometries 678 | 679 | // Geometry returns the nth sub-geometry of the geometry (eg., of a collection). 680 | func (g *Geometry) Geometry(n int) (*Geometry, error) { 681 | // According to GEOS C API, GEOSGetGeometryN returns a pointer to internal 682 | // storage and must not be destroyed directly, so we bypass the regular 683 | // constructor to avoid the finalizer. 684 | return geomFromPtrUnowned(cGEOSGetGeometryN(g.g, C.int(n))) 685 | } 686 | 687 | // Normalize computes the normal form of the geometry. 688 | // Modifies geometry in-place, clone first if this is not wanted/safe. 689 | func (g *Geometry) Normalize() error { 690 | _, err := intFromC("Normalize", cGEOSNormalize(g.g), -1) 691 | return err 692 | } 693 | 694 | // NPoint returns the number of points in the geometry. 695 | func (g *Geometry) NPoint() (int, error) { 696 | return intFromC("NPoint", cGEOSGeomGetNumPoints(g.g), -1) 697 | } 698 | 699 | type float64Getter func(*C.GEOSGeometry, *C.double) C.int 700 | 701 | // X returns the x ordinate of the geometry. 702 | // Geometry must be a Point. 703 | func (g *Geometry) X() (float64, error) { 704 | return g.float64FromC("X", cGEOSGeomGetX, -1) 705 | } 706 | 707 | // Y returns the y ordinate of the geometry. 708 | // Geometry must be a Point 709 | func (g *Geometry) Y() (float64, error) { 710 | return g.float64FromC("Y", cGEOSGeomGetY, -1) 711 | } 712 | 713 | // Holes returns a slice of geometries (LinearRings) representing the interior 714 | // rings of a polygon (possibly nil). 715 | // Geometry must be a Polygon. 716 | func (g *Geometry) Holes() ([]*Geometry, error) { 717 | n, err := intFromC("NInteriorRing", cGEOSGetNumInteriorRings(g.g), -1) 718 | if err != nil { 719 | return nil, err 720 | } 721 | holes := make([]*Geometry, n) 722 | for i := 0; i < n; i++ { 723 | // According to the GEOS C API, GEOSGetInteriorRingN returns a pointer 724 | // to internal storage and must not be destroyed directly, so we bypass 725 | // the usual constructor to avoid the finalizer. 726 | ring, err := geomFromPtrUnowned(cGEOSGetInteriorRingN(g.g, C.int(i))) 727 | if err != nil { 728 | return nil, err 729 | } 730 | holes[i] = ring 731 | } 732 | return holes, nil 733 | } 734 | 735 | // XXX: Holes() returns a [][]Coord? 736 | 737 | // Shell returns the exterior ring (a LinearRing) of the geometry. 738 | // Geometry must be a Polygon. 739 | func (g *Geometry) Shell() (*Geometry, error) { 740 | // According to the GEOS C API, GEOSGetExteriorRing returns a pointer 741 | // to internal storage and must not be destroyed directly, so we bypass 742 | // the usual constructor to avoid the finalizer. 743 | return geomFromPtrUnowned(cGEOSGetExteriorRing(g.g)) 744 | } 745 | 746 | // NCoordinate returns the number of coordinates of the geometry. 747 | func (g *Geometry) NCoordinate() (int, error) { 748 | return intFromC("NCoordinate", cGEOSGetNumCoordinates(g.g), -1) 749 | } 750 | 751 | // Coords returns a slice of Coord, a sequence of coordinates underlying the 752 | // point, linestring, or linear ring. 753 | func (g *Geometry) Coords() ([]Coord, error) { 754 | ptr := cGEOSGeom_getCoordSeq(g.g) 755 | if ptr == nil { 756 | return nil, Error() 757 | } 758 | //cs := coordSeqFromPtr(ptr) 759 | cs := &coordSeq{c: ptr} 760 | return coordSlice(cs) 761 | } 762 | 763 | // Dimension returns the number of dimensions geometry, eg., 1 for point, 2 for 764 | // linestring. 765 | func (g *Geometry) Dimension() int { 766 | return int(cGEOSGeom_getDimensions(g.g)) 767 | } 768 | 769 | // CoordDimension returns the number of dimensions of the coordinates of the 770 | // geometry (2 or 3). 771 | func (g *Geometry) CoordDimension() int { 772 | return int(cGEOSGeom_getCoordinateDimension(g.g)) 773 | } 774 | 775 | // Point returns the nth point of the geometry. 776 | // Geometry must be LineString. 777 | func (g *Geometry) Point(n int) (*Geometry, error) { 778 | return geomFromC("Point", cGEOSGeomGetPointN(g.g, C.int(n))) 779 | } 780 | 781 | // StartPoint returns the 0th point of the geometry. 782 | // Geometry must be LineString. 783 | func (g *Geometry) StartPoint() (*Geometry, error) { 784 | return geomFromC("StartPoint", cGEOSGeomGetStartPoint(g.g)) 785 | } 786 | 787 | // EndPoint returns the (n-1)th point of the geometry. 788 | // Geometry must be LineString. 789 | func (g *Geometry) EndPoint() (*Geometry, error) { 790 | return geomFromC("EndPoint", cGEOSGeomGetEndPoint(g.g)) 791 | } 792 | 793 | // Misc functions 794 | 795 | // Area returns the area of the geometry, which must be a areal geometry like 796 | // a polygon or multipolygon. 797 | func (g *Geometry) Area() (float64, error) { 798 | return g.float64FromC("Area", cGEOSArea, 0) 799 | } 800 | 801 | // Length returns the length of the geometry, which must be a lineal geometry 802 | // like a linestring or linear ring. 803 | func (g *Geometry) Length() (float64, error) { 804 | return g.float64FromC("Length", cGEOSLength, 0) 805 | } 806 | 807 | // Distance returns the Cartesian distance between the two geometries. 808 | func (g *Geometry) Distance(other *Geometry) (float64, error) { 809 | return g.binaryFloat("Distance", cGEOSDistance, other) 810 | } 811 | 812 | // HausdorffDistance computes the maximum distance of the geometry to the nearest 813 | // point in the other geometry (i.e., considers the whole shape and position of 814 | // the geometries). 815 | func (g *Geometry) HausdorffDistance(other *Geometry) (float64, error) { 816 | return g.binaryFloat("HausdorffDistance", cGEOSHausdorffDistance, other) 817 | } 818 | 819 | // HausdorffDistanceDensify computes the Hausdorff distance (see 820 | // HausdorffDistance) with an additional densification fraction amount. 821 | func (g *Geometry) HausdorffDistanceDensify(other *Geometry, densifyFrac float64) (float64, error) { 822 | var d C.double 823 | return float64FromC("HausdorffDistanceDensify", cGEOSHausdorffDistanceDensify(g.g, other.g, C.double(densifyFrac), &d), d) 824 | } 825 | 826 | // DE-9IM 827 | 828 | // Relate computes the intersection matrix (Dimensionally Extended 829 | // Nine-Intersection Model (DE-9IM) matrix) for the spatial relationship between 830 | // the two geometries. 831 | func (g *Geometry) Relate(other *Geometry) (string, error) { 832 | cs := cGEOSRelate(g.g, other.g) 833 | if cs == nil { 834 | return "", Error() 835 | } 836 | s := C.GoString(cs) 837 | //cGEOSFree(unsafe.Pointer(cs)) 838 | return s, nil 839 | } 840 | 841 | // RelatePat returns true if the DE-9IM matrix equals the intersection matrix of 842 | // the two geometries. 843 | func (g *Geometry) RelatePat(other *Geometry, pat string) (bool, error) { 844 | cs := C.CString(pat) 845 | defer C.free(unsafe.Pointer(cs)) 846 | return boolFromC("RelatePat", cGEOSRelatePattern(g.g, other.g, cs)) 847 | } 848 | 849 | // various wrappers around C API 850 | 851 | type unaryTopo func(*C.GEOSGeometry) *C.GEOSGeometry 852 | type unaryPred func(*C.GEOSGeometry) C.char 853 | 854 | func (g *Geometry) unaryTopo(name string, cfn unaryTopo) (*Geometry, error) { 855 | return geomFromC(name, cfn(g.g)) 856 | } 857 | 858 | func (g *Geometry) unaryPred(name string, cfn unaryPred) (bool, error) { 859 | return boolFromC(name, cfn(g.g)) 860 | } 861 | 862 | type binaryTopo func(*C.GEOSGeometry, *C.GEOSGeometry) *C.GEOSGeometry 863 | type binaryPred func(*C.GEOSGeometry, *C.GEOSGeometry) C.char 864 | 865 | func (g *Geometry) binaryTopo(name string, cfn binaryTopo, other *Geometry) (*Geometry, error) { 866 | return geomFromC(name, cfn(g.g, other.g)) 867 | } 868 | 869 | func (g *Geometry) binaryPred(name string, cfn binaryPred, other *Geometry) (bool, error) { 870 | return boolFromC(name, cfn(g.g, other.g)) 871 | } 872 | 873 | func geomFromCoordSeq(cs *coordSeq, name string, cfn func(*C.GEOSCoordSequence) *C.GEOSGeometry) (*Geometry, error) { 874 | return geomFromC(name, cfn(cs.c)) 875 | } 876 | 877 | func emptyGeom(name string, cfn func() *C.GEOSGeometry) (*Geometry, error) { 878 | return geomFromC(name, cfn()) 879 | } 880 | 881 | func geomFromC(name string, ptr *C.GEOSGeometry) (*Geometry, error) { 882 | if ptr == nil { 883 | return nil, Error() 884 | } 885 | return geomFromPtr(ptr), nil 886 | } 887 | 888 | func boolFromC(name string, c C.char) (bool, error) { 889 | if c == 2 { 890 | return false, Error() 891 | } 892 | return c == 1, nil 893 | } 894 | 895 | func intFromC(name string, i C.int, exception C.int) (int, error) { 896 | if i == exception { 897 | return 0, Error() 898 | } 899 | return int(i), nil 900 | } 901 | 902 | func (g *Geometry) float64FromC(name string, cfn float64Getter, exception C.int) (float64, error) { 903 | var d C.double 904 | i := cfn(g.g, &d) 905 | if i == exception { 906 | return 0.0, Error() 907 | } 908 | return float64(d), nil 909 | } 910 | 911 | func float64FromC(name string, rv C.int, d C.double) (float64, error) { 912 | if rv == 0 { 913 | return 0.0, Error() 914 | } 915 | return float64(d), nil 916 | } 917 | 918 | type binaryFloatGetter func(*C.GEOSGeometry, *C.GEOSGeometry, *C.double) C.int 919 | 920 | func (g *Geometry) binaryFloat(name string, cfn binaryFloatGetter, other *Geometry) (float64, error) { 921 | var d C.double 922 | return float64FromC(name, cfn(g.g, other.g, &d), d) 923 | } 924 | 925 | func (g *Geometry) simplify(name string, cfn func(*C.GEOSGeometry, C.double) *C.GEOSGeometry, d float64) (*Geometry, error) { 926 | return geomFromC(name, cfn(g.g, C.double(d))) 927 | } 928 | --------------------------------------------------------------------------------