Package org.geotools.data.shapefile.shp

Source Code of org.geotools.data.shapefile.shp.PolygonHandler

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
*    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 of the License.
*
*    This library is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    Lesser General Public License for more details.
*/
package org.geotools.data.shapefile.shp;

import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geotools.geometry.jts.coordinatesequence.CoordinateSequences;

import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.algorithm.RobustDeterminant;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
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;

/**
* Wrapper for a Shapefile polygon.
*
* @author aaime
* @author Ian Schneider
*
*
* @source $URL$
* @version $Id$
*/
public class PolygonHandler implements ShapeHandler {
    protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.data.shapefile");
   
    GeometryFactory geometryFactory;

    final ShapeType shapeType;

    public PolygonHandler(GeometryFactory gf) {
        shapeType = ShapeType.POLYGON;
        this.geometryFactory = gf;
    }

    public PolygonHandler(ShapeType type, GeometryFactory gf) throws ShapefileException {
        if ((type != ShapeType.POLYGON) && (type != ShapeType.POLYGONM)
                && (type != ShapeType.POLYGONZ)) {
            throw new ShapefileException(
                    "PolygonHandler constructor - expected type to be 5, 15, or 25.");
        }

        shapeType = type;
        this.geometryFactory = gf;
    }
   
    // returns true if testPoint is a point in the pointList list.
    boolean pointInList(Coordinate testPoint, Coordinate[] pointList) {
        Coordinate p;

        for (int t = pointList.length - 1; t >= 0; t--) {
            p = pointList[t];

            // nan test; x!=x iff x is nan
            if ((testPoint.x == p.x)
                    && (testPoint.y == p.y)
                    && ((testPoint.z == p.z) || (!(testPoint.z == testPoint.z)))
            ) {
                return true;
            }
        }

        return false;
    }

    public ShapeType getShapeType() {
        return shapeType;
    }

    public int getLength(Object geometry) {
        MultiPolygon multi;

        if (geometry instanceof MultiPolygon) {
            multi = (MultiPolygon) geometry;
        } else {
            multi = geometryFactory
                    .createMultiPolygon(new Polygon[] { (Polygon) geometry });
        }

        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();
        int length;

        if (shapeType == ShapeType.POLYGONZ) {
            length = 44 + (4 * nrings) + (16 * npoints) + (8 * npoints) + 16
                    + (8 * npoints) + 16;
        } else if (shapeType == ShapeType.POLYGONM) {
            length = 44 + (4 * nrings) + (16 * npoints) + (8 * npoints) + 16;
        } else if (shapeType == ShapeType.POLYGON) {
            length = 44 + (4 * nrings) + (16 * npoints);
        } else {
            throw new IllegalStateException(
                    "Expected ShapeType of Polygon, got " + shapeType);
        }
        return length;
    }

    public Object read(ByteBuffer buffer, ShapeType type, boolean flatFeature) {
        if (type == ShapeType.NULL) {
            return createNull();
        }
        // bounds
        buffer.position(buffer.position() + 4 * 8);

        int[] partOffsets;

        int numParts = buffer.getInt();
        int numPoints = buffer.getInt();
        int dimensions = (shapeType == ShapeType.POLYGONZ) && !flatFeature ? 3 : 2;

        partOffsets = new int[numParts];

        for (int i = 0; i < numParts; i++) {
            partOffsets[i] = buffer.getInt();
        }

        ArrayList shells = new ArrayList();
        ArrayList holes = new ArrayList();
        CoordinateSequence coords = readCoordinates(buffer, numPoints, dimensions);

        int offset = 0;
        int start;
        int finish;
        int 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;
            int close = 0; // '1' if the ring must be closed, '0' otherwise
            if ((coords.getOrdinate(start, 0) != coords.getOrdinate(finish - 1, 0))
                    || (coords.getOrdinate(start, 1) != coords.getOrdinate(finish - 1, 1))
            ) {
                close=1;
            }
            if (dimensions == 3) {
                if(coords.getOrdinate(start, 2) != coords.getOrdinate(finish - 1, 2)) {
                    close = 1;
                }
            }

            CoordinateSequence csRing = geometryFactory.getCoordinateSequenceFactory().create(length + close, dimensions);
            // double area = 0;
            // int sx = offset;
            for (int i = 0; i < length; i++) {
                csRing.setOrdinate(i, 0, coords.getOrdinate(offset, 0));
                csRing.setOrdinate(i, 1, coords.getOrdinate(offset, 1));
                if(dimensions == 3) {
                    csRing.setOrdinate(i, 2, coords.getOrdinate(offset, 2));
                }
                offset++;
            }
            if (close == 1) {
                csRing.setOrdinate(length, 0, coords.getOrdinate(start, 0));
                csRing.setOrdinate(length, 1, coords.getOrdinate(start, 1));
                if(dimensions == 3) {
                    csRing.setOrdinate(length, 2, coords.getOrdinate(start, 2));
                }
            }
            // REVISIT: polygons with only 1 or 2 points are not polygons -
            // geometryFactory will bomb so we skip if we find one.
            if (csRing.size() == 0 || csRing.size() > 3) {
                LinearRing ring = geometryFactory.createLinearRing(csRing);

                if (CoordinateSequences.isCCW(csRing)) {
                    // counter-clockwise
                    holes.add(ring);
                } else {
                    // clockwise
                    shells.add(ring);
                }
            }
        }

