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.generator;
13 
14 import org.locationtech.jts.geom.Coordinate;
15 import org.locationtech.jts.geom.Geometry;
16 import org.locationtech.jts.geom.GeometryFactory;
17 import org.locationtech.jts.geom.LinearRing;
18 import org.locationtech.jts.geom.Polygon;
19 import org.locationtech.jts.operation.valid.IsValidOp;
20 
21 /**
22  *
23  * This class is used to create a polygon within the specified bounding box.
24  *
25  * Successive calls to create may or may not return the same geometry topology.
26  *
27  * @author David Zwiers, Vivid Solutions.
28  */
29 public class PolygonGenerator extends GeometryGenerator {
30 	protected int numberPoints = 4;
31 	protected int numberHoles = 0;
32 	protected int generationAlgorithm = 0;
33 
34 	/**
35 	 * Creates rectangular polygons
36 	 */
37 	public static final int BOX = 0;
38 
39 	/**
40 	 * Creates polygons whose points will not be rectangular when there are more than 4 points
41 	 */
42 	public static final int ARC = 1;
43 
44 	private static final int RUNS = 5;
45 
46 	/**
47 	 * As the user increases the number of points, the probability of creating a random valid polygon decreases.
48 	 * Please take not of this when selecting the generation style, and the number of points.
49 	 *
50 	 * May return null if a geometry could not be created.
51 	 *
52 	 * @see #getNumberPoints()
53 	 * @see #setNumberPoints(int)
54 	 * @see #getGenerationAlgorithm()
55 	 * @see #setGenerationAlgorithm(int)
56 	 *
57 	 * @see #BOX
58 	 * @see #ARC
59 	 *
60 	 * @see org.locationtech.jts.generator.GeometryGenerator#create()
61 	 *
62 	 * @throws IllegalStateException When the alg is not valid or the number of points is invalid
63 	 * @throws NullPointerException when either the Geometry Factory, or the Bounding Box are undefined.
64 	 */
create()65 	public Geometry create() {
66 
67 		if(geometryFactory == null){
68 			throw new NullPointerException("GeometryFactory is not declared");
69 		}
70 		if(boundingBox == null || boundingBox.isNull()){
71 			throw new NullPointerException("Bounding Box is not declared");
72 		}
73 		if(numberPoints<4){
74 			throw new IllegalStateException("Too few points");
75 		}
76 
77 		double x = boundingBox.getMinX(); // base x
78 		double dx = boundingBox.getMaxX()-x;
79 
80 		double y = boundingBox.getMinY(); // base y
81 		double dy = boundingBox.getMaxY()-y;
82 
83 		Polygon p = null;
84 
85 		for(int i=0;i<RUNS;i++){
86 			switch(getGenerationAlgorithm()){
87 			case BOX:
88 				p = createBox(x,dx,y,dy,numberHoles,numberPoints,geometryFactory);
89 				break;
90 			case ARC:
91 				p = createArc(x,dx,y,dy,numberHoles,numberPoints,geometryFactory);
92 				break;
93 			default:
94 				throw new IllegalStateException("Invalid Alg. Specified");
95 			}
96 
97 			IsValidOp valid = new IsValidOp(p);
98 			if(valid.isValid()){
99 				return p;
100 			}
101 		}
102 		return null;
103 	}
104 
createArc(double x, double dx, double y, double dy, int nholes, int npoints, GeometryFactory gf)105 	private static Polygon createArc(double x, double dx, double y, double dy, int nholes, int npoints, GeometryFactory gf){
106 		// make outer ring first
107 		double radius = dx<dy?dx/3:dy/3;
108 
109 		double cx = x+(dx/2); // center
110 		double cy = y+(dy/2); // center
111 
112 		LinearRing outer = createArc(cx,cy,radius,npoints,gf);
113 
114 		if(nholes == 0){
115 			return gf.createPolygon(outer,null);
116 		}
117 
118 		LinearRing[] inner = new LinearRing[nholes];
119 
120 		radius *= .75;
121 		int degreesPerHole = 360/(nholes+1);
122 		int degreesPerGap = degreesPerHole/nholes;
123 		degreesPerGap = degreesPerGap<2?2:degreesPerGap;
124 		degreesPerHole = (360-(degreesPerGap*nholes))/nholes;
125 
126 		if(degreesPerHole < 2)
127 			throw new RuntimeException("Slices too small for poly. Use Box alg.");
128 
129 		int start = degreesPerGap/2;
130 		for(int i=0;i<nholes;i++){
131 			int st = start+(i*(degreesPerHole+degreesPerGap)); // start angle
132 			inner[i] = createTri(cx,cy,st,st+degreesPerHole,radius,gf);
133 		}
134 
135 
136 		return gf.createPolygon(outer,inner);
137 	}
138 
139 	private static LinearRing createTri(double cx, double cy ,int startAngle, int endAngle, double radius, GeometryFactory gf){
140 
141 		Coordinate[] coords = new Coordinate[4];
142 
143 		double fx1,fx2,fy1,fy2;
144 
145 		double angle = Math.toRadians(startAngle);
146 		fx1 = Math.sin(angle)*radius; // may be neg.
147 		fy1 = Math.cos(angle)*radius; // may be neg.
148 
149 		angle = Math.toRadians(endAngle);
150 		fx2 = Math.sin(angle)*radius; // may be neg.
151 		fy2 = Math.cos(angle)*radius; // may be neg.
152 
153 		coords[0] = new Coordinate(cx,cy);
154 		gf.getPrecisionModel().makePrecise(coords[0]);
155 		coords[1] = new Coordinate(cx+fx1,cy+fy1);
156 		gf.getPrecisionModel().makePrecise(coords[1]);
157 		coords[2] = new Coordinate(cx+fx2,cy+fy2);
158 		gf.getPrecisionModel().makePrecise(coords[2]);
159 		coords[3] = new Coordinate(cx,cy);
160 		gf.getPrecisionModel().makePrecise(coords[3]);
161 
162 		return gf.createLinearRing(coords);
163 	}
164 
165 	private static LinearRing createArc(double cx, double cy ,double radius, int npoints, GeometryFactory gf){
166 
167 		Coordinate[] coords = new Coordinate[npoints+1];
168 
169 		double theta = 360/npoints;
170 
171 		for(int i=0;i<npoints;i++){
172 			double angle = Math.toRadians(theta*i);
173 
174 			double fx = Math.sin(angle)*radius; // may be neg.
175 			double fy = Math.cos(angle)*radius; // may be neg.
176 
177 			coords[i] = new Coordinate(cx+fx,cy+fy);
178 			gf.getPrecisionModel().makePrecise(coords[i]);
179 		}
180 
181 		coords[npoints] = new Coordinate(coords[0]);
182 		gf.getPrecisionModel().makePrecise(coords[npoints]);
183 
184 		return gf.createLinearRing(coords);
185 	}
186 
187 	private static Polygon createBox(double x, double dx, double y, double dy, int nholes, int npoints, GeometryFactory gf){
188 		// make outer ring first
189 		LinearRing outer = createBox(x,dx,y,dy,npoints,gf);
190 
191 		if(nholes == 0){
192 			return gf.createPolygon(outer,null);
193 		}
194 
195 		LinearRing[] inner = new LinearRing[nholes];
196 
197 		int nrow = (int)Math.sqrt(nholes);
198 		int ncol = nholes/nrow;
199 
200 		double ddx = dx/(ncol+1);
201 		double ddy = dy/(nrow+1);
202 
203 		// spacers
204 		double spx = ddx/(ncol+1);
205 		double spy = ddy/(nrow+1);
206 
207 		// should have more grids than required
208 		int cindex = 0;
209 		for(int i=0;i<nrow;i++){
210 			for(int j=0;j<ncol;j++){
211 				if(cindex<nholes){
212 					// make another box
213 					int pts = npoints/2;
214 					pts = pts<4?4:pts;
215 
216 					inner[cindex++] = createBox(spx+x+j*(ddx+spx),ddx,spy+y+i*(ddy+spy),ddy,pts,gf);
217 				}
218 			}
219 		}
220 
221 		return gf.createPolygon(outer,inner);
222 	}
223 
224 	private static LinearRing createBox(double x, double dx, double y, double dy, int npoints, GeometryFactory gf){
225 
226 		//figure out the number of points per side
227 		int ptsPerSide = npoints/4;
228 		int rPtsPerSide = npoints%4;
229 		Coordinate[] coords = new Coordinate[npoints+1];
230 		coords[0] = new Coordinate(x,y); // start
231 		gf.getPrecisionModel().makePrecise(coords[0]);
232 
233 		int cindex = 1;
234 		for(int i=0;i<4;i++){ // sides
235 			int npts = ptsPerSide+(rPtsPerSide-->0?1:0);
236 			// npts atleast 1
237 
238 			if(i%2 == 1){ // odd vert
239 				double cy = dy/npts;
240 				if(i > 1) // down
241 					cy *=-1;
242 				double tx = coords[cindex-1].x;
243 				double sy = coords[cindex-1].y;
244 
245 				for(int j=0;j<npts;j++){
246 					coords[cindex] = new Coordinate(tx,sy+(j+1)*cy);
247 					gf.getPrecisionModel().makePrecise(coords[cindex++]);
248 				}
249 			}else{ // even horz
250 				double cx = dx/npts;
251 				if(i > 1) // down
252 					cx *=-1;
253 				double ty = coords[cindex-1].y;
254 				double sx = coords[cindex-1].x;
255 
256 				for(int j=0;j<npts;j++){
257 					coords[cindex] = new Coordinate(sx+(j+1)*cx,ty);
258 					gf.getPrecisionModel().makePrecise(coords[cindex++]);
259 				}
260 			}
261 		}
262 		coords[npoints] = new Coordinate(x,y); // end
263 		gf.getPrecisionModel().makePrecise(coords[npoints]);
264 
265 		return gf.createLinearRing(coords);
266 	}
267 
268 	/**
269 	 * @return Returns the generationAlgorithm.
270 	 */
271 	public int getGenerationAlgorithm() {
272 		return generationAlgorithm;
273 	}
274 
275 	/**
276 	 * @param generationAlgorithm The generationAlgorithm to set.
277 	 */
278 	public void setGenerationAlgorithm(int generationAlgorithm) {
279 		this.generationAlgorithm = generationAlgorithm;
280 	}
281 
282 	/**
283 	 * @return Returns the numberHoles.
284 	 */
285 	public int getNumberHoles() {
286 		return numberHoles;
287 	}
288 
289 	/**
290 	 * @param numberHoles The numberHoles to set.
291 	 */
292 	public void setNumberHoles(int numberHoles) {
293 		this.numberHoles = numberHoles;
294 	}
295 
296 	/**
297 	 * @return Returns the numberPoints.
298 	 */
299 	public int getNumberPoints() {
300 		return numberPoints;
301 	}
302 
303 	/**
304 	 * @param numberPoints The numberPoints to set.
305 	 */
306 	public void setNumberPoints(int numberPoints) {
307 		this.numberPoints = numberPoints;
308 	}
309 
310 }
311