imposm3/geom/multipolygon.go

239 lines
5.6 KiB
Go

package geom
import (
"errors"
"sort"
"github.com/omniscale/imposm3/element"
"github.com/omniscale/imposm3/geom/geos"
)
type PreparedRelation struct {
rings []*ring
rel *element.Relation
srid int
}
// 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.
func PrepareRelation(rel *element.Relation, srid int, maxRingGap float64) (PreparedRelation, error) {
rings, err := buildRings(rel, maxRingGap)
if err != nil {
return PreparedRelation{}, err
}
return PreparedRelation{rings, rel, srid}, nil
}
// 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)
if err != nil {
return Geometry{}, err
}
wkb := g.AsEwkbHex(geom)
if wkb == nil {
return Geometry{}, errors.New("unable to create WKB for relation")
}
return Geometry{Geom: geom, Wkb: wkb}, nil
}
func destroyRings(g *geos.Geos, rings []*ring) {
for _, r := range rings {
if r.geom != nil {
g.Destroy(r.geom)
r.geom = nil
}
}
}
func buildRings(rel *element.Relation, maxRingGap float64) ([]*ring, error) {
var rings []*ring
var incompleteRings []*ring
var completeRings []*ring
var mergedRings []*ring
var err error
g := geos.NewGeos()
defer g.Finish()
defer func() {
if err != nil {
destroyRings(g, mergedRings)
destroyRings(g, completeRings)
}
}()
// create rings for all WAY members
for _, member := range rel.Members {
if member.Way == nil {
continue
}
rings = append(rings, newRing(member.Way))
}
// create geometries for closed rings, collect incomplete rings
for _, r := range rings {
if r.isClosed() {
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
mergedRings = mergeRings(incompleteRings)
// create geometries for merged rings
for _, ring := range mergedRings {
if !ring.isClosed() && !ring.tryClose(maxRingGap) {
continue
}
ring.geom, err = Polygon(g, ring.nodes)
if err != nil {
return nil, err
}
completeRings = append(completeRings, ring)
}
if len(completeRings) == 0 {
err = ErrorNoRing // for defer
return nil, err
}
// sort by area (large to small)
for _, r := range completeRings {
r.area = r.geom.Area()
}
sort.Sort(sortableRingsDesc(completeRings))
return completeRings, nil
}
type sortableRingsDesc []*ring
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] }
// buildRelGeometry builds the geometry of rel by creating a multipolygon of all rings.
// rings need to be sorted by area (large to small).
func buildRelGeometry(g *geos.Geos, rel *element.Relation, rings []*ring) (*geos.Geom, error) {
totalRings := len(rings)
shells := map[*ring]bool{rings[0]: true}
for i := 0; i < totalRings; i++ {
testGeom := g.Prepare(rings[i].geom)
if testGeom == nil {
return nil, errors.New("Error while preparing geometry")
}
for j := i + 1; j < totalRings; j++ {
if g.PreparedContains(testGeom, rings[j].geom) {
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
rings[i].outer = false
} else {
shells[rings[j]] = true
rings[i].outer = true
}
}
}
if rings[i].containedBy == -1 {
// add as shell if it is not a hole
shells[rings[i]] = true
rings[i].outer = true
}
g.PreparedDestroy(testGeom)
}
var polygons []*geos.Geom
for shell, _ := range shells {
var interiors []*geos.Geom
for hole, _ := range shell.holes {
ring := g.Clone(g.ExteriorRing(hole.geom))
g.Destroy(hole.geom)
if ring == nil {
return nil, errors.New("Error while getting exterior ring.")
}
interiors = append(interiors, ring)
}
exterior := g.Clone(g.ExteriorRing(shell.geom))
g.Destroy(shell.geom)
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.")
}
}
var err error
result, err = g.MakeValid(result)
if err != nil {
return nil, err
}
g.DestroyLater(result)
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"
}
}
return result, nil
}
// ringIsHole returns true if rings[idx] is a hole, False if it is a
// shell (also if hole in a hole, etc)
func ringIsHole(rings []*ring, idx int) bool {
containedCounter := 0
for {
idx = rings[idx].containedBy
if idx == -1 {
break
}
containedCounter += 1
}
return containedCounter%2 == 1
}