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