/* * 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.distribution; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.factory.GeoTools; import org.geotools.geometry.jts.JTSFactoryFinder; import org.geotools.process.spatialstatistics.core.SSUtils; import org.geotools.util.logging.Logging; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateArrays; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Point; /** * StandardDistanceEllipse * * @author Minpa Lee, MangoSystem * * @source $URL$ */ public class StandardDeviationalEllipse { protected static final Logger LOGGER = Logging.getLogger(StandardDeviationalEllipse.class); private double sumX = 0.0; private double sumY = 0.0; private double sumZ = 0.0; private double weightSum = 0.0; private List<Event> events = new ArrayList<Event>(); private GeometryFactory gf = JTSFactoryFinder.getGeometryFactory(GeoTools.getDefaultHints()); class Event { public double x; public double y; public double weight; public Event() { } public Event(double x, double y, double weight) { this.x = x; this.y = y; this.weight = weight; } } public void addValue(Coordinate coordinate, double weight) { weightSum += weight; sumX += coordinate.x * weight; sumY += coordinate.y * weight; sumZ += coordinate.z * weight; events.add(new Event(coordinate.x, coordinate.y, weight)); } public double seX = 0; public double seY = 0; public double radianRotation2 = 0.0; public Geometry ellipseCircle = null; public Geometry calculateSDE(double stdDeviations) { if (ellipseCircle != null) { return ellipseCircle; } // Mean Center double meanX = sumX / weightSum; double meanY = sumY / weightSum; // Standard Ellipse double sigXY = 0, sigX = 0, sigY = 0; for (Event ce : events) { final double devX = ce.x - meanX; final double devY = ce.y - meanY; sigX += Math.pow(devX, 2.0) * ce.weight; sigY += Math.pow(devY, 2.0) * ce.weight; sigXY += devX * devY * ce.weight; } double denom = sigXY * 2.0; double diffXY = sigX - sigY; double sum1 = Math.pow(diffXY, 2.0) + 4.0 * Math.pow(sigXY, 2.0); double arctanVal = 0.0; if (Math.abs(denom) > 0) { double tempVal = (diffXY + Math.sqrt(sum1)) / denom; arctanVal = Math.atan(tempVal); } if (arctanVal < 0.0) { arctanVal += (Math.PI / 2.0); } double sinVal = Math.sin(arctanVal); double cosVal = Math.cos(arctanVal); double sqrt2 = Math.sqrt(2.0); double sigXYSinCos = 2.0 * sigXY * sinVal * cosVal; seX = (sqrt2 * Math.sqrt(((sigX * Math.pow(cosVal, 2.0)) - sigXYSinCos + (sigY * Math.pow( sinVal, 2.0))) / weightSum) * stdDeviations); seY = (sqrt2 * Math.sqrt(((sigX * Math.pow(sinVal, 2.0)) + sigXYSinCos + (sigY * Math.pow( cosVal, 2.0))) / weightSum) * stdDeviations); // Counter Clockwise from Noon double degreeRotation = 360.0 - SSUtils.convert2Degree(arctanVal); // Convert to Radians double radianRotation1 = SSUtils.convert2Radians(degreeRotation); // Add Rotation radianRotation2 = 360.0 - degreeRotation; if (seX > seY) { radianRotation2 += 90.0; if (radianRotation2 > 360.0) { radianRotation2 = radianRotation2 - 180.0; } } // Calculate a Point For Each Degree in Ellipse Polygon double seX2 = Math.pow(seX, 2.0); double seY2 = Math.pow(seY, 2.0); double cosRadian = Math.cos(radianRotation1); double sinRadian = Math.sin(radianRotation1); List<Coordinate> coordList = new ArrayList<Coordinate>(); for (int degree = 0; degree <= 360; degree++) { double radians = SSUtils.convert2Radians(degree); double tanVal2 = Math.pow(Math.tan(radians), 2.0); double dX = Math.sqrt((seX2 * seY2) / (seY2 + (seX2 * tanVal2))); double dY = Math.sqrt((seY2 * (seX2 - Math.pow(dX, 2.0))) / seX2); // Adjust for Quadrant if (degree >= 90 && degree < 180) { dX = -dX; } else if (degree >= 180 && degree < 270) { dX = -dX; dY = -dY; } else if (degree >= 270) { dY = -dY; } // Rotate X and Y double dXr = dX * cosRadian - dY * sinRadian; double dYr = dX * sinRadian + dY * cosRadian; if (Double.isNaN(dXr) || Double.isNaN(dYr) || Double.isInfinite(dXr) || Double.isInfinite(dYr)) { continue; } // Create Point Shifted to Ellipse Centroid coordList.add(new Coordinate(dXr + meanX, dYr + meanY)); } try { if (!coordList.get(0).equals(coordList.get(coordList.size() - 1))) { coordList.add(coordList.get(0)); } Coordinate[] coords = CoordinateArrays.toCoordinateArray(coordList); LinearRing ring = gf.createLinearRing(coords); ellipseCircle = gf.createPolygon(ring, null); } catch (Exception e) { LOGGER.log(Level.WARNING, e.getMessage(), e); ellipseCircle = null; } return ellipseCircle; } public Point getMeanCenter() { double meanX = sumX / weightSum; double meanY = sumY / weightSum; double meanZ = sumZ / weightSum; return gf.createPoint(new Coordinate(meanX, meanY, meanZ)); } }