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