/* * GeoTools java GIS tookit (c) The Centre for Computational Geography 2002 * * 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 */ package uk.ac.leeds.ccg.geotools; import java.lang.*; import java.awt.*; import java.util.*; /** * This class is intended for storing georaphical polygons such as boundary data * for wards, coastlines etc.
*
* $Log: GeoPolygon.java,v $
* Revision 1.22 2002/03/15 15:04:15 jmacgill
* removed debug statements from calculateArea method
*
* Revision 1.21 2002/03/15 14:36:44 jmacgill
* area calculations now itterate though all parts
* area caluclation call removed from one of the constructors
*
* Revision 1.20 2002/01/19 17:40:30 loxnard
* Fixed JavaDoc comments.
*
*
* @author James Macgill
* @author Mathieu Van Loon - getPointsAsArrayX, getPointsAsArrayY, dropVector
* @version $Revision: 1.22 $ $Date: 2002/03/15 15:04:15 $
* @since 0.5.0
*/
public class GeoPolygon extends GeoShape {
/**
* Control debugging. For now, either turn it off or on.
*/
private final static boolean DEBUG=false;
/**
* Performance switch, to turn off use of Vector. Use with CARE
*/
private boolean useVector = true;
/**
* Polygon centroid coordinates.
*/
private double xcent,ycent;
/**
* Number of points in polygon.
*/
public int npoints;
/**
* Area of polygon.
*/
private double area;
/**
* Array of points.
*/
public double xpoints[];
/**
* Array of points.
*/
public double[] ypoints;
private Vector storedPoints = null;
private boolean pointsStored = false;
private boolean areaDone,centroidDone;
/**
* Probably redundant constant.
*/
public static final int OUTSIDE = -1;
/**
* Probably redundant constant.
*/
public static final int NA = Integer.MAX_VALUE;
/**
* Create an empty polygon.
*/
public GeoPolygon(){
if ( useVector ) storedPoints = new Vector();
setBounds(new GeoRectangle());
} //Almost Empty
/**
* Construct a polygon with full details.
*
* @param id Polygon ID.
* @param xcent X-coordinate of centroid.
* @param ycent Y-coordinate of centroid.
* @param xpoints Vector of x values in Doubles (npoints in size).
* @param ypoints Vector of y values in Doubles (npoints in size) where npoints = number of points.
*/
public GeoPolygon(int id,double xcent,double ycent,Vector xpoints,Vector ypoints) {
if ( useVector ) storedPoints = new Vector();
this.id = id;
this.npoints = xpoints.size();
//System.out.println("Building with "+npoints);
this.xpoints = new double[npoints];
this.ypoints = new double[npoints];
for(int i=0;i
* This is computed on the fly, so might be expensive.
* Adapted from C function written by Paul Bourke.
* @return boolean True if polygon is clockwise.
*/
public boolean isClockwise(){
if (npoints < 3) return false;
int j,k,count=0;
double z;
for (int i=0;i
* Must be called during construction and after any changes to the polygon's pointset.
* N.B. Polygons with anti-clockwise point storage will result in a negative area.
*/
private void calculateArea(){
//move polygon to origin to remove negative y values and reduce coordinate sizes
GeoRectangle box = getBounds();
double xShift = 0-box.x;
double yShift = 0-box.y;
double area;
double total =0;
//Calculate area of each trapezoid formed by droping lines from each pair of point to the x-axis.
//x[i]
* Area must be set before calling this method, e.g. a call to calculateArea should have been made
* during construction.
* Polygons must have clockwise point encoding in order to have a centroid. Holes (encoded
* with anti-clockwise points) have undefined behaviour at this point.
* N.B. The centroid will be the 'centre of mass' but this is not guaranteed to be inside the
* polygon.
* @see uk.ac.leeds.ccg.geotools.GeoPolygon#calculateArea.
*/
protected void calculateCentroidLocation(){
GeoRectangle box = getBounds();
double xShift = 0-box.x;
double yShift = 0-box.y;
if(!areaDone)calculateArea();
if(area == 0){
//test and approx fix for polygons that are effectively lines or points.
xcent = (box.x+(box.width/2d));
ycent = (box.y+(box.height/2d));
}
else
{
double total =0;
double x=0,y=0,x1,x2,y1,y2;
for(int i=0;i
This code is somewhat experimental and has not been fully tested.
* @param p1 GeoPoint The first endpoint of the line to test.
* @param p2 GeoPoint The second endpoint of the line to test.
* @param polys A Vector of polygons to test the line against for inside/outside ness.
* @return boolean True if external.
*/
public boolean isOnOutside(GeoPoint p1,GeoPoint p2,Vector polys){
boolean outside = true;
for(int j=0;j
This code is somewhat experimental and has not been fully tested.
* In particular, if the polygons do not define a single clump then it will not work as expected.
* @param polys A Vector of GeoPolgyons to dissolve.
* @param id ID for this new polygon.
* @return GeoPolygon The dissolved version of the polys set.
*/
public static GeoPolygon dissolve(int id,Vector polys){
Vector outList = (Vector)polys.clone();
Vector holes = new Vector();Vector fills = new Vector();
int count = outList.size();
GeoPolygon multiPartTest;
for(int i=0;i
Both polygons must be contiguous.
* @param gp The GeoPolygon to dissolve into this one.
* @return GeoPolygon The resulting dissolved polygon.
*/
public GeoPolygon dissolve(GeoPolygon gp){
if(!isContiguous(gp)){throw new IllegalArgumentException("Polygon is not Contiguous, dissolve imposible");}
Vector a = getPoints();
Vector b = gp.getPoints();
int lastA = a.size()-2; // one for 0 counting one for duplicate final point.
int lastB = b.size()-2;
GeoPolygon d = new GeoPolygon();// construct with same id as this polygon?
//find first non shared point
int indexA = 0,indexB=0,start = 0;
while(b.contains(a.elementAt(indexA))){indexA++;}
start = indexA;
//System.out.println("Starting at "+indexA);
boolean done = false,first = true;
final int A=0,B=1,A2B=2,B2A=3;
int direction =1;
int state = A;
while(!done){
// System.out.println(""+indexA+" "+indexB+" "+state);
switch(state){
case A:
if(indexA==start && !first){done=true;continue;}
first = false;
d.addPoint((GeoPoint)a.elementAt(indexA));
indexA=(indexA+1)%(lastA+1);
if(b.contains(a.elementAt(indexA))){
state=A2B;
}
break;
case B:
d.addPoint((GeoPoint)b.elementAt(indexB));
indexB+=1*direction;
indexB%=(lastB+1);
if(indexB<0)indexB=lastB;
if(a.contains(b.elementAt(indexB))){
state = B2A;
}
break;
case A2B:
//add the last point of a
d.addPoint((GeoPoint)a.elementAt(indexA));
//which way round B do we go?
int join = b.indexOf(a.elementAt(indexA));
if(a.contains(b.elementAt((join+1)%lastB))){
direction = -1;
}
else{
direction = 1;
}
indexB = join+direction;
state = B;
break;
case B2A:
indexA = a.indexOf(b.elementAt(indexB));
state = A;
break;
}
}
d.addPoint((GeoPoint)a.elementAt(start));
d.calculateCentroidLocation();
return d;
}
/**
* Tests to see if a point is contained by this polygon.
* @param p The GeoPoint to test.
* @return boolean True if point is contained by this polygon.
*/
public boolean contains(GeoPoint p){
/*if(!getMultiPartBounds().contains(p)){
return false;
}*/
if(getBounds().contains(p)){
/* Start andys code */
int number_of_lines_crossed = 0; // holds lines intersecting with
number_of_lines_crossed = 0; // set lines crossed = 0 for each polygon
// lets us know which part start we're looking for
for(int i=0;i