2013-05-16 14:17:21 +04:00
|
|
|
package geom
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2014-10-21 19:08:56 +04:00
|
|
|
"sort"
|
|
|
|
|
2014-08-04 17:19:35 +04:00
|
|
|
"github.com/omniscale/imposm3/element"
|
|
|
|
"github.com/omniscale/imposm3/geom/geos"
|
2013-05-16 14:17:21 +04:00
|
|
|
)
|
|
|
|
|
2015-04-30 10:42:49 +03:00
|
|
|
type PreparedRelation struct {
|
2015-04-30 11:05:22 +03:00
|
|
|
rings []*ring
|
2013-07-15 13:39:43 +04:00
|
|
|
rel *element.Relation
|
|
|
|
srid int
|
|
|
|
}
|
|
|
|
|
2015-04-30 10:42:49 +03:00
|
|
|
// PrepareRelation is the first step in building a (multi-)polygon of a Relation.
|
|
|
|
// It builds rings from all ways and returns an error if there are unclosed rings.
|
|
|
|
// It also merges the Relation.Tags with the Tags of the outer way.
|
|
|
|
func PrepareRelation(rel *element.Relation, srid int, maxRingGap float64) (PreparedRelation, error) {
|
|
|
|
rings, err := buildRings(rel, maxRingGap)
|
2013-07-15 13:39:43 +04:00
|
|
|
if err != nil {
|
2015-04-30 10:42:49 +03:00
|
|
|
return PreparedRelation{}, err
|
2013-07-15 13:39:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
rel.Tags = relationTags(rel.Tags, rings[0].ways[0].Tags)
|
|
|
|
|
2015-04-30 10:42:49 +03:00
|
|
|
return PreparedRelation{rings, rel, srid}, nil
|
2013-07-15 13:39:43 +04:00
|
|
|
}
|
|
|
|
|
2015-04-30 10:42:49 +03:00
|
|
|
// Build creates the (multi)polygon Geometry of the Relation.
|
|
|
|
func (prep *PreparedRelation) Build() (Geometry, error) {
|
|
|
|
g := geos.NewGeos()
|
|
|
|
g.SetHandleSrid(prep.srid)
|
|
|
|
defer g.Finish()
|
|
|
|
|
|
|
|
geom, err := buildRelGeometry(g, prep.rel, prep.rings)
|
2013-07-15 13:39:43 +04:00
|
|
|
if err != nil {
|
2015-04-30 10:42:49 +03:00
|
|
|
return Geometry{}, err
|
2013-07-15 13:39:43 +04:00
|
|
|
}
|
|
|
|
|
2015-04-30 10:42:49 +03:00
|
|
|
wkb := g.AsEwkbHex(geom)
|
|
|
|
if wkb == nil {
|
|
|
|
return Geometry{}, errors.New("unable to create WKB for relation")
|
|
|
|
}
|
|
|
|
return Geometry{Geom: geom, Wkb: wkb}, nil
|
2013-07-15 13:39:43 +04:00
|
|
|
}
|
|
|
|
|
2015-04-30 11:05:22 +03:00
|
|
|
func destroyRings(g *geos.Geos, rings []*ring) {
|
2013-06-07 10:25:45 +04:00
|
|
|
for _, r := range rings {
|
|
|
|
if r.geom != nil {
|
|
|
|
g.Destroy(r.geom)
|
|
|
|
r.geom = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-30 11:05:22 +03:00
|
|
|
func buildRings(rel *element.Relation, maxRingGap float64) ([]*ring, error) {
|
|
|
|
var rings []*ring
|
|
|
|
var incompleteRings []*ring
|
|
|
|
var completeRings []*ring
|
|
|
|
var mergedRings []*ring
|
2013-05-16 14:17:21 +04:00
|
|
|
var err error
|
2013-06-07 10:25:45 +04:00
|
|
|
g := geos.NewGeos()
|
|
|
|
defer g.Finish()
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
destroyRings(g, mergedRings)
|
|
|
|
destroyRings(g, completeRings)
|
|
|
|
}
|
|
|
|
}()
|
2013-05-16 14:17:21 +04:00
|
|
|
|
|
|
|
// create rings for all WAY members
|
|
|
|
for _, member := range rel.Members {
|
|
|
|
if member.Way == nil {
|
|
|
|
continue
|
|
|
|
}
|
2015-04-30 11:05:22 +03:00
|
|
|
rings = append(rings, newRing(member.Way))
|
2013-05-16 14:17:21 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// create geometries for closed rings, collect incomplete rings
|
|
|
|
for _, r := range rings {
|
2015-04-30 11:05:22 +03:00
|
|
|
if r.isClosed() {
|
2013-05-16 14:17:21 +04:00
|
|
|
r.geom, err = Polygon(g, r.nodes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
completeRings = append(completeRings, r)
|
|
|
|
} else {
|
|
|
|
incompleteRings = append(incompleteRings, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// merge incomplete rings
|
2013-06-07 10:25:45 +04:00
|
|
|
mergedRings = mergeRings(incompleteRings)
|
2015-10-20 14:20:48 +03:00
|
|
|
|
2013-05-16 14:17:21 +04:00
|
|
|
// create geometries for merged rings
|
|
|
|
for _, ring := range mergedRings {
|
2015-04-30 11:05:22 +03:00
|
|
|
if !ring.isClosed() && !ring.tryClose(maxRingGap) {
|
2015-10-20 14:20:48 +03:00
|
|
|
continue
|
2013-05-16 14:17:21 +04:00
|
|
|
}
|
|
|
|
ring.geom, err = Polygon(g, ring.nodes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-20 14:20:48 +03:00
|
|
|
completeRings = append(completeRings, ring)
|
2013-05-16 14:17:21 +04:00
|
|
|
}
|
|
|
|
|
2015-10-20 14:20:48 +03:00
|
|
|
if len(completeRings) == 0 {
|
|
|
|
err = ErrorNoRing // for defer
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-07-15 13:39:43 +04:00
|
|
|
|
|
|
|
// sort by area (large to small)
|
|
|
|
for _, r := range completeRings {
|
|
|
|
r.area = r.geom.Area()
|
|
|
|
}
|
2015-04-30 11:05:22 +03:00
|
|
|
sort.Sort(sortableRingsDesc(completeRings))
|
2013-07-15 13:39:43 +04:00
|
|
|
|
2013-05-16 14:17:21 +04:00
|
|
|
return completeRings, nil
|
|
|
|
}
|
|
|
|
|
2015-04-30 11:05:22 +03:00
|
|
|
type sortableRingsDesc []*ring
|
2013-05-16 14:17:21 +04:00
|
|
|
|
2015-04-30 11:05:22 +03:00
|
|
|
func (r sortableRingsDesc) Len() int { return len(r) }
|
|
|
|
func (r sortableRingsDesc) Less(i, j int) bool { return r[i].area > r[j].area }
|
|
|
|
func (r sortableRingsDesc) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
2013-05-16 14:17:21 +04:00
|
|
|
|
2015-04-30 10:42:49 +03:00
|
|
|
// buildRelGeometry builds the geometry of rel by creating a multipolygon of all rings.
|
2013-07-15 13:39:43 +04:00
|
|
|
// rings need to be sorted by area (large to small).
|
2015-04-30 11:05:22 +03:00
|
|
|
func buildRelGeometry(g *geos.Geos, rel *element.Relation, rings []*ring) (*geos.Geom, error) {
|
2013-05-16 14:17:21 +04:00
|
|
|
totalRings := len(rings)
|
2015-04-30 11:05:22 +03:00
|
|
|
shells := map[*ring]bool{rings[0]: true}
|
2013-05-16 14:17:21 +04:00
|
|
|
for i := 0; i < totalRings; i++ {
|
2013-06-04 11:41:59 +04:00
|
|
|
testGeom := g.Prepare(rings[i].geom)
|
2013-06-05 13:57:38 +04:00
|
|
|
if testGeom == nil {
|
|
|
|
return nil, errors.New("Error while preparing geometry")
|
|
|
|
}
|
2013-05-16 14:17:21 +04:00
|
|
|
for j := i + 1; j < totalRings; j++ {
|
2013-06-04 11:41:59 +04:00
|
|
|
if g.PreparedContains(testGeom, rings[j].geom) {
|
2013-05-16 14:17:21 +04:00
|
|
|
if rings[j].containedBy != -1 {
|
|
|
|
// j is inside a larger ring, remove that relationship
|
|
|
|
// e.g. j is hole inside a hole (i)
|
|
|
|
delete(rings[rings[j].containedBy].holes, rings[j])
|
|
|
|
delete(shells, rings[j])
|
|
|
|
}
|
|
|
|
// remember parent
|
|
|
|
rings[j].containedBy = i
|
|
|
|
// add ring as hole or shell
|
|
|
|
if ringIsHole(rings, j) {
|
|
|
|
rings[i].holes[rings[j]] = true
|
2015-01-05 17:49:11 +03:00
|
|
|
rings[i].outer = false
|
2013-05-16 14:17:21 +04:00
|
|
|
} else {
|
|
|
|
shells[rings[j]] = true
|
2015-01-05 17:49:11 +03:00
|
|
|
rings[i].outer = true
|
2013-05-16 14:17:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if rings[i].containedBy == -1 {
|
|
|
|
// add as shell if it is not a hole
|
|
|
|
shells[rings[i]] = true
|
2015-01-05 17:49:11 +03:00
|
|
|
rings[i].outer = true
|
2013-05-16 14:17:21 +04:00
|
|
|
}
|
2013-06-05 13:57:38 +04:00
|
|
|
g.PreparedDestroy(testGeom)
|
2013-05-16 14:17:21 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
var polygons []*geos.Geom
|
|
|
|
for shell, _ := range shells {
|
|
|
|
var interiors []*geos.Geom
|
|
|
|
for hole, _ := range shell.holes {
|
2013-05-28 11:33:59 +04:00
|
|
|
ring := g.Clone(g.ExteriorRing(hole.geom))
|
2013-06-07 10:25:45 +04:00
|
|
|
g.Destroy(hole.geom)
|
2013-05-16 14:17:21 +04:00
|
|
|
if ring == nil {
|
|
|
|
return nil, errors.New("Error while getting exterior ring.")
|
|
|
|
}
|
|
|
|
interiors = append(interiors, ring)
|
|
|
|
}
|
2013-05-28 11:33:59 +04:00
|
|
|
exterior := g.Clone(g.ExteriorRing(shell.geom))
|
2013-06-07 10:25:45 +04:00
|
|
|
g.Destroy(shell.geom)
|
2013-05-16 14:17:21 +04:00
|
|
|
if exterior == nil {
|
|
|
|
return nil, errors.New("Error while getting exterior ring.")
|
|
|
|
}
|
|
|
|
polygon := g.Polygon(exterior, interiors)
|
|
|
|
if polygon == nil {
|
|
|
|
return nil, errors.New("Error while building polygon.")
|
|
|
|
}
|
|
|
|
polygons = append(polygons, polygon)
|
|
|
|
}
|
|
|
|
var result *geos.Geom
|
|
|
|
|
|
|
|
if len(polygons) == 1 {
|
|
|
|
result = polygons[0]
|
|
|
|
} else {
|
|
|
|
result = g.MultiPolygon(polygons)
|
|
|
|
if result == nil {
|
|
|
|
return nil, errors.New("Error while building multi-polygon.")
|
|
|
|
}
|
|
|
|
}
|
2015-11-21 20:10:20 +03:00
|
|
|
var err error
|
|
|
|
result, err = g.MakeValid(result)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2013-06-05 13:53:01 +04:00
|
|
|
}
|
|
|
|
|
2013-05-28 11:33:59 +04:00
|
|
|
g.DestroyLater(result)
|
2013-05-16 14:17:21 +04:00
|
|
|
|
2015-01-05 17:49:11 +03:00
|
|
|
outer := make(map[int64]struct{})
|
|
|
|
for i := range rings {
|
|
|
|
if rings[i].outer {
|
|
|
|
for _, w := range rings[i].ways {
|
|
|
|
outer[w.Id] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := range rel.Members {
|
|
|
|
mid := rel.Members[i].Id
|
|
|
|
if _, ok := outer[mid]; ok {
|
|
|
|
rel.Members[i].Role = "outer"
|
|
|
|
} else {
|
|
|
|
rel.Members[i].Role = "inner"
|
2013-05-16 17:15:58 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-16 14:17:21 +04:00
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2013-05-16 17:15:58 +04:00
|
|
|
func relationTags(relTags, wayTags element.Tags) element.Tags {
|
|
|
|
result := make(element.Tags)
|
|
|
|
for k, v := range relTags {
|
|
|
|
if k == "name" || k == "type" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
result[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(result) == 0 {
|
|
|
|
// relation does not have tags? use way tags
|
|
|
|
for k, v := range wayTags {
|
|
|
|
result[k] = v
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// add back name (if present)
|
|
|
|
if name, ok := relTags["name"]; ok {
|
|
|
|
result["name"] = name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2013-05-16 14:17:21 +04:00
|
|
|
// ringIsHole returns true if rings[idx] is a hole, False if it is a
|
|
|
|
// shell (also if hole in a hole, etc)
|
2015-04-30 11:05:22 +03:00
|
|
|
func ringIsHole(rings []*ring, idx int) bool {
|
2013-05-16 14:17:21 +04:00
|
|
|
|
|
|
|
containedCounter := 0
|
|
|
|
for {
|
|
|
|
idx = rings[idx].containedBy
|
|
|
|
if idx == -1 {
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
containedCounter += 1
|
|
|
|
}
|
|
|
|
return containedCounter%2 == 1
|
|
|
|
}
|