        // quick optimization: if there's only one shell no need to check
        // for holes inclusion
        if (shells.size() == 1) {
            return createMulti((LinearRing) shells.get(0), holes);
        }
        // if for some reason, there is only one hole, we just reverse it and
        // carry on.
        else if (holes.size() == 1 && shells.size() == 0) {
            return createMulti((LinearRing) holes.get(0));
        } else {

            // build an association between shells and holes
            final ArrayList holesForShells = assignHolesToShells(shells, holes);

            Geometry g = buildGeometries(shells, holes, holesForShells);

            return g;
        }
    }

    /**
     * @param buffer
     * @param numPoints
     */
    private CoordinateSequence readCoordinates(final ByteBuffer buffer,
            final int numPoints, final int dimensions) {
        CoordinateSequence cs = geometryFactory.getCoordinateSequenceFactory().create(numPoints, dimensions);

        DoubleBuffer dbuffer = buffer.asDoubleBuffer();
        double[] ordinates = new double[numPoints * 2];
        dbuffer.get(ordinates);
        for (int t = 0; t < numPoints; t++) {
            cs.setOrdinate(t, 0, ordinates[t * 2]);
            cs.setOrdinate(t, 1, ordinates[t * 2 + 1]);
        }
       
        if (dimensions > 2) {
            // z
            dbuffer.position(dbuffer.position() + 2);
            dbuffer.get(ordinates, 0, numPoints);

            for (int t = 0; t < numPoints; t++) {
                cs.setOrdinate(t, 2, ordinates[t]);
            }
        }

        return cs;
    }

    /**
     * @param shells
     * @param holes
     * @param holesForShells
     */
    private Geometry buildGeometries(final List shells, final List holes,
            final List holesForShells) {
        Polygon[] polygons;

        // if we have shells, lets use them
        if (shells.size() > 0) {
            polygons = new Polygon[shells.size()];
            // oh, this is a bad record with only holes
        } else {
            polygons = new Polygon[holes.size()];
        }

        // this will do nothing for the "only holes case"
        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]));
        }

        // this will take care of the "only holes case"
        // we just reverse each hole
        if (shells.size() == 0) {
            for (int i = 0, ii = holes.size(); i < ii; i++) {
                LinearRing hole = (LinearRing) holes.get(i);
                polygons[i] = geometryFactory.createPolygon(hole, null);
            }
        }

        Geometry g = geometryFactory.createMultiPolygon(polygons);

        return g;
    }

    /**
     * <b>Package private for testing</b>
     *
     * @param shells
     * @param holes
     */
    ArrayList assignHolesToShells(final ArrayList shells, final ArrayList 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 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)
                        && (CGAlgorithms.isPointInRing(testPt, coordList) || (pointInList(
                                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) {
                // now reverse this bad "hole" and turn it into a shell
                shells.add(testRing);
                holesForShells.add(new ArrayList());
            } else {
                ((ArrayList) holesForShells.get(shells.indexOf(minShell)))
                        .add(testRing);
            }
        }

        return holesForShells;
    }

    private MultiPolygon createMulti(LinearRing single) {
        return createMulti(single, java.util.Collections.EMPTY_LIST);
    }

    private MultiPolygon createMulti(LinearRing single, List holes) {
        return geometryFactory
                .createMultiPolygon(new Polygon[] { geometryFactory
                        .createPolygon(single, (LinearRing[]) holes
                                .toArray(new LinearRing[holes.size()])) });
    }

    private MultiPolygon createNull() {
        return geometryFactory.createMultiPolygon(null);
    }

    public void write(ByteBuffer buffer, Object geometry) {
        MultiPolygon multi;
       
        if (geometry instanceof MultiPolygon) {
          multi = (MultiPolygon) geometry;
        } else {
          multi = geometryFactory.createMultiPolygon(new Polygon[] { (Polygon) geometry });
        }
       
        Envelope box = multi.getEnvelopeInternal();
        buffer.putDouble(box.getMinX());
        buffer.putDouble(box.getMinY());
        buffer.putDouble(box.getMaxX());
        buffer.putDouble(box.getMaxY());
       
        //need to find the total number of rings and points
        final int nrings;
        final CoordinateSequence []coordinates;
        {
            List allCoords = new ArrayList();
            for (int t = 0; t < multi.getNumGeometries(); t++) {
              Polygon p;
              p = (Polygon) multi.getGeometryN(t);
              allCoords.add(p.getExteriorRing().getCoordinateSequence());
              for(int ringN = 0; ringN < p.getNumInteriorRing(); ringN++){
                  allCoords.add(p.getInteriorRingN(ringN).getCoordinateSequence());
              }
            }
            coordinates = (CoordinateSequence[])allCoords.toArray(new CoordinateSequence[allCoords.size()]);
            nrings = coordinates.length;
        }
       
        final int npoints = multi.getNumPoints();
       
        buffer.putInt(nrings);
        buffer.putInt(npoints);
       
        int count = 0;
        for (int t = 0; t < nrings; t++) {
          buffer.putInt(count);
          count = count + coordinates[t].size();
        }
       
        final double[] zExtreame = {Double.NaN, Double.NaN};
       
        //write out points here!.. and gather up min and max z values
        for (int ringN = 0; ringN < nrings; ringN++) {
            CoordinateSequence coords = coordinates[ringN];
           
            JTSUtilities.zMinMax(coords, zExtreame);
           
            final int seqSize = coords.size();
            for(int coordN = 0; coordN < seqSize; coordN++){
                buffer.putDouble(coords.getOrdinate(coordN, 0));
                buffer.putDouble(coords.getOrdinate(coordN, 1));
            }
        }
       
        if (shapeType == ShapeType.POLYGONZ) {
          //z     
          if (Double.isNaN(zExtreame[0])) {
            buffer.putDouble(0.0);
            buffer.putDouble(0.0);
          } else {
            buffer.putDouble(zExtreame[0]);
            buffer.putDouble(zExtreame[1]);
          }
         
          for (int ringN = 0; ringN < nrings; ringN++) {
              CoordinateSequence coords = coordinates[ringN];
         
              final int seqSize = coords.size();
              double z;
              for (int coordN = 0; coordN < seqSize; coordN++) {
                  z = coords.getOrdinate(coordN, 2);
                  if (Double.isNaN(z)) {
                      buffer.putDouble(0.0);
                  } else {
                      buffer.putDouble(z);
                  }
              }
          }
        }
       
        if (shapeType == ShapeType.POLYGONM || shapeType == ShapeType.POLYGONZ) {
          //m
          buffer.putDouble(-10E40);
          buffer.putDouble(-10E40);
         
          for (int t = 0; t < npoints; t++) {
            buffer.putDouble(-10E40);
          }
        }
    }
   

}

