close open rings when end-nodes are close to each other but not identical

Oliver Tonnhofer 2014-10-21 17:08:56 +02:00
parent 5c376496c4
commit e440ca7489
6 changed files with 57 additions and 37 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)

View File

@ -357,7 +357,7 @@
<node id="7301" version="1" timestamp="2011-11-11T00:11:11Z" lat="60" lon="60"/>
<node id="7302" version="1" timestamp="2011-11-11T00:11:11Z" lat="60" lon="62"/>
<node id="7303" version="1" timestamp="2011-11-11T00:11:11Z" lat="62" lon="62"/>
<node id="7313" version="1" timestamp="2011-11-11T00:11:11Z" lat="62" lon="62"/>
<node id="7313" version="1" timestamp="2011-11-11T00:11:11Z" lat="62" lon="62.0000001"/>
<node id="7314" version="1" timestamp="2011-11-11T00:11:11Z" lat="62" lon="60"/>
<way id="7301" version="1" timestamp="2011-11-11T00:11:11Z">
<nd ref="7301"/>

View File

@ -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)

View File

@ -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)