001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2002, Centre for Computational Geography
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.data.shapefile.shp;
018:
019: import java.nio.ByteBuffer;
020: import java.util.ArrayList;
021: import java.util.List;
022: import java.util.logging.Logger;
023:
024: import com.vividsolutions.jts.algorithm.CGAlgorithms;
025: import com.vividsolutions.jts.algorithm.RobustCGAlgorithms;
026: import com.vividsolutions.jts.geom.Coordinate;
027: import com.vividsolutions.jts.geom.Envelope;
028: import com.vividsolutions.jts.geom.Geometry;
029: import com.vividsolutions.jts.geom.GeometryFactory;
030: import com.vividsolutions.jts.geom.LinearRing;
031: import com.vividsolutions.jts.geom.MultiPolygon;
032: import com.vividsolutions.jts.geom.Polygon;
033:
034: /**
035: * Wrapper for a Shapefile polygon.
036: * @author aaime
037: * @author Ian Schneider
038: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/shp/PolygonHandler.java $
039: * @version $Id: PolygonHandler.java 27862 2007-11-12 19:51:19Z desruisseaux $
040: */
041: public class PolygonHandler implements ShapeHandler {
042: GeometryFactory geometryFactory = new GeometryFactory();
043: RobustCGAlgorithms cga = new RobustCGAlgorithms();
044:
045: final ShapeType shapeType;
046:
047: public PolygonHandler() {
048: shapeType = ShapeType.POLYGON;
049: }
050:
051: public PolygonHandler(ShapeType type) throws ShapefileException {
052: if ((type != ShapeType.POLYGON) && (type != ShapeType.POLYGONM)
053: && (type != ShapeType.POLYGONZ)) {
054: throw new ShapefileException(
055: "PolygonHandler constructor - expected type to be 5, 15, or 25.");
056: }
057:
058: shapeType = type;
059: }
060:
061: //returns true if testPoint is a point in the pointList list.
062: boolean pointInList(Coordinate testPoint, Coordinate[] pointList) {
063: Coordinate p;
064:
065: for (int t = pointList.length - 1; t >= 0; t--) {
066: p = pointList[t];
067:
068: if ((testPoint.x == p.x)
069: && (testPoint.y == p.y)
070: && ((testPoint.z == p.z) || (!(testPoint.z == testPoint.z))) //nan test; x!=x iff x is nan
071: ) {
072: return true;
073: }
074: }
075:
076: return false;
077: }
078:
079: public ShapeType getShapeType() {
080: return shapeType;
081: }
082:
083: public int getLength(Object geometry) {
084: MultiPolygon multi;
085:
086: if (geometry instanceof MultiPolygon) {
087: multi = (MultiPolygon) geometry;
088: } else {
089: multi = geometryFactory
090: .createMultiPolygon(new Polygon[] { (Polygon) geometry });
091: }
092:
093: int nrings = 0;
094:
095: for (int t = 0; t < multi.getNumGeometries(); t++) {
096: Polygon p;
097: p = (Polygon) multi.getGeometryN(t);
098: nrings = nrings + 1 + p.getNumInteriorRing();
099: }
100:
101: int npoints = multi.getNumPoints();
102: int length;
103:
104: if (shapeType == ShapeType.POLYGONZ) {
105: length = 44 + (4 * nrings) + (16 * npoints) + (8 * npoints)
106: + 16 + (8 * npoints) + 16;
107: } else if (shapeType == ShapeType.POLYGONM) {
108: length = 44 + (4 * nrings) + (16 * npoints) + (8 * npoints)
109: + 16;
110: } else if (shapeType == ShapeType.POLYGON) {
111: length = 44 + (4 * nrings) + (16 * npoints);
112: } else {
113: throw new IllegalStateException(
114: "Expected ShapeType of Polygon, got " + shapeType);
115: }
116: return length;
117: }
118:
119: public Object read(ByteBuffer buffer, ShapeType type) {
120: if (type == ShapeType.NULL) {
121: return createNull();
122: }
123: //bounds
124: buffer.position(buffer.position() + 4 * 8);
125:
126: int[] partOffsets;
127:
128: int numParts = buffer.getInt();
129: int numPoints = buffer.getInt();
130:
131: partOffsets = new int[numParts];
132:
133: for (int i = 0; i < numParts; i++) {
134: partOffsets[i] = buffer.getInt();
135: }
136:
137: ArrayList shells = new ArrayList();
138: ArrayList holes = new ArrayList();
139: Coordinate[] coords = readCoordinates(buffer, numPoints);
140:
141: if (shapeType == ShapeType.POLYGONZ) {
142: //z
143: buffer.position(buffer.position() + 2 * 8);
144:
145: for (int t = 0; t < numPoints; t++) {
146: coords[t].z = buffer.getDouble();
147: }
148: }
149:
150: int offset = 0;
151: int start;
152: int finish;
153: int length;
154:
155: for (int part = 0; part < numParts; part++) {
156: start = partOffsets[part];
157:
158: if (part == (numParts - 1)) {
159: finish = numPoints;
160: } else {
161: finish = partOffsets[part + 1];
162: }
163:
164: length = finish - start;
165:
166: // Use the progressive CCW algorithm.
167: // basically the area algorithm for polygons
168: // which also tells us vertex order based upon the
169: // sign of the area.
170: Coordinate[] points = new Coordinate[length];
171: //double area = 0;
172: //int sx = offset;
173: for (int i = 0; i < length; i++) {
174: points[i] = coords[offset++];
175: //int j = sx + (i + 1) % length;
176: //area += points[i].x * coords[j].y;
177: //area -= points[i].y * coords[j].x;
178: }
179: //area = -area / 2;
180: //REVISIT: polyons with only 1 or 2 points are not polygons - geometryFactory will bomb so we skip if we find one.
181: if (points.length == 0 || points.length > 3) {
182: LinearRing ring = geometryFactory
183: .createLinearRing(points);
184:
185: if (CGAlgorithms.isCCW(points)) {
186: // counter-clockwise
187: holes.add(ring);
188: } else {
189: // clockwise
190: shells.add(ring);
191: }
192: }
193: }
194:
195: // quick optimization: if there's only one shell no need to check
196: // for holes inclusion
197: if (shells.size() == 1) {
198: return createMulti((LinearRing) shells.get(0), holes);
199: }
200: // if for some reason, there is only one hole, we just reverse it and carry on.
201: else if (holes.size() == 1 && shells.size() == 0) {
202: org.geotools.util.logging.Logging.getLogger(
203: "org.geotools.data.shapefile").warning(
204: "only one hole in this polygon record");
205: return createMulti(JTSUtilities
206: .reverseRing((LinearRing) holes.get(0)));
207: } else {
208:
209: // build an association between shells and holes
210: final ArrayList holesForShells = assignHolesToShells(
211: shells, holes);
212:
213: Geometry g = buildGeometries(shells, holes, holesForShells);
214:
215: return g;
216: }
217: }
218:
219: /**
220: * @param buffer
221: * @param numPoints
222: */
223: private Coordinate[] readCoordinates(final ByteBuffer buffer,
224: final int numPoints) {
225: Coordinate[] coords = new Coordinate[numPoints];
226:
227: for (int t = 0; t < numPoints; t++) {
228: coords[t] = new Coordinate(buffer.getDouble(), buffer
229: .getDouble());
230: }
231:
232: return coords;
233: }
234:
235: /**
236: * @param shells
237: * @param holes
238: * @param holesForShells
239: */
240: private Geometry buildGeometries(final List shells,
241: final List holes, final List holesForShells) {
242: Polygon[] polygons;
243:
244: // if we have shells, lets use them
245: if (shells.size() > 0) {
246: polygons = new Polygon[shells.size()];
247: // oh, this is a bad record with only holes
248: } else {
249: polygons = new Polygon[holes.size()];
250: }
251:
252: // this will do nothing for the "only holes case"
253: for (int i = 0; i < shells.size(); i++) {
254: polygons[i] = geometryFactory.createPolygon(
255: (LinearRing) shells.get(i),
256: (LinearRing[]) ((ArrayList) holesForShells.get(i))
257: .toArray(new LinearRing[0]));
258: }
259:
260: // this will take care of the "only holes case"
261: // we just reverse each hole
262: if (shells.size() == 0) {
263: for (int i = 0, ii = holes.size(); i < ii; i++) {
264: LinearRing hole = (LinearRing) holes.get(i);
265: polygons[i] = geometryFactory.createPolygon(
266: JTSUtilities.reverseRing(hole),
267: new LinearRing[0]);
268: }
269: }
270:
271: Geometry g = geometryFactory.createMultiPolygon(polygons);
272:
273: return g;
274: }
275:
276: /**
277: * <b>Package private for testing</b>
278: * @param shells
279: * @param holes
280: */
281: ArrayList assignHolesToShells(final ArrayList shells,
282: final ArrayList holes) {
283: ArrayList holesForShells = new ArrayList(shells.size());
284: for (int i = 0; i < shells.size(); i++) {
285: holesForShells.add(new ArrayList());
286: }
287:
288: //find homes
289: for (int i = 0; i < holes.size(); i++) {
290: LinearRing testRing = (LinearRing) holes.get(i);
291: LinearRing minShell = null;
292: Envelope minEnv = null;
293: Envelope testEnv = testRing.getEnvelopeInternal();
294: Coordinate testPt = testRing.getCoordinateN(0);
295: LinearRing tryRing;
296:
297: for (int j = 0; j < shells.size(); j++) {
298: tryRing = (LinearRing) shells.get(j);
299:
300: Envelope tryEnv = tryRing.getEnvelopeInternal();
301: if (minShell != null) {
302: minEnv = minShell.getEnvelopeInternal();
303: }
304:
305: boolean isContained = false;
306: Coordinate[] coordList = tryRing.getCoordinates();
307:
308: if (tryEnv.contains(testEnv)
309: && (CGAlgorithms.isPointInRing(testPt,
310: coordList) || (pointInList(testPt,
311: coordList)))) {
312: isContained = true;
313: }
314:
315: // check if this new containing ring is smaller than the current minimum ring
316: if (isContained) {
317: if ((minShell == null) || minEnv.contains(tryEnv)) {
318: minShell = tryRing;
319: }
320: }
321: }
322:
323: if (minShell == null) {
324: org.geotools.util.logging.Logging
325: .getLogger("org.geotools.data.shapefile")
326: .warning(
327: "polygon found with a hole thats not inside a shell");
328: // now reverse this bad "hole" and turn it into a shell
329: shells.add(JTSUtilities.reverseRing(testRing));
330: holesForShells.add(new ArrayList());
331: } else {
332: ((ArrayList) holesForShells.get(shells
333: .indexOf(minShell))).add(testRing);
334: }
335: }
336:
337: return holesForShells;
338: }
339:
340: private MultiPolygon createMulti(LinearRing single) {
341: return createMulti(single, java.util.Collections.EMPTY_LIST);
342: }
343:
344: private MultiPolygon createMulti(LinearRing single, List holes) {
345: return geometryFactory
346: .createMultiPolygon(new Polygon[] { geometryFactory
347: .createPolygon(single, (LinearRing[]) holes
348: .toArray(new LinearRing[holes.size()])) });
349: }
350:
351: private MultiPolygon createNull() {
352: return geometryFactory.createMultiPolygon(null);
353: }
354:
355: public void write(ByteBuffer buffer, Object geometry) {
356: MultiPolygon multi;
357:
358: if (geometry instanceof MultiPolygon) {
359: multi = (MultiPolygon) geometry;
360: } else {
361: multi = geometryFactory
362: .createMultiPolygon(new Polygon[] { (Polygon) geometry });
363: }
364:
365: Envelope box = multi.getEnvelopeInternal();
366: buffer.putDouble(box.getMinX());
367: buffer.putDouble(box.getMinY());
368: buffer.putDouble(box.getMaxX());
369: buffer.putDouble(box.getMaxY());
370:
371: //need to find the total number of rings and points
372: int nrings = 0;
373:
374: for (int t = 0; t < multi.getNumGeometries(); t++) {
375: Polygon p;
376: p = (Polygon) multi.getGeometryN(t);
377: nrings = nrings + 1 + p.getNumInteriorRing();
378: }
379:
380: int u = 0;
381: int[] pointsPerRing = new int[nrings];
382:
383: for (int t = 0; t < multi.getNumGeometries(); t++) {
384: Polygon p;
385: p = (Polygon) multi.getGeometryN(t);
386: pointsPerRing[u] = p.getExteriorRing().getNumPoints();
387: u++;
388:
389: for (int v = 0; v < p.getNumInteriorRing(); v++) {
390: pointsPerRing[u] = p.getInteriorRingN(v).getNumPoints();
391: u++;
392: }
393: }
394:
395: int npoints = multi.getNumPoints();
396:
397: buffer.putInt(nrings);
398: buffer.putInt(npoints);
399:
400: int count = 0;
401:
402: for (int t = 0; t < nrings; t++) {
403: buffer.putInt(count);
404: count = count + pointsPerRing[t];
405: }
406:
407: //write out points here!
408: Coordinate[] coords = multi.getCoordinates();
409:
410: for (int t = 0; t < coords.length; t++) {
411: buffer.putDouble(coords[t].x);
412: buffer.putDouble(coords[t].y);
413: }
414:
415: if (shapeType == ShapeType.POLYGONZ) {
416: //z
417: double[] zExtreame = JTSUtilities.zMinMax(multi
418: .getCoordinates());
419:
420: if (Double.isNaN(zExtreame[0])) {
421: buffer.putDouble(0.0);
422: buffer.putDouble(0.0);
423: } else {
424: buffer.putDouble(zExtreame[0]);
425: buffer.putDouble(zExtreame[1]);
426: }
427:
428: for (int t = 0; t < npoints; t++) {
429: double z = coords[t].z;
430:
431: if (Double.isNaN(z)) {
432: buffer.putDouble(0.0);
433: } else {
434: buffer.putDouble(z);
435: }
436: }
437: }
438:
439: if (shapeType == ShapeType.POLYGONM
440: || shapeType == ShapeType.POLYGONZ) {
441: //m
442: buffer.putDouble(-10E40);
443: buffer.putDouble(-10E40);
444:
445: for (int t = 0; t < npoints; t++) {
446: buffer.putDouble(-10E40);
447: }
448: }
449: }
450:
451: }
452:
453: /*
454: * $Log: PolygonHandler.java,v $
455: * Revision 1.9 2004/02/17 18:10:23 ianschneider
456: * changed to use GeometryFactory for Geometry creation
457: *
458: * Revision 1.8 2003/07/24 19:10:02 ianschneider
459: * *** empty log message ***
460: *
461: * Revision 1.7 2003/07/24 18:32:10 ianschneider
462: * more test updates, fixed Z type writing
463: *
464: * Revision 1.6 2003/07/23 23:41:09 ianschneider
465: * more testing updates
466: *
467: * Revision 1.5 2003/07/23 00:59:59 ianschneider
468: * Lots of PMD fix ups
469: *
470: * Revision 1.4 2003/07/21 21:15:29 jmacgill
471: * small fix for shapefiles with an invalid hole (only 1 or 2 points)
472: *
473: * Revision 1.3 2003/05/19 21:38:55 jmacgill
474: * refactored read method to break it up a little
475: *
476: * Revision 1.2 2003/05/19 20:51:30 ianschneider
477: * removed System.out print statements
478: *
479: * Revision 1.1 2003/05/14 17:51:21 ianschneider
480: * migrated packages
481: *
482: * Revision 1.3 2003/04/30 23:19:46 ianschneider
483: * Added construction of multi geometries for default return values, even if only one geometry.
484: * This could have effects through system.
485: *
486: * Revision 1.2 2003/03/30 20:21:09 ianschneider
487: * Moved buffer branch to main
488: *
489: * Revision 1.1.2.5 2003/03/29 22:30:09 ianschneider
490: * For case of hole without shell - reverse hole, add to shell list
491: *
492: * Revision 1.1.2.4 2003/03/26 19:30:30 ianschneider
493: * Made hack to reverse polygon records if they contains only holes
494: *
495: * Revision 1.1.2.3 2003/03/12 15:30:18 ianschneider
496: * made ShapeType final for handlers - once they're created, it won't change.
497: *
498: * Revision 1.1.2.2 2003/03/07 00:36:41 ianschneider
499: *
500: * Added back the additional ShapeType parameter in ShapeHandler.read. ShapeHandler's need
501: * return their own special "null" shape if needed. Fixed the ShapefileReader to not throw
502: * exceptions for "null" shapes. Fixed ShapefileReader to accomodate junk after the last valid
503: * record. The theory goes, if the shape number is proper, that is, one greater than the
504: * previous, we consider that a valid record and attempt to read it. I suppose, by chance, the
505: * junk could coincide with the next record number. Stupid ESRI. Fixed some record-length
506: * calculations which resulted in writing of bad shapefiles.
507: *
508: * Revision 1.1.2.1 2003/03/06 01:16:34 ianschneider
509: *
510: * The initial changes for moving to java.nio. Added some documentation and improved
511: * exception handling. Works for reading, may work for writing as of now.
512: *
513: * Revision 1.1 2003/02/27 22:35:50 aaime
514: * New shapefile module, initial commit
515: *
516: * Revision 1.2 2003/01/22 18:31:05 jaquino
517: * Enh: Make About Box configurable
518: *
519: * Revision 1.2 2002/09/09 20:46:22 dblasby
520: * Removed LEDatastream refs and replaced with EndianData[in/out]putstream
521: *
522: * Revision 1.1 2002/08/27 21:04:58 dblasby
523: * orginal
524: *
525: * Revision 1.3 2002/03/05 10:51:01 andyt
526: * removed use of factory from write method
527: *
528: * Revision 1.2 2002/03/05 10:23:59 jmacgill
529: * made sure geometries were created using the factory methods
530: *
531: * Revision 1.1 2002/02/28 00:38:50 jmacgill
532: * Renamed files to more intuitve names
533: *
534: * Revision 1.4 2002/02/13 00:23:53 jmacgill
535: * First semi working JTS version of Shapefile code
536: *
537: * Revision 1.3 2002/02/11 18:44:22 jmacgill
538: * replaced geometry constructions with calls to geometryFactory.createX methods
539: *
540: * Revision 1.2 2002/02/11 18:28:41 jmacgill
541: * rewrote to have static read and write methods
542: *
543: * Revision 1.1 2002/02/11 16:54:43 jmacgill
544: * added shapefile code and directories
545: *
546: */
|