Skip to content

Commit

Permalink
feature merge
Browse files Browse the repository at this point in the history
  • Loading branch information
msbarry committed Sep 23, 2023
1 parent f556af2 commit 664a810
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ public final class Feature {
private ZoomFunction<Number> pixelTolerance = null;

private String numPointsAttr = null;
private boolean removeHolesBelowMinSize = false;

private Feature(String layer, Geometry geom, long id) {
this.layer = layer;
Expand Down Expand Up @@ -731,5 +732,14 @@ public String toString() {
", attrs=" + attrs +
'}';
}

public Feature setRemoveHolesBelowMinSize(boolean b) {
this.removeHolesBelowMinSize = b;
return this;
}

public boolean getRemoveHolesBelowMinSize() {
return this.removeHolesBelowMinSize;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.Polygonal;
import org.locationtech.jts.index.strtree.STRtree;
Expand Down Expand Up @@ -87,6 +91,77 @@ public static List<VectorTile.Feature> mergeLineStrings(List<VectorTile.Feature>
return mergeLineStrings(features, minLength, tolerance, buffer, false);
}

public static List<VectorTile.Feature> mergeMultiPoint(List<VectorTile.Feature> features) {
return mergeGeometries(
features,
GeometryType.POINT,
Point.class,
MultiPoint.class,
GeoUtils::combinePoints
);
}

public static List<VectorTile.Feature> mergeMultiPolygon(List<VectorTile.Feature> features) {
return mergeGeometries(
features,
GeometryType.POLYGON,
Polygon.class,
MultiPolygon.class,
GeoUtils::combinePolygons
);
}

public static List<VectorTile.Feature> mergeMultiLineString(List<VectorTile.Feature> features) {
return mergeGeometries(
features,
GeometryType.LINE,
LineString.class,
MultiLineString.class,
GeoUtils::combineLineStrings
);
}

private static <S extends Geometry, M extends GeometryCollection> List<VectorTile.Feature> mergeGeometries(
List<VectorTile.Feature> features,
GeometryType geometryType,
Class<S> singleClass,
Class<M> multiClass,
Function<List<S>, Geometry> combine
) {
List<VectorTile.Feature> result = new ArrayList<>(features.size());
var groupedByAttrs = groupByAttrs(features, result, geometryType);
for (List<VectorTile.Feature> groupedFeatures : groupedByAttrs) {
VectorTile.Feature feature1 = groupedFeatures.get(0);
if (groupedFeatures.size() == 1) {
result.add(feature1);
} else {
List<S> geoms = new ArrayList<>();
for (var feature : groupedFeatures) {
try {
// TODO can we avoid decoding/encoding?
var geom = feature.geometry().decode();
if (singleClass.isInstance(geom)) {
geoms.add(singleClass.cast(geom));
} else if (multiClass.isInstance(geom)) {
var mp = multiClass.cast(geom);
for (int i = 0; i < mp.getNumGeometries(); i++) {
geoms.add(singleClass.cast(mp.getGeometryN(i)));
}
} else if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Unexpected geometry type in merge({}): {}",
geometryType.name().toLowerCase(),
geom.getClass());
}
} catch (GeometryException e) {
e.log("Error merging merging into a multi" + geometryType.name().toLowerCase() + ": " + feature);
}
}
result.add(feature1.copyWithNewGeometry(combine.apply(geoms)));
}
}
return result;
}

/**
* Merges linestrings with the same attributes as {@link #mergeLineStrings(List, Function, double, double, boolean)}
* except sets {@code resimplify=false} by default.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/
package com.onthegomap.planetiler.geo;

import org.locationtech.jts.algorithm.Area;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
Expand Down Expand Up @@ -45,12 +46,26 @@ public static Geometry simplify(Geometry geom, double distanceTolerance) {
return (new DPTransformer(distanceTolerance)).transform(geom);
}

public static Geometry simplify(Geometry geom, double distanceTolerance, double minHoleSize) {
if (geom.isEmpty()) {
return geom.copy();
}

return (new DPTransformer(distanceTolerance, minHoleSize)).transform(geom);
}

private static class DPTransformer extends GeometryTransformer {

private final double sqTolerance;
private final double minHoleSize;

private DPTransformer(double distanceTolerance) {
this(distanceTolerance, 0);
}

private DPTransformer(double distanceTolerance, double minHoleSize) {
this.sqTolerance = distanceTolerance * distanceTolerance;
this.minHoleSize = minHoleSize;
}

/**
Expand Down Expand Up @@ -142,7 +157,8 @@ protected Geometry transformPolygon(Polygon geom, Geometry parent) {
protected Geometry transformLinearRing(LinearRing geom, Geometry parent) {
boolean removeDegenerateRings = parent instanceof Polygon;
Geometry simpResult = super.transformLinearRing(geom, parent);
if (removeDegenerateRings && !(simpResult instanceof LinearRing)) {
if (removeDegenerateRings && (!(simpResult instanceof LinearRing ring) ||
(minHoleSize > 0 && Area.ofRing(ring.getCoordinateSequence()) <= minHoleSize))) {
return null;
}
return simpResult;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,22 @@
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.mbtiles.Mbtiles;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -684,4 +693,95 @@ void mergeMultiPolygonExcludeSmallInnerRings() throws GeometryException {
)
);
}

@Test
void mergeMultipoints() throws GeometryException {
testMultigeometryMerger(
i -> newPoint(i, 2 * i),
items -> newMultiPoint(items.toArray(Point[]::new)),
rectangle(0, 1),
FeatureMerge::mergeMultiPoint
);
}

@Test
void mergeMultipolygons() throws GeometryException {
testMultigeometryMerger(
i -> rectangle(i, i + 1),
items -> newMultiPolygon(items.toArray(Polygon[]::new)),
newPoint(0, 0),
FeatureMerge::mergeMultiPolygon
);
}

@Test
void mergeMultiline() throws GeometryException {
testMultigeometryMerger(
i -> newLineString(i, i + 1, i + 2, i + 3),
items -> newMultiLineString(items.toArray(LineString[]::new)),
newPoint(0, 0),
FeatureMerge::mergeMultiLineString
);
}


public static void main(String[] args) {
List<VectorTile.Feature> features = new ArrayList<>();
for (int i = 0; i < 1_000; i++) {
double[] points = IntStream.range(0, 100).mapToDouble(Double::valueOf).toArray();
var lineString = newLineString(points);
features.add(new VectorTile.Feature("layer", i, VectorTile.encodeGeometry(lineString), Map.of("a", 1)));
}
for (int j = 0; j < 10; j++) {
long start = System.currentTimeMillis();
for (int i = 0; i < 1_000; i++) {
FeatureMerge.mergeMultiLineString(features);
}
System.err.println(System.currentTimeMillis() - start);
}
}

<S extends Geometry, M extends GeometryCollection> void testMultigeometryMerger(
IntFunction<S> generateGeometry,
Function<List<S>, M> combineJTS,
Geometry otherGeometry,
UnaryOperator<List<VectorTile.Feature>> merge
) throws GeometryException {
var geom1 = generateGeometry.apply(1);
var geom2 = generateGeometry.apply(2);
var geom3 = generateGeometry.apply(3);
var geom4 = generateGeometry.apply(4);

assertTopologicallyEquivalentFeatures(
List.of(),
merge.apply(List.of())
);

assertTopologicallyEquivalentFeatures(
List.of(
feature(1, geom1, Map.of("a", 1))
),
merge.apply(
List.of(
feature(1, geom1, Map.of("a", 1))
)
)
);

assertTopologicallyEquivalentFeatures(
List.of(
feature(4, otherGeometry, Map.of("a", 1)),
feature(1, combineJTS.apply(List.of(geom1, geom2, geom3)), Map.of("a", 1)),
feature(3, geom4, Map.of("a", 2))
),
merge.apply(
List.of(
feature(1, geom1, Map.of("a", 1)),
feature(2, combineJTS.apply(List.of(geom2, geom3)), Map.of("a", 1)),
feature(3, geom4, Map.of("a", 2)),
feature(4, otherGeometry, Map.of("a", 1))
)
)
);
}
}

0 comments on commit 664a810

Please sign in to comment.