All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.org.locationtech.jts.dissolve.LineDissolver Maven / Gradle / Ivy

The newest version!
/*
 * 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.jts.dissolve;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import org.locationtech.jts.edgegraph.HalfEdge;
import org.locationtech.jts.edgegraph.MarkHalfEdge;
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;



/**
 * Dissolves the linear components 
 * from a collection of {@link Geometry}s
 * into a set of maximal-length {@link Linestring}s
 * in which every unique segment appears once only.
 * The output linestrings run between node vertices
 * of the input, which are vertices which have
 * either degree 1, or degree 3 or greater.
 * 

* Use cases for dissolving linear components * include generalization * (in particular, simplifying polygonal coverages), * and visualization * (in particular, avoiding symbology conflicts when * depicting shared polygon boundaries). *

* This class does not node the input lines. * If there are line segments crossing in the input, * they will still cross in the output. * * @author Martin Davis * */ public class LineDissolver { /** * Dissolves the linear components in a geometry. * * @param g the geometry to dissolve * @return the dissolved lines */ public static Geometry dissolve(Geometry g) { LineDissolver d = new LineDissolver(); d.add(g); return d.getResult(); } private Geometry result; private GeometryFactory factory; private DissolveEdgeGraph graph; private List lines = new ArrayList(); public LineDissolver() { graph = new DissolveEdgeGraph(); } /** * Adds a {@link Geometry} to be dissolved. * Any number of geometries may be added by calling this method multiple times. * Any type of Geometry may be added. The constituent linework will be * extracted to be dissolved. * * @param geometry geometry to be line-merged */ public void add(Geometry geometry) { geometry.apply(new GeometryComponentFilter() { public void filter(Geometry component) { if (component instanceof LineString) { add((LineString)component); } } }); } /** * Adds a collection of Geometries to be processed. May be called multiple times. * Any dimension of Geometry may be added; the constituent linework will be * extracted. * * @param geometries the geometries to be line-merged */ public void add(Collection geometries) { for (Iterator i = geometries.iterator(); i.hasNext(); ) { Geometry geometry = (Geometry) i.next(); add(geometry); } } private void add(LineString lineString) { if (factory == null) { this.factory = lineString.getFactory(); } CoordinateSequence seq = lineString.getCoordinateSequence(); boolean doneStart = false; for (int i = 1; i < seq.size(); i++) { DissolveHalfEdge e = (DissolveHalfEdge) graph.addEdge(seq.getCoordinate(i-1), seq.getCoordinate(i)); // skip zero-length edges if (e == null) continue; /** * Record source initial segments, so that they can be reflected in output when needed * (i.e. during formation of isolated rings) */ if (! doneStart) { e.setStart(); doneStart = true; } } } /** * Gets the dissolved result as a MultiLineString. * * @return the dissolved lines */ public Geometry getResult() { if (result == null) computeResult(); return result; } private void computeResult() { Collection edges = graph.getVertexEdges(); for (Iterator i = edges.iterator(); i.hasNext(); ) { HalfEdge e = (HalfEdge) i.next(); if (MarkHalfEdge.isMarked(e)) continue; process(e); } result = factory.buildGeometry(lines); } private Stack nodeEdgeStack = new Stack(); private void process(HalfEdge e) { HalfEdge eNode = e.prevNode(); // if edge is in a ring, just process this edge if (eNode == null) eNode = e; stackEdges(eNode); // extract lines from node edges in stack buildLines(); } /** * For each edge in stack * (which must originate at a node) * extracts the line it initiates. */ private void buildLines() { while (! nodeEdgeStack.empty()) { HalfEdge e = (HalfEdge) nodeEdgeStack.pop(); if (MarkHalfEdge.isMarked(e)) continue; buildLine(e); } } private DissolveHalfEdge ringStartEdge; /** * Updates the tracked ringStartEdge * if the given edge has a lower origin * (using the standard {@link Coordinate} ordering). * * Identifying the lowest starting node meets two goals: *

    *
  • It ensures that isolated input rings are created using the original node and orientation *
  • For isolated rings formed from multiple input linestrings, * it provides a canonical node and orientation for the output * (rather than essentially random, and thus hard to test). *
* * @param e */ private void updateRingStartEdge(DissolveHalfEdge e) { if (! e.isStart()) { e = (DissolveHalfEdge) e.sym(); if (! e.isStart()) return; } // here e is known to be a start edge if (ringStartEdge == null) { ringStartEdge = e; return; } if (e.orig().compareTo(ringStartEdge.orig()) < 0) { ringStartEdge = e; } } /** * Builds a line starting from the given edge. * The start edge origin is a node (valence = 1 or >= 3), * unless it is part of a pure ring. * A pure ring has no other incident lines. * In this case the start edge may occur anywhere on the ring. * * The line is built up to the next node encountered, * or until the start edge is re-encountered * (which happens if the edges form a ring). * * @param eStart */ private void buildLine(HalfEdge eStart) { CoordinateList line = new CoordinateList(); DissolveHalfEdge e = (DissolveHalfEdge) eStart; ringStartEdge = null; MarkHalfEdge.markBoth(e); line.add(e.orig().copy(), false); // scan along the path until a node is found (if one exists) while (e.sym().degree() == 2) { updateRingStartEdge(e); DissolveHalfEdge eNext = (DissolveHalfEdge) e.next(); // check if edges form a ring - if so, we're done if (eNext == eStart) { buildRing(ringStartEdge); return; } // add point to line, and move to next edge line.add(eNext.orig().copy(), false); e = eNext; MarkHalfEdge.markBoth(e); } // add final node line.add(e.dest().clone(), false); // queue up the final node edges stackEdges(e.sym()); // store the scanned line addLine(line); } private void buildRing(HalfEdge eStartRing) { CoordinateList line = new CoordinateList(); HalfEdge e = eStartRing; line.add(e.orig().copy(), false); // scan along the path until a node is found (if one exists) while (e.sym().degree() == 2) { HalfEdge eNext = e.next(); // check if edges form a ring - if so, we're done if (eNext == eStartRing) break; // add point to line, and move to next edge line.add(eNext.orig().copy(), false); e = eNext; } // add final node line.add(e.dest().copy(), false); // store the scanned line addLine(line); } private void addLine(CoordinateList line) { lines.add(factory.createLineString(line.toCoordinateArray())); } /** * Adds edges around this node to the stack. * * @param node */ private void stackEdges(HalfEdge node) { HalfEdge e = node; do { if (! MarkHalfEdge.isMarked(e)) nodeEdgeStack.add(e); e = e.oNext(); } while (e != node); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy