diff --git a/geom/geojson/geojson.go b/geom/geojson/geojson.go index f02f14a..c60e2ed 100644 --- a/geom/geojson/geojson.go +++ b/geom/geojson/geojson.go @@ -3,6 +3,7 @@ package geojson import ( "encoding/json" "errors" + "fmt" "imposm3/geom/geos" "imposm3/proj" "io" @@ -68,6 +69,24 @@ func newLineStringFromCoords(coords []interface{}) (lineString, error) { type polygon []lineString +type polygonFeature struct { + polygon polygon + properties map[string]string +} + +type Feature struct { + Geom *geos.Geom + Properties map[string]string +} + +func stringProperties(properties map[string]interface{}) map[string]string { + result := make(map[string]string, len(properties)) + for k, v := range properties { + result[k] = fmt.Sprintf("%v", v) + } + return result +} + func newPolygonFromCoords(coords []interface{}) (polygon, error) { poly := polygon{} @@ -85,24 +104,24 @@ func newPolygonFromCoords(coords []interface{}) (polygon, error) { return poly, nil } -func newMultiPolygonFromCoords(coords []interface{}) ([]polygon, error) { - mp := []polygon{} +func newMultiPolygonFeaturesFromCoords(coords []interface{}) ([]polygonFeature, error) { + features := []polygonFeature{} for _, part := range coords { polyCoords, ok := part.([]interface{}) if !ok { - return mp, errors.New("multipolygon polygon not a list") + return features, errors.New("multipolygon polygon not a list") } poly, err := newPolygonFromCoords(polyCoords) if err != nil { - return mp, err + return features, err } - mp = append(mp, poly) + features = append(features, polygonFeature{poly, nil}) } - return mp, nil + return features, nil } -func ParseGeoJson(r io.Reader) ([]*geos.Geom, error) { +func ParseGeoJson(r io.Reader) ([]Feature, error) { decoder := json.NewDecoder(r) obj := &object{} @@ -111,7 +130,7 @@ func ParseGeoJson(r io.Reader) ([]*geos.Geom, error) { return nil, err } - polygons, err := constructPolygons(obj) + polygons, err := constructPolygonFeatures(obj) if err != nil { return nil, err @@ -119,20 +138,20 @@ func ParseGeoJson(r io.Reader) ([]*geos.Geom, error) { g := geos.NewGeos() defer g.Finish() - result := []*geos.Geom{} + result := []Feature{} for _, p := range polygons { - geom, err := geosPolygon(g, p) + geom, err := geosPolygon(g, p.polygon) if err != nil { return nil, err } - result = append(result, geom) + result = append(result, Feature{geom, p.properties}) } return result, err } -func constructPolygons(obj *object) ([]polygon, error) { +func constructPolygonFeatures(obj *object) ([]polygonFeature, error) { switch obj.Type { case "Point": return nil, errors.New("only polygon or MultiPolygon are supported") @@ -140,22 +159,29 @@ func constructPolygons(obj *object) ([]polygon, error) { return nil, errors.New("only polygon or MultiPolygon are supported") case "Polygon": poly, err := newPolygonFromCoords(obj.Coordinates) - return []polygon{poly}, err + return []polygonFeature{{poly, nil}}, err case "MultiPolygon": - poly, err := newMultiPolygonFromCoords(obj.Coordinates) + poly, err := newMultiPolygonFeaturesFromCoords(obj.Coordinates) return poly, err case "Feature": - geom, err := constructPolygons(obj.Geometry) - return geom, err + features, err := constructPolygonFeatures(obj.Geometry) + if err != nil { + return nil, err + } + properties := stringProperties(obj.Properties) + for i, _ := range features { + features[i].properties = properties + } + return features, err case "FeatureCollection": - features := make([]polygon, 0) + features := make([]polygonFeature, 0) for _, obj := range obj.Features { - geom, err := constructPolygons(&obj) + f, err := constructPolygonFeatures(&obj) if err != nil { return nil, err } - features = append(features, geom...) + features = append(features, f...) } return features, nil default: diff --git a/geom/geojson/geojson_test.go b/geom/geojson/geojson_test.go index d55614c..6c146f5 100644 --- a/geom/geojson/geojson_test.go +++ b/geom/geojson/geojson_test.go @@ -8,49 +8,49 @@ import ( func TestParsePolygon(t *testing.T) { r := bytes.NewBufferString(`{"type": "Polygon", "coordinates": [[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 2000], [1000, 1000]]]}`) - geoms, err := ParseGeoJson(r) + features, err := ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 1 { - t.Fatal(geoms) + if len(features) != 1 { + t.Fatal(features) } - if math.Abs(geoms[0].Area()-1000000) > 0.00001 { - t.Fatal(geoms[0].Area()) + if math.Abs(features[0].Geom.Area()-1000000) > 0.00001 { + t.Fatal(features[0].Geom.Area()) } // ignore z values r = bytes.NewBufferString(`{"type": "Polygon", "coordinates": [[[1000, 1000, 1000], [2000, 1000, 1000], [2000, 2000, 1000], [1000, 2000, 1000], [1000, 1000, 1000]]]}`) - geoms, err = ParseGeoJson(r) + features, err = ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 1 { - t.Fatal(geoms) + if len(features) != 1 { + t.Fatal(features) } - if math.Abs(geoms[0].Area()-1000000) > 0.00001 { - t.Fatal(geoms[0].Area()) + if math.Abs(features[0].Geom.Area()-1000000) > 0.00001 { + t.Fatal(features[0].Geom.Area()) } r = bytes.NewBufferString(`{"type": "Polygon", "coordinates": [[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 2000], [1000, 1000]], [[500, 500], [600, 500], [600, 600], [500, 600], [500, 500]]]}`) - geoms, err = ParseGeoJson(r) + features, err = ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 1 { - t.Fatal(geoms) + if len(features) != 1 { + t.Fatal(features) } - if math.Abs(geoms[0].Area()-990000) > 0.00001 { - t.Fatal(geoms[0].Area()) + if math.Abs(features[0].Geom.Area()-990000) > 0.00001 { + t.Fatal(features[0].Geom.Area()) } } @@ -60,14 +60,14 @@ func TestParseMultiPolygon(t *testing.T) { [[[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 1000]]], [[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 1000]]]] }`) - geoms, err := ParseGeoJson(r) + features, err := ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 2 { - t.Fatal(geoms) + if len(features) != 2 { + t.Fatal(features) } } @@ -75,17 +75,17 @@ func TestParseFeature(t *testing.T) { r := bytes.NewBufferString(`{"type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 2000], [1000, 1000]]] }}`) - geoms, err := ParseGeoJson(r) + features, err := ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 1 { - t.Fatal(geoms) + if len(features) != 1 { + t.Fatal(features) } - if math.Abs(geoms[0].Area()-1000000) > 0.00001 { - t.Fatal(geoms[0].Area()) + if math.Abs(features[0].Geom.Area()-1000000) > 0.00001 { + t.Fatal(features[0].Geom.Area()) } } @@ -98,40 +98,47 @@ func TestParseFeatureCollection(t *testing.T) { {"type": "Polygon", "coordinates": [[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 2000], [1000, 1000]]]} } ]}`) - geoms, err := ParseGeoJson(r) + features, err := ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 2 { - t.Fatal(geoms) + if len(features) != 2 { + t.Fatal(features) } - if math.Abs(geoms[0].Area()-1000000) > 0.00001 { - t.Fatal(geoms[0].Area()) + if math.Abs(features[0].Geom.Area()-1000000) > 0.00001 { + t.Fatal(features[0].Geom.Area()) } } func TestParseGeoJson(t *testing.T) { r := bytes.NewBufferString(`{"type": "FeatureCollection", "features": [ - {"type": "Feature", "geometry": + {"type": "Feature", "properties": {"foo": "bar", "baz": 42}, "geometry": {"type": "Polygon", "coordinates": [[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 2000], [1000, 1000]]]} }, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[1000, 1000], [2000, 1000], [2000, 2000], [1000, 2000], [1000, 1000]]]} } ]}`) - geoms, err := ParseGeoJson(r) + features, err := ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 2 { - t.Fatal(geoms) + if len(features) != 2 { + t.Fatal(features) } - if math.Abs(geoms[0].Area()-1000000) > 0.00001 { - t.Fatal(geoms[0].Area()) + if v, ok := features[0].Properties["foo"]; !ok || v != "bar" { + t.Errorf("foo != bar, but '%v'", v) + } + if v, ok := features[0].Properties["baz"]; !ok || v != "42" { + t.Errorf("baz != 42, but '%v'", v) + } + + if math.Abs(features[0].Geom.Area()-1000000) > 0.00001 { + t.Fatal(features[0].Geom.Area()) } } @@ -145,16 +152,16 @@ func TestParseGeoJsonTransform(t *testing.T) { {"type": "Polygon", "coordinates": [[[9, 53], [10, 53], [10, 54], [9, 54], [9, 53]]]} } ]}`) - geoms, err := ParseGeoJson(r) + features, err := ParseGeoJson(r) if err != nil { t.Fatal(err) } - if len(geoms) != 2 { - t.Fatal(geoms) + if len(features) != 2 { + t.Fatal(features) } - if math.Abs(geoms[0].Area()-20834374847.98027) > 0.01 { - t.Fatal(geoms[0].Area()) + if math.Abs(features[0].Geom.Area()-20834374847.98027) > 0.01 { + t.Fatal(features[0].Geom.Area()) } } diff --git a/geom/geos/index.go b/geom/geos/index.go index 9239c34..edbb9d1 100644 --- a/geom/geos/index.go +++ b/geom/geos/index.go @@ -46,8 +46,19 @@ func (this *Geos) IndexAdd(index *Index, geom *Geom) { index.geoms = append(index.geoms, IndexGeom{geom}) } +// IndexQueryGeoms queries the index for intersections with geom. +func (this *Geos) IndexQueryGeoms(index *Index, geom *Geom) []IndexGeom { + hits := this.IndexQuery(index, geom) + + var geoms []IndexGeom + for _, idx := range hits { + geoms = append(geoms, index.geoms[idx]) + } + return geoms +} + // IndexQuery queries the index for intersections with geom. -func (this *Geos) IndexQuery(index *Index, geom *Geom) []IndexGeom { +func (this *Geos) IndexQuery(index *Index, geom *Geom) []int { index.mu.Lock() defer index.mu.Unlock() var num C.uint32_t @@ -58,9 +69,9 @@ func (this *Geos) IndexQuery(index *Index, geom *Geom) []IndexGeom { hits := (*[2 << 16]C.uint32_t)(unsafe.Pointer(r))[:num] defer C.free(unsafe.Pointer(r)) - var geoms []IndexGeom - for _, idx := range hits { - geoms = append(geoms, index.geoms[idx]) + indices := make([]int, len(hits)) + for i := range hits { + indices[i] = int(hits[i]) } - return geoms + return indices } diff --git a/geom/limit/limit.go b/geom/limit/limit.go index 8120db6..fdd6703 100644 --- a/geom/limit/limit.go +++ b/geom/limit/limit.go @@ -140,7 +140,7 @@ func NewFromGeoJsonWithBuffered(source string, buffer float64) (*Limiter, error) } defer f.Close() - geoms, err := geojson.ParseGeoJson(f) + features, err := geojson.ParseGeoJson(f) if err != nil { return nil, err } @@ -158,9 +158,9 @@ func NewFromGeoJsonWithBuffered(source string, buffer float64) (*Limiter, error) withBuffer = true } - for _, geom := range geoms { + for _, feature := range features { if withBuffer { - simplified := g.SimplifyPreserveTopology(geom, 1000) + simplified := g.SimplifyPreserveTopology(feature.Geom, 1000) if simplified == nil { return nil, errors.New("couldn't simplify limitto") } @@ -172,9 +172,9 @@ func NewFromGeoJsonWithBuffered(source string, buffer float64) (*Limiter, error) // buffered gets destroyed in UnionPolygons bufferedPolygons = append(bufferedPolygons, buffered) } - polygons = append(polygons, geom) + polygons = append(polygons, feature.Geom) - parts, err := SplitPolygonAtAutoGrid(g, geom) + parts, err := SplitPolygonAtAutoGrid(g, feature.Geom) if err != nil { return nil, err @@ -267,7 +267,7 @@ func (l *Limiter) Clip(geom *geos.Geom) ([]*geos.Geom, error) { l.geomPrepMu.Unlock() // we have intersections, query index to get intersecting parts - hits := g.IndexQuery(l.index, geom) + hits := g.IndexQueryGeoms(l.index, geom) geomType := g.Type(geom)