/*
* $Log: PolygonHandler.java,v $ Revision 1.9 2004/02/17 18:10:23 ianschneider
* changed to use GeometryFactory for Geometry creation
*
* Revision 1.8 2003/07/24 19:10:02 ianschneider *** empty log message ***
*
* Revision 1.7 2003/07/24 18:32:10 ianschneider more test updates, fixed Z type
* writing
*
* Revision 1.6 2003/07/23 23:41:09 ianschneider more testing updates
*
* Revision 1.5 2003/07/23 00:59:59 ianschneider Lots of PMD fix ups
*
* Revision 1.4 2003/07/21 21:15:29 jmacgill small fix for shapefiles with an
* invalid hole (only 1 or 2 points)
*
* Revision 1.3 2003/05/19 21:38:55 jmacgill refactored read method to break it
* up a little
*
* Revision 1.2 2003/05/19 20:51:30 ianschneider removed System.out print
* statements
*
* Revision 1.1 2003/05/14 17:51:21 ianschneider migrated packages
*
* Revision 1.3 2003/04/30 23:19:46 ianschneider Added construction of multi
* geometries for default return values, even if only one geometry. This could
* have effects through system.
*
* Revision 1.2 2003/03/30 20:21:09 ianschneider Moved buffer branch to main
*
* Revision 1.1.2.5 2003/03/29 22:30:09 ianschneider For case of hole without
* shell - reverse hole, add to shell list
*
* Revision 1.1.2.4 2003/03/26 19:30:30 ianschneider Made hack to reverse
* polygon records if they contains only holes
*
* Revision 1.1.2.3 2003/03/12 15:30:18 ianschneider made ShapeType final for
* handlers - once they're created, it won't change.
*
* Revision 1.1.2.2 2003/03/07 00:36:41 ianschneider
*
* Added back the additional ShapeType parameter in ShapeHandler.read.
* ShapeHandler's need return their own special "null" shape if needed. Fixed
* the ShapefileReader to not throw exceptions for "null" shapes. Fixed
* ShapefileReader to accomodate junk after the last valid record. The theory
* goes, if the shape number is proper, that is, one greater than the previous,
* we consider that a valid record and attempt to read it. I suppose, by chance,
* the junk could coincide with the next record number. Stupid ESRI. Fixed some
* record-length calculations which resulted in writing of bad shapefiles.
*
* Revision 1.1.2.1 2003/03/06 01:16:34 ianschneider
*
* The initial changes for moving to java.nio. Added some documentation and
* improved exception handling. Works for reading, may work for writing as of
* now.
*
* Revision 1.1 2003/02/27 22:35:50 aaime New shapefile module, initial commit
*
* 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
*
*/ 
TOP

Related Classes of org.geotools.data.shapefile.shp.PolygonHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.