close open ways when end-nodes are close to each other but not identical
parent
e440ca7489
commit
0a311d62d7
|
@ -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 (
|
||||||
|
|
16
geom/ring.go
16
geom/ring.go
|
@ -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) {
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue