1
2
3
4
5
6
7
8
9 package org.locationtech.spatial4j.shape.impl;
10
11 import org.locationtech.spatial4j.context.SpatialContext;
12 import org.locationtech.spatial4j.distance.DistanceUtils;
13 import org.locationtech.spatial4j.shape.BaseShape;
14 import org.locationtech.spatial4j.shape.Point;
15 import org.locationtech.spatial4j.shape.Rectangle;
16 import org.locationtech.spatial4j.shape.Shape;
17 import org.locationtech.spatial4j.shape.SpatialRelation;
18
19
20
21
22
23
24 public class RectangleImpl extends BaseShape<SpatialContext> implements Rectangle {
25
26 private double minX;
27 private double maxX;
28 private double minY;
29 private double maxY;
30
31
32 public RectangleImpl(double minX, double maxX, double minY, double maxY, SpatialContext ctx) {
33 super(ctx);
34
35 reset(minX, maxX, minY, maxY);
36 }
37
38
39 public RectangleImpl(Point lowerLeft, Point upperRight, SpatialContext ctx) {
40 this(lowerLeft.getX(), upperRight.getX(),
41 lowerLeft.getY(), upperRight.getY(), ctx);
42 }
43
44
45 public RectangleImpl(Rectangle r, SpatialContext ctx) {
46 this(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY(), ctx);
47 }
48
49 @Override
50 public void reset(double minX, double maxX, double minY, double maxY) {
51 assert ! isEmpty();
52 this.minX = minX;
53 this.maxX = maxX;
54 this.minY = minY;
55 this.maxY = maxY;
56 assert minY <= maxY || Double.isNaN(minY) : "minY, maxY: "+minY+", "+maxY;
57 }
58
59 @Override
60 public boolean isEmpty() {
61 return Double.isNaN(minX);
62 }
63
64 @Override
65 public Rectangle getBuffered(double distance, SpatialContext ctx) {
66 if (ctx.isGeo()) {
67
68 if (maxY + distance >= 90) {
69 return ctx.makeRectangle(-180, 180, Math.max(-90, minY - distance), 90);
70 } else if (minY - distance <= -90) {
71 return ctx.makeRectangle(-180, 180, -90, Math.min(90, maxY + distance));
72 } else {
73
74 double latDistance = distance;
75 double closestToPoleY = Math.abs(maxY) > Math.abs(minY) ? maxY : minY;
76 double lonDistance = DistanceUtils.calcBoxByDistFromPt_deltaLonDEG(
77 closestToPoleY, minX, distance);
78
79 if (lonDistance * 2 + getWidth() >= 360)
80 return ctx.makeRectangle(-180, 180, minY - latDistance, maxY + latDistance);
81 return ctx.makeRectangle(
82 DistanceUtils.normLonDEG(minX - lonDistance),
83 DistanceUtils.normLonDEG(maxX + lonDistance),
84 minY - latDistance, maxY + latDistance);
85 }
86 } else {
87 Rectangle worldBounds = ctx.getWorldBounds();
88 double newMinX = Math.max(worldBounds.getMinX(), minX - distance);
89 double newMaxX = Math.min(worldBounds.getMaxX(), maxX + distance);
90 double newMinY = Math.max(worldBounds.getMinY(), minY - distance);
91 double newMaxY = Math.min(worldBounds.getMaxY(), maxY + distance);
92 return ctx.makeRectangle(newMinX, newMaxX, newMinY, newMaxY);
93 }
94 }
95
96 @Override
97 public boolean hasArea() {
98 return maxX != minX && maxY != minY;
99 }
100
101 @Override
102 public double getArea(SpatialContext ctx) {
103 if (ctx == null) {
104 return getWidth() * getHeight();
105 } else {
106 return ctx.getDistCalc().area(this);
107 }
108 }
109
110 @Override
111 public boolean getCrossesDateLine() {
112 return (minX > maxX);
113 }
114
115 @Override
116 public double getHeight() {
117 return maxY - minY;
118 }
119
120 @Override
121 public double getWidth() {
122 double w = maxX - minX;
123 if (w < 0) {
124 w += 360;
125 assert w >= 0;
126 }
127 return w;
128 }
129
130 @Override
131 public double getMaxX() {
132 return maxX;
133 }
134
135 @Override
136 public double getMaxY() {
137 return maxY;
138 }
139
140 @Override
141 public double getMinX() {
142 return minX;
143 }
144
145 @Override
146 public double getMinY() {
147 return minY;
148 }
149
150 @Override
151 public Rectangle getBoundingBox() {
152 return this;
153 }
154
155 @Override
156 public SpatialRelation relate(Shape other) {
157 if (isEmpty() || other.isEmpty())
158 return SpatialRelation.DISJOINT;
159 if (other instanceof Point) {
160 return relate((Point) other);
161 }
162 if (other instanceof Rectangle) {
163 return relate((Rectangle) other);
164 }
165 return other.relate(this).transpose();
166 }
167
168 public SpatialRelation relate(Point point) {
169 if (point.getY() > getMaxY() || point.getY() < getMinY())
170 return SpatialRelation.DISJOINT;
171
172 double minX = this.minX;
173 double maxX = this.maxX;
174 double pX = point.getX();
175 if (ctx.isGeo()) {
176
177 double rawWidth = maxX - minX;
178 if (rawWidth < 0) {
179 maxX = minX + (rawWidth + 360);
180 }
181
182 if (pX < minX) {
183 pX += 360;
184 } else if (pX > maxX) {
185 pX -= 360;
186 } else {
187 return SpatialRelation.CONTAINS;
188 }
189 }
190 if (pX < minX || pX > maxX)
191 return SpatialRelation.DISJOINT;
192 return SpatialRelation.CONTAINS;
193 }
194
195 public SpatialRelation relate(Rectangle rect) {
196 SpatialRelation yIntersect = relateYRange(rect.getMinY(), rect.getMaxY());
197 if (yIntersect == SpatialRelation.DISJOINT)
198 return SpatialRelation.DISJOINT;
199
200 SpatialRelation xIntersect = relateXRange(rect.getMinX(), rect.getMaxX());
201 if (xIntersect == SpatialRelation.DISJOINT)
202 return SpatialRelation.DISJOINT;
203
204 if (xIntersect == yIntersect)
205 return xIntersect;
206
207
208 if (getMinY() == rect.getMinY() && getMaxY() == rect.getMaxY())
209 return xIntersect;
210 if (getMinX() == rect.getMinX() && getMaxX() == rect.getMaxX()
211 || (ctx.isGeo() && verticalAtDateline(this, rect))) {
212 return yIntersect;
213 }
214
215 return SpatialRelation.INTERSECTS;
216 }
217
218
219 private static boolean verticalAtDateline(RectangleImpl rect1, Rectangle rect2) {
220 if (rect1.getMinX() == rect1.getMaxX() && rect2.getMinX() == rect2.getMaxX()) {
221 if (rect1.getMinX() == -180) {
222 return rect2.getMinX() == +180;
223 } else if (rect1.getMinX() == +180) {
224 return rect2.getMinX() == -180;
225 }
226 }
227 return false;
228 }
229
230
231 private static SpatialRelation relate_range(double int_min, double int_max, double ext_min, double ext_max) {
232 if (ext_min > int_max || ext_max < int_min) {
233 return SpatialRelation.DISJOINT;
234 }
235
236 if (ext_min >= int_min && ext_max <= int_max) {
237 return SpatialRelation.CONTAINS;
238 }
239
240 if (ext_min <= int_min && ext_max >= int_max) {
241 return SpatialRelation.WITHIN;
242 }
243 return SpatialRelation.INTERSECTS;
244 }
245
246 @Override
247 public SpatialRelation relateYRange(double ext_minY, double ext_maxY) {
248 return relate_range(minY, maxY, ext_minY, ext_maxY);
249 }
250
251 @Override
252 public SpatialRelation relateXRange(double ext_minX, double ext_maxX) {
253
254 double minX = this.minX;
255 double maxX = this.maxX;
256 if (ctx.isGeo()) {
257
258 double rawWidth = maxX - minX;
259 if (rawWidth == 360)
260 return SpatialRelation.CONTAINS;
261 if (rawWidth < 0) {
262 maxX = minX + (rawWidth + 360);
263 }
264 double ext_rawWidth = ext_maxX - ext_minX;
265 if (ext_rawWidth == 360)
266 return SpatialRelation.WITHIN;
267 if (ext_rawWidth < 0) {
268 ext_maxX = ext_minX + (ext_rawWidth + 360);
269 }
270
271 if (maxX < ext_minX) {
272 minX += 360;
273 maxX += 360;
274 } else if (ext_maxX < minX) {
275 ext_minX += 360;
276 ext_maxX += 360;
277 }
278 }
279
280 return relate_range(minX, maxX, ext_minX, ext_maxX);
281 }
282
283 @Override
284 public String toString() {
285 return "Rect(minX=" + minX + ",maxX=" + maxX + ",minY=" + minY + ",maxY=" + maxY + ")";
286 }
287
288 @Override
289 public Point getCenter() {
290 if (Double.isNaN(minX))
291 return ctx.makePoint(Double.NaN, Double.NaN);
292 final double y = getHeight() / 2 + minY;
293 double x = getWidth() / 2 + minX;
294 if (minX > maxX)
295 x = DistanceUtils.normLonDEG(x);
296 return new PointImpl(x, y, ctx);
297 }
298
299 @Override
300 public boolean equals(Object obj) {
301 return equals(this,obj);
302 }
303
304
305
306
307 public static boolean equals(Rectangle thiz, Object o) {
308 assert thiz != null;
309 if (thiz == o) return true;
310 if (!(o instanceof Rectangle)) return false;
311
312 RectangleImpl rectangle = (RectangleImpl) o;
313
314 if (Double.compare(rectangle.getMaxX(), thiz.getMaxX()) != 0) return false;
315 if (Double.compare(rectangle.getMaxY(), thiz.getMaxY()) != 0) return false;
316 if (Double.compare(rectangle.getMinX(), thiz.getMinX()) != 0) return false;
317 if (Double.compare(rectangle.getMinY(), thiz.getMinY()) != 0) return false;
318
319 return true;
320 }
321
322 @Override
323 public int hashCode() {
324 return hashCode(this);
325 }
326
327
328
329
330 public static int hashCode(Rectangle thiz) {
331 int result;
332 long temp;
333 temp = thiz.getMinX() != +0.0d ? Double.doubleToLongBits(thiz.getMinX()) : 0L;
334 result = (int) (temp ^ (temp >>> 32));
335 temp = thiz.getMaxX() != +0.0d ? Double.doubleToLongBits(thiz.getMaxX()) : 0L;
336 result = 31 * result + (int) (temp ^ (temp >>> 32));
337 temp = thiz.getMinY() != +0.0d ? Double.doubleToLongBits(thiz.getMinY()) : 0L;
338 result = 31 * result + (int) (temp ^ (temp >>> 32));
339 temp = thiz.getMaxY() != +0.0d ? Double.doubleToLongBits(thiz.getMaxY()) : 0L;
340 result = 31 * result + (int) (temp ^ (temp >>> 32));
341 return result;
342 }
343 }