/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.utils; import java.util.ArrayList; import java.util.List; import org.voltdb.types.GeographyPointValue; import org.voltdb.types.GeographyValue; /** * A class to create polygons. */ public class PolygonFactory { /** * Create a regular convex polygon, with an optional hole. * * Note that the resulting polygon will be symmetric around any line * through the center and a vertex. Consequently, the centroid of such * a polygon must be the center of the polygon. * * @param center The center of the polygon. * @param firstVertex The coordinates of the first vertex. * @param numVertices The number of vertices. * @param sizeOfHole If this is positive, we also create a hole whose vertices are * at the same angle from the polygon's center, but whose distance * is scaled by sizeOfHole. This value must be in the range [0,1). * @return */ public static GeographyValue CreateRegularConvex( GeographyPointValue center, GeographyPointValue firstVertex, int numVertices, double sizeOfHole) { assert(0 <= sizeOfHole && sizeOfHole < 1.0); double phi = 360.0/numVertices; GeographyPointValue holeFirstVertex = null; if (sizeOfHole > 0) { holeFirstVertex = firstVertex.scale(center, sizeOfHole); } List<GeographyPointValue> oneLoop = new ArrayList<GeographyPointValue>(); List<GeographyPointValue> hole = (sizeOfHole < 0 ? null : new ArrayList<GeographyPointValue>()); // We will add the nth point at angle n*phi. We want to // add points in a counter clockwise order, so phi must be // a positive angle. We will have twice as many vertices // as points. for (int idx = 0; idx < numVertices; idx += 1) { oneLoop.add(firstVertex.rotate(idx*phi, center)); if (sizeOfHole > 0) { hole.add(holeFirstVertex.rotate(-(idx*phi), center)); } } // Add the closing vertices. oneLoop.add(firstVertex); if (sizeOfHole > 0) { hole.add(holeFirstVertex); } List<List<GeographyPointValue>> loops = new ArrayList<List<GeographyPointValue>>(); loops.add(oneLoop); if (sizeOfHole > 0) { loops.add(hole); } return new GeographyValue(loops); } /** * Create a star-shaped polygon with an optional hole. This polygon will have * numPointsInStar outer points, and numPointsInStar inner points. For each * outer point, OP, distance(center, OP) = distance(center, firstVertex). For * each inner point, IP, distance(center, IP) = ratioOfPointLength*distance(center, firstVertex). * * If sizeOfHole is positive, then there is a hole with inner and outer vertices, as * with the exterior shell. For each hole exterior point, HEP, * distance(center, HEP) = sizeOfHole*distance(center, firstVertex). For each * hole interior point, HIP, * distance(center, HIP) = sizeOfHole*rationOfPointLength*distance(center, firstVertex). * So, the hole is equal to the exterior shell scaled by the number sizeOfHole. * * Note that this polygon will be symmetric around any line through the center and * an outer or inner point. Consequently, the centroid of the generated polygon * must be the center. * * @param center The center of the polygon. * @param firstVertex The first vertex. * @param numPointsInStar The number of exterior points in the star. * @param ratioOfPointLength The outer/inner scale factor. This must be in the range (0,1]. * @param sizeOfHole The scale factor for the hole. This must be in the range [0,1). * @return * @throws IllegalArgumentException */ public static GeographyValue CreateStar( GeographyPointValue center, GeographyPointValue firstVertex, int numPointsInStar, double ratioOfPointLength, // pass in 1.0 for a polygon with twice the number of vertices, or .1 for a very jagged star double sizeOfHole) throws IllegalArgumentException { // I don't think a 1 or 2 pointed star is possible. if (numPointsInStar < 3) { throw new IllegalArgumentException("Star polygons must have 3 or more points."); } if (sizeOfHole < 0 || 1.0 <= sizeOfHole) { throw new IllegalArgumentException("Star polygon hole size must be in the range [0.0, 1.0)"); } if (ratioOfPointLength <= 0 || 1.0 < ratioOfPointLength) { throw new IllegalArgumentException("Star polygon external/internal radius ration must be in the range (0.0, 1.0]"); } // We will add the nth point at angle n*phi. We want to // add points in a counter clockwise order, so phi must be // a positive angle. We will have twice as many vertices // as points. double phi = 360.0/(2*numPointsInStar); GeographyPointValue innerFirstVertex = firstVertex.scale(center, ratioOfPointLength); GeographyPointValue holeFirstVertex = null; GeographyPointValue innerHoleFirstVertex = null; // Calculate hole radii. if (sizeOfHole > 0) { holeFirstVertex = firstVertex.scale(center, sizeOfHole); innerHoleFirstVertex = firstVertex.scale(center, sizeOfHole * ratioOfPointLength); } // The outer and inner radii go here. We will // index into this array using the last bit of // an integer loop index. GeographyPointValue firstVertices[] = { firstVertex, innerFirstVertex }; // We use the same trick here. GeographyPointValue holeFirstVertices[] = { holeFirstVertex, innerHoleFirstVertex }; // // We have to add all shells in counter clockwise order, and all // holes in clockwise order. This amounts to rotating the shell // generator vector by phi and the hole generator vector by -phi. // List<GeographyPointValue> outerLoop = new ArrayList<GeographyPointValue>(); List<GeographyPointValue> holeLoop = null; if (sizeOfHole > 0) { holeLoop = new ArrayList<GeographyPointValue>(); } for (int idx = 0; idx < 2*numPointsInStar; idx += 1) { GeographyPointValue vert = null; GeographyPointValue holeVert = null; // Even vertices are the points. // Odd vertices are the valleys, if that's the right // term. vert = firstVertices[idx % 2]; holeVert = holeFirstVertices[idx % 2]; outerLoop.add(vert.rotate(idx*phi, center)); if (sizeOfHole > 0) { holeLoop.add(holeVert.rotate(-(idx*phi), center)); } } outerLoop.add(outerLoop.get(0)); if (sizeOfHole > 0) { holeLoop.add(holeLoop.get(0)); } List<List<GeographyPointValue>> loops = new ArrayList<List<GeographyPointValue>>(); loops.add(outerLoop); if (sizeOfHole > 0) { loops.add(holeLoop); } return new GeographyValue(loops); } }