private void buildMultiPolygon()

in baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationMultiPolygonBuilder.java [85:175]


  private void buildMultiPolygon(Relation relation) {
    // Categorize the members of the relation by their role
    var outerMembers = new ArrayList<Member>();
    var innerMembers = new ArrayList<Member>();
    var otherMembers = new ArrayList<Member>();
    for (var member : relation.getMembers()) {
      if (MemberType.WAY.equals(member.type())) {
        switch (member.role()) {
          case "outer" -> outerMembers.add(member);
          case "inner" -> innerMembers.add(member);
          default -> otherMembers.add(member);
        }
      }
    }

    // Prepare the outer polygons
    var outerPolygons = createPolygons(outerMembers);

    // Prepare the inner polygons
    var innerPolygons = createPolygons(innerMembers);
    innerPolygons = combinePolygons(innerPolygons);

    // Prepare the other polygons
    var otherPolygons = createPolygons(otherMembers);
    otherPolygons = combinePolygons(otherPolygons);

    // Categorize the other polygons as inner or outer
    for (var otherPolygon : otherPolygons) {

      // If the outer polygon contains the other polygon, it is an inner polygon
      for (var outerPolygon : outerPolygons) {
        if (outerPolygon.contains(otherPolygon)) {
          innerPolygons.add(otherPolygon);
        }
      }

      // Otherwise, it is an outer polygon
      if (!innerPolygons.contains(otherPolygon)) {
        outerPolygons.add(otherPolygon);
      }
    }

    // Merge the outer and inner polygons to build the relation geometry
    var polygons = new ArrayList<Polygon>();

    // Iterate over the outer polygons
    for (var outerPolygon : outerPolygons) {

      // Initialize the shell and holes of the polygon
      var shell = outerPolygon.getExteriorRing();
      var holes = new ArrayList<LinearRing>();
      for (int i = 0; i < outerPolygon.getNumInteriorRing(); i++) {
        holes.add(outerPolygon.getInteriorRingN(i));
      }

      // Use a prepared geometry to speed up the contains operation
      var preparedOuterPolygon = PreparedGeometryFactory.prepare(outerPolygon);

      // Find to which inner polygon the outer polygon belongs
      for (var innerPolygon : innerPolygons) {
        if (preparedOuterPolygon.contains(innerPolygon)) {

          // The exterior ring of the inner polygon is a hole in the outer polygon
          holes.add(innerPolygon.getExteriorRing());

          // The interior rings of the inner polygon are in fact additional polygons
          for (int i = 0; i < innerPolygon.getNumInteriorRing(); i++) {
            var innerPolygonHole =
                GeometryUtils.GEOMETRY_FACTORY_WGS84
                    .createPolygon(innerPolygon.getInteriorRingN(i));
            repairPolygon(innerPolygonHole, polygons);
          }
        }
      }

      // Build the polygon from the shell and holes
      var polygon = GeometryUtils.GEOMETRY_FACTORY_WGS84.createPolygon(shell,
          holes.toArray(new LinearRing[0]));
      repairPolygon(polygon, polygons);
    }

    // Build the multipolygon from the polygons
    if (!polygons.isEmpty()) {
      var multiPolygon =
          GeometryUtils.GEOMETRY_FACTORY_WGS84.createMultiPolygon(polygons.toArray(new Polygon[0]));
      relation.setGeometry(multiPolygon);
    } else {
      var emptyMultiPolygon = GeometryUtils.GEOMETRY_FACTORY_WGS84.createMultiPolygon();
      relation.setGeometry(emptyMultiPolygon);
    }
  }