/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2006 Vivid Solutions
* (C) 2001-2008, 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.geometry.iso.util.algorithm2D;
import java.util.Iterator;
import java.util.List;
import org.geotools.geometry.iso.aggregate.MultiSurfaceImpl;
import org.geotools.geometry.iso.coordinate.DirectPositionImpl;
import org.geotools.geometry.iso.primitive.CurveImpl;
import org.geotools.geometry.iso.primitive.RingImpl;
import org.geotools.geometry.iso.primitive.RingImplUnsafe;
import org.geotools.geometry.iso.primitive.SurfaceBoundaryImpl;
import org.geotools.geometry.iso.primitive.SurfaceImpl;
import org.geotools.geometry.iso.root.GeometryImpl;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.primitive.OrientableSurface;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Computes the centroid of an area geometry.
* <h2>Algorithm</h2>
* Based on the usual algorithm for calculating the centroid as a weighted sum
* of the centroids of a decomposition of the area into (possibly overlapping)
* triangles. The algorithm has been extended to handle holes and
* multi-polygons. See
* <code>http://www.faqs.org/faqs/graphics/algorithms-faq/</code> for further
* details of the basic approach.
*
* @source $URL$
*/
public class CentroidArea2D {
static final int X = 0;
static final int Y = 1;
static final int Z = 2;
//private FeatGeomFactoryImpl factory = null;
private CoordinateReferenceSystem crs = null;
// the point all triangles are based at
private DirectPosition basePt = null;
// partial area sum
private double areasum2 = 0;
// partial centroid sum
//private DirectPositionImpl cg3 = new DirectPositionImpl();
double centSumX = 0.0;
double centSumY = 0.0;
double centSumZ = 0.0;
/**
* Creates a new Centroid operation
*
* @param crs
*/
public CentroidArea2D(CoordinateReferenceSystem crs) {
this.crs = crs;
this.basePt = null;
}
/**
* Adds the area defined by a Geometry to the centroid total. If the
* geometry has no area it does not contribute to the centroid.
*
* @param geom
* the geometry to add
*/
public void add(GeometryImpl geom) {
if (geom instanceof SurfaceImpl) {
SurfaceBoundaryImpl sb = ((SurfaceImpl) geom).getBoundary();
this.setBasePoint( ((CurveImpl)sb.getExterior().getGenerators().iterator().next()).getStartPoint());
this.addSurface(sb);
} else if (geom instanceof MultiSurfaceImpl) {
Iterator<OrientableSurface> surfaces = ((MultiSurfaceImpl) geom).getElements().iterator();
while (surfaces.hasNext()) {
this.add((GeometryImpl) surfaces.next());
}
}
}
public DirectPositionImpl getCentroid() {
DirectPositionImpl centroid = new DirectPositionImpl(crs); //this.factory.getGeometryFactoryImpl().createDirectPosition();
centroid.setX(this.centSumX / 3 / this.areasum2);
centroid.setY(this.centSumY / 3 / this.areasum2);
return centroid;
}
private void setBasePoint(DirectPosition basePt) {
if (this.basePt == null)
this.basePt = basePt;
}
private void addSurface(SurfaceBoundaryImpl sb) {
this.addShell(((RingImplUnsafe)sb.getExterior()).asDirectPositions());
for (int i = 0; i < sb.getInteriors().size(); i++) {
this.addHole(((RingImplUnsafe)sb.getInteriors().get(i)).asDirectPositions());
}
}
private void addShell(List<DirectPosition> pts) {
boolean isPositiveArea = !CGAlgorithms.isCCW(pts);
for (int i = 0; i < pts.size() - 1; i++) {
addTriangle(basePt, pts.get(i), pts.get(i+1), isPositiveArea);
}
}
private void addHole(List<DirectPosition> pts) {
boolean isPositiveArea = CGAlgorithms.isCCW(pts);
for (int i = 0; i < pts.size() - 1; i++) {
addTriangle(basePt, pts.get(i), pts.get(i+1), isPositiveArea);
}
}
private void addTriangle(DirectPosition p0, DirectPosition p1, DirectPosition p2,
boolean isPositiveArea) {
double sign = (isPositiveArea) ? 1.0 : -1.0;
//this.centroid3(p0, p1, p2);
double tempSumX = p0.getOrdinate(X) + p1.getOrdinate(X) + p2.getOrdinate(X);
double tempSumY = p0.getOrdinate(Y) + p1.getOrdinate(Y)+ p2.getOrdinate(Y);
//double tempSumZ = 0.0;
double area2 = area2(p0, p1, p2);
this.centSumX += sign * area2 * tempSumX;
this.centSumY += sign * area2 * tempSumY;
this.areasum2 += sign * area2;
}
/**
* Returns twice the signed area of the triangle p1-p2-p3, positive if a,b,c
* are oriented ccw, and negative if cw.
*/
private static double area2(DirectPosition p1, DirectPosition p2, DirectPosition p3) {
return (p2.getOrdinate(X) - p1.getOrdinate(X)) * (p3.getOrdinate(Y) - p1.getOrdinate(Y))
- (p3.getOrdinate(X) - p1.getOrdinate(X)) * (p2.getOrdinate(Y) - p1.getOrdinate(Y));
}
}