Class GeometricShapeFactory

  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.util;
 13  
 14 import org.locationtech.jts.geom.Coordinate;
 15 import org.locationtech.jts.geom.Envelope;
 16 import org.locationtech.jts.geom.Geometry;
 17 import org.locationtech.jts.geom.GeometryFactory;
 18 import org.locationtech.jts.geom.LineString;
 19 import org.locationtech.jts.geom.LinearRing;
 20 import org.locationtech.jts.geom.Polygon;
 21 import org.locationtech.jts.geom.PrecisionModel;
 22 import org.locationtech.jts.geom.util.AffineTransformation;
 23  
 24 /**
 25  * Computes various kinds of common geometric shapes.
 26  * Provides various ways of specifying the location and extent
 27  * and rotations of the generated shapes,
 28  * as well as number of line segments used to form them.
 29  * <p>
 30  * <b>Example of usage:</b>
 31  * <pre>
 32  *  GeometricShapeFactory gsf = new GeometricShapeFactory();
 33  *  gsf.setSize(100);
 34  *  gsf.setNumPoints(100);
 35  *  gsf.setBase(new Coordinate(100, 100));
 36  *  gsf.setRotation(0.5);
 37  *  Polygon rect = gsf.createRectangle();
 38  * </pre>
 39  *
 40  * @version 1.7
 41  */
 42 public class GeometricShapeFactory
 43 {
 44   protected GeometryFactory geomFact;
 45   protected PrecisionModel precModel = null;
 46   protected Dimensions dim = new Dimensions();
 47   protected int nPts = 100;
 48   
 49   /**
 50    * Default is no rotation.
 51    */
 52   protected double rotationAngle = 0.0;
 53  
 54   /**
 55    * Create a shape factory which will create shapes using the default
 56    * {@link GeometryFactory}.
 57    */
 58   public GeometricShapeFactory()
 59   {
 60     this(new GeometryFactory());
 61   }
 62  
 63   /**
 64    * Create a shape factory which will create shapes using the given
 65    * {@link GeometryFactory}.
 66    *
 67    * @param geomFact the factory to use
 68    */
 69   public GeometricShapeFactory(GeometryFactory geomFact)
 70   {
 71     this.geomFact = geomFact;
 72     precModel = geomFact.getPrecisionModel();
 73   }
 74  
 75   public void setEnvelope(Envelope env)
 76   {
 77       dim.setEnvelope(env);
 78   }
 79   
 80   /**
 81    * Sets the location of the shape by specifying the base coordinate
 82    * (which in most cases is the
 83    * lower left point of the envelope containing the shape).
 84    *
 85    * @param base the base coordinate of the shape
 86    */
 87   public void setBase(Coordinate base)  {  dim.setBase(base);    }
 88   /**
 89    * Sets the location of the shape by specifying the centre of
 90    * the shape's bounding box
 91    *
 92    * @param centre the centre coordinate of the shape
 93    */
 94   public void setCentre(Coordinate centre)  {  dim.setCentre(centre);    }
 95  
 96   /**
 97    * Sets the total number of points in the created {@link Geometry}.
 98    * The created geometry will have no more than this number of points,
 99    * unless more are needed to create a valid geometry.
100    */
101   public void setNumPoints(int nPts) { this.nPts = nPts; }
102  
103   /**
104    * Sets the size of the extent of the shape in both x and y directions.
105    *
106    * @param size the size of the shape's extent
107    */
108   public void setSize(double size) { dim.setSize(size); }
109  
110   /**
111    * Sets the width of the shape.
112    *
113    * @param width the width of the shape
114    */
115   public void setWidth(double width) { dim.setWidth(width); }
116  
117   /**
118    * Sets the height of the shape.
119    *
120    * @param height the height of the shape
121    */
122   public void setHeight(double height) { dim.setHeight(height); }
123  
124   /**
125    * Sets the rotation angle to use for the shape.
126    * The rotation is applied relative to the centre of the shape.
127    * 
128    * @param radians the rotation angle in radians.
129    */
130   public void setRotation(double radians)
131   {
132     rotationAngle = radians;
133   }
134   
135   protected Geometry rotate(Geometry geom)
136   {
137     if (rotationAngle != 0.0) {
138       AffineTransformation trans = AffineTransformation.rotationInstance(rotationAngle, 
139           dim.getCentre().x, dim.getCentre().y);
140       geom.apply(trans);
141     }
142     return geom;
143   }
144   
145   /**
146    * Creates a rectangular {@link Polygon}.
147    *
148    * @return a rectangular Polygon
149    *
150    */
151   public Polygon createRectangle()
152   {
153     int i;
154     int ipt = 0;
155     int nSide = nPts / 4;
156     if (nSide < 1nSide = 1;
157     double XsegLen = dim.getEnvelope().getWidth() / nSide;
158     double YsegLen = dim.getEnvelope().getHeight() / nSide;
159  
160     Coordinate[] pts = new Coordinate[4 * nSide + 1];
161     Envelope env = dim.getEnvelope();
162  
163     //double maxx = env.getMinX() + nSide * XsegLen;
164     //double maxy = env.getMinY() + nSide * XsegLen;
165  
166     for (i = 0; i < nSide; i++) {
167       double x = env.getMinX() + i * XsegLen;
168       double y = env.getMinY();
169       pts[ipt++] = coord(x, y);
170     }
171     for (i = 0; i < nSide; i++) {
172       double x = env.getMaxX();
173       double y = env.getMinY() + i * YsegLen;
174       pts[ipt++] = coord(x, y);
175     }
176     for (i = 0; i < nSide; i++) {
177       double x = env.getMaxX() - i * XsegLen;
178       double y = env.getMaxY();
179       pts[ipt++] = coord(x, y);
180     }
181     for (i = 0; i < nSide; i++) {
182       double x = env.getMinX();
183       double y = env.getMaxY() - i * YsegLen;
184       pts[ipt++] = coord(x, y);
185     }
186     pts[ipt++] = new Coordinate(pts[0]);
187  
188     LinearRing ring = geomFact.createLinearRing(pts);
189     Polygon poly = geomFact.createPolygon(ring);
190     return (Polygon) rotate(poly);
191   }
192  
193 //* @deprecated use {@link createEllipse} instead
194   /**
195    * Creates a circular or elliptical {@link Polygon}.
196    *
197    * @return a circle or ellipse
198    */
199   public Polygon createCircle()
200   {
201     return createEllipse();
202   }
203   
204   /**
205    * Creates an elliptical {@link Polygon}.
206    * If the supplied envelope is square the 
207    * result will be a circle. 
208    *
209    * @return an ellipse or circle
210    */
211   public Polygon createEllipse()
212   {
213  
214     Envelope env = dim.getEnvelope();
215     double xRadius = env.getWidth() / 2.0;
216     double yRadius = env.getHeight() / 2.0;
217  
218     double centreX = env.getMinX() + xRadius;
219     double centreY = env.getMinY() + yRadius;
220  
221     Coordinate[] pts = new Coordinate[nPts + 1];
222     int iPt = 0;
223     for (int i = 0; i < nPts; i++) {
224         double ang = i * (2 * Math.PI / nPts);
225         double x = xRadius * Math.cos(ang) + centreX;
226         double y = yRadius * Math.sin(ang) + centreY;
227         pts[iPt++] = coord(x, y);
228     }
229     pts[iPt] = new Coordinate(pts[0]);
230  
231     LinearRing ring = geomFact.createLinearRing(pts);
232     Polygon poly = geomFact.createPolygon(ring);
233     return (Polygon) rotate(poly);
234   }
235   /**
236    * Creates a squircular {@link Polygon}.
237    *
238    * @return a squircle
239    */
240   public Polygon createSquircle()
241   /**
242    * Creates a squircular {@link Polygon}.
243    *
244    * @return a squircle
245    */
246   {
247       return createSupercircle(4);
248   }
249   
250   /**
251    * Creates a supercircular {@link Polygon}
252    * of a given positive power.
253    *
254    * @return a supercircle
255    */
256   public Polygon createSupercircle(double power)
257   {
258       double recipPow = 1.0 / power;
259       
260     double radius = dim.getMinSize() / 2;
261     Coordinate centre = dim.getCentre();
262     
263     double r4 = Math.pow(radius, power);
264     double y0 = radius;
265     
266     double xyInt = Math.pow(r4 / 2, recipPow);
267     
268     int nSegsInOct = nPts / 8;
269     int totPts = nSegsInOct * 8 + 1;
270     Coordinate[] pts = new Coordinate[totPts];
271     double xInc = xyInt / nSegsInOct;
272     
273     for (int i = 0; i <= nSegsInOct; i++) {
274           double x = 0.0;
275           double y = y0;
276         if (i != 0) {
277             x = xInc * i;
278             double x4 = Math.pow(x, power);
279             y = Math.pow(r4 - x4, recipPow);
280         }
281       pts[i] = coordTrans(x, y, centre);
282       pts[2 * nSegsInOct - i] = coordTrans(y, x, centre);
283       
284       pts[2 * nSegsInOct + i] = coordTrans(y, -x, centre);
285       pts[4 * nSegsInOct - i] = coordTrans(x, -y, centre);
286       
287       pts[4 * nSegsInOct + i] = coordTrans(-x, -y, centre);
288       pts[6 * nSegsInOct - i] = coordTrans(-y, -x, centre);
289       
290       pts[6 * nSegsInOct + i] = coordTrans(-y, x, centre);
291       pts[8 * nSegsInOct - i] = coordTrans(-x, y, centre);
292     }
293     pts[pts.length-1] = new Coordinate(pts[0]);
294  
295     LinearRing ring = geomFact.createLinearRing(pts);
296     Polygon poly = geomFact.createPolygon(ring);
297     return (Polygon) rotate(poly);
298   }
299  
300    /**
301     * Creates an elliptical arc, as a {@link LineString}.
302     * The arc is always created in a counter-clockwise direction.
303     * This can easily be reversed if required by using 
304     * {#link LineString.reverse()}
305     *
306     * @param startAng start angle in radians
307     * @param angExtent size of angle in radians
308     * @return an elliptical arc
309     */
310   public LineString createArc(
311      double startAng,
312      double angExtent)
313   {
314     Envelope env = dim.getEnvelope();
315     double xRadius = env.getWidth() / 2.0;
316     double yRadius = env.getHeight() / 2.0;
317  
318     double centreX = env.getMinX() + xRadius;
319     double centreY = env.getMinY() + yRadius;
320  
321      double angSize = angExtent;
322      if (angSize <= 0.0 || angSize > 2 * Math.PI)
323        angSize = 2 * Math.PI;
324      double angInc = angSize / (nPts - 1);
325  
326      Coordinate[] pts = new Coordinate[nPts];
327      int iPt = 0;
328      for (int i = 0; i < nPts; i++) {
329          double ang = startAng + i * angInc;
330          double x = xRadius * Math.cos(ang) + centreX;
331          double y = yRadius * Math.sin(ang) + centreY;
332          pts[iPt++] = coord(x, y);
333      }
334      LineString line = geomFact.createLineString(pts);
335      return (LineString) rotate(line);
336    }
337  
338   /**
339    * Creates an elliptical arc polygon.
340    * The polygon is formed from the specified arc of an ellipse
341    * and the two radii connecting the endpoints to the centre of the ellipse.
342    *
343    * @param startAng start angle in radians
344    * @param angExtent size of angle in radians
345    * @return an elliptical arc polygon
346    */
347   public Polygon createArcPolygon(double startAng, double angExtent) {
348     Envelope env = dim.getEnvelope();
349     double xRadius = env.getWidth() / 2.0;
350     double yRadius = env.getHeight() / 2.0;
351  
352     double centreX = env.getMinX() + xRadius;
353     double centreY = env.getMinY() + yRadius;
354  
355     double angSize = angExtent;
356     if (angSize <= 0.0 || angSize > 2 * Math.PI)
357       angSize = 2 * Math.PI;
358     double angInc = angSize / (nPts - 1);
359     // double check = angInc * nPts;
360     // double checkEndAng = startAng + check;
361  
362     Coordinate[] pts = new Coordinate[nPts + 2];
363  
364     int iPt = 0;
365     pts[iPt++] = coord(centreX, centreY);
366     for (int i = 0; i < nPts; i++) {
367       double ang = startAng + angInc * i;
368  
369       double x = xRadius * Math.cos(ang) + centreX;
370       double y = yRadius * Math.sin(ang) + centreY;
371       pts[iPt++] = coord(x, y);
372     }
373     pts[iPt++] = coord(centreX, centreY);
374     LinearRing ring = geomFact.createLinearRing(pts);
375     Polygon poly = geomFact.createPolygon(ring);
376     return (Polygon) rotate(poly);
377   }
378  
379   protected Coordinate coord(double x, double y)
380   {
381       Coordinate pt = new Coordinate(x, y);
382     precModel.makePrecise(pt);
383     return pt;
384   }
385   
386   protected Coordinate coordTrans(double x, double y, Coordinate trans)
387   {
388       return coord(x + trans.x, y + trans.y);
389   }
390   
391   static protected class Dimensions
392   {
393     public Coordinate base;
394     public Coordinate centre;
395     public double width;
396     public double height;
397  
398     public void setBase(Coordinate base)  {  this.base = base;    }
399     public Coordinate getBase() { return base; }
400     
401     public void setCentre(Coordinate centre)  {  this.centre = centre;    }
402     public Coordinate getCentre() 
403     { 
404       if (centre == null) {
405         centre = new Coordinate(base.x + width/2, base.y + height/2);
406       }
407       return centre; 
408     }
409    
410     public void setSize(double size)
411     {
412       height = size;
413       width = size;
414     }
415  
416     public double getMinSize()
417     {
418         return Math.min(width, height);
419     }
420     public void setWidth(double width) { this.width = width; }
421     public double getWidth() { return width; }
422     public double getHeight() { return height; }
423     
424     public void setHeight(double height) { this.height = height; }
425  
426     public void setEnvelope(Envelope env)
427     {
428         this.width = env.getWidth();
429         this.height = env.getHeight();
430         this.base = new Coordinate(env.getMinX(), env.getMinY());
431         this.centre = new Coordinate(env.centre());
432     }
433     
434     public Envelope getEnvelope() {
435       if (base != null) {
436         return new Envelope(base.x, base.x + width, base.y, base.y + height);
437       }
438       if (centre != null) {
439         return new Envelope(centre.x - width/2, centre.x + width/2,
440                             centre.y - height/2, centre.y + height/2);
441       }
442       return new Envelope(0, width, 0, height);
443     }
444     
445   }
446 }
447