001: package org.geotools.shapefile;
002:
003: import java.io.IOException;
004: import java.lang.reflect.Array;
005: import java.util.ArrayList;
006:
007: import com.vividsolutions.jts.algorithm.CGAlgorithms;
008: import com.vividsolutions.jts.algorithm.RobustCGAlgorithms;
009: import com.vividsolutions.jts.geom.*;
010: import com.vividsolutions.jump.io.EndianDataInputStream;
011: import com.vividsolutions.jump.io.EndianDataOutputStream;
012:
013: /**
014: * Wrapper for a Shapefile polygon.
015: */
016: public class PolygonHandler implements ShapeHandler {
017: protected static CGAlgorithms cga = new RobustCGAlgorithms();
018: int myShapeType;
019:
020: public PolygonHandler() {
021: myShapeType = 5;
022: }
023:
024: public PolygonHandler(int type) throws InvalidShapefileException {
025: if ((type != 5) && (type != 15) && (type != 25))
026: throw new InvalidShapefileException(
027: "PolygonHandler constructor - expected type to be 5, 15, or 25.");
028:
029: myShapeType = type;
030: }
031:
032: //returns true if testPoint is a point in the pointList list.
033: boolean pointInList(Coordinate testPoint, Coordinate[] pointList) {
034: int t, numpoints;
035: Coordinate p;
036:
037: numpoints = Array.getLength(pointList);
038: for (t = 0; t < numpoints; t++) {
039: p = pointList[t];
040: if ((testPoint.x == p.x)
041: && (testPoint.y == p.y)
042: && ((testPoint.z == p.z) || (!(testPoint.z == testPoint.z))) //nan test; x!=x iff x is nan
043: ) {
044: return true;
045: }
046: }
047: return false;
048: }
049:
050: public Geometry read(EndianDataInputStream file,
051: GeometryFactory geometryFactory, int contentLength)
052: throws IOException, InvalidShapefileException {
053:
054: int actualReadWords = 0; //actual number of words read (word = 16bits)
055:
056: // file.setLittleEndianMode(true);
057: int shapeType = file.readIntLE();
058: actualReadWords += 2;
059:
060: if (shapeType == 0) {
061: return new MultiPolygon(null, new PrecisionModel(), 0); //null shape
062: }
063:
064: if (shapeType != myShapeType) {
065: throw new InvalidShapefileException(
066: "PolygonHandler.read() - got shape type "
067: + shapeType + " but was expecting "
068: + myShapeType);
069: }
070:
071: //bounds
072: file.readDoubleLE();
073: file.readDoubleLE();
074: file.readDoubleLE();
075: file.readDoubleLE();
076:
077: actualReadWords += 4 * 4;
078:
079: int partOffsets[];
080:
081: int numParts = file.readIntLE();
082: int numPoints = file.readIntLE();
083: actualReadWords += 4;
084:
085: partOffsets = new int[numParts];
086:
087: for (int i = 0; i < numParts; i++) {
088: partOffsets[i] = file.readIntLE();
089: actualReadWords += 2;
090: }
091:
092: //LinearRing[] rings = new LinearRing[numParts];
093: ArrayList shells = new ArrayList();
094: ArrayList holes = new ArrayList();
095: Coordinate[] coords = new Coordinate[numPoints];
096:
097: for (int t = 0; t < numPoints; t++) {
098: coords[t] = new Coordinate(file.readDoubleLE(), file
099: .readDoubleLE());
100: actualReadWords += 8;
101: }
102:
103: if (myShapeType == 15) {
104: //z
105: file.readDoubleLE(); //zmin
106: file.readDoubleLE(); //zmax
107: actualReadWords += 8;
108: for (int t = 0; t < numPoints; t++) {
109: coords[t].z = file.readDoubleLE();
110: actualReadWords += 4;
111: }
112: }
113:
114: if (myShapeType >= 15) {
115: // int fullLength = 22 + (2*numParts) + (8*numPoints) + 8 + (4*numPoints)+ 8 + (4*numPoints);
116: int fullLength;
117: if (myShapeType == 15) {
118: //polyZ (with M)
119: fullLength = 22 + (2 * numParts) + (8 * numPoints) + 8
120: + (4 * numPoints) + 8 + (4 * numPoints);
121: } else {
122: //polyM (with M)
123: fullLength = 22 + (2 * numParts) + (8 * numPoints) + 8
124: + (4 * numPoints);
125: }
126: if (contentLength >= fullLength) {
127: file.readDoubleLE(); //mmin
128: file.readDoubleLE(); //mmax
129: actualReadWords += 8;
130: for (int t = 0; t < numPoints; t++) {
131: file.readDoubleLE();
132: actualReadWords += 4;
133: }
134: }
135: }
136:
137: //verify that we have read everything we need
138: while (actualReadWords < contentLength) {
139: int junk = file.readShortBE();
140: actualReadWords += 1;
141: }
142:
143: int offset = 0;
144: int start, finish, length;
145: for (int part = 0; part < numParts; part++) {
146: start = partOffsets[part];
147: if (part == numParts - 1) {
148: finish = numPoints;
149: } else {
150: finish = partOffsets[part + 1];
151: }
152: length = finish - start;
153: Coordinate points[] = new Coordinate[length];
154: for (int i = 0; i < length; i++) {
155: points[i] = coords[offset];
156: offset++;
157: }
158: LinearRing ring = geometryFactory.createLinearRing(points);
159: if (cga.isCCW(points)) {
160: holes.add(ring);
161: } else {
162: shells.add(ring);
163: }
164: }
165:
166: //now we have a list of all shells and all holes
167: ArrayList holesForShells = new ArrayList(shells.size());
168: for (int i = 0; i < shells.size(); i++) {
169: holesForShells.add(new ArrayList());
170: }
171:
172: //find homes
173: for (int i = 0; i < holes.size(); i++) {
174: LinearRing testRing = (LinearRing) holes.get(i);
175: LinearRing minShell = null;
176: Envelope minEnv = null;
177: Envelope testEnv = testRing.getEnvelopeInternal();
178: Coordinate testPt = testRing.getCoordinateN(0);
179: LinearRing tryRing;
180: for (int j = 0; j < shells.size(); j++) {
181: tryRing = (LinearRing) shells.get(j);
182: Envelope tryEnv = tryRing.getEnvelopeInternal();
183: if (minShell != null)
184: minEnv = minShell.getEnvelopeInternal();
185: boolean isContained = false;
186: Coordinate[] coordList = tryRing.getCoordinates();
187:
188: if (tryEnv.contains(testEnv)
189: && (cga.isPointInRing(testPt, coordList) || (pointInList(
190: testPt, coordList))))
191: isContained = true;
192: // check if this new containing ring is smaller than the current minimum ring
193: if (isContained) {
194: if (minShell == null || minEnv.contains(tryEnv)) {
195: minShell = tryRing;
196: }
197: }
198: }
199:
200: if (minShell == null) {
201: System.out
202: .println("polygon found with a hole thats not inside a shell");
203: } else {
204: ((ArrayList) holesForShells.get(shells
205: .indexOf(minShell))).add(testRing);
206: }
207: }
208:
209: Polygon[] polygons = new Polygon[shells.size()];
210: for (int i = 0; i < shells.size(); i++) {
211: polygons[i] = geometryFactory.createPolygon(
212: (LinearRing) shells.get(i),
213: (LinearRing[]) ((ArrayList) holesForShells.get(i))
214: .toArray(new LinearRing[0]));
215: }
216:
217: if (polygons.length == 1) {
218: return polygons[0];
219: }
220:
221: holesForShells = null;
222: shells = null;
223: holes = null;
224: //its a multi part
225:
226: Geometry result = geometryFactory.createMultiPolygon(polygons);
227: // if (!(result.isValid() ))
228: // System.out.println("geom isnt valid");
229: return result;
230: }
231:
232: public void write(Geometry geometry, EndianDataOutputStream file)
233: throws IOException {
234:
235: MultiPolygon multi;
236: if (geometry instanceof MultiPolygon) {
237: multi = (MultiPolygon) geometry;
238: } else {
239: multi = new MultiPolygon(
240: new Polygon[] { (Polygon) geometry }, geometry
241: .getPrecisionModel(), geometry.getSRID());
242: }
243: //file.setLittleEndianMode(true);
244: file.writeIntLE(getShapeType());
245:
246: Envelope box = multi.getEnvelopeInternal();
247: file.writeDoubleLE(box.getMinX());
248: file.writeDoubleLE(box.getMinY());
249: file.writeDoubleLE(box.getMaxX());
250: file.writeDoubleLE(box.getMaxY());
251:
252: //need to find the total number of rings and points
253: int nrings = 0;
254:
255: for (int t = 0; t < multi.getNumGeometries(); t++) {
256: Polygon p;
257: p = (Polygon) multi.getGeometryN(t);
258: nrings = nrings + 1 + p.getNumInteriorRing();
259: }
260:
261: int u = 0;
262: int[] pointsPerRing = new int[nrings];
263: for (int t = 0; t < multi.getNumGeometries(); t++) {
264: Polygon p;
265: p = (Polygon) multi.getGeometryN(t);
266: pointsPerRing[u] = p.getExteriorRing().getNumPoints();
267: u++;
268: for (int v = 0; v < p.getNumInteriorRing(); v++) {
269: pointsPerRing[u] = p.getInteriorRingN(v).getNumPoints();
270: u++;
271: }
272: }
273:
274: int npoints = multi.getNumPoints();
275:
276: file.writeIntLE(nrings);
277: file.writeIntLE(npoints);
278:
279: int count = 0;
280: for (int t = 0; t < nrings; t++) {
281: file.writeIntLE(count);
282: count = count + pointsPerRing[t];
283: }
284:
285: //write out points here!
286: Coordinate[] coords = multi.getCoordinates();
287: int num;
288: num = Array.getLength(coords);
289: for (int t = 0; t < num; t++) {
290: file.writeDoubleLE(coords[t].x);
291: file.writeDoubleLE(coords[t].y);
292: }
293:
294: if (myShapeType == 15) {
295: //z
296: double[] zExtreame = zMinMax(multi);
297: if (Double.isNaN(zExtreame[0])) {
298: file.writeDoubleLE(0.0);
299: file.writeDoubleLE(0.0);
300: } else {
301: file.writeDoubleLE(zExtreame[0]);
302: file.writeDoubleLE(zExtreame[1]);
303: }
304: for (int t = 0; t < npoints; t++) {
305: double z = coords[t].z;
306: if (Double.isNaN(z))
307: file.writeDoubleLE(0.0);
308: else
309: file.writeDoubleLE(z);
310: }
311: }
312:
313: if (myShapeType >= 15) {
314: //m
315: file.writeDoubleLE(-10E40);
316: file.writeDoubleLE(-10E40);
317: for (int t = 0; t < npoints; t++) {
318: file.writeDoubleLE(-10E40);
319: }
320: }
321: }
322:
323: public int getShapeType() {
324: return myShapeType;
325: }
326:
327: public int getLength(Geometry geometry) {
328:
329: MultiPolygon multi;
330: if (geometry instanceof MultiPolygon) {
331: multi = (MultiPolygon) geometry;
332: } else {
333: multi = new MultiPolygon(
334: new Polygon[] { (Polygon) geometry }, geometry
335: .getPrecisionModel(), geometry.getSRID());
336: }
337:
338: int nrings = 0;
339:
340: for (int t = 0; t < multi.getNumGeometries(); t++) {
341: Polygon p;
342: p = (Polygon) multi.getGeometryN(t);
343: nrings = nrings + 1 + p.getNumInteriorRing();
344: }
345:
346: int npoints = multi.getNumPoints();
347:
348: if (myShapeType == 15) {
349: return 22 + (2 * nrings) + 8 * npoints + 4 * npoints + 8
350: + 4 * npoints + 8;
351: }
352: if (myShapeType == 25) {
353: return 22 + (2 * nrings) + 8 * npoints + 4 * npoints + 8;
354: }
355:
356: return 22 + (2 * nrings) + 8 * npoints;
357: }
358:
359: double[] zMinMax(Geometry g) {
360: double zmin, zmax;
361: boolean validZFound = false;
362: Coordinate[] cs = g.getCoordinates();
363: double[] result = new double[2];
364:
365: zmin = Double.NaN;
366: zmax = Double.NaN;
367: double z;
368:
369: for (int t = 0; t < cs.length; t++) {
370: z = cs[t].z;
371: if (!(Double.isNaN(z))) {
372: if (validZFound) {
373: if (z < zmin)
374: zmin = z;
375: if (z > zmax)
376: zmax = z;
377: } else {
378: validZFound = true;
379: zmin = z;
380: zmax = z;
381: }
382: }
383:
384: }
385:
386: result[0] = (zmin);
387: result[1] = (zmax);
388: return result;
389:
390: }
391:
392: }
393:
394: /*
395: * $Log$
396: * Revision 1.1 2005/06/16 14:54:43 javamap
397: * *** empty log message ***
398: *
399: * Revision 1.1 2005/04/29 13:30:59 javamap
400: * *** empty log message ***
401: *
402: * Revision 1.5 2003/09/23 17:15:26 dblasby
403: * *** empty log message ***
404: *
405: * Revision 1.4 2003/07/25 18:49:15 dblasby
406: * Allow "extra" data after the content. Fixes the ICI shapefile bug.
407: *
408: * Revision 1.3 2003/02/04 02:10:37 jaquino
409: * Feature: EditWMSQuery dialog
410: *
411: * Revision 1.2 2003/01/22 18:31:05 jaquino
412: * Enh: Make About Box configurable
413: *
414: * Revision 1.2 2002/09/09 20:46:22 dblasby
415: * Removed LEDatastream refs and replaced with EndianData[in/out]putstream
416: *
417: * Revision 1.1 2002/08/27 21:04:58 dblasby
418: * orginal
419: *
420: * Revision 1.3 2002/03/05 10:51:01 andyt
421: * removed use of factory from write method
422: *
423: * Revision 1.2 2002/03/05 10:23:59 jmacgill
424: * made sure geometries were created using the factory methods
425: *
426: * Revision 1.1 2002/02/28 00:38:50 jmacgill
427: * Renamed files to more intuitve names
428: *
429: * Revision 1.4 2002/02/13 00:23:53 jmacgill
430: * First semi working JTS version of Shapefile code
431: *
432: * Revision 1.3 2002/02/11 18:44:22 jmacgill
433: * replaced geometry constructions with calls to geometryFactory.createX methods
434: *
435: * Revision 1.2 2002/02/11 18:28:41 jmacgill
436: * rewrote to have static read and write methods
437: *
438: * Revision 1.1 2002/02/11 16:54:43 jmacgill
439: * added shapefile code and directories
440: *
441: */
|