package org.geotools.shapefile;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.algorithm.RobustCGAlgorithms;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jump.io.EndianDataInputStream;
import com.vividsolutions.jump.io.EndianDataOutputStream;
/**
* Wrapper for a Shapefile Polygon.
*/
public class PolygonHandler implements ShapeHandler {
protected static CGAlgorithms cga = new RobustCGAlgorithms();
int myShapeType;
public PolygonHandler() {
myShapeType = 5;
}
public PolygonHandler(int type) throws InvalidShapefileException {
if ((type != 5) && (type != 15) && (type != 25)) {
throw new InvalidShapefileException("PolygonHandler constructor - expected type to be 5, 15, or 25.");
}
myShapeType = type;
}
public Geometry read(EndianDataInputStream file ,
GeometryFactory geometryFactory,
int contentLength) throws IOException, InvalidShapefileException {
int actualReadWords = 0; //actual number of 16 bits words read
Geometry geom = null;
int shapeType = file.readIntLE();
actualReadWords += 2;
if (shapeType == 0) {
geom = geometryFactory.createMultiPolygon(new Polygon[0]); //null shape
}
else if ( shapeType != myShapeType ) {
throw new InvalidShapefileException(
"PolygonHandler.read() - got shape type " + shapeType + " but was expecting " + myShapeType
);
}
else {
//bounds
file.readDoubleLE();
file.readDoubleLE();
file.readDoubleLE();
file.readDoubleLE();
actualReadWords += 4*4;
int partOffsets[];
int numParts = file.readIntLE();
int numPoints = file.readIntLE();
actualReadWords += 4;
partOffsets = new int[numParts];
for(int i = 0 ; i<numParts ; i++) {
partOffsets[i]=file.readIntLE();
actualReadWords += 2;
}
ArrayList<LinearRing> shells = new ArrayList<LinearRing>();
ArrayList<LinearRing> holes = new ArrayList<LinearRing>();
//Bad rings are CCW rings not nested in another ring
//and rings with more than 0 and less than 4 points
ArrayList<LineString> badRings = new ArrayList<LineString>();
Coordinate[] coords = new Coordinate[numPoints];
for(int t=0 ; t<numPoints ; t++) {
coords[t]= new Coordinate(file.readDoubleLE(),file.readDoubleLE());
actualReadWords += 8;
}
if (myShapeType == 15) { // PolygonZ
file.readDoubleLE(); //zmin
file.readDoubleLE(); //zmax
actualReadWords += 8;
for(int t=0 ; t<numPoints ; t++) {
coords[t].z = file.readDoubleLE();
actualReadWords += 4;
}
}
if (myShapeType >= 15) { // PolygonM or PolygonZ
int fullLength;
if (myShapeType == 15) { //polyZ (with M)
fullLength = 22 + (2*numParts) + (8*numPoints) + 8 + (4*numPoints)+ 8 + (4*numPoints);
}
else { //polyM (with M)
fullLength = 22 + (2*numParts) + (8*numPoints) + 8+ (4*numPoints) ;
}
if (contentLength >= fullLength) {
file.readDoubleLE(); //mmin
file.readDoubleLE(); //mmax
actualReadWords += 8;
for(int t=0 ; t<numPoints ; t++) {
file.readDoubleLE();
actualReadWords += 4;
}
}
}
int offset = 0;
int start,finish,length;
for(int part=0 ; part<numParts ; part++) {
start = partOffsets[part];
if(part == numParts-1){finish = numPoints;}
else {
finish=partOffsets[part+1];
}
length = finish-start;
Coordinate points[] = new Coordinate[length];
for(int i=0;i<length;i++){
points[i]=coords[offset];
offset++;
}
//REVISIT: polygons with only 1 or 2 points are not polygons - geometryFactory will bomb so we skip if we find one.
if((points.length == 0 || points.length > 3) && points[0].equals(points[points.length-1])) {
try {
LinearRing ring = geometryFactory.createLinearRing(points);
if(CGAlgorithms.isCCW(points)) {
holes.add(ring);
}
else {
shells.add(ring);
}
} catch(IllegalArgumentException iae) {
LineString ring = geometryFactory.createLineString(points);
badRings.add(ring);
}
}
else {
LineString ring = geometryFactory.createLineString(points);
badRings.add(ring);
}
}
if ((shells.size()>1) && (holes.size()== 0)) {
//some shells may be CW holes - esri tolerates this
holes = findCWHoles(shells, geometryFactory); //find all rings contained in others
if (holes.size() > 0) {
shells.removeAll(holes);
ArrayList ccwHoles = new ArrayList(holes.size());
for (int i=0 ; i<holes.size() ; i++) {
ccwHoles.add( reverseRing((LinearRing) holes.get(i)) );
}
holes = ccwHoles;
}
}
//now we have a list of all shells and all holes
ArrayList holesForShells = new ArrayList(shells.size());
ArrayList holesWithoutShells = new ArrayList();
for(int i=0 ; i<shells.size() ; i++) {
holesForShells.add(new ArrayList());
}
//find holes
for(int i=0 ; i<holes.size() ; i++){
LinearRing testRing = (LinearRing)holes.get(i);
LinearRing minShell = null;
Envelope minEnv = null;
Envelope testEnv = testRing.getEnvelopeInternal();
Coordinate testPt = testRing.getCoordinateN(0);
LinearRing tryRing;
for(int j=0 ; j<shells.size() ; j++){
tryRing = (LinearRing) shells.get(j);
Envelope tryEnv = tryRing.getEnvelopeInternal();
if (minShell != null) minEnv = minShell.getEnvelopeInternal();
boolean isContained = false;
Coordinate[] coordList = tryRing.getCoordinates() ;
if (tryEnv.contains(testEnv) && (cga.isPointInRing(testPt,coordList )))
isContained = true;
// check if this new containing ring is smaller than the current minimum ring
if (isContained) {
if (minShell == null || minEnv.contains(tryEnv)) {
minShell = tryRing;
}
}
}
if (minShell == null) {
holesWithoutShells.add(testRing);
}
else {
((ArrayList)holesForShells.get(shells.indexOf(minShell))).add(testRing);
}
}
Polygon[] polygons = new Polygon[shells.size() + holesWithoutShells.size()];
for(int i=0 ; i<shells.size() ; i++){
polygons[i]=geometryFactory.createPolygon((LinearRing)shells.get(i),(LinearRing[])((ArrayList)holesForShells.get(i)).toArray(new LinearRing[0]));
}
for (int i=0 ; i<holesWithoutShells.size() ; i++) {
polygons[shells.size() + i]=geometryFactory.createPolygon((LinearRing)holesWithoutShells.get(i), null);
badRings.add((LinearRing)holesWithoutShells.get(i));
}
if(polygons.length==1) { // it's a simple Polygon
geom = polygons[0];
}
else { //its a multi part
geom = geometryFactory.createMultiPolygon(polygons);
}
//add bad rings as Geometry userData so that advanced users can retrieve them
if (badRings.size() > 0) {
geom.setUserData(geometryFactory.createMultiLineString(badRings.toArray(new LineString[0])));
}
holesForShells = null;
holesWithoutShells = null;
shells = null;
holes = null;
}
//verify that we have read everything we need
while (actualReadWords < contentLength) {
int junk = file.readShortBE();
actualReadWords += 1;
}
return geom;
}
ArrayList findCWHoles(ArrayList shells, GeometryFactory geometryFactory) {
ArrayList holesCW = new ArrayList(shells.size());
LinearRing[] noHole = new LinearRing[0];
for (int i = 0; i < shells.size(); i++) {
LinearRing iRing = (LinearRing) shells.get(i);
Envelope iEnv = iRing.getEnvelopeInternal();
Coordinate[] coordList = iRing.getCoordinates();
LinearRing jRing;
for (int j = 0; j < shells.size(); j++) {
if (i == j) continue;
jRing = (LinearRing) shells.get(j);
Envelope jEnv = jRing.getEnvelopeInternal();
Coordinate jPt = jRing.getCoordinateN(0);
Coordinate jPt2 = jRing.getCoordinateN(1);
if (iEnv.contains(jEnv)
//&& (CGAlgorithms.isPointInRing(jPt, coordList) || pointInList(jPt, coordList))
//&& (CGAlgorithms.isPointInRing(jPt2, coordList) || pointInList(jPt2, coordList))) {
&& (CGAlgorithms.isPointInRing(jPt, coordList))
&& (CGAlgorithms.isPointInRing(jPt2, coordList))) {
if (!holesCW.contains(jRing)) {
Polygon iPoly = geometryFactory.createPolygon(iRing,noHole);
Polygon jPoly = geometryFactory.createPolygon(jRing,noHole);
if (iPoly.contains(jPoly)) holesCW.add(jRing);
}
}
}
}
return holesCW;
}
/**
* reverses the order of points in lr (is CW -> CCW or CCW->CW)
*/
LinearRing reverseRing(LinearRing lr) {
int numPoints = lr.getNumPoints();
Coordinate[] newCoords = new Coordinate[numPoints];
for (int t=0 ; t<numPoints ; t++) {
newCoords[t] = lr.getCoordinateN(numPoints - t - 1);
}
return new LinearRing(newCoords, new PrecisionModel(), 0);
}
public void write(Geometry geometry, EndianDataOutputStream file) throws IOException{
if (geometry.isEmpty()) {
file.writeIntLE(0);
return;
}
MultiPolygon multi;
if(geometry instanceof MultiPolygon) {
multi = (MultiPolygon)geometry;
}
else {
multi = new MultiPolygon(new Polygon[]{(Polygon)geometry}, geometry.getPrecisionModel(), geometry.getSRID());
}
file.writeIntLE(getShapeType());
Envelope box = multi.getEnvelopeInternal();
file.writeDoubleLE(box.getMinX());
file.writeDoubleLE(box.getMinY());
file.writeDoubleLE(box.getMaxX());
file.writeDoubleLE(box.getMaxY());
//need to find the total number of rings and points
int nrings=0;
for (int t=0 ; t<multi.getNumGeometries() ; t++) {
Polygon p;
p = (Polygon) multi.getGeometryN(t);
nrings = nrings + 1 + p.getNumInteriorRing();
}
int u=0;
int[] pointsPerRing = new int[nrings];
for (int t=0 ; t<multi.getNumGeometries() ; t++) {
Polygon p;
p = (Polygon) multi.getGeometryN(t);
pointsPerRing[u] = p.getExteriorRing().getNumPoints();
u++;
for(int v=0 ; v<p.getNumInteriorRing() ; v++) {
pointsPerRing[u] = p.getInteriorRingN(v).getNumPoints();
u++;
}
}
int npoints = multi.getNumPoints();
file.writeIntLE(nrings);
file.writeIntLE(npoints);
int count =0;
for(int t=0 ; t<nrings ; t++) {
file.writeIntLE(count);
count = count + pointsPerRing[t] ;
}
//write out points here!
Coordinate[] coords = multi.getCoordinates();
int num;
num = Array.getLength(coords);
for(int t=0 ; t<num ; t++) {
file.writeDoubleLE(coords[t].x);
file.writeDoubleLE(coords[t].y);
}
if (myShapeType == 15) { //z
double[] zExtreame = zMinMax(multi);
if (Double.isNaN(zExtreame[0] )) {
file.writeDoubleLE(0.0);
file.writeDoubleLE(0.0);
}
else {
file.writeDoubleLE(zExtreame[0]);
file.writeDoubleLE(zExtreame[1]);
}
for (int t=0 ; t<npoints ; t++) {
double z = coords[t].z;
if (Double.isNaN(z))
file.writeDoubleLE(0.0);
else
file.writeDoubleLE(z);
}
}
if (myShapeType >= 15) { //m
file.writeDoubleLE(-10E40);
file.writeDoubleLE(-10E40);
for(int t=0 ; t<npoints ; t++) {
file.writeDoubleLE(-10E40);
}
}
}
public int getShapeType() {
return myShapeType;
}
public int getLength(Geometry geometry) {
if (geometry.isEmpty()) return 2;
MultiPolygon multi;
if(geometry instanceof MultiPolygon) {
multi = (MultiPolygon)geometry;
}
else {
multi = new MultiPolygon(new Polygon[]{(Polygon)geometry},geometry.getPrecisionModel(),geometry.getSRID());
}
int nrings=0;
for (int t=0 ; t<multi.getNumGeometries() ; t++) {
Polygon p;
p = (Polygon) multi.getGeometryN(t);
nrings = nrings + 1 + p.getNumInteriorRing();
}
int npoints = multi.getNumPoints();
if (myShapeType == 15) {
return 22 + (2*nrings) + 8*npoints + 4*npoints + 8 + 4*npoints + 8;
}
if (myShapeType==25) {
return 22 + (2*nrings) + 8*npoints + 4*npoints + 8 ;
}
return 22 + (2*nrings) + 8*npoints;
}
double[] zMinMax(Geometry g) {
double zmin = Double.NaN;
double zmax = Double.NaN;
boolean validZFound = false;
Coordinate[] cs = g.getCoordinates();
double z;
for (int t=0 ; t<cs.length ; t++) {
z= cs[t].z ;
if (!(Double.isNaN( z ))) {
if (validZFound) {
if (z < zmin) zmin = z;
if (z > zmax) zmax = z;
}
else {
validZFound = true;
zmin = z ;
zmax = z ;
}
}
}
return new double[]{zmin, zmax};
}
/**
* Return a empty geometry.
*/
public Geometry getEmptyGeometry(GeometryFactory factory) {
return factory.createMultiPolygon(new Polygon[0]);
}
}
/*
* $Log$
* Revision 1.7 2009/05/10 michaudm
* Fix a bug in findCWHoles. Could create a 'outer hole' because the test to
* check if a ring contains another ring was a quick and dirty test.
*
* Revision 1.6 2008/04/22 20:55:36 beckerl
* Restored the original inline code in read() and added the CW hole detection. The new geotools routines always created Multipolygons.
*
* Revision 1.3 2007/01/03 22:43:17 rlittlefield
* changed so that the holesWithoutShells array initialized to zero length
*
* Revision 1.2 2007/01/03 16:48:43 rlittlefield
* modified code so that holes without shells are not excluded
*
* Revision 1.1 2006/11/28 22:30:57 beckerl
* First SkyJUMP commit. Prior version numbers lost.
*
* Revision 1.1 2006/02/28 22:42:14 ashsdesigner
* Initial commit of larry's jump/org Eclipse project folder
*
* Revision 1.5 2003/09/23 17:15:26 dblasby
* *** empty log message ***
*
* Revision 1.4 2003/07/25 18:49:15 dblasby
* Allow "extra" data after the content. Fixes the ICI shapefile bug.
*
* Revision 1.3 2003/02/04 02:10:37 jaquino
* Feature: EditWMSQuery dialog
*
* Revision 1.2 2003/01/22 18:31:05 jaquino
* Enh: Make About Box configurable
*
* Revision 1.2 2002/09/09 20:46:22 dblasby
* Removed LEDatastream refs and replaced with EndianData[in/out]putstream
*
* Revision 1.1 2002/08/27 21:04:58 dblasby
* orginal
*
* Revision 1.3 2002/03/05 10:51:01 andyt
* removed use of factory from write method
*
* Revision 1.2 2002/03/05 10:23:59 jmacgill
* made sure geometries were created using the factory methods
*
* Revision 1.1 2002/02/28 00:38:50 jmacgill
* Renamed files to more intuitve names
*
* Revision 1.4 2002/02/13 00:23:53 jmacgill
* First semi working JTS version of Shapefile code
*
* Revision 1.3 2002/02/11 18:44:22 jmacgill
* replaced geometry constructions with calls to geometryFactory.createX methods
*
* Revision 1.2 2002/02/11 18:28:41 jmacgill
* rewrote to have static read and write methods
*
* Revision 1.1 2002/02/11 16:54:43 jmacgill
* added shapefile code and directories
*
*/