/* * 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.operations; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.process.spatialstatistics.storage.IFeatureInserter; 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.Geometry; import com.vividsolutions.jts.geom.GeometryComponentFilter; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.prep.PreparedGeometry; import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory; import com.vividsolutions.jts.index.strtree.STRtree; /** * Removes portions of a line that extend a specified distance past a line intersection (dangles). * * @author Minpa Lee, MangoSystem * * @source $URL$ * */ public class TrimLineOperation extends GeneralOperation { protected static final Logger LOGGER = Logging.getLogger(TrimLineOperation.class); private STRtree spatialIndex; private double dangleLength = 0d; private boolean deleteShort = true; public TrimLineOperation() { } public SimpleFeatureCollection execute(SimpleFeatureCollection lineFeatures, double dangleLength, boolean deleteShort) throws IOException { this.dangleLength = dangleLength; this.deleteShort = deleteShort; if (dangleLength <= 0) { return lineFeatures; } // prepare spatial index buildSpatialIndex(lineFeatures); // prepare transactional feature store SimpleFeatureType featureType = lineFeatures.getSchema(); IFeatureInserter featureWriter = getFeatureWriter(featureType); SimpleFeatureIterator featureIter = lineFeatures.features(); try { List<LineString> segments = new ArrayList<LineString>(); while (featureIter.hasNext()) { SimpleFeature feature = featureIter.next(); // trim segments.clear(); Geometry multiPart = (Geometry) feature.getDefaultGeometry(); for (int index = 0; index < multiPart.getNumGeometries(); index++) { LineString part = (LineString) multiPart.getGeometryN(index); LineString segment = processLineString(feature.getID(), part); if (segment != null) { segments.add(segment); } } // build geometry if (segments.size() == 0) { continue; } LineString[] lsArray = GeometryFactory.toLineStringArray(segments); Geometry trimmed = multiPart.getFactory().createMultiLineString(lsArray); if (trimmed == null || trimmed.isEmpty()) { continue; } // create & insert feature SimpleFeature newFeature = featureWriter.buildFeature(); featureWriter.copyAttributes(feature, newFeature, false); newFeature.setDefaultGeometry(trimmed); featureWriter.write(newFeature); segments.clear(); } } catch (IOException e) { featureWriter.rollback(e); } finally { featureWriter.close(featureIter); } return featureWriter.getFeatureCollection(); } private LineString processLineString(String id, LineString part) { PreparedGeometry prepared = PreparedGeometryFactory.prepare(part); GeometryFactory factory = part.getFactory(); List<LineString> lineStrings = new ArrayList<LineString>(); for (@SuppressWarnings("unchecked") Iterator<NearFeature> iter = (Iterator<NearFeature>) spatialIndex.query( part.getEnvelopeInternal()).iterator(); iter.hasNext();) { NearFeature sample = iter.next(); if (sample.id.equals(id) || prepared.disjoint(sample.location)) { continue; } lineStrings.add(sample.location); } // isolated line if (lineStrings.size() == 0) { if (deleteShort && part.getLength() <= dangleLength) { return null; } return part; } // post process LineString[] lsArray = GeometryFactory.toLineStringArray(lineStrings); MultiLineString multi = factory.createMultiLineString(lsArray); List<Point> intersections = extractPoints(part.intersection(multi)); final double[] minDist = { Double.MAX_VALUE, Double.MAX_VALUE }; final Point[] minPoint = { null, null }; for (Point source : intersections) { double startDist = source.distance(part.getStartPoint()); double endDist = source.distance(part.getEndPoint()); if (startDist < endDist) { if (startDist < minDist[0]) { minDist[0] = startDist; minPoint[0] = source; } } else { if (endDist < minDist[1]) { minDist[1] = endDist; minPoint[1] = source; } } } // connected line if (minDist[0] == 0d && minDist[1] == 0d) { return part; } // build trimmed LineString Coordinate[] coordinates = part.getCoordinates(); if (minDist[0] > 0 && minDist[0] <= dangleLength) { coordinates[0] = minPoint[0].getCoordinate(); } if (minDist[1] > 0 && minDist[1] <= dangleLength) { coordinates[coordinates.length - 1] = minPoint[1].getCoordinate(); } // check deleteShort LineString trimmed = factory.createLineString(coordinates); if (deleteShort && trimmed.getLength() <= dangleLength) { return null; } return trimmed; } private List<Point> extractPoints(Geometry intersections) { final List<Point> points = new ArrayList<Point>(); intersections.apply(new GeometryComponentFilter() { @Override public void filter(Geometry geom) { if (geom instanceof Point) { points.add((Point) geom); } } }); return points; } private void buildSpatialIndex(SimpleFeatureCollection features) { spatialIndex = new STRtree(); SimpleFeatureIterator featureIter = features.features(); try { while (featureIter.hasNext()) { SimpleFeature feature = featureIter.next(); Geometry multiPart = (Geometry) feature.getDefaultGeometry(); for (int index = 0; index < multiPart.getNumGeometries(); index++) { LineString part = (LineString) multiPart.getGeometryN(index); NearFeature nearFeature = new NearFeature(part, feature.getID()); spatialIndex.insert(part.getEnvelopeInternal(), nearFeature); } } } finally { featureIter.close(); } } static final class NearFeature { public LineString location; public String id; public NearFeature(LineString location, String id) { this.location = location; this.id = id; } } }