
org.h2gis.drivers.shp.internal.PolygonHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of h2drivers Show documentation
Show all versions of h2drivers Show documentation
Add H2 read/write support for file formats such as ESRI shape file
/**
* H2GIS is a library that brings spatial support to the H2 Database Engine
* .
*
* H2GIS is distributed under GPL 3 license. It is produced by CNRS
* .
*
* H2GIS is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* H2GIS 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* H2GIS. If not, see .
*
* For more information, please consult:
* or contact directly: info_at_h2gis.org
*/
/*
* GeoTools - OpenSource mapping toolkit
* http://geotools.org
* (C) 2002-2006, Geotools Project Managment Committee (PMC)
* (C) 2002, Centre for Computational Geography
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.h2gis.drivers.shp.internal;
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;
import org.h2gis.drivers.utility.CoordinatesUtils;
import org.h2gis.drivers.utility.ReadBufferManager;
import org.h2gis.drivers.utility.WriteBufferManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Wrapper for a Shapefile polygon.
*
* @author aaime
* @author Ian Schneider
* @see "http://svn.geotools.org/geotools/tags/2.3.1/plugin/shapefile/src/org/geotools/data/shapefile/shp/PolygonHandler.java"
* @version $Id: PolygonHandler.java 22264 2006-10-19 10:10:35Z acuster $
*/
public class PolygonHandler implements ShapeHandler {
GeometryFactory geometryFactory = new GeometryFactory();
final ShapeType shapeType;
public PolygonHandler() {
shapeType = ShapeType.POLYGON;
}
public PolygonHandler(ShapeType type) 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;
}
@Override
public ShapeType getShapeType() {
return shapeType;
}
@Override
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;
}
@Override
public Geometry read(ReadBufferManager buffer, ShapeType type)
throws IOException {
if (type == ShapeType.NULL) {
return createNull();
}
// bounds
buffer.skip(4 * 8);
int[] partOffsets;
int numParts = buffer.getInt();
int numPoints = buffer.getInt();
int dimensions = shapeType == ShapeType.POLYGONZ ? 3 : 2;
partOffsets = new int[numParts];
for (int i = 0; i < numParts; i++) {
partOffsets[i] = buffer.getInt();
}
List shells = new ArrayList(numParts);
List holes = new ArrayList(numParts);
CoordinateSequence coords = readCoordinates(buffer, numPoints,
dimensions);
if (shapeType == ShapeType.POLYGONZ) {
// z
buffer.skip(2 * 8);
for (int t = 0; t < numPoints; t++) {
coords.setOrdinate(t, 2, buffer.getDouble());
}
}
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 ringLength;
// If the polygon is closed (first vertices equal to the last one)
if(coords.getCoordinate(offset).equals(coords.getCoordinate(offset+length-1))) {
ringLength = length;
} else {
// The polygon is open, need to add an additional coordinate
ringLength = length + 1;
}
CoordinateSequence csRing = geometryFactory.getCoordinateSequenceFactory().create(ringLength, 3);
// 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++;
}
// Close the ring if necessary
if(ringLength>length) {
csRing.setOrdinate(length, 0, csRing.getOrdinate(0, 0));
csRing.setOrdinate(length, 1, csRing.getOrdinate(0, 1));
if (dimensions == 3) {
csRing.setOrdinate(length, 2, csRing.getOrdinate(0, 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 (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(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.isEmpty()) {
return createMulti((LinearRing) holes.get(0).reverse());
} else {
// build an association between shells and holes
final List> holesForShells = assignHolesToShells(shells, holes);
Geometry g = buildGeometries(shells, holes, holesForShells);
return g;
}
}
private MultiPolygon createNull() {
return geometryFactory.createMultiPolygon(null);
}
/**
* Computes whether a ring defined by an array of {@link Coordinate}s is
* oriented counter-clockwise.
*
* - The list of points is assumed to have the first and last points
* equal.
*
- This will handle coordinate lists which contain repeated points.
*
* This algorithm is only guaranteed to work with valid rings. If the
* ring is invalid (e.g. self-crosses or touches), the computed result may
* not be correct.
*
* @param ring
* an array of Coordinates forming a ring
* @return true if the ring is oriented counter-clockwise.
*/
public static boolean isCCW(CoordinateSequence ring) {
// # of points without closing endpoint
int nPts = ring.size() - 1;
// find highest point
double hiy = ring.getOrdinate(0, 1);
int hiIndex = 0;
for (int i = 1; i <= nPts; i++) {
if (ring.getOrdinate(i, 1) > hiy) {
hiy = ring.getOrdinate(i, 1);
hiIndex = i;
}
}
// find distinct point before highest point
int iPrev = hiIndex;
do {
iPrev -= 1;
if (iPrev < 0) {
iPrev = nPts;
}
} while (equals2D(ring, iPrev, hiIndex) && iPrev != hiIndex);
// find distinct point after highest point
int iNext = hiIndex;
do {
iNext = (iNext + 1) % nPts;
} while (equals2D(ring, iNext, hiIndex) && iNext != hiIndex);
/**
* This check catches cases where the ring contains an A-B-A
* configuration of points. This can happen if the ring does not contain
* 3 distinct points (including the case where the input array has fewer
* than 4 elements), or it contains coincident line segments.
*/
if (equals2D(ring, iPrev, hiIndex) || equals2D(ring, iNext, hiIndex)
|| equals2D(ring, iPrev, iNext)) {
return false;
}
int disc = computeOrientation(ring, iPrev, hiIndex, iNext);
/**
* If disc is exactly 0, lines are collinear. There are two possible
* cases: (1) the lines lie along the x axis in opposite directions (2)
* the lines lie on top of one another
*
* (1) is handled by checking if next is left of prev ==> CCW (2) will
* never happen if the ring is valid, so don't check for it (Might want
* to assert this)
*/
boolean isCCW = false;
if (disc == 0) {
// poly is CCW if prev x is right of next x
isCCW = (ring.getOrdinate(iPrev, 0) > ring.getOrdinate(iNext, 0));
} else {
// if area is positive, points are ordered CCW
isCCW = (disc > 0);
}
return isCCW;
}
private static boolean equals2D(CoordinateSequence cs, int i, int j) {
return cs.getOrdinate(i, 0) == cs.getOrdinate(j, 0)
&& cs.getOrdinate(i, 1) == cs.getOrdinate(j, 1);
}
public static int computeOrientation(CoordinateSequence cs, int p1, int p2,
int q) {
// travelling along p1->p2, turn counter clockwise to get to q return 1,
// travelling along p1->p2, turn clockwise to get to q return -1,
// p1, p2 and q are colinear return 0.
double p1x = cs.getOrdinate(p1, 0);
double p1y = cs.getOrdinate(p1, 1);
double p2x = cs.getOrdinate(p2, 0);
double p2y = cs.getOrdinate(p2, 1);
double qx = cs.getOrdinate(q, 0);
double qy = cs.getOrdinate(q, 1);
double dx1 = p2x - p1x;
double dy1 = p2y - p1y;
double dx2 = qx - p2x;
double dy2 = qy - p2y;
return RobustDeterminant.signOfDet2x2(dx1, dy1, dx2, dy2);
}
/**
* @param buffer
* @param numPoints
* @throws java.io.IOException
*/
private CoordinateSequence readCoordinates(final ReadBufferManager buffer,
final int numPoints, final int dimensions) throws IOException {
CoordinateSequence cs = geometryFactory.getCoordinateSequenceFactory().create(numPoints, dimensions);
for (int t = 0; t < numPoints; t++) {
cs.setOrdinate(t, 0, buffer.getDouble());
cs.setOrdinate(t, 1, buffer.getDouble());
}
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.isEmpty()) {
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++) {
final List hole = holesForShells.get(i);
polygons[i] = geometryFactory.createPolygon(shells.get(i),
hole.toArray(new LinearRing[hole.size()]));
}
// this will take care of the "only holes case"
// we just reverse each hole
if (shells.isEmpty()) {
for (int i = 0, ii = holes.size(); i < ii; i++) {
LinearRing hole = holes.get(i);
polygons[i] = geometryFactory.createPolygon((LinearRing) hole.reverse(), new LinearRing[0]);
}
}
Geometry g = geometryFactory.createMultiPolygon(polygons);
return g;
}
/**
* Package private for testing
*
* @param shells
* @param holes
*/
List> assignHolesToShells(
final List shells,
final List holes) {
List> 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 = 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 = 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) || (CoordinatesUtils.contains(
coordList, testPt)))) {
isContained = true;
}
// check if this new containing ring is smaller than the current
// minimum ring
if (isContained && ((minShell == null) || minEnv.contains(tryEnv))) {
minShell = tryRing;
}
}
if (minShell == null) {
// Logger.getLogger("org.geotools.data.shapefile").warning(
// "polygon found with a hole thats not inside a shell");
// now reverse this bad "hole" and turn it into a shell
shells.add((LinearRing) testRing.reverse());
holesForShells.add(new ArrayList());
} else {
(holesForShells.get(shells.indexOf(minShell))).add(testRing);
}
}
return holesForShells;
}
private MultiPolygon createMulti(LinearRing single) {
return createMulti(single, new ArrayList(0));
}
private MultiPolygon createMulti(LinearRing single, List holes) {
return geometryFactory.createMultiPolygon(new Polygon[]{geometryFactory.createPolygon(single, holes.toArray(new LinearRing[holes.size()]))});
}
@Override
public void write(WriteBufferManager buffer, Object geometry)
throws IOException {
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
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();
buffer.putInt(nrings);
buffer.putInt(npoints);
int count = 0;
for (int t = 0; t < nrings; t++) {
buffer.putInt(count);
count += pointsPerRing[t];
}
// write out points here!
Coordinate[] coords = multi.getCoordinates();
for (int t = 0; t < coords.length; t++) {
buffer.putDouble(coords[t].x);
buffer.putDouble(coords[t].y);
}
if (shapeType == ShapeType.POLYGONZ) {
// z
double[] zExtreame = CoordinatesUtils.zMinMax(multi.getCoordinates());
if (Double.isNaN(zExtreame[0])) {
buffer.putDouble(0.0);
buffer.putDouble(0.0);
} else {
buffer.putDouble(zExtreame[0]);
buffer.putDouble(zExtreame[1]);
}
for (int t = 0; t < npoints; t++) {
double z = coords[t].z;
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
*/
© 2015 - 2025 Weber Informatics LLC | Privacy Policy