Package org.opensphere.geometry.algorithm

Source Code of org.opensphere.geometry.algorithm.ConcaveHull

/*
* This file is part of the OpenSphere project which aims to
* develop geospatial algorithms.
*
* Copyright (C) 2012 Eric Grosso
*
* 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:
* Eric Grosso, eric.grosso.os@gmail.com
*
*/
package org.opensphere.geometry.algorithm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.opensphere.geometry.triangulation.DoubleComparator;
import org.opensphere.geometry.triangulation.model.Edge;
import org.opensphere.geometry.triangulation.model.Triangle;
import org.opensphere.geometry.triangulation.model.Vertex;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
import com.vividsolutions.jts.operation.linemerge.LineMerger;
import com.vividsolutions.jts.triangulate.ConformingDelaunayTriangulationBuilder;
import com.vividsolutions.jts.triangulate.quadedge.QuadEdge;
import com.vividsolutions.jts.triangulate.quadedge.QuadEdgeSubdivision;
import com.vividsolutions.jts.triangulate.quadedge.QuadEdgeTriangle;
import com.vividsolutions.jts.util.UniqueCoordinateArrayFilter;

/**
* Computes a concave hull of a {@link Geometry} which is
* a concave {@link Geometry} that contains all the points
* in the input {@link Geometry}.
* The concave hull is not be defined as unique; here, it is
* defined according to a threshold which is the maximum length
* of border edges of the concave hull.
*
* <p>
* Uses the Duckham and al. (2008) algorithm defined in the paper
* untitled "Efficient generation of simple polygons for characterizing
* the shape of a set of points in the plane".
*
* @author Eric Grosso
*
*/
public class ConcaveHull {

  private GeometryFactory geomFactory;
  private GeometryCollection geometries;
  private double threshold;
 
  public HashMap<LineSegment, Integer> segments = new HashMap<LineSegment, Integer>();
  public HashMap<Integer, Edge> edges = new HashMap<Integer, Edge>();
  public HashMap<Integer, Triangle> triangles = new HashMap<Integer, Triangle>();
  public TreeMap<Integer, Edge> lengths = new TreeMap<Integer, Edge>();

  public HashMap<Integer, Edge> shortLengths = new HashMap<Integer, Edge>();
 
  public HashMap<Coordinate,Integer> coordinates = new HashMap<Coordinate, Integer>();
  public HashMap<Integer, Vertex> vertices = new HashMap<Integer, Vertex>();


  /**
   * Create a new concave hull construction for the input {@link Geometry}.
   *
   * @param geometry
   * @param threshold
   */
  public ConcaveHull(Geometry geometry, double threshold) {
    this.geometries = transformIntoPointGeometryCollection(geometry);
    this.threshold = threshold;
    this.geomFactory = geometry.getFactory();
  }
 
  /**
   * Create a new concave hull construction for the input {@link GeometryCollection}.
   *
   * @param geometries
   * @param threshold
   */
  public ConcaveHull(GeometryCollection geometries, double threshold) {
    this.geometries = transformIntoPointGeometryCollection(geometries);
    this.threshold = threshold;
    this.geomFactory = geometries.getFactory();
  }

