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)