001: /*
002: * The JTS Topology Suite is a collection of Java classes that
003: * implement the fundamental operations required to validate a given
004: * geo-spatial data set to a known topological specification.
005: *
006: * Copyright (C) 2001 Vivid Solutions
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public
019: * License along with this library; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: *
022: * For more information, contact:
023: *
024: * Vivid Solutions
025: * Suite #1A
026: * 2328 Government Street
027: * Victoria BC V8T 5G5
028: * Canada
029: *
030: * (250)385-6040
031: * www.vividsolutions.com
032: */
033: package com.vividsolutions.jts.generator;
034:
035: import com.vividsolutions.jts.geom.*;
036: import com.vividsolutions.jts.geom.Geometry;
037: import com.vividsolutions.jts.operation.valid.IsValidOp;
038:
039: /**
040: *
041: * This class is used to create a polygon within the specified bounding box.
042: *
043: * Sucessive calls to create may or may not return the same geometry topology.
044: *
045: * @author David Zwiers, Vivid Solutions.
046: */
047: public class PolygonGenerator extends GeometryGenerator {
048: protected int numberPoints = 4;
049: protected int numberHoles = 0;
050: protected int generationAlgorithm = 0;
051:
052: /**
053: * Creates rectangular polygons
054: */
055: public static final int BOX = 0;
056:
057: /**
058: * Creates polygons whose points will not be rectangular when there are more than 4 points
059: */
060: public static final int ARC = 1;
061:
062: private static final int RUNS = 5;
063:
064: /**
065: * As the user increases the number of points, the probability of creating a random valid polygon decreases.
066: * Please take not of this when selecting the generation style, and the number of points.
067: *
068: * May return null if a geometry could not be created.
069: *
070: * @see #getNumberPoints()
071: * @see #setNumberPoints(int)
072: * @see #getGenerationAlgorithm()
073: * @see #setGenerationAlgorithm(int)
074: *
075: * @see #BOX
076: * @see #ARC
077: *
078: * @see com.vividsolutions.jts.generator.GeometryGenerator#create()
079: *
080: * @throws IllegalStateException When the alg is not valid or the number of points is invalid
081: * @throws NullPointerException when either the Geometry Factory, or the Bounding Box are undefined.
082: */
083: public Geometry create() {
084:
085: if (geometryFactory == null) {
086: throw new NullPointerException(
087: "GeometryFactory is not declared");
088: }
089: if (boundingBox == null || boundingBox.isNull()) {
090: throw new NullPointerException(
091: "Bounding Box is not declared");
092: }
093: if (numberPoints < 4) {
094: throw new IllegalStateException("Too few points");
095: }
096:
097: double x = boundingBox.getMinX(); // base x
098: double dx = boundingBox.getMaxX() - x;
099:
100: double y = boundingBox.getMinY(); // base y
101: double dy = boundingBox.getMaxY() - y;
102:
103: Polygon p = null;
104:
105: for (int i = 0; i < RUNS; i++) {
106: switch (getGenerationAlgorithm()) {
107: case BOX:
108: p = createBox(x, dx, y, dy, numberHoles, numberPoints,
109: geometryFactory);
110: break;
111: case ARC:
112: p = createArc(x, dx, y, dy, numberHoles, numberPoints,
113: geometryFactory);
114: break;
115: default:
116: throw new IllegalStateException(
117: "Invalid Alg. Specified");
118: }
119:
120: IsValidOp valid = new IsValidOp(p);
121: if (valid.isValid()) {
122: return p;
123: }
124: }
125: return null;
126: }
127:
128: private static Polygon createArc(double x, double dx, double y,
129: double dy, int nholes, int npoints, GeometryFactory gf) {
130: // make outer ring first
131: double radius = dx < dy ? dx / 3 : dy / 3;
132:
133: double cx = x + (dx / 2); // center
134: double cy = y + (dy / 2); // center
135:
136: LinearRing outer = createArc(cx, cy, radius, npoints, gf);
137:
138: if (nholes == 0) {
139: return gf.createPolygon(outer, null);
140: }
141:
142: LinearRing[] inner = new LinearRing[nholes];
143:
144: radius *= .75;
145: int degreesPerHole = 360 / (nholes + 1);
146: int degreesPerGap = degreesPerHole / nholes;
147: degreesPerGap = degreesPerGap < 2 ? 2 : degreesPerGap;
148: degreesPerHole = (360 - (degreesPerGap * nholes)) / nholes;
149:
150: if (degreesPerHole < 2)
151: throw new RuntimeException(
152: "Slices too small for poly. Use Box alg.");
153:
154: int start = degreesPerGap / 2;
155: for (int i = 0; i < nholes; i++) {
156: int st = start + (i * (degreesPerHole + degreesPerGap)); // start angle
157: inner[i] = createTri(cx, cy, st, st + degreesPerHole,
158: radius, gf);
159: }
160:
161: return gf.createPolygon(outer, inner);
162: }
163:
164: private static LinearRing createTri(double cx, double cy,
165: int startAngle, int endAngle, double radius,
166: GeometryFactory gf) {
167:
168: Coordinate[] coords = new Coordinate[4];
169:
170: double fx1, fx2, fy1, fy2;
171:
172: double angle = Math.toRadians(startAngle);
173: fx1 = Math.sin(angle) * radius; // may be neg.
174: fy1 = Math.cos(angle) * radius; // may be neg.
175:
176: angle = Math.toRadians(endAngle);
177: fx2 = Math.sin(angle) * radius; // may be neg.
178: fy2 = Math.cos(angle) * radius; // may be neg.
179:
180: coords[0] = new Coordinate(cx, cy);
181: gf.getPrecisionModel().makePrecise(coords[0]);
182: coords[1] = new Coordinate(cx + fx1, cy + fy1);
183: gf.getPrecisionModel().makePrecise(coords[1]);
184: coords[2] = new Coordinate(cx + fx2, cy + fy2);
185: gf.getPrecisionModel().makePrecise(coords[2]);
186: coords[3] = new Coordinate(cx, cy);
187: gf.getPrecisionModel().makePrecise(coords[3]);
188:
189: return gf.createLinearRing(coords);
190: }
191:
192: private static LinearRing createArc(double cx, double cy,
193: double radius, int npoints, GeometryFactory gf) {
194:
195: Coordinate[] coords = new Coordinate[npoints + 1];
196:
197: double theta = 360 / npoints;
198:
199: for (int i = 0; i < npoints; i++) {
200: double angle = Math.toRadians(theta * i);
201:
202: double fx = Math.sin(angle) * radius; // may be neg.
203: double fy = Math.cos(angle) * radius; // may be neg.
204:
205: coords[i] = new Coordinate(cx + fx, cy + fy);
206: gf.getPrecisionModel().makePrecise(coords[i]);
207: }
208:
209: coords[npoints] = new Coordinate(coords[0]);
210: gf.getPrecisionModel().makePrecise(coords[npoints]);
211:
212: return gf.createLinearRing(coords);
213: }
214:
215: private static Polygon createBox(double x, double dx, double y,
216: double dy, int nholes, int npoints, GeometryFactory gf) {
217: // make outer ring first
218: LinearRing outer = createBox(x, dx, y, dy, npoints, gf);
219:
220: if (nholes == 0) {
221: return gf.createPolygon(outer, null);
222: }
223:
224: LinearRing[] inner = new LinearRing[nholes];
225:
226: int nrow = (int) Math.sqrt(nholes);
227: int ncol = nholes / nrow;
228:
229: double ddx = dx / (ncol + 1);
230: double ddy = dy / (nrow + 1);
231:
232: // spacers
233: double spx = ddx / (ncol + 1);
234: double spy = ddy / (nrow + 1);
235:
236: // should have more grids than required
237: int cindex = 0;
238: for (int i = 0; i < nrow; i++) {
239: for (int j = 0; j < ncol; j++) {
240: if (cindex < nholes) {
241: // make another box
242: int pts = npoints / 2;
243: pts = pts < 4 ? 4 : pts;
244:
245: inner[cindex++] = createBox(spx + x + j
246: * (ddx + spx), ddx, spy + y + i
247: * (ddy + spy), ddy, pts, gf);
248: }
249: }
250: }
251:
252: return gf.createPolygon(outer, inner);
253: }
254:
255: private static LinearRing createBox(double x, double dx, double y,
256: double dy, int npoints, GeometryFactory gf) {
257:
258: //figure out the number of points per side
259: int ptsPerSide = npoints / 4;
260: int rPtsPerSide = npoints % 4;
261: Coordinate[] coords = new Coordinate[npoints + 1];
262: coords[0] = new Coordinate(x, y); // start
263: gf.getPrecisionModel().makePrecise(coords[0]);
264:
265: int cindex = 1;
266: for (int i = 0; i < 4; i++) { // sides
267: int npts = ptsPerSide + (rPtsPerSide-- > 0 ? 1 : 0);
268: // npts atleast 1
269:
270: if (i % 2 == 1) { // odd vert
271: double cy = dy / npts;
272: if (i > 1) // down
273: cy *= -1;
274: double tx = coords[cindex - 1].x;
275: double sy = coords[cindex - 1].y;
276:
277: for (int j = 0; j < npts; j++) {
278: coords[cindex] = new Coordinate(tx, sy + (j + 1)
279: * cy);
280: gf.getPrecisionModel()
281: .makePrecise(coords[cindex++]);
282: }
283: } else { // even horz
284: double cx = dx / npts;
285: if (i > 1) // down
286: cx *= -1;
287: double ty = coords[cindex - 1].y;
288: double sx = coords[cindex - 1].x;
289:
290: for (int j = 0; j < npts; j++) {
291: coords[cindex] = new Coordinate(sx + (j + 1) * cx,
292: ty);
293: gf.getPrecisionModel()
294: .makePrecise(coords[cindex++]);
295: }
296: }
297: }
298: coords[npoints] = new Coordinate(x, y); // end
299: gf.getPrecisionModel().makePrecise(coords[npoints]);
300:
301: return gf.createLinearRing(coords);
302: }
303:
304: /**
305: * @return Returns the generationAlgorithm.
306: */
307: public int getGenerationAlgorithm() {
308: return generationAlgorithm;
309: }
310:
311: /**
312: * @param generationAlgorithm The generationAlgorithm to set.
313: */
314: public void setGenerationAlgorithm(int generationAlgorithm) {
315: this .generationAlgorithm = generationAlgorithm;
316: }
317:
318: /**
319: * @return Returns the numberHoles.
320: */
321: public int getNumberHoles() {
322: return numberHoles;
323: }
324:
325: /**
326: * @param numberHoles The numberHoles to set.
327: */
328: public void setNumberHoles(int numberHoles) {
329: this .numberHoles = numberHoles;
330: }
331:
332: /**
333: * @return Returns the numberPoints.
334: */
335: public int getNumberPoints() {
336: return numberPoints;
337: }
338:
339: /**
340: * @param numberPoints The numberPoints to set.
341: */
342: public void setNumberPoints(int numberPoints) {
343: this.numberPoints = numberPoints;
344: }
345:
346: }
|