@ParameterizedTest
  @ValueSource(strings = {"multipolygon", "boundary", "land_area"})
  void testOsmMultipolygon(String relationType) throws Exception {
    record TestRelationInfo(long id, String name) implements OsmRelationInfo {}
    var results = runWithOsmElements(
      Map.of("threads", "1"),
      List.of(
        new OsmElement.Node(1, GeoUtils.getWorldLat(0.125), GeoUtils.getWorldLon(0.125)),
        new OsmElement.Node(2, GeoUtils.getWorldLat(0.125), GeoUtils.getWorldLon(0.875)),
        new OsmElement.Node(3, GeoUtils.getWorldLat(0.875), GeoUtils.getWorldLon(0.875)),
        new OsmElement.Node(4, GeoUtils.getWorldLat(0.875), GeoUtils.getWorldLon(0.125)),

        new OsmElement.Node(5, GeoUtils.getWorldLat(0.25), GeoUtils.getWorldLon(0.25)),
        new OsmElement.Node(6, GeoUtils.getWorldLat(0.25), GeoUtils.getWorldLon(0.75)),
        new OsmElement.Node(7, GeoUtils.getWorldLat(0.75), GeoUtils.getWorldLon(0.75)),
        new OsmElement.Node(8, GeoUtils.getWorldLat(0.75), GeoUtils.getWorldLon(0.25)),

        new OsmElement.Node(9, GeoUtils.getWorldLat(0.375), GeoUtils.getWorldLon(0.375)),
        new OsmElement.Node(10, GeoUtils.getWorldLat(0.375), GeoUtils.getWorldLon(0.625)),
        new OsmElement.Node(11, GeoUtils.getWorldLat(0.625), GeoUtils.getWorldLon(0.625)),
        new OsmElement.Node(12, GeoUtils.getWorldLat(0.625), GeoUtils.getWorldLon(0.375)),
        new OsmElement.Node(13, GeoUtils.getWorldLat(0.375 + 1e-12), GeoUtils.getWorldLon(0.375)),

        with(new OsmElement.Way(14), way -> way.nodes().add(1, 2, 3, 4, 1)),
        with(new OsmElement.Way(15), way -> way.nodes().add(5, 6, 7, 8, 5)),
        with(new OsmElement.Way(16), way -> way.nodes().add(9, 10, 11, 12, 13)),

        with(new OsmElement.Relation(17), rel -> {
          rel.setTag("type", relationType);
          rel.setTag("attr", "value");
          rel.setTag("should_emit", "yes");
          rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 14, "outer"));
          rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 15, null)); // missing
          rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 16, "inner")); // incorrect
          rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 14, "outer")); // duplicate
        }),
        with(new OsmElement.Relation(18), rel -> {
          rel.setTag("type", "relation");
          rel.setTag("name", "rel name");
          rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 17, "outer"));
        })
      ),
      in -> in.hasTag("type", "relation") ?
        List.of(new TestRelationInfo(in.id(), in.getString("name"))) :
        null,
      (in, features) -> {
        if (in.hasTag("should_emit")) {
          features.polygon("layer")
            .setZoomRange(0, 0)
            .setAttr("name", "name value")
            .inheritAttrFromSource("attr")
            .setAttr("relname",
              in.relationInfo(TestRelationInfo.class).stream().map(c -> c.relation().name).findFirst().orElse(null));
        }
      }
    );

    assertSubmap(Map.of(
      TileCoord.ofXYZ(0, 0, 0), List.of(
        feature(newMultiPolygon(
          newPolygon(
            rectangleCoordList(0.125 * 256, 0.875 * 256),
            List.of(
              rectangleCoordList(0.25 * 256, 0.75 * 256)
            )
          ),
          rectangle(0.375 * 256, 0.625 * 256)
        ), Map.of(
          "attr", "value",
          "name", "name value",
          "relname", "rel name"
        ), 173)
      )
    ), results.tiles);
  }
In-memory tests with fake data and profiles to ensure all features work end-to-end.
planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java