  /**
   * Transform into GeometryCollection.
   *
   * @param geom
   *     input geometry
   * @return
   *     a geometry collection
   */
  private static GeometryCollection transformIntoPointGeometryCollection(Geometry geom) {
    UniqueCoordinateArrayFilter filter = new UniqueCoordinateArrayFilter();
    geom.apply(filter);
    Coordinate[] coord = filter.getCoordinates();
   
    Geometry[] geometries = new Geometry[coord.length];
    for (int i = 0 ; i < coord.length ; i++) {
      Coordinate[] c = new Coordinate[] { coord[i] };
      CoordinateArraySequence cs = new CoordinateArraySequence(c);
      geometries[i] = new Point(cs, geom.getFactory());
    }
   
    return new GeometryCollection(geometries, geom.getFactory());
  }

 
  /**
   * Transform into GeometryCollection.
   *
   * @param geom
   *     input geometry
   * @return
   *     a geometry collection
   */
  private static GeometryCollection transformIntoPointGeometryCollection(GeometryCollection gc) {
    UniqueCoordinateArrayFilter filter = new UniqueCoordinateArrayFilter();
    gc.apply(filter);
    Coordinate[] coord = filter.getCoordinates();
   
    Geometry[] geometries = new Geometry[coord.length];
    for (int i = 0 ; i < coord.length ; i++) {
      Coordinate[] c = new Coordinate[] { coord[i] };
      CoordinateArraySequence cs = new CoordinateArraySequence(c);
      geometries[i] = new Point(cs, gc.getFactory());
    }
   
    return new GeometryCollection(geometries, gc.getFactory());
  }

 
  /**
   * Returns a {@link Geometry} that represents the concave hull of the input
   * geometry according to the threshold.
   * The returned geometry contains the minimal number of points needed to
   * represent the concave hull.
   *
   * @return if the concave hull contains 3 or more points, a {@link Polygon};
   * 2 points, a {@link LineString};
   * 1 point, a {@link Point};
   * 0 points, an empty {@link GeometryCollection}.
   */
  public Geometry getConcaveHull() {

    if (this.geometries.getNumGeometries() == 0) {
      return this.geomFactory.createGeometryCollection(null);
    }
    if (this.geometries.getNumGeometries() == 1) {
      return this.geometries.getGeometryN(0);
    }
    if (this.geometries.getNumGeometries() == 2) {
      return this.geomFactory.createLineString(this.geometries.getCoordinates());
    }

    return concaveHull();
  }
 
