1package geojson
2
3import "github.com/tidwall/tile38/geojson/geohash"
4
5// MultiPolygon is a geojson object with the type "MultiPolygon"
6type MultiPolygon struct {
7	Coordinates [][][]Position
8	BBox        *BBox
9	bboxDefined bool
10	polygons    []Polygon
11}
12
13func fillMultiPolygon(coordinates [][][]Position, bbox *BBox, err error) (MultiPolygon, error) {
14	polygons := make([]Polygon, len(coordinates))
15	if err == nil {
16		for i, ps := range coordinates {
17			polygons[i], err = fillPolygon(ps, nil, nil)
18			if err != nil {
19				break
20			}
21		}
22	}
23	bboxDefined := bbox != nil
24	if !bboxDefined {
25		cbbox := calculatedBBox(polygons, nil)
26		bbox = &cbbox
27	}
28	return MultiPolygon{
29		Coordinates: coordinates,
30		BBox:        bbox,
31		bboxDefined: bboxDefined,
32		polygons:    polygons,
33	}, err
34}
35
36func calculatedBBox(polygons []Polygon, bbox *BBox) BBox {
37	if bbox != nil {
38		return *bbox
39	}
40	var cbbox BBox
41	for i, p := range polygons {
42		if i == 0 {
43			cbbox = p.CalculatedBBox()
44		} else {
45			cbbox = cbbox.union(p.CalculatedBBox())
46		}
47	}
48	return cbbox
49}
50
51// CalculatedBBox is exterior bbox containing the object.
52func (g MultiPolygon) CalculatedBBox() BBox {
53	return calculatedBBox(g.polygons, g.BBox)
54}
55
56// CalculatedPoint is a point representation of the object.
57func (g MultiPolygon) CalculatedPoint() Position {
58	return g.CalculatedBBox().center()
59}
60
61// Geohash converts the object to a geohash value.
62func (g MultiPolygon) Geohash(precision int) (string, error) {
63	p := g.CalculatedPoint()
64	return geohash.Encode(p.Y, p.X, precision)
65}
66
67// PositionCount return the number of coordinates.
68func (g MultiPolygon) PositionCount() int {
69	return level4PositionCount(g.Coordinates, g.BBox)
70}
71
72// Weight returns the in-memory size of the object.
73func (g MultiPolygon) Weight() int {
74	return level4Weight(g.Coordinates, g.BBox)
75}
76
77// MarshalJSON allows the object to be encoded in json.Marshal calls.
78func (g MultiPolygon) MarshalJSON() ([]byte, error) {
79	return g.appendJSON(nil), nil
80}
81
82func (g MultiPolygon) appendJSON(json []byte) []byte {
83	return appendLevel4JSON(json, "MultiPolygon", g.Coordinates, g.BBox, g.bboxDefined)
84}
85
86// JSON is the json representation of the object. This might not be exactly the same as the original.
87func (g MultiPolygon) JSON() string {
88	return string(g.appendJSON(nil))
89}
90
91// String returns a string representation of the object. This might be JSON or something else.
92func (g MultiPolygon) String() string {
93	return g.JSON()
94}
95
96func (g MultiPolygon) bboxPtr() *BBox {
97	return g.BBox
98}
99func (g MultiPolygon) hasPositions() bool {
100	if g.bboxDefined {
101		return true
102	}
103	for _, c := range g.Coordinates {
104		for _, c := range c {
105			if len(c) > 0 {
106				return true
107			}
108		}
109	}
110	return false
111}
112
113func (g MultiPolygon) getPolygon(index int) Polygon {
114	if index < len(g.polygons) {
115		return g.polygons[index]
116	}
117	return Polygon{Coordinates: g.Coordinates[index]}
118}
119
120// WithinBBox detects if the object is fully contained inside a bbox.
121func (g MultiPolygon) WithinBBox(bbox BBox) bool {
122	if g.bboxDefined {
123		return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
124	}
125	if len(g.Coordinates) == 0 {
126		return false
127	}
128	for i := range g.Coordinates {
129		if !g.getPolygon(i).WithinBBox(bbox) {
130			return false
131		}
132	}
133	return true
134}
135
136// IntersectsBBox detects if the object intersects a bbox.
137func (g MultiPolygon) IntersectsBBox(bbox BBox) bool {
138	if g.bboxDefined {
139		return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
140	}
141	for i := range g.Coordinates {
142		if g.getPolygon(i).IntersectsBBox(bbox) {
143			return true
144		}
145	}
146	return false
147}
148
149// Within detects if the object is fully contained inside another object.
150func (g MultiPolygon) Within(o Object) bool {
151	return withinObjectShared(g, o,
152		func(v Polygon) bool {
153			if len(g.Coordinates) == 0 {
154				return false
155			}
156			if !v.Within(o) {
157				return false
158			}
159			return true
160		},
161	)
162}
163
164// Intersects detects if the object intersects another object.
165func (g MultiPolygon) Intersects(o Object) bool {
166	return intersectsObjectShared(g, o,
167		func(v Polygon) bool {
168			if len(g.Coordinates) == 0 {
169				return false
170			}
171			if v.Intersects(o) {
172				return true
173			}
174			return false
175		},
176	)
177}
178
179// Nearby detects if the object is nearby a position.
180func (g MultiPolygon) Nearby(center Position, meters float64) bool {
181	return nearbyObjectShared(g, center.X, center.Y, meters)
182}
183
184// IsBBoxDefined returns true if the object has a defined bbox.
185func (g MultiPolygon) IsBBoxDefined() bool {
186	return g.bboxDefined
187}
188
189// IsGeometry return true if the object is a geojson geometry object. false if it something else.
190func (g MultiPolygon) IsGeometry() bool {
191	return true
192}
193