/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.util; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.LinearRing; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.impl.BoundingBoxDoubleXY; import com.revolsys.geometry.model.impl.PointDoubleXY; /** * Computes various kinds of common geometric shapes. * Provides various ways of specifying the location and extent * and rotations of the generated shapes, * as well as number of line segments used to form them. * <p> * <b>Example of usage:</b> * <pre> * GeometricShapeFactory gsf = new GeometricShapeFactory(); * gsf.setSize(100); * gsf.setNumPoints(100); * gsf.setBase(new BaseLasPoint(100.0, 100.0)); * gsf.setRotation(0.5); * Polygon rect = gsf.createRectangle(); * </pre> * * @version 1.7 */ public class GeometricShapeFactory { protected class Dimensions { public Point base; public Point centre; public double height; public double width; public Point getBase() { return this.base; } public Point getCentre() { if (this.centre == null) { this.centre = new PointDoubleXY(this.base.getX() + this.width / 2, this.base.getY() + this.height / 2); } return this.centre; } public BoundingBox getEnvelope() { if (this.base != null) { return new BoundingBoxDoubleXY(this.base.getX(), this.base.getY(), this.base.getX() + this.width, this.base.getY() + this.height); } if (this.centre != null) { return new BoundingBoxDoubleXY(this.centre.getX() - this.width / 2, this.centre.getY() - this.height / 2, this.centre.getX() + this.width / 2, this.centre.getY() + this.height / 2); } return new BoundingBoxDoubleXY(0, 0, this.width, this.height); } public double getHeight() { return this.height; } public double getMinSize() { return Math.min(this.width, this.height); } public double getWidth() { return this.width; } public void setBase(final Point base) { this.base = base; } public void setCentre(final Point centre) { this.centre = centre; } public void setEnvelope(final BoundingBox env) { this.width = env.getWidth(); this.height = env.getHeight(); this.base = new PointDoubleXY(env.getMinX(), env.getMinY()); this.centre = env.getCentre().newPoint2D(); } public void setHeight(final double height) { this.height = height; } public void setSize(final double size) { this.height = size; this.width = size; } public void setWidth(final double width) { this.width = width; } } protected Dimensions dim = new Dimensions(); protected GeometryFactory geomFact; protected int vertexCount = 100; /** * Construct a new shape factory which will create shapes using the default * {@link GeometryFactory}. */ public GeometricShapeFactory() { this(GeometryFactory.DEFAULT_3D); } /** * Construct a new shape factory which will create shapes using the given * {@link GeometryFactory}. * * @param geomFact the factory to use */ public GeometricShapeFactory(final GeometryFactory geomFact) { this.geomFact = geomFact; } protected Point coordTrans(final double x, final double y, final Point trans) { return newPoint(x + trans.getX(), y + trans.getY()); } /** * Creates an elliptical arc, as a {@link LineString}. * The arc is always created in a counter-clockwise direction. * This can easily be reversed if required by using * {#link LineString.reverse()} * * @param startAng start angle in radians * @param angExtent size of angle in radians * @return an elliptical arc */ public LineString newArc(final double startAng, final double angExtent) { final BoundingBox env = this.dim.getEnvelope(); final double xRadius = env.getWidth() / 2.0; final double yRadius = env.getHeight() / 2.0; final double centreX = env.getMinX() + xRadius; final double centreY = env.getMinY() + yRadius; double angSize = angExtent; if (angSize <= 0.0 || angSize > 2 * Math.PI) { angSize = 2 * Math.PI; } final double angInc = angSize / (this.vertexCount - 1); final Point[] pts = new Point[this.vertexCount]; int iPt = 0; for (int i = 0; i < this.vertexCount; i++) { final double ang = startAng + i * angInc; final double x = xRadius * Math.cos(ang) + centreX; final double y = yRadius * Math.sin(ang) + centreY; pts[iPt++] = newPoint(x, y); } final LineString line = this.geomFact.lineString(pts); return line; } /** * Creates an elliptical arc polygon. * The polygon is formed from the specified arc of an ellipse * and the two radii connecting the endpoints to the centre of the ellipse. * * @param startAng start angle in radians * @param angExtent size of angle in radians * @return an elliptical arc polygon */ public Polygon newArcPolygon(final double startAng, final double angExtent) { final BoundingBox env = this.dim.getEnvelope(); final double xRadius = env.getWidth() / 2.0; final double yRadius = env.getHeight() / 2.0; final double centreX = env.getMinX() + xRadius; final double centreY = env.getMinY() + yRadius; double angSize = angExtent; if (angSize <= 0.0 || angSize > 2 * Math.PI) { angSize = 2 * Math.PI; } final double angInc = angSize / (this.vertexCount - 1); // double check = angInc * nPts; // double checkEndAng = startAng + check; final Point[] pts = new Point[this.vertexCount + 2]; int iPt = 0; pts[iPt++] = newPoint(centreX, centreY); for (int i = 0; i < this.vertexCount; i++) { final double ang = startAng + angInc * i; final double x = xRadius * Math.cos(ang) + centreX; final double y = yRadius * Math.sin(ang) + centreY; pts[iPt++] = newPoint(x, y); } pts[iPt++] = newPoint(centreX, centreY); final LinearRing ring = this.geomFact.linearRing(pts); final Polygon poly = this.geomFact.polygon(ring); return poly; } // * @deprecated use {@link createEllipse} instead /** * Creates a circular or elliptical {@link Polygon}. * * @return a circle or ellipse */ public Polygon newCircle() { return newEllipse(); } /** * Creates an elliptical {@link Polygon}. * If the supplied envelope is square the * result will be a circle. * * @return an ellipse or circle */ public Polygon newEllipse() { final BoundingBox env = this.dim.getEnvelope(); final double xRadius = env.getWidth() / 2.0; final double yRadius = env.getHeight() / 2.0; final double centreX = env.getMinX() + xRadius; final double centreY = env.getMinY() + yRadius; final double[] coordinates = new double[(this.vertexCount + 1) * 2]; int coordinateIndex = 0; for (int i = 0; i < this.vertexCount; i++) { final double ang = i * (2 * Math.PI / this.vertexCount); final double x = xRadius * Math.cos(ang) + centreX; final double y = yRadius * Math.sin(ang) + centreY; coordinates[coordinateIndex++] = x; coordinates[coordinateIndex++] = y; } coordinates[coordinateIndex++] = 0; coordinates[coordinateIndex++] = 1; final Polygon poly = this.geomFact.polygon(2, coordinates); return poly; } protected Point newPoint(final double x, final double y) { return new PointDoubleXY(this.geomFact, x, y); } /** * Creates a rectangular {@link Polygon}. * * @return a rectangular Polygon * */ public Polygon newRectangle() { int i; int ipt = 0; int nSide = this.vertexCount / 4; if (nSide < 1) { nSide = 1; } final double XsegLen = this.dim.getEnvelope().getWidth() / nSide; final double YsegLen = this.dim.getEnvelope().getHeight() / nSide; final Point[] pts = new Point[4 * nSide + 1]; final BoundingBox env = this.dim.getEnvelope(); // double maxx = env.getMinX() + nSide * XsegLen; // double maxy = env.getMinY() + nSide * XsegLen; for (i = 0; i < nSide; i++) { final double x = env.getMinX() + i * XsegLen; final double y = env.getMinY(); pts[ipt++] = newPoint(x, y); } for (i = 0; i < nSide; i++) { final double x = env.getMaxX(); final double y = env.getMinY() + i * YsegLen; pts[ipt++] = newPoint(x, y); } for (i = 0; i < nSide; i++) { final double x = env.getMaxX() - i * XsegLen; final double y = env.getMaxY(); pts[ipt++] = newPoint(x, y); } for (i = 0; i < nSide; i++) { final double x = env.getMinX(); final double y = env.getMaxY() - i * YsegLen; pts[ipt++] = newPoint(x, y); } pts[ipt++] = pts[0]; final LinearRing ring = this.geomFact.linearRing(pts); final Polygon poly = this.geomFact.polygon(ring); return poly; } /** * Creates a squircular {@link Polygon}. * * @return a squircle */ public Polygon newSquircle() /** * Creates a squircular {@link Polygon}. * * @return a squircle */ { return newSupercircle(4); } /** * Creates a supercircular {@link Polygon} * of a given positive power. * * @return a supercircle */ public Polygon newSupercircle(final double power) { final double recipPow = 1.0 / power; final double radius = this.dim.getMinSize() / 2; final Point centre = this.dim.getCentre(); final double r4 = Math.pow(radius, power); final double y0 = radius; final double xyInt = Math.pow(r4 / 2, recipPow); final int nSegsInOct = this.vertexCount / 8; final int totPts = nSegsInOct * 8 + 1; final Point[] pts = new Point[totPts]; final double xInc = xyInt / nSegsInOct; for (int i = 0; i <= nSegsInOct; i++) { double x = 0.0; double y = y0; if (i != 0) { x = xInc * i; final double x4 = Math.pow(x, power); y = Math.pow(r4 - x4, recipPow); } pts[i] = coordTrans(x, y, centre); pts[2 * nSegsInOct - i] = coordTrans(y, x, centre); pts[2 * nSegsInOct + i] = coordTrans(y, -x, centre); pts[4 * nSegsInOct - i] = coordTrans(x, -y, centre); pts[4 * nSegsInOct + i] = coordTrans(-x, -y, centre); pts[6 * nSegsInOct - i] = coordTrans(-y, -x, centre); pts[6 * nSegsInOct + i] = coordTrans(-y, x, centre); pts[8 * nSegsInOct - i] = coordTrans(-x, y, centre); } pts[pts.length - 1] = pts[0]; final LinearRing ring = this.geomFact.linearRing(pts); final Polygon poly = this.geomFact.polygon(ring); return poly; } /** * Sets the location of the shape by specifying the base coordinate * (which in most cases is the * lower left point of the envelope containing the shape). * * @param base the base coordinate of the shape */ public void setBase(final Point base) { this.dim.setBase(base); } /** * Sets the location of the shape by specifying the centre of * the shape's bounding box * * @param centre the centre coordinate of the shape */ public void setCentre(final Point centre) { this.dim.setCentre(centre); } public void setEnvelope(final BoundingBox env) { this.dim.setEnvelope(env); } /** * Sets the height of the shape. * * @param height the height of the shape */ public void setHeight(final double height) { this.dim.setHeight(height); } /** * Sets the total number of points in the created {@link Geometry}. * The created geometry will have no more than this number of points, * unless more are needed to Construct a new valid geometry. */ public void setNumPoints(final int nPts) { this.vertexCount = nPts; } /** * Sets the size of the extent of the shape in both x and y directions. * * @param size the size of the shape's extent */ public void setSize(final double size) { this.dim.setSize(size); } /** * Sets the width of the shape. * * @param width the width of the shape */ public void setWidth(final double width) { this.dim.setWidth(width); } }