@ParameterizedTest
  @ValueSource(booleans = {false, true})
  void testMergeLineStrings(boolean connectEndpoints) throws Exception {
    double y = 0.5 + Z15_WIDTH / 2;
    double lat = GeoUtils.getWorldLat(y);

    double x1 = 0.5 + Z15_WIDTH / 4;
    double lng1 = GeoUtils.getWorldLon(x1);
    double lng2 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 10d / 256);
    double lng3 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 20d / 256);
    double lng4 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 30d / 256);

    var results = runWithReaderFeatures(
      Map.of("threads", "1", "maxzoom", "15"),
      List.of(
        // merge at z13 (same "group"):
        newReaderFeature(newLineString(
          lng1, lat,
          lng2, lat
        ), Map.of("group", "1", "other", "1")),
        newReaderFeature(newLineString(
          lng2, lat,
          lng3, lat
        ), Map.of("group", "1", "other", "2")),
        // don't merge at z13:
        newReaderFeature(newLineString(
          lng3, lat,
          lng4, lat
        ), Map.of("group", "2", "other", "3"))
      ),
      (in, features) -> features.line("layer")
        .setMinZoom(13)
        .setAttrWithMinzoom("z14attr", in.getTag("other"), 14)
        .inheritAttrFromSource("group"),
      (layer, zoom, items) -> connectEndpoints ?
        FeatureMerge.mergeLineStrings(items, 0, 0, 0) :
        FeatureMerge.mergeMultiLineString(items)
    );

    assertSubmap(sortListValues(Map.of(
      TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15), List.of(
        feature(newLineString(64, 128, 74, 128), Map.of("group", "1", "z14attr", "1")),
        feature(newLineString(74, 128, 84, 128), Map.of("group", "1", "z14attr", "2")),
        feature(newLineString(84, 128, 94, 128), Map.of("group", "2", "z14attr", "3"))
      ),
      TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
        feature(newLineString(32, 64, 37, 64), Map.of("group", "1", "z14attr", "1")),
        feature(newLineString(37, 64, 42, 64), Map.of("group", "1", "z14attr", "2")),
        feature(newLineString(42, 64, 47, 64), Map.of("group", "2", "z14attr", "3"))
      ),
      TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), connectEndpoints ? List.of(
        // merge 32->37 and 37->42 since they have same attrs
        feature(newLineString(16, 32, 21, 32), Map.of("group", "1")),
        feature(newLineString(21, 32, 23.5, 32), Map.of("group", "2"))
      ) : List.of(
        feature(newMultiLineString(
          newLineString(16, 32, 18.5, 32),
          newLineString(18.5, 32, 21, 32)
        ), Map.of("group", "1")),
        feature(newLineString(21, 32, 23.5, 32), Map.of("group", "2"))
      )
    )), sortListValues(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