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

org.opentripplanner.graph_builder.module.osm.OsmAreaGroup Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.graph_builder.module.osm;

import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Polygon;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.graph_builder.module.osm.Ring.RingConstructionException;
import org.opentripplanner.osm.model.OsmEntity;
import org.opentripplanner.osm.model.OsmLevel;
import org.opentripplanner.osm.model.OsmNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A group of possibly-contiguous areas sharing the same level
 */
class OsmAreaGroup {

  private static final Logger LOG = LoggerFactory.getLogger(OsmAreaGroup.class);

  /*
   * The list of underlying areas, used when generating edges out of the visibility graph
   */
  Collection areas;

  /**
   * The joined outermost rings of the areas (with inner rings for holes as necessary).
   */
  List outermostRings = new ArrayList<>();

  public final Geometry union;

  public OsmAreaGroup(Collection areas) {
    this.areas = areas;

    // Merging non-convex polygons is complicated, so we need to convert to JTS, let JTS do the
    // hard work,
    // then convert back.
    List allRings = new ArrayList<>();

    // However, JTS will lose the coord<->osmnode mapping, and we will have to reconstruct it.
    HashMap nodeMap = new HashMap<>();
    for (OsmArea area : areas) {
      for (Ring ring : area.outermostRings) {
        allRings.add(ring.jtsPolygon);
        for (OsmNode node : ring.nodes) {
          nodeMap.put(new Coordinate(node.lon, node.lat), node);
        }
        for (Ring inner : ring.getHoles()) {
          for (OsmNode node : inner.nodes) {
            nodeMap.put(new Coordinate(node.lon, node.lat), node);
          }
        }
      }
    }
    GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();
    Geometry allPolygons = geometryFactory.createMultiPolygon(allRings.toArray(new Polygon[0]));
    this.union = allPolygons.union();

    if (this.union instanceof GeometryCollection coll) {
      GeometryCollection mp = coll;
      for (int i = 0; i < mp.getNumGeometries(); ++i) {
        Geometry geom = mp.getGeometryN(i);
        if (geom instanceof Polygon polygon) {
          outermostRings.add(toRing(polygon, nodeMap));
        } else {
          LOG.warn("Unexpected non-polygon when merging areas: {}", geom);
        }
      }
    } else if (this.union instanceof Polygon polygon) {
      outermostRings.add(toRing(polygon, nodeMap));
    } else {
      LOG.warn("Unexpected non-polygon when merging areas: {}", this.union);
    }
  }

  public static List groupAreas(Map areasLevels) {
    DisjointSet groups = new DisjointSet<>();
    Multimap areasForNode = LinkedListMultimap.create();
    for (OsmArea area : areasLevels.keySet()) {
      for (Ring ring : area.outermostRings) {
        for (Ring inner : ring.getHoles()) {
          for (OsmNode node : inner.nodes) {
            areasForNode.put(node, area);
          }
        }
        for (OsmNode node : ring.nodes) {
          areasForNode.put(node, area);
        }
      }
    }

    // areas that can be joined must share nodes and levels
    for (OsmNode osmNode : areasForNode.keySet()) {
      for (OsmArea area1 : areasForNode.get(osmNode)) {
        OsmLevel level1 = areasLevels.get(area1);
        for (OsmArea area2 : areasForNode.get(osmNode)) {
          OsmLevel level2 = areasLevels.get(area2);
          if ((level1 == null && level2 == null) || (level1 != null && level1.equals(level2))) {
            groups.union(area1, area2);
          }
        }
      }
    }

    List out = new ArrayList<>();
    for (Set areaSet : groups.sets()) {
      try {
        out.add(new OsmAreaGroup(areaSet));
      } catch (RingConstructionException e) {
        for (OsmArea area : areaSet) {
          LOG.debug(
            "Failed to create merged area for " +
            area +
            ".  This area might not be at fault; it might be one of the other areas in this list."
          );
          out.add(new OsmAreaGroup(Arrays.asList(area)));
        }
      }
    }
    return out;
  }

  public OsmEntity getSomeOsmObject() {
    return areas.iterator().next().parent;
  }

  /**
   * Check if area group has a trivial geometry of one boundary ring and one respective area
   * In such a case it is known that the area and the boundary ring match
   *
   * @return true if area group consists of one polygion only
   */
  public boolean isSimpleAreaGroup() {
    return areas.size() == 1 && outermostRings.size() == 1;
  }

  private Ring toRing(Polygon polygon, HashMap nodeMap) {
    List shell = new ArrayList<>();
    for (Coordinate coord : polygon.getExteriorRing().getCoordinates()) {
      OsmNode node = nodeMap.get(coord);
      if (node == null) {
        throw new RingConstructionException();
      }
      shell.add(node);
    }
    Ring ring = new Ring(shell);
    // now the holes
    for (int i = 0; i < polygon.getNumInteriorRing(); ++i) {
      LineString interior = polygon.getInteriorRingN(i);
      List hole = new ArrayList<>();
      for (Coordinate coord : interior.getCoordinates()) {
        OsmNode node = nodeMap.get(coord);
        if (node == null) {
          throw new RingConstructionException();
        }
        hole.add(node);
      }
      ring.addHole(new Ring(hole));
    }

    return ring;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy