package net.refractions.linecleaner.cleansing; import java.io.IOException; import java.util.Collection; import java.util.logging.Level; import net.refractions.linecleaner.FeatureUtil; import net.refractions.linecleaner.GeometryUtil; import net.refractions.linecleaner.LoggingSystem; import net.refractions.udig.project.internal.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.geotools.data.DefaultQuery; import org.geotools.data.FeatureStore; import org.geotools.data.Query; import org.geotools.feature.AttributeType; import org.geotools.feature.Feature; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.geotools.feature.IllegalAttributeException; import org.geotools.filter.FidFilter; import org.geotools.filter.Filter; import org.geotools.filter.FilterFactoryFinder; import org.geotools.filter.IllegalFilterException; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.operation.linemerge.LineMerger; public class PseudoNodeProcessor extends AbstractProcessor { private LoggingSystem loggingSystem; private double intersectionDistanceTolerance = 0.0; public PseudoNodeProcessor(Map map, FeatureStore featureStore) { this(map, featureStore, Level.WARNING); } public PseudoNodeProcessor(Map map, FeatureStore featureStore, Level loggingLevel) { super(map, featureStore); loggingSystem = LoggingSystem.getInstance(); } public PseudoNodeProcessor(Map map, FeatureStore featureStore, double intersectionDistanceTolerance) { this(map, featureStore, Level.WARNING); this.intersectionDistanceTolerance = intersectionDistanceTolerance; } public PseudoNodeProcessor(Map map, FeatureStore featureStore, Level loggingLevel, double intersectionDistanceTolerance) { this(map, featureStore, loggingLevel); this.intersectionDistanceTolerance = intersectionDistanceTolerance; } protected void runInternal(IProgressMonitor monitor, PauseMonitor pauseMonitor) throws IOException { if (monitor == null) monitor = new NullProgressMonitor(); loggingSystem.setCurrentAction(LoggingSystem.PSEUDO_NODES); loggingSystem.begin(); long start=System.currentTimeMillis(); monitor.beginTask("", featureStore.getCount(Query.ALL)); monitor.subTask("Removing pseudo nodes"); MemoryFeatureIterator iter = MemoryFeatureIterator.createDefault(this.featureStore, map); try { while (iter.hasNext()) { monitor.worked(1); if (monitor.isCanceled()) { break; } if (pauseMonitor != null && pauseMonitor.isPaused()) { while (pauseMonitor.isPaused()) { synchronized (this) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } Feature feature = iter.next(); Geometry geom = feature.getDefaultGeometry(); GeometryFactory gf = geom.getFactory(); Coordinate[] coordinates = geom.getCoordinates(); Point startPoint = gf.createPoint(coordinates[0]); Point endPoint = gf.createPoint(coordinates[coordinates.length-1]); try { processNode(startPoint); processNode(endPoint); } catch (IllegalFilterException e) { throw (IOException) new IOException().initCause(e); } catch (IllegalAttributeException e) { throw (IOException) new IOException().initCause(e); } } } finally { iter.close(); monitor.done(); } System.out.println("Pseudo Nodes done in: "+(System.currentTimeMillis()-start)/1000.00+" seconds"); loggingSystem.finish(); } protected FeatureCollection getConnectedFeatures(Point p) throws IllegalFilterException, IOException { String typename = featureStore.getSchema().getTypeName(); String geomName = featureStore.getSchema().getDefaultGeometry().getName(); // if we're just doing straight pseudo-noding, use an intersection filter if (this.intersectionDistanceTolerance == 0.0) { Filter intersectsFilter = FeatureUtil.intersectsGeom(geomName, p); FeatureCollection connectedFeatures = this.featureStore.getFeatures( new DefaultQuery(typename, intersectsFilter, new String[] { geomName })); return connectedFeatures; } // pseudo-noding with a certain tolerance on what's considered an intersection Filter bboxFilter = GeometryUtil.getBBoxFilter(featureStore, p, this.intersectionDistanceTolerance); Filter distanceFilter = FeatureUtil.distanceToGeom(geomName, p, this.intersectionDistanceTolerance); FeatureCollection connectedFeatures = this.featureStore.getFeatures( new DefaultQuery(typename, bboxFilter.and(distanceFilter), new String[] {geomName})); return connectedFeatures; } private void processNode(Point node) throws IllegalFilterException, IOException, IllegalAttributeException { FeatureCollection connectedFeatures = getConnectedFeatures(node); Feature first = null; Feature second = null; // We'll attempt to get two features off connectedFeatures, otherwise simply return. // NOTE: we can't use connectedFeatures.size() == 2 here cause size() is borken. FeatureIterator iter = connectedFeatures.features(); try { if (iter.hasNext()) first = iter.next(); else return; if (iter.hasNext()) second = iter.next(); else return; if (iter.hasNext()) return; } finally { iter.close(); } if (first.getID().equals(second.getID())) { loggingSystem.info("Found a feature that is a cycle. "+LoggingSystem.featureToString(first)); return; } Geometry firstGeom = first.getDefaultGeometry(); Geometry secondGeom = second.getDefaultGeometry(); // there's no point in merging zero-length lines. if (firstGeom.getLength() == 0 || secondGeom.getLength() == 0) { return; } LineMerger merger = new LineMerger(); merger.add(firstGeom); merger.add(secondGeom); Collection merged = merger.getMergedLineStrings(); if (merged.size() != 1) { loggingSystem.warning("Lines did not merge properly! Lines might not share end-nodes. " + LoggingSystem.featureToString(first)+ " and " + LoggingSystem.featureToString(second)); return; } LineString result = (LineString) merged.iterator().next(); // don't allow self-intersecting lines if (!result.isSimple()) { return; } GeometryFactory factory = new GeometryFactory(); Geometry mergedLine = factory.createMultiLineString(new LineString[] { result }); modifyFeature(first, mergedLine); loggingSystem.modify(first, "Merged features '"+first.getID()+"' and '"+second.getID()+"'."); deleteFeature(second); loggingSystem.delete(second); } private void modifyFeature(Feature feature, Geometry newGeometry) throws IllegalAttributeException, IOException { String fid = feature.getID(); FidFilter fidFilter = FilterFactoryFinder.createFilterFactory().createFidFilter(fid); String xpath = feature.getFeatureType().getDefaultGeometry().getName(); feature.setAttribute(xpath, newGeometry); AttributeType attributeType = feature.getFeatureType().getAttributeType(xpath); featureStore.modifyFeatures(attributeType, newGeometry, fidFilter); } private void deleteFeature(Feature feature) throws IOException { String fid = feature.getID(); FidFilter fidFilter = FilterFactoryFinder.createFilterFactory().createFidFilter(fid); featureStore.removeFeatures(fidFilter); } }