/* * Copyright (c) 2016 Vivid Solutions. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v. 1.0 which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * * http://www.eclipse.org/org/documents/edl-v10.php. */ package org.locationtech.jtslab.snapround;/* * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateList; import org.locationtech.jts.geom.CoordinateSequence; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryComponentFilter; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.PrecisionModel; import org.locationtech.jts.noding.NodedSegmentString; import org.locationtech.jts.noding.Noder; import org.locationtech.jts.noding.snapround.MCIndexSnapRounder; import org.locationtech.jtslab.geom.util.GeometryEditorEx; /** * Nodes a {@link Geometry} using Snap-Rounding * to a given {@link PrecisionModel}. * <ul> * <li>Point geometries are not handled. They are skipped if present in the input. * <li>Linestrings which collapse to a point due to snapping are removed. * <li>Polygonal output may not be valid. * Invalid output is due to the introduction of topology collapses. * This should be straightforward to clean using standard heuristics (e.g. buffer(0) ). * </ul> * The input geometry coordinates are expected to be rounded * to the given precision model. * This class does not perform that function. * <code>GeometryPrecisionReducer</code> may be used to do this. */ public class GeometrySnapRounder { private PrecisionModel pm; private boolean isLineworkOnly = false; /** * Creates a new snap-rounder which snap-rounds to a grid specified * by the given {@link PrecisionModel}. * * @param pm the precision model for the grid to snap-round to */ public GeometrySnapRounder(PrecisionModel pm) { this.pm = pm; } public void setLineworkOnly(boolean isLineworkOnly) { this.isLineworkOnly = isLineworkOnly; } /** * Snap-rounds the given geometry. * * * @param geom * @return */ public Geometry execute(Geometry geom) { // TODO: reduce precision of input automatically // TODO: add switch to GeometryPrecisionReducer to NOT check & clean invalid polygonal geometry (not needed here) // TODO: OR just do precision reduction with custom code here List segStrings = extractTaggedSegmentStrings(geom, pm); snapRound(segStrings); if (isLineworkOnly) { return toNodedLines(segStrings, geom.getFactory()); } Geometry geomSnapped = replaceLines(geom, segStrings); Geometry geomClean = ensureValid(geomSnapped); return geomClean; } private Geometry toNodedLines(Collection segStrings, GeometryFactory geomFact) { List lines = new ArrayList(); for (Iterator it = segStrings.iterator(); it.hasNext(); ) { NodedSegmentString nss = (NodedSegmentString) it.next(); // skip collapsed lines if (nss.size() < 2) continue; //Coordinate[] pts = getCoords(nss); Coordinate[] pts = nss.getNodeList().getSplitCoordinates(); lines.add(geomFact.createLineString(pts)); } return geomFact.buildGeometry(lines); } private Geometry replaceLines(Geometry geom, List segStrings) { Map nodedLinesMap = nodedLinesMap(segStrings); GeometryCoordinateReplacer lineReplacer = new GeometryCoordinateReplacer(nodedLinesMap); GeometryEditorEx geomEditor = new GeometryEditorEx(lineReplacer); Geometry snapped = geomEditor.edit(geom); return snapped; } private void snapRound(List segStrings) { //Noder sr = new SimpleSnapRounder(pm); Noder sr = new MCIndexSnapRounder(pm); sr.computeNodes(segStrings); } private HashMap nodedLinesMap(Collection segStrings) { HashMap ptsMap = new HashMap(); for (Iterator it = segStrings.iterator(); it.hasNext(); ) { NodedSegmentString nss = (NodedSegmentString) it.next(); // skip collapsed lines if (nss.size() < 2) continue; Coordinate[] pts = nss.getNodeList().getSplitCoordinates(); ptsMap.put(nss.getData(), pts); } return ptsMap; } static List extractTaggedSegmentStrings(Geometry geom, final PrecisionModel pm) { final List segStrings = new ArrayList(); GeometryComponentFilter filter = new GeometryComponentFilter() { public void filter(Geometry geom) { // Extract linework for lineal components only if (! (geom instanceof LineString) ) return; // skip empty lines if (geom.getNumPoints() <= 0) return; Coordinate[] roundPts = round( ((LineString)geom).getCoordinateSequence(), pm); segStrings.add(new NodedSegmentString(roundPts, geom)); } }; geom.apply(filter); return segStrings; } static Coordinate[] round(CoordinateSequence seq, PrecisionModel pm) { if (seq.size() == 0) return new Coordinate[0]; CoordinateList coordList = new CoordinateList(); // copy coordinates and reduce for (int i = 0; i < seq.size(); i++) { Coordinate coord = new Coordinate( seq.getOrdinate(i, Coordinate.X), seq.getOrdinate(i, Coordinate.Y) ); pm.makePrecise(coord); coordList.add(coord, false); } Coordinate[] coord = coordList.toCoordinateArray(); //TODO: what if seq is too short? return coord; } private static Geometry ensureValid(Geometry geom) { if (geom.isValid()) return geom; return cleanPolygonal(geom); } private static Geometry cleanPolygonal(Geometry geom) { return PolygonCleaner.clean(geom); } }