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

com.hazelcast.shaded.org.locationtech.jts.simplify.PolygonHullSimplifier Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * Copyright (c) 2022 Martin Davis.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.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-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package com.hazelcast.shaded.org.locationtech.jts.simplify;

import java.util.ArrayList;
import java.util.List;

import com.hazelcast.shaded.org.locationtech.jts.algorithm.Area;
import com.hazelcast.shaded.org.locationtech.jts.geom.Geometry;
import com.hazelcast.shaded.org.locationtech.jts.geom.GeometryFactory;
import com.hazelcast.shaded.org.locationtech.jts.geom.LinearRing;
import com.hazelcast.shaded.org.locationtech.jts.geom.MultiPolygon;
import com.hazelcast.shaded.org.locationtech.jts.geom.Polygon;
import com.hazelcast.shaded.org.locationtech.jts.geom.Polygonal;
import com.hazelcast.shaded.org.locationtech.jts.math.MathUtil;

/**
 * Computes topology-preserving simplified hulls of polygonal geometry.
 * Both outer and inner hulls can be computed.
 * Outer hulls contain the input geometry and are larger in area.
 * Inner hulls are contained by the input geometry and are smaller in area.
 * In both the hull vertices are a subset of the input vertices.
 * The hull construction attempts to minimize the area difference
 * with the input geometry.
 * Hulls are generally concave if the input is.
 * Computed hulls are topology-preserving: 
 * they do not contain any self-intersections or overlaps, 
 * so the result polygonal geometry is valid.
 * 

* Polygons with holes and MultiPolygons are supported. * The result has the same geometric type and structure as the input. *

* The number of vertices in the computed hull is determined by a target parameter. * Two parameters are supported: *

    *
  1. Vertex Number fraction: the fraction of the input vertices retained in the result. * Value 1 produces the original geometry. * Smaller values produce less concave results. * For outer hulls, value 0 produces the convex hull (with triangles for any holes). * For inner hulls, value 0 produces a triangle (if no holes are present). *
  2. *
  3. Area Delta ratio: the ratio of the change in area to the input area. * Value 0 produces the original geometry. * Larger values produce less concave results. *
  4. *
* The algorithm ensures that the result does not cause the target parameter * to be exceeded. This allows computing outer or inner hulls * with a small area delta ratio as an effective way of removing * narrow gores and spikes. * * @author Martin Davis * */ public class PolygonHullSimplifier { /** * Computes a topology-preserving simplified hull of a polygonal geometry, * with hull shape determined by a target parameter * specifying the fraction of the input vertices retained in the result. * Larger values compute less concave results. * A value of 1 produces the convex hull; a value of 0 produces the original geometry. * Either outer or inner hulls can be computed. * * @param geom the polygonal geometry to process * @param isOuter indicates whether to compute an outer or inner hull * @param vertexNumFraction the target fraction of number of input vertices in result * @return the hull geometry */ public static Geometry hull(Geometry geom, boolean isOuter, double vertexNumFraction) { PolygonHullSimplifier hull = new PolygonHullSimplifier(geom, isOuter); hull.setVertexNumFraction( Math.abs(vertexNumFraction)); return hull.getResult(); } /** * Computes a topology-preserving simplified hull of a polygonal geometry, * with hull shape determined by a target parameter * specifying the ratio of maximum difference in area to original area. * Larger values compute less concave results. * A value of 0 produces the original geometry. * Either outer or inner hulls can be computed. * * @param geom the polygonal geometry to process * @param isOuter indicates whether to compute an outer or inner hull * @param areaDeltaRatio the target ratio of area difference to original area * @return the hull geometry */ public static Geometry hullByAreaDelta(Geometry geom, boolean isOuter, double areaDeltaRatio) { PolygonHullSimplifier hull = new PolygonHullSimplifier(geom, isOuter); hull.setAreaDeltaRatio( Math.abs(areaDeltaRatio)); return hull.getResult(); } private Geometry inputGeom; private boolean isOuter; private double vertexNumFraction = -1; private double areaDeltaRatio = -1; private GeometryFactory geomFactory; /** * Creates a new instance * to compute a simplified hull of a polygonal geometry. * An outer or inner hull is computed * depending on the value of isOuter. * * @param inputGeom the polygonal geometry to process * @param isOuter indicates whether to compute an outer or inner hull */ public PolygonHullSimplifier(Geometry inputGeom, boolean isOuter) { this.inputGeom = inputGeom; this.geomFactory = inputGeom.getFactory(); this.isOuter = isOuter; if (! (inputGeom instanceof Polygonal)) { throw new IllegalArgumentException("Input geometry must be polygonal"); } } /** * Sets the target fraction of input vertices * which are retained in the result. * The value should be in the range [0,1]. * * @param vertexNumFraction a fraction of the number of input vertices */ public void setVertexNumFraction(double vertexNumFraction) { double frac = MathUtil.clamp(vertexNumFraction, 0, 1); this.vertexNumFraction = frac; } /** * Sets the target maximum ratio of the change in area of the result to the input area. * The value must be 0 or greater. * * @param areaDeltaRatio a ratio of the change in area of the result */ public void setAreaDeltaRatio(double areaDeltaRatio) { this.areaDeltaRatio = areaDeltaRatio; } /** * Gets the result polygonal hull geometry. * * @return the polygonal geometry for the hull */ public Geometry getResult() { //-- handle trivial parameter values if (vertexNumFraction == 1 || areaDeltaRatio == 0) { return inputGeom.copy(); } if (inputGeom instanceof MultiPolygon) { /** * Only outer hulls where there is more than one polygon * can potentially overlap. * Shell outer hulls could overlap adjacent shell hulls * or hole hulls surrounding them; * hole outer hulls could overlap contained shell hulls. */ boolean isOverlapPossible = isOuter && inputGeom.getNumGeometries() > 1; if (isOverlapPossible) { return computeMultiPolygonAll((MultiPolygon) inputGeom); } else { return computeMultiPolygonEach((MultiPolygon) inputGeom); } } else if (inputGeom instanceof Polygon) { return computePolygon((Polygon) inputGeom); } throw new IllegalArgumentException("Input geometry must be polygonal"); } /** * Computes hulls for MultiPolygon elements for * the cases where hulls might overlap. * * @param multiPoly the MultiPolygon to process * @return the hull geometry */ private Geometry computeMultiPolygonAll(MultiPolygon multiPoly) { RingHullIndex hullIndex = new RingHullIndex(); int nPoly = multiPoly.getNumGeometries(); @SuppressWarnings("unchecked") List[] polyHulls = (List[]) new ArrayList[nPoly]; //TODO: investigate if reordering input elements improves result //-- prepare element polygon hulls and index for (int i = 0 ; i < multiPoly.getNumGeometries(); i++) { Polygon poly = (Polygon) multiPoly.getGeometryN(i); List ringHulls = initPolygon(poly, hullIndex); polyHulls[i] = ringHulls; } //-- compute hull polygons List polys = new ArrayList(); for (int i = 0 ; i < multiPoly.getNumGeometries(); i++) { Polygon poly = (Polygon) multiPoly.getGeometryN(i); Polygon hull = polygonHull(poly, polyHulls[i], hullIndex); polys.add(hull); } return geomFactory.createMultiPolygon(GeometryFactory.toPolygonArray(polys)); } private Geometry computeMultiPolygonEach(MultiPolygon multiPoly) { List polys = new ArrayList(); for (int i = 0 ; i < multiPoly.getNumGeometries(); i++) { Polygon poly = (Polygon) multiPoly.getGeometryN(i); Polygon hull = computePolygon(poly); polys.add(hull); } return geomFactory.createMultiPolygon(GeometryFactory.toPolygonArray(polys)); } private Polygon computePolygon(Polygon poly) { RingHullIndex hullIndex = null; /** * For a single polygon overlaps are only possible for inner hulls * and where holes are present. */ boolean isOverlapPossible = ! isOuter && poly.getNumInteriorRing() > 0; if (isOverlapPossible) hullIndex = new RingHullIndex(); List hulls = initPolygon(poly, hullIndex); Polygon hull = polygonHull(poly, hulls, hullIndex); return hull; } /** * Create all ring hulls for the rings of a polygon, * so that all are in the hull index if required. * * @param poly the polygon being processed * @param hullIndex the hull index if present, or null * @return the list of ring hulls */ private List initPolygon(Polygon poly, RingHullIndex hullIndex) { List hulls = new ArrayList(); if (poly.isEmpty()) return hulls; double areaTotal = 0.0; if (areaDeltaRatio >= 0) { areaTotal = ringArea(poly); } hulls.add( createRingHull( poly.getExteriorRing(), isOuter, areaTotal, hullIndex)); for (int i = 0; i < poly.getNumInteriorRing(); i++) { //Assert: interior ring is not empty hulls.add( createRingHull( poly.getInteriorRingN(i), ! isOuter, areaTotal, hullIndex)); } return hulls; } private double ringArea(Polygon poly) { double area = Area.ofRing( poly.getExteriorRing().getCoordinateSequence()); for (int i = 0; i < poly.getNumInteriorRing(); i++) { area += Area.ofRing( poly.getInteriorRingN(i).getCoordinateSequence()); } return area; } private RingHull createRingHull(LinearRing ring, boolean isOuter, double areaTotal, RingHullIndex hullIndex) { RingHull ringHull = new RingHull(ring, isOuter); if (vertexNumFraction >= 0) { int targetVertexCount = (int) Math.ceil(vertexNumFraction * (ring.getNumPoints() - 1)); ringHull.setMinVertexNum(targetVertexCount); } else if (areaDeltaRatio >= 0) { double ringArea = Area.ofRing(ring.getCoordinateSequence()); double ringWeight = ringArea / areaTotal; double maxAreaDelta = ringWeight * areaDeltaRatio * ringArea; ringHull.setMaxAreaDelta(maxAreaDelta); } if (hullIndex != null) hullIndex.add(ringHull); return ringHull; } private Polygon polygonHull(Polygon poly, List ringHulls, RingHullIndex hullIndex) { if (poly.isEmpty()) return geomFactory.createPolygon(); int ringIndex = 0; LinearRing shellHull = ringHulls.get(ringIndex++).getHull(hullIndex); List holeHulls = new ArrayList(); for (int i = 0; i < poly.getNumInteriorRing(); i++) { LinearRing hull = ringHulls.get(ringIndex++).getHull(hullIndex); //TODO: handle empty holeHulls.add(hull); } LinearRing[] resultHoles = GeometryFactory.toLinearRingArray(holeHulls); return geomFactory.createPolygon(shellHull, resultHoles); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy