diff --git a/geom/multipolygon.go b/geom/multipolygon.go index 2111013..3fceac8 100644 --- a/geom/multipolygon.go +++ b/geom/multipolygon.go @@ -2,34 +2,20 @@ package geom import ( "errors" + "sort" + "github.com/omniscale/imposm3/element" "github.com/omniscale/imposm3/geom/geos" - "sort" ) -func BuildRelation(rel *element.Relation, srid int) error { - rings, err := BuildRings(rel) - if err != nil { - return err - } - - rel.Tags = relationTags(rel.Tags, rings[0].ways[0].Tags) - - _, err = BuildRelGeometry(rel, rings, srid) - if err != nil { - return err - } - return nil -} - type preparedRelation struct { rings []*Ring rel *element.Relation srid int } -func PrepareRelation(rel *element.Relation, srid int) (*preparedRelation, error) { - rings, err := BuildRings(rel) +func PrepareRelation(rel *element.Relation, srid int, maxRingGap float64) (*preparedRelation, error) { + rings, err := BuildRings(rel, maxRingGap) if err != nil { return nil, err } @@ -57,7 +43,7 @@ func destroyRings(g *geos.Geos, rings []*Ring) { } } -func BuildRings(rel *element.Relation) ([]*Ring, error) { +func BuildRings(rel *element.Relation, maxRingGap float64) ([]*Ring, error) { var rings []*Ring var incompleteRings []*Ring var completeRings []*Ring @@ -101,7 +87,7 @@ func BuildRings(rel *element.Relation) ([]*Ring, error) { } // create geometries for merged rings for _, ring := range mergedRings { - if !ring.IsClosed() { + if !ring.IsClosed() && !ring.TryClose(maxRingGap) { err = ErrorNoRing // for defer return nil, err } diff --git a/geom/multipolygon_test.go b/geom/multipolygon_test.go index db0e99e..2923505 100644 --- a/geom/multipolygon_test.go +++ b/geom/multipolygon_test.go @@ -1,10 +1,11 @@ package geom import ( - "github.com/omniscale/imposm3/element" - "github.com/omniscale/imposm3/geom/geos" "math" "testing" + + "github.com/omniscale/imposm3/element" + "github.com/omniscale/imposm3/geom/geos" ) type coord struct { @@ -25,6 +26,16 @@ func makeWay(id int64, tags element.Tags, coords []coord) element.Way { return way } +func buildRelation(rel *element.Relation, srid int) error { + prep, err := PrepareRelation(rel, srid, 0.1) + if err != nil { + return err + } + + _, err = prep.Build() + return err +} + func TestSimplePolygonWithHole(t *testing.T) { w1 := makeWay(1, element.Tags{}, []coord{ {1, 0, 0}, @@ -48,7 +59,7 @@ func TestSimplePolygonWithHole(t *testing.T) { {2, element.WAY, "inner", &w2}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -88,7 +99,7 @@ func TestMultiPolygonWithHoleAndRelName(t *testing.T) { {2, element.WAY, "inner", &w2}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -139,7 +150,7 @@ func TestMultiPolygonWithMultipleHoles(t *testing.T) { {3, element.WAY, "inner", &w3}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -205,7 +216,7 @@ func TestMultiPolygonWithNeastedHoles(t *testing.T) { {5, element.WAY, "inner", &w5}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -247,7 +258,7 @@ func TestPolygonFromThreeWays(t *testing.T) { {3, element.WAY, "inner", &w3}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -296,7 +307,7 @@ func TestTouchingPolygonsWithHole(t *testing.T) { {2, element.WAY, "outer", &w2}, {3, element.WAY, "inner", &w3}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -335,7 +346,7 @@ func TestInsertedWaysDifferentTags(t *testing.T) { {2, element.WAY, "inner", &w2}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -374,7 +385,7 @@ func TestInsertMultipleTags(t *testing.T) { {2, element.WAY, "inner", &w2}, } - BuildRelation(&rel, 3857) + buildRelation(&rel, 3857) g := geos.NewGeos() defer g.Finish() @@ -420,7 +431,7 @@ func TestBrokenPolygonSelfIntersect(t *testing.T) { {2, element.WAY, "inner", &w2}, } - err := BuildRelation(&rel1, 3857) + err := buildRelation(&rel1, 3857) if err != nil { t.Fatal(err) } @@ -461,7 +472,7 @@ func TestBrokenPolygonSelfIntersect(t *testing.T) { {2, element.WAY, "inner", &w2}, } - err = BuildRelation(&rel2, 3857) + err = buildRelation(&rel2, 3857) if err != nil { t.Fatal(err) } @@ -510,7 +521,7 @@ func TestBrokenPolygonSelfIntersectTriangle(t *testing.T) { {2, element.WAY, "inner", &w2}, } - err := BuildRelation(&rel, 3857) + err := buildRelation(&rel, 3857) if err != nil { t.Fatal(err) } @@ -550,7 +561,7 @@ func TestBrokenPolygonSelfIntersectTriangle(t *testing.T) { {2, element.WAY, "inner", &w4}, } - err = BuildRelation(&rel, 3857) + err = buildRelation(&rel, 3857) if err != nil { t.Fatal(err) } diff --git a/geom/ring.go b/geom/ring.go index a07dda7..0889aa6 100644 --- a/geom/ring.go +++ b/geom/ring.go @@ -1,6 +1,8 @@ package geom import ( + "math" + "github.com/omniscale/imposm3/element" "github.com/omniscale/imposm3/geom/geos" ) @@ -20,6 +22,22 @@ func (r *Ring) IsClosed() bool { return len(r.refs) >= 4 && r.refs[0] == r.refs[len(r.refs)-1] } +// TryClose closes the ring if both end nodes are nearly identical. +// Returns true if it succeeds. +func (r *Ring) TryClose(maxRingGap float64) bool { + if len(r.refs) < 4 { + return false + } + start, end := r.nodes[0], r.nodes[len(r.nodes)-1] + dist := math.Hypot(start.Lat-end.Lat, start.Long-end.Long) + if dist < maxRingGap { + r.refs[len(r.refs)-1] = r.refs[0] + r.nodes[len(r.nodes)-1] = r.nodes[0] + return true + } + return false +} + func (r *Ring) MarkInserted(tags element.Tags) { if r.inserted == nil { r.inserted = make(map[int64]bool) diff --git a/test/complete_db.osm b/test/complete_db.osm index f061a13..b6cd142 100644 --- a/test/complete_db.osm +++ b/test/complete_db.osm @@ -357,7 +357,7 @@ - + diff --git a/test/complete_db_test.py b/test/complete_db_test.py index 4906e7e..9db33c0 100644 --- a/test/complete_db_test.py +++ b/test/complete_db_test.py @@ -254,7 +254,6 @@ def test_generalized_linestring_is_valid(): assert road['geometry'].is_valid, road['geometry'].wkt assert road['geometry'].length > 1000000 -@unittest.skip("not implemented") def test_relation_with_gap(): """Multipolygon with gap (overlapping but different endpoints) gets closed""" park = t.query_row(t.db_conf, 'osm_landusages', -7301) diff --git a/writer/relations.go b/writer/relations.go index f472c10..9262b18 100644 --- a/writer/relations.go +++ b/writer/relations.go @@ -19,6 +19,7 @@ type RelationWriter struct { singleIdSpace bool rel chan *element.Relation polygonMatcher mapping.RelWayMatcher + maxRingGap float64 } func NewRelationWriter( @@ -31,6 +32,10 @@ func NewRelationWriter( matcher mapping.RelWayMatcher, srid int, ) *OsmElemWriter { + maxRingGap := 1e-1 // 0.1m + if srid == 4326 { + maxRingGap = 1e-6 // ~0.1m + } rw := RelationWriter{ OsmElemWriter: OsmElemWriter{ osmCache: osmCache, @@ -43,6 +48,7 @@ func NewRelationWriter( singleIdSpace: singleIdSpace, polygonMatcher: matcher, rel: rel, + maxRingGap: maxRingGap, } rw.OsmElemWriter.writer = &rw return &rw.OsmElemWriter @@ -90,7 +96,7 @@ NextRel: // prepare relation first (build rings and compute actual // relation tags) - prepedRel, err := geom.PrepareRelation(r, rw.srid) + prepedRel, err := geom.PrepareRelation(r, rw.srid, rw.maxRingGap) if err != nil { if errl, ok := err.(ErrorLevel); !ok || errl.Level() > 0 { log.Warn(err)