diff --git a/.gitignore b/.gitignore index a8a45d7..93e465d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store /_site /tags +.idea diff --git a/geos/Makefile b/geos/Makefile index e36f098..1596f0e 100644 --- a/geos/Makefile +++ b/geos/Makefile @@ -1,6 +1,6 @@ .PHONY: fmt check -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 +SRCS = coord.go coordseq.go cwrappers.go geom.go geos.c geos.go geos.h helper.go prepared.go strtree.go types.go wkb.go wkt.go all: build diff --git a/geos/example_test.go b/geos/example_test.go index 488df4f..537010a 100644 --- a/geos/example_test.go +++ b/geos/example_test.go @@ -3,7 +3,7 @@ package geos_test import ( "fmt" - "github.com/paulsmith/gogeos/geos" + "github.com/robertogyn19/gogeos/geos" ) func ExampleGeometry_LineInterpolatePoint() { diff --git a/geos/examples/strtree/main.go b/geos/examples/strtree/main.go new file mode 100644 index 0000000..eb627d0 --- /dev/null +++ b/geos/examples/strtree/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/robertogyn19/gogeos/geos" +) + +// Callback used to get items from query/iterate functions +func handleResult(item []byte) { + i := Item{} + json.Unmarshal(item, &i) + + fmt.Printf("Item: %v\n", i) +} + +type Item struct { + Index int + Name string + Price float64 +} + +/** +Implement geos.STRTreeItem interface to be able to insert +*/ +func (i Item) Parse() []byte { + j, _ := json.Marshal(i) + return j +} + +func main() { + strTreeCapacity := 10 + strtree := geos.NewSTRTree(strTreeCapacity) + + points := []geos.Coord{geos.NewCoord(-46, -23)} + + for i := 1; i < strTreeCapacity; i++ { + points = append(points, geos.NewCoord(0, 0)) + } + + array := []geos.STRTreeItem{} + for i := 0; i < strTreeCapacity; i++ { + name := fmt.Sprintf("Product-%d", i+1) + p := float64((i + 1) * 10) + array = append(array, Item{i + 1, name, p}) + } + + for i, item := range array { + g, _ := geos.NewPoint(points[i]) + strtree.Insert(g, item) + } + + wktPolygon := "POLYGON ((-46.19064331 -23.15930069, -46.19064331 -22.79580631, -45.81367493 -22.79580631, -45.81367493 -23.15930069, -46.19064331 -23.15930069))" + polygon, _ := geos.FromWKT(wktPolygon) + + fmt.Println("Query with polygon (should print only one item):") + strtree.Query(polygon, handleResult) + + fmt.Println("\nIterate all items (should iterate all items):") + strtree.Iterate(handleResult) +} diff --git a/geos/geos.c b/geos/geos.c index 1f34522..3f81759 100644 --- a/geos/geos.c +++ b/geos/geos.c @@ -26,3 +26,7 @@ char *gogeos_get_last_error(void) { GEOSContextHandle_t gogeos_initGEOS() { return initGEOS_r(gogeos_notice_handler, gogeos_error_handler); } + +void strTreeQueryCallbackGo(void *item, void *data) { + StrTreeQueryCallbackGo(item, data); +} diff --git a/geos/geos.h b/geos/geos.h index 4662458..3e4f01e 100644 --- a/geos/geos.h +++ b/geos/geos.h @@ -8,3 +8,6 @@ void gogeos_notice_handler(const char *fmt, ...); void gogeos_error_handler(const char *fmt, ...); char *gogeos_get_last_error(void); GEOSContextHandle_t gogeos_initGEOS(); + +typedef void (*strTreeQueryCallback)(void*, void*); +void strTreeQueryCallbackGo(void *item, void *data); \ No newline at end of file diff --git a/geos/geos_test.go b/geos/geos_test.go index c99c7bd..c11e28d 100644 --- a/geos/geos_test.go +++ b/geos/geos_test.go @@ -6,7 +6,7 @@ import ( ) func TestVersion(t *testing.T) { - const re = `3\.3\.\d+-CAPI-1\.7\.\d+$` + const re = `3\.[3-4]\.\d+-CAPI-1\.[7-8]\.\d+.*` version := Version() matched, err := regexp.MatchString(re, version) if err != nil { diff --git a/geos/strtree.go b/geos/strtree.go new file mode 100644 index 0000000..8ec566c --- /dev/null +++ b/geos/strtree.go @@ -0,0 +1,126 @@ +package geos + +/* +#include "geos.h" +*/ +import "C" + +import ( + "fmt" + "runtime" + "unsafe" + "sync" +) + +type STRTreeCallbackFunc func(item []byte) +type STRTreeItem interface { + Parse() []byte +} + +type strTree struct { + r *C.GEOSSTRtree + cb C.GEOSQueryCallback +} + +func NewSTRTree(capacity int) *strTree { + r := cGEOSSTRtree_create(C.size_t(capacity)) + + if r == nil { + return nil + } + + strtree := &strTree{r, NewCallback()} + runtime.SetFinalizer(strtree, (*strTree).destroy) + + return strtree +} + +func (tree *strTree) Insert(g *Geometry, item STRTreeItem) { + bstr := item.Parse() + cstr := C.CString(string(bstr)) + p := (*C.void)(unsafe.Pointer(cstr)) + + cGEOSSTRtree_insert(tree.r, g.g, p) +} + +func (tree *strTree) Query(g *Geometry, cb STRTreeCallbackFunc) { + cbid := register(cb) + ccbid := C.int(cbid) + cGEOSSTRtree_query(tree.r, g.g, tree.cb, (*C.void)(unsafe.Pointer(&ccbid))) + defer unregister(cbid) +} + +func (tree *strTree) Iterate(cb STRTreeCallbackFunc) { + cbid := register(cb) + ccbid := C.int(cbid) + cGEOSSTRtree_iterate(tree.r, tree.cb, (*C.void)(unsafe.Pointer(&ccbid))) + defer unregister(cbid) +} + +func (tree *strTree) destroy() { + cGEOSSTRtree_destroy(tree.r) + tree.r = nil +} + +func NewCallback() C.GEOSQueryCallback { + return NewCustomCallback(unsafe.Pointer(C.strTreeQueryCallbackGo)) +} + +func NewCustomCallback(pointer unsafe.Pointer) C.GEOSQueryCallback { + return (C.GEOSQueryCallback)(pointer) +} + +/* ------------------------------------------------------------------------- */ +/* */ +/* Callback functions */ +/* */ +/* ------------------------------------------------------------------------- */ + +var mu sync.Mutex +var fns = make(map[int]STRTreeCallbackFunc) +var cbIndex int + +//export StrTreeQueryCallbackGo +func StrTreeQueryCallbackGo(item unsafe.Pointer, data unsafe.Pointer) { + s := itemBytes(item) + + if data == nil { + fmt.Println("Callback data not found. Forgot to register a callback?") + return + } + + ccbid := *(*C.int)(data) + cbid := int(ccbid) + cb := lookup(cbid) + cb(s) +} + +func itemBytes(item unsafe.Pointer) []byte { + value1 := (*C.char)(item) + return []byte(C.GoString(value1)) +} + +func register(fn STRTreeCallbackFunc) int { + mu.Lock() + defer mu.Unlock() + cbIndex++ + + for fns[cbIndex] != nil { + cbIndex++ + } + + fns[cbIndex] = fn + return cbIndex +} + +func lookup(i int) STRTreeCallbackFunc { + mu.Lock() + defer mu.Unlock() + return fns[i] +} + +func unregister(i int) { + mu.Lock() + defer mu.Unlock() + delete(fns, i) +} \ No newline at end of file diff --git a/geos/strtree_test.go b/geos/strtree_test.go new file mode 100644 index 0000000..1fe98ec --- /dev/null +++ b/geos/strtree_test.go @@ -0,0 +1,45 @@ +package geos + +import ( + "testing" +) + +func TestNewSTRTree(t *testing.T) { + tree := NewSTRTree(10) + + if tree == nil { + t.Error("STRTree should not be nil") + } +} + +type itemTest string + +func (i itemTest) Parse() []byte { + return []byte(i) +} + +func TestStrTree_Insert(t *testing.T) { + tree := NewSTRTree(10) + + item := itemTest("test1") + g, _ := FromWKT("POINT(-46 -23)") + tree.Insert(g, item) + + items := 0 + + cb := func(bitem []byte) { + items++ + + if items != 1 { + t.Error("Should have only one item") + } + + if itemTest(bitem) != item { + t.Errorf("Expect item: %s, got %s", item, bitem) + } + } + + tree.Iterate(cb) +} + +// TODO Improve tests \ No newline at end of file