-
Notifications
You must be signed in to change notification settings - Fork 16
/
multipolygon.go
124 lines (110 loc) · 2.98 KB
/
multipolygon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package geom
import (
"math"
"github.com/ctessum/polyclip-go"
)
// MultiPolygon is a holder for multiple related polygons.
type MultiPolygon []Polygon
// Bounds gives the rectangular extents of the MultiPolygon.
func (mp MultiPolygon) Bounds() *Bounds {
b := NewBounds()
for _, polygon := range mp {
b.Extend(polygon.Bounds())
}
return b
}
// Area returns the combined area of the polygons in p.
// The function works correctly for polygons with
// holes, regardless of the winding order of the holes, but may give the wrong
// result for self-intersecting polygons, or polygons in mp that overlap each other.
func (mp MultiPolygon) Area() float64 {
a := 0.
for _, pp := range mp {
a += pp.Area()
}
return math.Abs(a)
}
// Intersection returns the area(s) shared by mp and p2.
func (mp MultiPolygon) Intersection(p2 Polygonal) Polygonal {
return mp.op(p2, polyclip.INTERSECTION)
}
// Union returns the combination of mp and p2.
func (mp MultiPolygon) Union(p2 Polygonal) Polygonal {
return mp.op(p2, polyclip.UNION)
}
// XOr returns the area(s) occupied by either mp or p2 but not both.
func (mp MultiPolygon) XOr(p2 Polygonal) Polygonal {
return mp.op(p2, polyclip.XOR)
}
// Difference subtracts p2 from mp.
func (mp MultiPolygon) Difference(p2 Polygonal) Polygonal {
return mp.op(p2, polyclip.DIFFERENCE)
}
func (mp MultiPolygon) op(p2 Polygonal, op polyclip.Op) Polygonal {
var pp polyclip.Polygon
for _, ppx := range mp {
pp = append(pp, ppx.toPolyClip()...)
}
var pp2 polyclip.Polygon
for _, pp2x := range p2.Polygons() {
pp2 = append(pp2, pp2x.toPolyClip()...)
}
return polyClipToPolygon(pp.Construct(op, pp2))
}
// Polygons returns the polygons that make up mp.
func (mp MultiPolygon) Polygons() []Polygon {
return mp
}
// Centroid calculates the centroid of mp, from
// wikipedia: http://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon.
// The polygon can have holes, but each ring must be closed (i.e.,
// p[0] == p[n-1], where the ring has n points) and must not be
// self-intersecting.
// The algorithm will not check to make sure the holes are
// actually inside the outer rings.
func (mp MultiPolygon) Centroid() Point {
var A, xA, yA float64
for _, p := range mp {
b := p.ringBounds()
for i, r := range p {
a := area(r, i, p, b)
cx, cy := 0., 0.
for i := 0; i < len(r)-1; i++ {
cx += (r[i].X + r[i+1].X) *
(r[i].X*r[i+1].Y - r[i+1].X*r[i].Y)
cy += (r[i].Y + r[i+1].Y) *
(r[i].X*r[i+1].Y - r[i+1].X*r[i].Y)
}
cx /= 6 * a
cy /= 6 * a
A += a
xA += cx * a
yA += cy * a
}
}
return Point{X: xA / A, Y: yA / A}
}
// Len returns the number of points in the receiver.
func (mp MultiPolygon) Len() int {
var i int
for _, p := range mp {
i += p.Len()
}
return i
}
// Points returns an iterator for the points in the receiver.
func (mp MultiPolygon) Points() func() Point {
var i, j, k int
return func() Point {
if i == len(mp[k][j]) {
j++
i = 0
if j == len(mp[k]) {
k++
j = 0
}
}
i++
return mp[k][j][i-1]
}
}