/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014, Open Source Geospatial Foundation (OSGeo) * * 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; * version 2.1 of the License. * * 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. */ package org.geotools.process.spatialstatistics.operations; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Logger; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.process.spatialstatistics.core.FeatureTypes; import org.geotools.process.spatialstatistics.core.SSUtils; import org.geotools.process.spatialstatistics.enumeration.RadialType; import org.geotools.process.spatialstatistics.storage.IFeatureInserter; import org.geotools.util.logging.Logging; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Creates a radial polar grids from geometry(centroid) or features. * * @author Minpa Lee, MangoSystem * * @source $URL$ */ public class PolarGridsOperation extends GeneralOperation { protected static final Logger LOGGER = Logging.getLogger(PolarGridsOperation.class); static final String TYPE_NAME = "PolarGrids"; static final int DEFAULT_SIDES = 8; static final int DEFAULT_SEGS = 8; static final String ANGLE_FIELD = "angle"; static final String RADIUS_FIELD = "radius"; static final String AZIMUTH_FIELD = "azimuth"; private boolean is8Sides = false; private RadialType radialType = RadialType.Polar; private boolean outsideOnly = true; public RadialType getRadialType() { return radialType; } public void setRadialType(RadialType radialType) { this.radialType = radialType; } public boolean isOutsideOnly() { return outsideOnly; } public void setOutsideOnly(boolean outsideOnly) { this.outsideOnly = outsideOnly; } public SimpleFeatureCollection execute(SimpleFeatureCollection features, double[] radius) throws IOException { return execute(features, radius, DEFAULT_SIDES); } public SimpleFeatureCollection execute(SimpleFeatureCollection features, double[] radius, int sides) throws IOException { return execute(features, radius, DEFAULT_SIDES, RadialType.Polar); } public SimpleFeatureCollection execute(SimpleFeatureCollection features, double[] radius, int sides, RadialType radialType) throws IOException { this.radialType = radialType; this.is8Sides = sides == 8; // sort arrays Arrays.sort(radius); SimpleFeatureType inputSchema = features.getSchema(); SimpleFeatureType featureType = FeatureTypes.build(inputSchema, TYPE_NAME, Polygon.class); featureType = FeatureTypes.add(featureType, ANGLE_FIELD, Double.class, 38); featureType = FeatureTypes.add(featureType, RADIUS_FIELD, Double.class, 38); if (sides == 8) { featureType = FeatureTypes.add(featureType, AZIMUTH_FIELD, String.class, 5); } // prepare transactional feature store IFeatureInserter featureWriter = getFeatureWriter(featureType); SimpleFeatureIterator featureIter = features.features(); try { while (featureIter.hasNext()) { SimpleFeature feature = featureIter.next(); Geometry geometry = (Geometry) feature.getDefaultGeometry(); if (geometry == null || geometry.isEmpty()) { continue; } createRadialGrids(featureWriter, feature, geometry.getCentroid(), radius, sides); } } catch (Exception e) { featureWriter.rollback(e); } finally { featureWriter.close(featureIter); } return featureWriter.getFeatureCollection(); } public SimpleFeatureCollection execute(Geometry center, double[] radius) throws IOException { return execute(center, radius, DEFAULT_SIDES); } public SimpleFeatureCollection execute(Geometry center, double[] radius, int sides) throws IOException { return execute(center, radius, DEFAULT_SIDES, RadialType.Polar); } public SimpleFeatureCollection execute(Geometry center, double[] radius, int sides, RadialType radialType) throws IOException { this.radialType = radialType; this.is8Sides = sides == 8; // sort arrays Arrays.sort(radius); // prepare feature type CoordinateReferenceSystem crs = null; if (center.getUserData() != null && center.getUserData() instanceof CoordinateReferenceSystem) { crs = (CoordinateReferenceSystem) center.getUserData(); } SimpleFeatureType featureType = FeatureTypes.getDefaultType(TYPE_NAME, Polygon.class, crs); featureType = FeatureTypes.add(featureType, ANGLE_FIELD, Double.class, 38); featureType = FeatureTypes.add(featureType, RADIUS_FIELD, Double.class, 38); if (sides == 8) { featureType = FeatureTypes.add(featureType, AZIMUTH_FIELD, String.class, 5); } // prepare transactional feature store IFeatureInserter featureWriter = getFeatureWriter(featureType); try { createRadialGrids(featureWriter, null, center.getCentroid(), radius, sides); } catch (Exception e) { featureWriter.rollback(e); } finally { featureWriter.close(); } return featureWriter.getFeatureCollection(); } private void createRadialGrids(IFeatureInserter featureWriter, SimpleFeature refFeature, Point center, double[] radius, int sides) throws IOException { Coordinate source = center.getCoordinate(); double stepAngle = 360.0d / (double) sides; double halfStep = radialType == RadialType.Base ? 0.0 : stepAngle / 2.0d; Geometry current; for (int sideIndex = 0; sideIndex < sides; sideIndex++) { double fromDeg = halfStep + (sideIndex * stepAngle); double toDeg = halfStep + ((sideIndex + 1) * stepAngle); for (int idx = 0; idx < radius.length; idx++) { // create feature and set geometry if (outsideOnly && idx > 0) { current = createCircularArc(source, fromDeg, toDeg, radius[idx - 1], radius[idx]); } else { current = createCircularArc(source, fromDeg, toDeg, radius[idx]); } SimpleFeature newFeature = featureWriter.buildFeature(); if (refFeature != null) { featureWriter.copyAttributes(refFeature, newFeature, false); } newFeature.setDefaultGeometry(current.clone()); newFeature.setAttribute(ANGLE_FIELD, fromDeg); newFeature.setAttribute(RADIUS_FIELD, radius[idx]); if (is8Sides) { newFeature.setAttribute(AZIMUTH_FIELD, getAzimuth(fromDeg)); } featureWriter.write(newFeature); } } } private Geometry createCircularArc(Coordinate source, double fromDeg, double toDeg, double fromRadius, double toRadius) { final double step = Math.abs(toDeg - fromDeg) / (double) DEFAULT_SEGS; List<Coordinate> coordinates = new ArrayList<Coordinate>(); double radian; // first interior rings for (int index = 0; index <= DEFAULT_SEGS; index++) { if (index == 0) { radian = SSUtils.convert2Radians(fromDeg); } else if (index == DEFAULT_SEGS) { radian = SSUtils.convert2Radians(toDeg); } else { radian = SSUtils.convert2Radians(fromDeg + (index * step)); } coordinates.add(createCoordinate(source, radian, fromRadius)); } // second outer rings for (int index = DEFAULT_SEGS; index >= 0; index--) { if (index == 0) { radian = SSUtils.convert2Radians(fromDeg); } else if (index == DEFAULT_SEGS) { radian = SSUtils.convert2Radians(toDeg); } else { radian = SSUtils.convert2Radians(fromDeg + (index * step)); } coordinates.add(createCoordinate(source, radian, toRadius)); } // close rings coordinates.add(coordinates.get(0)); // create polygon Coordinate[] coords = new Coordinate[coordinates.size()]; coordinates.toArray(coords); return gf.createPolygon(gf.createLinearRing(coords), null); } private Geometry createCircularArc(Coordinate source, double fromDeg, double toDeg, double radius) { final double step = Math.abs(toDeg - fromDeg) / (double) DEFAULT_SEGS; List<Coordinate> coordinates = new ArrayList<Coordinate>(); coordinates.add(source); double radian; for (int index = DEFAULT_SEGS; index >= 0; index--) { if (index == 0) { radian = SSUtils.convert2Radians(fromDeg); } else if (index == DEFAULT_SEGS) { radian = SSUtils.convert2Radians(toDeg); } else { radian = SSUtils.convert2Radians(fromDeg + (index * step)); } coordinates.add(createCoordinate(source, radian, radius)); } // close rings coordinates.add(coordinates.get(0)); // create polygon Coordinate[] coords = new Coordinate[coordinates.size()]; coordinates.toArray(coords); return gf.createPolygon(gf.createLinearRing(coords), null); } private Coordinate createCoordinate(Coordinate source, double radian, double radius) { double dx = Math.cos(radian) * radius; double dy = Math.sin(radian) * radius; return new Coordinate(source.x + dx, source.y + dy); } private String getAzimuth(double degree) { // sector = [Azimuth] 'sector angle in degrees, must be 0,45,90,135,180,225,270,or 315 degree = degree > 360 ? degree - 360 : degree; String azimuth = ""; if (radialType == RadialType.Base) { if (degree >= 0 && degree < 45) { azimuth = "NEE"; } else if (degree >= 45 && degree < 90) { azimuth = "NNE"; } else if (degree >= 90 && degree < 135) { azimuth = "NNW"; } else if (degree >= 135 && degree < 180) { azimuth = "NWW"; } else if (degree >= 180 && degree < 225) { azimuth = "SWW"; } else if (degree >= 225 && degree < 270) { azimuth = "SSW"; } else if (degree >= 270 && degree < 315) { azimuth = "SSE"; } else if (degree >= 315 && degree < 360) { azimuth = "SEE"; } } else { if (degree >= 22.5 && degree < 67.5) { azimuth = "NE"; } else if (degree >= 67.5 && degree < 112.5) { azimuth = "N"; } else if (degree >= 112.5 && degree < 157.5) { azimuth = "NW"; } else if (degree >= 157.5 && degree < 202.5) { azimuth = "W"; } else if (degree >= 202.5 && degree < 247.5) { azimuth = "SW"; } else if (degree >= 247.5 && degree < 292.5) { azimuth = "S"; } else if (degree >= 292.5 && degree < 337.5) { azimuth = "SE"; } else if (degree >= 337.5 || degree < 22.5) { azimuth = "E"; } } return azimuth; } }