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

Oliver Tonnhofer 2014-10-22 13:36:06 +02:00
parent e440ca7489
commit 0a311d62d7
6 changed files with 49 additions and 24 deletions

View File

@ -2,6 +2,7 @@ package element
import ( import (
"fmt" "fmt"
"math"
"sort" "sort"
"github.com/omniscale/imposm3/geom/geos" "github.com/omniscale/imposm3/geom/geos"
@ -40,6 +41,26 @@ func (w *Way) IsClosed() bool {
return len(w.Refs) >= 4 && w.Refs[0] == w.Refs[len(w.Refs)-1] return len(w.Refs) >= 4 && w.Refs[0] == w.Refs[len(w.Refs)-1]
} }
func (w *Way) TryClose(maxGap float64) bool {
return TryCloseWay(w.Refs, w.Nodes, maxGap)
}
// TryCloseWay closes the way if both end nodes are nearly identical.
// Returns true if it succeeds.
func TryCloseWay(refs []int64, nodes []Node, maxGap float64) bool {
if len(refs) < 4 {
return false
}
start, end := nodes[0], nodes[len(nodes)-1]
dist := math.Hypot(start.Lat-end.Lat, start.Long-end.Long)
if dist < maxGap {
refs[len(refs)-1] = refs[0]
nodes[len(nodes)-1] = nodes[0]
return true
}
return false
}
type MemberType int type MemberType int
const ( const (

View File

@ -1,8 +1,6 @@
package geom package geom
import ( import (
"math"
"github.com/omniscale/imposm3/element" "github.com/omniscale/imposm3/element"
"github.com/omniscale/imposm3/geom/geos" "github.com/omniscale/imposm3/geom/geos"
) )
@ -22,20 +20,8 @@ func (r *Ring) IsClosed() bool {
return len(r.refs) >= 4 && r.refs[0] == r.refs[len(r.refs)-1] 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 { func (r *Ring) TryClose(maxRingGap float64) bool {
if len(r.refs) < 4 { return element.TryCloseWay(r.refs, r.nodes, maxRingGap)
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) { func (r *Ring) MarkInserted(tags element.Tags) {

View File

@ -353,7 +353,7 @@
<tag k="name" v="self-intersecting"/> <tag k="name" v="self-intersecting"/>
</way> </way>
<!-- relation with "gap" (ways overlap, but are only sharing one endpoint) --> <!-- relation/way with "gap" (ways overlap, but are only sharing one endpoint) -->
<node id="7301" version="1" timestamp="2011-11-11T00:11:11Z" lat="60" lon="60"/> <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="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="7303" version="1" timestamp="2011-11-11T00:11:11Z" lat="62" lon="62"/>
@ -377,6 +377,15 @@
<tag k="type" v="multipolygon"/> <tag k="type" v="multipolygon"/>
</relation> </relation>
<way id="7311" version="1" timestamp="2011-11-11T00:11:11Z">
<nd ref="7313"/> <!-- same coord as 7303 -->
<nd ref="7314"/>
<nd ref="7301"/>
<nd ref="7302"/>
<nd ref="7303"/>
<tag k="landuse" v="park"/>
</way>
<!-- test that single node ways or incomplete polygons are _not_ inserted --> <!-- test that single node ways or incomplete polygons are _not_ inserted -->
<node id="30001" version="1" timestamp="2011-11-11T00:11:11Z" lat="47" lon="80"/> <node id="30001" version="1" timestamp="2011-11-11T00:11:11Z" lat="47" lon="80"/>
<node id="30002" version="1" timestamp="2011-11-11T00:11:11Z" lat="47" lon="80"/> <node id="30002" version="1" timestamp="2011-11-11T00:11:11Z" lat="47" lon="80"/>

View File

@ -254,11 +254,14 @@ def test_generalized_linestring_is_valid():
assert road['geometry'].is_valid, road['geometry'].wkt assert road['geometry'].is_valid, road['geometry'].wkt
assert road['geometry'].length > 1000000 assert road['geometry'].length > 1000000
def test_relation_with_gap(): def test_ring_with_gap():
"""Multipolygon with gap (overlapping but different endpoints) gets closed""" """Multipolygon and way with gap (overlapping but different endpoints) gets closed"""
park = t.query_row(t.db_conf, 'osm_landusages', -7301) park = t.query_row(t.db_conf, 'osm_landusages', -7301)
assert park['geometry'].is_valid, park assert park['geometry'].is_valid, park
park = t.query_row(t.db_conf, 'osm_landusages', 7311)
assert park['geometry'].is_valid, park
def test_updated_nodes1(): def test_updated_nodes1():
"""Zig-Zag line is inserted.""" """Zig-Zag line is inserted."""
road = t.query_row(t.db_conf, 'osm_roads', 60000) road = t.query_row(t.db_conf, 'osm_roads', 60000)

View File

@ -19,7 +19,7 @@ type RelationWriter struct {
singleIdSpace bool singleIdSpace bool
rel chan *element.Relation rel chan *element.Relation
polygonMatcher mapping.RelWayMatcher polygonMatcher mapping.RelWayMatcher
maxRingGap float64 maxGap float64
} }
func NewRelationWriter( func NewRelationWriter(
@ -32,9 +32,9 @@ func NewRelationWriter(
matcher mapping.RelWayMatcher, matcher mapping.RelWayMatcher,
srid int, srid int,
) *OsmElemWriter { ) *OsmElemWriter {
maxRingGap := 1e-1 // 0.1m maxGap := 1e-1 // 0.1m
if srid == 4326 { if srid == 4326 {
maxRingGap = 1e-6 // ~0.1m maxGap = 1e-6 // ~0.1m
} }
rw := RelationWriter{ rw := RelationWriter{
OsmElemWriter: OsmElemWriter{ OsmElemWriter: OsmElemWriter{
@ -48,7 +48,7 @@ func NewRelationWriter(
singleIdSpace: singleIdSpace, singleIdSpace: singleIdSpace,
polygonMatcher: matcher, polygonMatcher: matcher,
rel: rel, rel: rel,
maxRingGap: maxRingGap, maxGap: maxGap,
} }
rw.OsmElemWriter.writer = &rw rw.OsmElemWriter.writer = &rw
return &rw.OsmElemWriter return &rw.OsmElemWriter
@ -96,7 +96,7 @@ NextRel:
// prepare relation first (build rings and compute actual // prepare relation first (build rings and compute actual
// relation tags) // relation tags)
prepedRel, err := geom.PrepareRelation(r, rw.srid, rw.maxRingGap) prepedRel, err := geom.PrepareRelation(r, rw.srid, rw.maxGap)
if err != nil { if err != nil {
if errl, ok := err.(ErrorLevel); !ok || errl.Level() > 0 { if errl, ok := err.(ErrorLevel); !ok || errl.Level() > 0 {
log.Warn(err) log.Warn(err)

View File

@ -19,6 +19,7 @@ type WayWriter struct {
ways chan *element.Way ways chan *element.Way
lineMatcher mapping.WayMatcher lineMatcher mapping.WayMatcher
polygonMatcher mapping.WayMatcher polygonMatcher mapping.WayMatcher
maxGap float64
} }
func NewWayWriter( func NewWayWriter(
@ -32,6 +33,10 @@ func NewWayWriter(
lineMatcher mapping.WayMatcher, lineMatcher mapping.WayMatcher,
srid int, srid int,
) *OsmElemWriter { ) *OsmElemWriter {
maxGap := 1e-1 // 0.1m
if srid == 4326 {
maxGap = 1e-6 // ~0.1m
}
ww := WayWriter{ ww := WayWriter{
OsmElemWriter: OsmElemWriter{ OsmElemWriter: OsmElemWriter{
osmCache: osmCache, osmCache: osmCache,
@ -45,6 +50,7 @@ func NewWayWriter(
lineMatcher: lineMatcher, lineMatcher: lineMatcher,
polygonMatcher: polygonMatcher, polygonMatcher: polygonMatcher,
ways: ways, ways: ways,
maxGap: maxGap,
} }
ww.OsmElemWriter.writer = &ww ww.OsmElemWriter.writer = &ww
return &ww.OsmElemWriter return &ww.OsmElemWriter
@ -91,7 +97,7 @@ func (ww *WayWriter) loop() {
} }
inserted = true inserted = true
} }
if w.IsClosed() && !insertedAsRelation { if !insertedAsRelation && (w.IsClosed() || w.TryClose(ww.maxGap)) {
// only add polygons that were not inserted as a MultiPolygon relation // only add polygons that were not inserted as a MultiPolygon relation
if matches := ww.polygonMatcher.MatchWay(w); len(matches) > 0 { if matches := ww.polygonMatcher.MatchWay(w); len(matches) > 0 {
err := ww.buildAndInsert(geos, w, matches, true) err := ww.buildAndInsert(geos, w, matches, true)