  /**
   * Create the concave hull.
   *
   * @return
   *     the concave hull
   */
  private Geometry concaveHull() {
   
    // triangulation: create a DelaunayTriangulationBuilder object 
    ConformingDelaunayTriangulationBuilder cdtb = new ConformingDelaunayTriangulationBuilder();

    // add geometry collection
    cdtb.setSites(this.geometries);
   
    QuadEdgeSubdivision qes = cdtb.getSubdivision();
   
    Collection<QuadEdge> quadEdges = qes.getEdges();
    List<QuadEdgeTriangle> qeTriangles = QuadEdgeTriangle.createOn(qes);
    Collection<com.vividsolutions.jts.triangulate.quadedge.Vertex> qeVertices =
      qes.getVertices(false);
   
    int iV = 0;
    for (com.vividsolutions.jts.triangulate.quadedge.Vertex v : qeVertices) {
      this.coordinates.put(v.getCoordinate(), iV);
      this.vertices.put(iV, new Vertex(iV, v.getCoordinate()));
      iV++;
    }
   
    // border
    List<QuadEdge> qeFrameBorder = new ArrayList<QuadEdge>();
    List<QuadEdge> qeFrame = new ArrayList<QuadEdge>();
    List<QuadEdge> qeBorder = new ArrayList<QuadEdge>();

    for (QuadEdge qe : quadEdges) {
      if (qes.isFrameBorderEdge(qe)) {
        qeFrameBorder.add(qe);
      }
      if (qes.isFrameEdge(qe)) {
        qeFrame.add(qe);
      }
    }

    // border
    for (int j = 0 ; j < qeFrameBorder.size() ; j++) {
      QuadEdge q = qeFrameBorder.get(j);
      if (! qeFrame.contains(q)) {
        qeBorder.add(q);
      }
    }
   
    // deletion of exterior edges
    for (QuadEdge qe : qeFrame) {
      qes.delete(qe);
    }
   
    HashMap<QuadEdge, Double> qeDistances = new HashMap<QuadEdge, Double>();
    for (QuadEdge qe : quadEdges) {
      qeDistances.put(qe, qe.toLineSegment().getLength());
    }
       
    DoubleComparator dc = new DoubleComparator(qeDistances);
    TreeMap<QuadEdge, Double> qeSorted = new TreeMap<QuadEdge, Double>(dc);
    qeSorted.putAll(qeDistances);

    // edges creation
    int i = 0;
    for (QuadEdge qe : qeSorted.keySet()) {
      LineSegment s = qe.toLineSegment();
      s.normalize();
     
      Integer idS = this.coordinates.get(s.p0);
      Integer idD = this.coordinates.get(s.p1);
      Vertex oV = this.vertices.get(idS);
      Vertex eV = this.vertices.get(idD);
     
      Edge edge;
      if (qeBorder.contains(qe)) {
        oV.setBorder(true);
        eV.setBorder(true);
        edge = new Edge(i, s, oV, eV, true);
        if (s.getLength() < this.threshold) {
          this.shortLengths.put(i, edge);
        } else {
          this.lengths.put(i, edge);
        }
      } else {
        edge = new Edge(i, s, oV, eV, false);
      }
      this.edges.put(i, edge);
      this.segments.put(s, i);
      i++;
    }

    // hm of linesegment and hm of edges // with id as key
    // hm of triangles using hm of ls and connection with hm of edges
   
    i = 0;
    for (QuadEdgeTriangle qet : qeTriangles) {
      LineSegment sA = qet.getEdge(0).toLineSegment();
      LineSegment sB = qet.getEdge(1).toLineSegment();
      LineSegment sC = qet.getEdge(2).toLineSegment();
      sA.normalize();
      sB.normalize();
      sC.normalize();
     
      Edge edgeA = this.edges.get(this.segments.get(sA));
      Edge edgeB = this.edges.get(this.segments.get(sB));
      Edge edgeC = this.edges.get(this.segments.get(sC));
      if (edgeA == null || edgeB == null || edgeC == null)
          continue;

      Triangle triangle = new Triangle(i, qet.isBorder()?true:false);
      triangle.addEdge(edgeA);
      triangle.addEdge(edgeB);
      triangle.addEdge(edgeC);

      edgeA.addTriangle(triangle);
      edgeB.addTriangle(triangle);
      edgeC.addTriangle(triangle);

      this.triangles.put(i, triangle);
      i++;
    }

    // add triangle neighbourood
    for (Edge edge : this.edges.values()) {
      if (edge.getTriangles().size() > 1) {
        Triangle tA = edge.getTriangles().get(0);
        Triangle tB = edge.getTriangles().get(1);
        tA.addNeighbour(tB);
        tB.addNeighbour(tA);
      }
    }

   
    // concave hull algorithm
    int index = 0;
    while (index != -1) {
      index = -1;

      Edge e = null;

      // find the max length (smallest id so first entry)
      int si = this.lengths.size();

      if (si != 0) {
        Entry<Integer, Edge> entry = this.lengths.firstEntry();
        int ind = entry.getKey();
        if (entry.getValue().getGeometry().getLength() > this.threshold) {
          index = ind;
          e = entry.getValue();
        }
      }

      if (index != -1) {
        Triangle triangle = e.getTriangles().get(0);
        List<Triangle> neighbours = triangle.getNeighbours();
        // irregular triangle test
        if (neighbours.size() == 1) {
          this.shortLengths.put(e.getId(), e);
          this.lengths.remove(e.getId());
        } else  {
          Edge e0 = triangle.getEdges().get(0);
          Edge e1 = triangle.getEdges().get(1);
          // test if all the vertices are on the border
          if (e0.getOV().isBorder() && e0.getEV().isBorder()
              && e1.getOV().isBorder() && e1.getEV().isBorder()) {
            this.shortLengths.put(e.getId(), e);
            this.lengths.remove(e.getId());
          } else {
            // management of triangles
                  if (neighbours.size() < 1) continue; //not sure this is safe
            Triangle tA = neighbours.get(0);
            Triangle tB = neighbours.get(1);
            tA.setBorder(true); // FIXME not necessarily useful
            tB.setBorder(true); // FIXME not necessarily useful
            this.triangles.remove(triangle.getId());
            tA.removeNeighbour(triangle);
            tB.removeNeighbour(triangle);
           
            // new edges
            List<Edge> ee = triangle.getEdges();
            Edge eA = ee.get(0);
            Edge eB = ee.get(1);
            Edge eC = ee.get(2);
           
            if (eA.isBorder()) {
              this.edges.remove(eA.getId());
              eB.setBorder(true);
              eB.getOV().setBorder(true);
              eB.getEV().setBorder(true);
              eC.setBorder(true);
              eC.getOV().setBorder(true);
              eC.getEV().setBorder(true);

              // clean the relationships with the triangle
              eB.removeTriangle(triangle);
              eC.removeTriangle(triangle);

              if (eB.getGeometry().getLength() < this.threshold) {
                this.shortLengths.put(eB.getId(), eB);
              } else {
                this.lengths.put(eB.getId(), eB);
              }
              if (eC.getGeometry().getLength() < this.threshold) {
                this.shortLengths.put(eC.getId(), eC);
              } else {
                this.lengths.put(eC.getId(), eC);
              }
              this.lengths.remove(eA.getId());
            } else if (eB.isBorder()) {
              this.edges.remove(eB.getId());
              eA.setBorder(true);
              eA.getOV().setBorder(true);
              eA.getEV().setBorder(true);
              eC.setBorder(true);
              eC.getOV().setBorder(true);
              eC.getEV().setBorder(true);

              // clean the relationships with the triangle
              eA.removeTriangle(triangle);
              eC.removeTriangle(triangle);

              if (eA.getGeometry().getLength() < this.threshold) {
                this.shortLengths.put(eA.getId(), eA);
              } else {
                this.lengths.put(eA.getId(), eA);
              }
              if (eC.getGeometry().getLength() < this.threshold) {
                this.shortLengths.put(eC.getId(), eC);
              } else {
                this.lengths.put(eC.getId(), eC);
              }
              this.lengths.remove(eB.getId());
            } else {
              this.edges.remove(eC.getId());
              eA.setBorder(true);
              eA.getOV().setBorder(true);
              eA.getEV().setBorder(true);
              eB.setBorder(true);
              eB.getOV().setBorder(true);
              eB.getEV().setBorder(true);
              // clean the relationships with the triangle
              eA.removeTriangle(triangle);
              eB.removeTriangle(triangle);

              if (eA.getGeometry().getLength() < this.threshold) {
                this.shortLengths.put(eA.getId(), eA);
              } else {
                this.lengths.put(eA.getId(), eA);
              }
              if (eB.getGeometry().getLength() < this.threshold) {
                this.shortLengths.put(eB.getId(), eB);
              } else {
                this.lengths.put(eB.getId(), eB);
              }
              this.lengths.remove(eC.getId());
            }
          }
        }
      }
    }
   
    // concave hull creation
    List<LineString> edges = new ArrayList<LineString>();
    for (Edge e : this.lengths.values()) {
      LineString l = e.getGeometry().toGeometry(this.geomFactory);
      edges.add(l);
    }

    for (Edge e : this.shortLengths.values()) {
      LineString l = e.getGeometry().toGeometry(this.geomFactory);
      edges.add(l);
    }

    // merge
    LineMerger lineMerger = new LineMerger();
    lineMerger.add(edges);
    LineString merge = (LineString)lineMerger.getMergedLineStrings().iterator().next();
   
    if (merge.isRing()) {
      LinearRing lr = new LinearRing(merge.getCoordinateSequence(), this.geomFactory);
      Polygon concaveHull = new Polygon(lr, null, this.geomFactory);
      return concaveHull;
    }
   
    return merge;
  }

}
TOP

Related Classes of org.opensphere.geometry.algorithm.ConcaveHull

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.