/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014, 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.process.spatialstatistics.transformation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.logging.Logger; import org.geotools.data.DataUtilities; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.util.logging.Logging; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateArrays; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryComponentFilter; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.operation.polygonize.Polygonizer; import com.vividsolutions.jts.util.Assert; /** * Repair Geometry SimpleFeatureCollection Implementation * * @author Minpa Lee, MangoSystem * * @source $URL$ */ public class RepairGeometryFeatureCollection extends GXTSimpleFeatureCollection { protected static final Logger LOGGER = Logging.getLogger(RepairGeometryFeatureCollection.class); public RepairGeometryFeatureCollection(SimpleFeatureCollection delegate) { super(delegate); } @Override public SimpleFeatureIterator features() { return new RepairGeometryFeatureIterator(delegate.features(), getSchema()); } @Override public int size() { return DataUtilities.count(features()); } static class RepairGeometryFeatureIterator implements SimpleFeatureIterator { private SimpleFeatureIterator delegate; private int featureID = 0; private SimpleFeatureBuilder builder; private SimpleFeature next; private String typeName; public RepairGeometryFeatureIterator(SimpleFeatureIterator delegate, SimpleFeatureType schema) { this.delegate = delegate; this.builder = new SimpleFeatureBuilder(schema); this.typeName = schema.getTypeName(); } public void close() { delegate.close(); } public boolean hasNext() { while (next == null && delegate.hasNext()) { // repair geometry SimpleFeature feature = delegate.next(); Geometry repaired = validate((Geometry) feature.getDefaultGeometry()); if (repaired == null) { continue; } // build the next feature for (Object attribute : feature.getAttributes()) { if (attribute instanceof Geometry) { attribute = repaired; } builder.add(attribute); } next = builder.buildFeature(buildID(typeName, ++featureID)); builder.reset(); } return next != null; } public SimpleFeature next() throws NoSuchElementException { if (!hasNext()) { throw new NoSuchElementException("hasNext() returned false!"); } SimpleFeature result = next; next = null; return result; } private Geometry validate(Geometry source) { if (source == null || source.isEmpty()) { return null; } if (source.isValid() && source.isSimple()) { return source; } // validate coordinates, remove empty shell/holes, duplicated points Geometry valid = validateEmptyAndDuplicate(source); if (valid == null || valid.isEmpty()) { return null; } if (valid.isValid() && valid.isSimple()) { return valid; } // reconstruct self-intersection geometry Class<?> geomBinding = valid.getClass(); if (geomBinding.isAssignableFrom(MultiPolygon.class)) { return validatePolygon(valid); } else if (geomBinding.isAssignableFrom(Polygon.class)) { return validatePolygon(valid); } else if (geomBinding.isAssignableFrom(MultiLineString.class)) { return validateLineString(valid); } else if (geomBinding.isAssignableFrom(LineString.class)) { return validateLineString(valid); } else { Assert.shouldNeverReachHere(source.toText()); } return null; } private Geometry validatePolygon(Geometry source) { Polygonizer polygonizer = new Polygonizer(); for (int index = 0; index < source.getNumGeometries(); index++) { Polygon polygon = (Polygon) source.getGeometryN(index); visitLinearRing(polygonizer, polygon.getExteriorRing()); for (int n = 0; n < polygon.getNumInteriorRing(); n++) { visitLinearRing(polygonizer, polygon.getInteriorRingN(n)); } } @SuppressWarnings("unchecked") Collection<Polygon> polygons = polygonizer.getPolygons(); if (polygons.size() == 0) { return null; } if (polygons.size() == 1) { Polygon polygon = polygons.iterator().next(); return source.getFactory().createMultiPolygon(new Polygon[] { polygon }); } Iterator<Polygon> iter = polygons.iterator(); Geometry repaired = iter.next(); while (iter.hasNext()) { repaired = repaired.symDifference(iter.next()); } return repaired; } private void visitLinearRing(Polygonizer polygonizer, LineString line) { GeometryFactory factory = line.getFactory(); if (line instanceof LinearRing) { line = factory.createLineString(line.getCoordinateSequence()); } // set dirty Geometry union = line.union(factory.createPoint(line.getCoordinateN(0))); polygonizer.add(union); } private MultiLineString validateLineString(Geometry line) { GeometryFactory factory = line.getFactory(); // set dirty Geometry union = line.union(factory.createPoint(line.getCoordinate())); final List<LineString> lines = new ArrayList<LineString>(); union.apply(new GeometryComponentFilter() { @Override public void filter(Geometry geom) { if (geom instanceof LineString) { if (!geom.isEmpty()) { lines.add((LineString) geom); } } } }); if (lines.size() == 0) { return null; } return factory.createMultiLineString(lines.toArray(new LineString[0])); } private Geometry validateEmptyAndDuplicate(Geometry source) { GeometryFactory factory = source.getFactory(); Class<?> geomBinding = source.getClass(); if (geomBinding.isAssignableFrom(MultiPolygon.class)) { List<Polygon> polygons = new ArrayList<Polygon>(); for (int idx = 0; idx < source.getNumGeometries(); idx++) { Polygon edit = (Polygon) validateEmptyAndDuplicate(source.getGeometryN(idx)); if (edit == null) { continue; } polygons.add(edit); } if (polygons.size() == 0) { return null; } return factory.createMultiPolygon(polygons.toArray(new Polygon[0])); } else if (geomBinding.isAssignableFrom(Polygon.class)) { Polygon polygon = (Polygon) source; // exterior ring LineString exterior = validateRing((LineString) validateEmptyAndDuplicate(polygon .getExteriorRing())); if (exterior == null || exterior.isEmpty() || exterior.getLength() == 0) { return null; } LinearRing shell = factory.createLinearRing(exterior.getCoordinateSequence()); // interior ring List<LinearRing> holes = new ArrayList<LinearRing>(); for (int idx = 0; idx < polygon.getNumInteriorRing(); idx++) { LineString interior = validateRing((LineString) validateEmptyAndDuplicate(polygon .getInteriorRingN(idx))); if (interior == null || interior.isEmpty() || interior.getLength() == 0) { continue; } holes.add(factory.createLinearRing(interior.getCoordinateSequence())); } return factory.createPolygon(shell, holes.toArray(new LinearRing[0])); } else if (geomBinding.isAssignableFrom(MultiLineString.class)) { List<LineString> lines = new ArrayList<LineString>(); for (int idx = 0; idx < source.getNumGeometries(); idx++) { LineString edit = (LineString) validateEmptyAndDuplicate((LineString) source .getGeometryN(idx)); if (edit == null) { continue; } lines.add(edit); } if (lines.size() == 0) { return null; } return factory.createMultiLineString(lines.toArray(new LineString[0])); } else if (geomBinding.isAssignableFrom(LineString.class)) { List<Coordinate> coordList = new ArrayList<Coordinate>(); Coordinate[] coords = source.getCoordinates(); for (int idx = 1, length = coords.length - 1; idx < length; idx++) { Coordinate coordinate = coords[idx]; if (!isValid(coordinate) || coordList.contains(coordinate)) { continue; } coordList.add(coordinate); } // first coordinate if (coordList.size() == 0 || !coordList.get(0).equals(coords[0])) { coordList.add(0, coords[0]); } // last coordinate if (!coordList.get(coordList.size() - 1).equals(coords[coords.length - 1])) { coordList.add(coords[coords.length - 1]); } if (coordList.size() < 2) { return null; } return factory.createLineString(CoordinateArrays.toCoordinateArray(coordList)); } else if (geomBinding.isAssignableFrom(LinearRing.class)) { LineString line = factory.createLineString(source.getCoordinates()); return validateEmptyAndDuplicate(line); } else if (geomBinding.isAssignableFrom(MultiPoint.class)) { List<Coordinate> coordList = new ArrayList<Coordinate>(); for (Coordinate coordinate : source.getCoordinates()) { if (!isValid(coordinate) || coordList.contains(coordinate)) { continue; } coordList.add(coordinate); } return factory.createMultiPoint(CoordinateArrays.toCoordinateArray(coordList)); } else if (geomBinding.isAssignableFrom(Point.class)) { if (isValid(source.getCoordinate())) { return source; } } return null; } private LineString validateRing(LineString ring) { if (ring == null || ring.isEmpty() || ring.getLength() == 0) { return null; } GeometryFactory factory = ring.getFactory(); if (!ring.isClosed()) { List<Coordinate> sources = Arrays.asList(ring.getCoordinates()); List<Coordinate> coordList = new ArrayList<Coordinate>(sources); coordList.add(coordList.get(0)); ring = factory.createLineString(coordList.toArray(new Coordinate[0])); } return factory.createLinearRing(ring.getCoordinateSequence()); } private boolean isValid(Coordinate coord) { if (Double.isNaN(coord.x) || Double.isInfinite(coord.x)) { return false; } if (Double.isNaN(coord.y) || Double.isInfinite(coord.y)) { return false; } return true; } } }