package org.geotools.shapefile;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import jeql.io.EndianDataInputStream;
import jeql.io.EndianDataOutputStream;
import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.algorithm.RobustCGAlgorithms;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
/**
* 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;
}
// returns true if testPoint is a point in the pointList list.
boolean pointInList(Coordinate testPoint, Coordinate[] pointList)
{
int t, numpoints;
Coordinate p;
numpoints = Array.getLength(pointList);
for (t = 0; t < numpoints; t++) {
p = pointList[t];
if ((testPoint.x == p.x) && (testPoint.y == p.y)
&& ((testPoint.z == p.z) || (!(testPoint.z == testPoint.z))) // nan
// test;
// x!=x
// iff x
// is nan
) {
return true;
}
}
return false;
}
public Geometry read(EndianDataInputStream file,
GeometryFactory geometryFactory, int contentLength) throws IOException,
InvalidShapefileException
{
int actualReadWords = 0; // actual number of words read (word = 16bits)
// file.setLittleEndianMode(true);
int shapeType = file.readIntLE();
actualReadWords += 2;
if (shapeType == 0) {
return new MultiPolygon(null, new PrecisionModel(), 0); // null shape
}
if (shapeType != myShapeType) {
throw new InvalidShapefileException(
"PolygonHandler.read() - got shape type " + shapeType
+ " but was expecting " + myShapeType);
}
// 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;
}
// LinearRing[] rings = new LinearRing[numParts];
ArrayList shells = new ArrayList();
ArrayList holes = new ArrayList();
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) {
// z
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) {
// int fullLength = 22 + (2*numParts) + (8*numPoints) + 8 + (4*numPoints)+
// 8 + (4*numPoints);
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;
}
}
}
// verify that we have read everything we need
while (actualReadWords < contentLength) {
// skip first short
file.readShortBE();
actualReadWords += 1;
}
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++;
}
LinearRing ring = geometryFactory.createLinearRing(points);
if (CGAlgorithms.isCCW(points)) {
holes.add(ring);
}
else {
shells.add(ring);
}
}
ArrayList holesForShells = assignHolesToShells(shells, holes);
Polygon[] polygons = new Polygon[shells.size()];
for (int i = 0; i < shells.size(); i++) {
ArrayList shellHoles = (ArrayList) holesForShells.get(i);
if (holes.size() > 0) {
polygons[i] = geometryFactory.createPolygon((LinearRing) shells.get(i),
(LinearRing[]) (shellHoles).toArray(new LinearRing[0]));
}
else {
polygons[i] = geometryFactory.createPolygon((LinearRing) shells.get(i), null);
}
}
if (polygons.length == 1) {
return polygons[0];
}
holesForShells = null;
shells = null;
holes = null;
// its a multi part
Geometry result = geometryFactory.createMultiPolygon(polygons);
// if (!(result.isValid() ))
// System.out.println("geom isnt valid");
return result;
}
private ArrayList assignHolesToShells(ArrayList shells, ArrayList holes)
{
// now we have a list of all shells and all holes
ArrayList holesForShells = new ArrayList(shells.size());
for (int i = 0; i < shells.size(); i++) {
holesForShells.add(new ArrayList());
}
// find homes
for (int i = 0; i < holes.size(); i++) {
LinearRing testHole = (LinearRing) holes.get(i);
LinearRing minShell = null;
Envelope minEnv = null;
Envelope testHoleEnv = testHole.getEnvelopeInternal();
Coordinate testHolePt = testHole.getCoordinateN(0);
LinearRing tryShell;
int nShells = shells.size();
for (int j = 0; j < nShells; j++) {
tryShell = (LinearRing) shells.get(j);
Envelope tryShellEnv = tryShell.getEnvelopeInternal();
if (! tryShellEnv.contains(testHoleEnv)) continue;
boolean isContained = false;
Coordinate[] coordList = tryShell.getCoordinates();
if (nShells <= 1
|| CGAlgorithms.isPointInRing(testHolePt, coordList)
|| pointInList(testHolePt, coordList))
isContained = true;
// check if new containing ring is smaller than the current minimum ring
if (minShell != null)
minEnv = minShell.getEnvelopeInternal();
if (isContained) {
if (minShell == null || minEnv.contains(tryShellEnv)) {
minShell = tryShell;
}
}
}
if (minShell == null) {
System.err.println("Found polygon with a hole not inside a shell");
}
else {
// ((ArrayList)holesForShells.get(shells.indexOf(minShell))).add(testRing);
((ArrayList) holesForShells.get(findIndex(shells, minShell)))
.add(testHole);
}
}
return holesForShells;
}
/**
* Finds a object in a list. Should be much faster than indexof
*
* @param list
* @param o
* @return
*/
private static int findIndex(ArrayList list, Object o)
{
int n = list.size();
for (int i = 0; i < n; i++) {
if (list.get(i) == o)
return i;
}
return -1;
}
public void write(Geometry geometry, EndianDataOutputStream file)
throws IOException
{
Geometry multi = geometry;
/*
* // MD - no longer needed MultiPolygon multi; if(geometry instanceof
* MultiPolygon){ multi = (MultiPolygon)geometry; } else{ multi = new
* MultiPolygon(new
* Polygon[]{(Polygon)geometry},geometry.getPrecisionModel()
* ,geometry.getSRID()); }
*/
// file.setLittleEndianMode(true);
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)
{
Geometry multi = geometry;
/*
* // MD - no longer needed 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, zmax;
boolean validZFound = false;
Coordinate[] cs = g.getCoordinates();
double[] result = new double[2];
zmin = Double.NaN;
zmax = Double.NaN;
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;
}
}
}
result[0] = (zmin);
result[1] = (zmax);
return result;
}
}
/*
* $Log: PolygonHandler.java,v $ 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
*/