Class OctagonalEnvelope

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12 package org.locationtech.jts.geom;
 13  
 14 /**
 15  * A bounding container for a {@link Geometry} which is in the shape of a general octagon.
 16  * The OctagonalEnvelope of a geometric object
 17  * is a geometry which is a tight bound
 18  * along the (up to) four extremal rectilinear parallels
 19  * and along the (up to) four extremal diagonal parallels.
 20  * Depending on the shape of the contained
 21  * geometry, the octagon may be degenerate to any extreme
 22  * (e.g. it may be a rectangle, a line, or a point).
 23  */
 24 public class OctagonalEnvelope
 25 {
 26   /**
 27    * Gets the octagonal envelope of a geometry
 28    * @param geom the geometry
 29    * @return the octagonal envelope of the geometry
 30    */
 31   public static Geometry octagonalEnvelope(Geometry geom) {
 32     return (new OctagonalEnvelope(geom)).toGeometry(geom.getFactory());
 33   }
 34   
 35   private static double computeA(double x, double y)
 36   {
 37     return x + y;
 38   }
 39  
 40   private static double computeB(double x, double y)
 41   {
 42     return x - y;
 43   }
 44  
 45   private static double SQRT2 = Math.sqrt(2.0);
 46   
 47   // initialize in the null state
 48   private double minX = Double.NaN;
 49   private double maxX;
 50   private double minY;
 51   private double maxY;
 52   private double minA;
 53   private double maxA;
 54   private double minB;
 55   private double maxB;
 56  
 57   /**
 58    * Creates a new null bounding octagon
 59    */
 60   public OctagonalEnvelope()
 61   {
 62   }
 63  
 64   /**
 65    * Creates a new null bounding octagon bounding a {@link Coordinate}
 66    * 
 67    * @param p the coordinate to bound
 68    */
 69   public OctagonalEnvelope(Coordinate p)
 70   {
 71     expandToInclude(p);
 72   }
 73  
 74   /**
 75    * Creates a new null bounding octagon bounding a pair of {@link Coordinate}s
 76    * 
 77    * @param p0 a coordinate to bound
 78    * @param p1 a coordinate to bound
 79    */
 80   public OctagonalEnvelope(Coordinate p0, Coordinate p1)
 81   {
 82     expandToInclude(p0);
 83     expandToInclude(p1);
 84   }
 85  
 86   /**
 87    * Creates a new null bounding octagon bounding an {@link Envelope}
 88    */
 89   public OctagonalEnvelope(Envelope env)
 90   {
 91     expandToInclude(env);
 92   }
 93  
 94   /**
 95    * Creates a new null bounding octagon bounding an {@link OctagonalEnvelope}
 96    * (the copy constructor).
 97    */
 98   public OctagonalEnvelope(OctagonalEnvelope oct)
 99   {
100     expandToInclude(oct);
101   }
102  
103   /**
104    * Creates a new null bounding octagon bounding a {@link Geometry}
105    */
106   public OctagonalEnvelope(Geometry geom)
107   {
108     expandToInclude(geom);
109   }
110  
111  
112   public double getMinX() { return minX; }
113   public double getMaxX() { return maxX; }
114   public double getMinY() { return minY; }
115   public double getMaxY() { return maxY; }
116   public double getMinA() { return minA; }
117   public double getMaxA() { return maxA; }
118   public double getMinB() { return minB; }
119   public double getMaxB() { return maxB; }
120  
121   public boolean isNull() { return Double.isNaN(minX); }
122  
123   /**
124    *  Sets the value of this object to the null value
125    */
126   public void setToNull() {
127     minX = Double.NaN;
128   }
129  
130   public void expandToInclude(Geometry g)
131   {
132     g.apply(new BoundingOctagonComponentFilter(this));
133   }
134  
135   public OctagonalEnvelope expandToInclude(CoordinateSequence seq)
136   {
137     for (int i = 0; i < seq.size(); i++) {
138       double x = seq.getX(i);
139       double y = seq.getY(i);
140       expandToInclude(x, y);
141     }
142     return this;
143   }
144  
145   public OctagonalEnvelope expandToInclude(OctagonalEnvelope oct)
146   {
147     if (oct.isNull()) return this;
148  
149     if (isNull()) {
150       minX = oct.minX;
151       maxX = oct.maxX;
152       minY = oct.minY;
153       maxY = oct.maxY;
154       minA = oct.minA;
155       maxA = oct.maxA;
156       minB = oct.minB;
157       maxB = oct.maxB;
158       return this;
159     }
160     if (oct.minX < minX) minX = oct.minX;
161     if (oct.maxX > maxX) maxX = oct.maxX;
162     if (oct.minY < minY) minY = oct.minY;
163     if (oct.maxY > maxY) maxY = oct.maxY;
164     if (oct.minA < minA) minA = oct.minA;
165     if (oct.maxA > maxA) maxA = oct.maxA;
166     if (oct.minB < minB) minB = oct.minB;
167     if (oct.maxB > maxB) maxB = oct.maxB;
168     return this;
169   }
170  
171   public OctagonalEnvelope expandToInclude(Coordinate p)
172   {
173     expandToInclude(p.x, p.y);
174     return this;
175   }
176  
177   public OctagonalEnvelope expandToInclude(Envelope env)
178   {
179     expandToInclude(env.getMinX(), env.getMinY());
180     expandToInclude(env.getMinX(), env.getMaxY());
181     expandToInclude(env.getMaxX(), env.getMinY());
182     expandToInclude(env.getMaxX(), env.getMaxY());
183     return this;
184   }
185  
186   public OctagonalEnvelope expandToInclude(double x, double y)
187   {
188     double A = computeA(x, y);
189     double B = computeB(x, y);
190  
191     if (isNull()) {
192       minX = x;
193       maxX = x;
194       minY = y;
195       maxY = y;
196       minA = A;
197       maxA = A;
198       minB = B;
199       maxB = B;
200     }
201     else {
202       if (x < minX) minX = x;
203       if (x > maxX) maxX = x;
204       if (y < minY) minY = y;
205       if (y > maxY) maxY = y;
206       if (A < minA) minA = A;
207       if (A > maxA) maxA = A;
208       if (B < minB) minB = B;
209       if (B > maxB) maxB = B;
210     }
211     return this;
212   }
213  
214   public void expandBy(double distance)
215   {
216     if (isNull()) return;
217  
218     double diagonalDistance = SQRT2 * distance;
219  
220     minX -= distance;
221     maxX += distance;
222     minY -= distance;
223     maxY += distance;
224     minA -= diagonalDistance;
225     maxA += diagonalDistance;
226     minB -= diagonalDistance;
227     maxB += diagonalDistance;
228  
229     if (! isValid())
230       setToNull();
231   }
232  
233   /**
234    * Tests if the extremal values for this octagon are valid.
235    *
236    * @return <code>true</code> if this object has valid values
237    */
238   private boolean isValid()
239   {
240     if (isNull()) return true;
241     return minX <= maxX
242                  && minY <= maxY
243                  && minA <= maxA
244                  && minB <= maxB;
245   }
246  
247   public boolean intersects(OctagonalEnvelope other)
248   {
249     if (isNull() || other.isNull()) { return false; }
250  
251     if (minX > other.maxX) return false;
252     if (maxX < other.minX) return false;
253     if (minY > other.maxY) return false;
254     if (maxY < other.minY) return false;
255     if (minA > other.maxA) return false;
256     if (maxA < other.minA) return false;
257     if (minB > other.maxB) return false;
258     if (maxB < other.minB) return false;
259     return true;
260   }
261  
262   public boolean intersects(Coordinate p)
263   {
264     if (minX > p.x) return false;
265     if (maxX < p.x) return false;
266     if (minY > p.y) return false;
267     if (maxY < p.y) return false;
268     
269     double A = computeA(p.x, p.y);
270     double B = computeB(p.x, p.y);
271     if (minA > A) return false;
272     if (maxA < A) return false;
273     if (minB > B) return false;
274     if (maxB < B) return false;
275     return true;
276   }
277  
278   public boolean contains(OctagonalEnvelope other)
279   {
280     if (isNull() || other.isNull()) { return false; }
281  
282     return other.minX >= minX
283         && other.maxX <= maxX
284         && other.minY >= minY
285         && other.maxY <= maxY
286         && other.minA >= minA
287         && other.maxA <= maxA
288         && other.minB >= minB
289         && other.maxB <= maxB;
290   }
291  
292   public Geometry toGeometry(GeometryFactory geomFactory)
293   {
294     if (isNull()) {
295       return geomFactory.createPoint();
296     }
297  
298     Coordinate px00 = new Coordinate(minX, minA - minX);
299     Coordinate px01 = new Coordinate(minX, minX - minB);
300  
301     Coordinate px10 = new Coordinate(maxX, maxX - maxB);
302     Coordinate px11 = new Coordinate(maxX, maxA - maxX);
303  
304     Coordinate py00 = new Coordinate(minA - minY, minY);
305     Coordinate py01 = new Coordinate(minY + maxB, minY);
306  
307     Coordinate py10 = new Coordinate(maxY + minB, maxY);
308     Coordinate py11 = new Coordinate(maxA - maxY, maxY);
309  
310     PrecisionModel pm = geomFactory.getPrecisionModel();
311     pm.makePrecise(px00);
312     pm.makePrecise(px01);
313     pm.makePrecise(px10);
314     pm.makePrecise(px11);
315     pm.makePrecise(py00);
316     pm.makePrecise(py01);
317     pm.makePrecise(py10);
318     pm.makePrecise(py11);
319  
320     CoordinateList coordList = new CoordinateList();
321     coordList.add(px00, false);
322     coordList.add(px01, false);
323     coordList.add(py10, false);
324     coordList.add(py11, false);
325     coordList.add(px11, false);
326     coordList.add(px10, false);
327     coordList.add(py01, false);
328     coordList.add(py00, false);
329  
330     if (coordList.size() == 1) {
331       return geomFactory.createPoint(px00);
332     }
333     if (coordList.size() == 2) {
334       Coordinate[] pts = coordList.toCoordinateArray();
335       return geomFactory.createLineString(pts);
336     }
337     // must be a polygon, so add closing point
338     coordList.add(px00, false);
339     Coordinate[] pts = coordList.toCoordinateArray();
340     return geomFactory.createPolygon(geomFactory.createLinearRing(pts));
341   }
342  
343   private static class BoundingOctagonComponentFilter
344   implements GeometryComponentFilter
345   {
346     OctagonalEnvelope oe;
347     
348     BoundingOctagonComponentFilter(OctagonalEnvelope oe) {
349       this.oe = oe;
350     }
351     
352      public void filter(Geometry geom)
353      {
354        if (geom instanceof LineString) {
355          oe.expandToInclude( ((LineString) geom).getCoordinateSequence());
356        }
357        else if (geom instanceof Point) {
358          oe.expandToInclude( ((Point) geom).getCoordinateSequence());
359        }
360      }
361   }
362 }
363