Skip to content

Commit

Permalink
Improve LinkSimplify to work less aggressively and handle edge cases … (
Browse files Browse the repository at this point in the history
#60)

* Improve LinkSimplify to work less aggressively and handle edge cases [#53]
* allow multiple ways in connectors.
* allow connectors to touch a highway at any point, not just endpoints.

* formatting
  • Loading branch information
bdon authored Jul 14, 2023
1 parent c7aab17 commit 2c55a60
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 44 deletions.
17 changes: 11 additions & 6 deletions tiles/src/main/java/com/protomaps/basemap/layers/Roads.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.onthegomap.planetiler.FeatureMerge;
import com.onthegomap.planetiler.ForwardingProfile;
import com.onthegomap.planetiler.VectorTile;
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.protomaps.basemap.feature.FeatureId;
import com.protomaps.basemap.names.OsmNames;
Expand Down Expand Up @@ -145,12 +146,16 @@ public void processFeature(SourceFeature sourceFeature, FeatureCollector feature
}

@Override
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {

items = linkSimplify(items, "highway", "motorway", "motorway_link");
items = linkSimplify(items, "highway", "trunk", "trunk_link");
items = linkSimplify(items, "highway", "primary", "primary_link");
items = linkSimplify(items, "highway", "secondary", "secondary_link");
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) throws GeometryException {

// limit the application of LinkSimplify to where cloverleafs are unlikely to be at tile edges.
// TODO: selectively apply each class depending on zoom level.
if (zoom < 12) {
items = linkSimplify(items, "highway", "motorway", "motorway_link");
items = linkSimplify(items, "highway", "trunk", "trunk_link");
items = linkSimplify(items, "highway", "primary", "primary_link");
items = linkSimplify(items, "highway", "secondary", "secondary_link");
}

for (var item : items) {
item.attrs().remove("highway");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,79 @@
import com.onthegomap.planetiler.VectorTile;
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.geo.GeometryType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.locationtech.jts.geom.Coordinate;


public class LinkSimplify {
public static List<VectorTile.Feature> linkSimplify(List<VectorTile.Feature> items, String key, String mainval,
String linkval) {
Map<Coordinate, Integer> degrees = new HashMap<>();

for (VectorTile.Feature item : items) {
if (item.geometry().geomType() == GeometryType.LINE && item.attrs().get(key).equals(linkval)) {
try {
Coordinate[] coordinates = item.geometry().decode().getCoordinates();
if (coordinates.length == 0)
continue;
Coordinate start = coordinates[0];
Coordinate end = coordinates[coordinates.length - 1];
degrees.put(start, 0);
degrees.put(end, 0);
} catch (GeometryException e) {
/**
* Post-processing to remove "hairballs" from road networks.
* <p>
* OpenStreetMap uses the highway=motorway_link tag to connect motorways to motorways, as well as connect motorways to
* lower-class highway features. If we include all ways at all zooms, motorways will grow "hairballs" once the
* lower-class features are generalized away.
* <p>
* LinkSimplify uses some very basic heuristics to distinguish "connectors" and "offramps": If a link way's 2
* endpoints both touch (a motorway at any point | another link at an endpoint), keep it. (we're not guaranteed
* connectors are a single way, it can be multiple) Otherwise throw it away (an offramp)
* <p>
* The above logic generalizes to any passed tag, not just highway=motorway | motorway_link.
* </p>
*/
public static List<VectorTile.Feature> linkSimplify(List<VectorTile.Feature> items, String key, String mainval,
String linkval) throws GeometryException {

}
}
}
Map<Coordinate, Integer> degrees = new HashMap<>();

for (VectorTile.Feature item : items) {
if (item.geometry().geomType() == GeometryType.LINE && item.attrs().get(key).equals(mainval)) {
try {
if (item.geometry().geomType() == GeometryType.LINE) {
if (item.attrs().get(key).equals(linkval)) {
Coordinate[] coordinates = item.geometry().decode().getCoordinates();
if (coordinates.length == 0)
continue;
Coordinate start = coordinates[0];
Coordinate end = coordinates[coordinates.length - 1];
if (degrees.containsKey(start)) {
degrees.put(start, degrees.get(start) + 1);
} else {
degrees.put(start, 1);
}
if (degrees.containsKey(end)) {
degrees.put(end, degrees.get(end) + 1);
} else {
degrees.put(end, 1);
}

} else if (item.attrs().get(key).equals(mainval)) {
Coordinate[] coordinates = item.geometry().decode().getCoordinates();
for (Coordinate c : coordinates) {
if (degrees.containsKey(c)) {
degrees.put(c, degrees.get(c) + 1);
} else {
degrees.put(c, 1);
}
}
} catch (GeometryException e) {
}
}
}

List<VectorTile.Feature> retval = new ArrayList<>();
List<VectorTile.Feature> output = new ArrayList<>();

for (VectorTile.Feature item : items) {
if (item.geometry().geomType() == GeometryType.LINE && item.attrs().get(key).equals(linkval)) {
try {
Coordinate[] coordinates = item.geometry().decode().getCoordinates();
if (coordinates.length == 0)
continue;
Coordinate start = coordinates[0];
Coordinate end = coordinates[coordinates.length - 1];
if (degrees.get(start) == 1 && degrees.get(end) == 1) {
retval.add(item);
}
} catch (GeometryException e) {
Coordinate[] coordinates = item.geometry().decode().getCoordinates();
if (coordinates.length == 0)
continue;
Coordinate start = coordinates[0];
Coordinate end = coordinates[coordinates.length - 1];
if (degrees.get(start) >= 2 && degrees.get(end) >= 2) {
output.add(item);
}
} else {
retval.add(item);
output.add(item);
}
}
return retval;
return output;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,98 @@
package com.protomaps.basemap.postprocess;

public class LinkSimplifyTest {}
import static com.onthegomap.planetiler.TestUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.onthegomap.planetiler.VectorTile;
import com.onthegomap.planetiler.geo.GeometryException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;

public class LinkSimplifyTest {

@Test
void testNoop() throws GeometryException {
List<VectorTile.Feature> items = new ArrayList<>();

items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(0, 0, 10, 0)),
Map.of("highway", "tag1_link")
));

var result = LinkSimplify.linkSimplify(items, "highway", "tag1", "tag1_link");
assertEquals(0, result.size());
}

@Test
void testLinkConnectsMainAtAnyPoint() throws GeometryException {
List<VectorTile.Feature> items = new ArrayList<VectorTile.Feature>();

items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(0, 0, 10, 0, 20, 0)),

Map.of("highway", "tag1")
));
items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(10, 0, 10, 10)),
Map.of("highway", "tag1_link")
));
items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(0, 10, 10, 10, 20, 10)),
Map.of("highway", "tag1")
));

var result = LinkSimplify.linkSimplify(items, "highway", "tag1", "tag1_link");
assertEquals(3, result.size());
}

@Test
void testLinkIsOfframp() throws GeometryException {
List<VectorTile.Feature> items = new ArrayList<VectorTile.Feature>();

items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(0, 0, 10, 0, 20, 0)),

Map.of("highway", "tag1")
));
items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(10, 0, 10, 10)),
Map.of("highway", "tag1_link")
));

var result = LinkSimplify.linkSimplify(items, "highway", "tag1", "tag1_link");
assertEquals(1, result.size());
}

@Test
void testLinkConnectsLinksAtEdges() throws GeometryException {
List<VectorTile.Feature> items = new ArrayList<VectorTile.Feature>();

items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(0, 0, 10, 0)),
Map.of("highway", "tag1")
));
items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(10, 0, 20, 0)),
Map.of("highway", "tag1_link")
));
items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(20, 0, 30, 0)),
Map.of("highway", "tag1_link")
));
items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(30, 0, 40, 0)),
Map.of("highway", "tag1_link")
));
items.add(new VectorTile.Feature("mylayer", 1,
VectorTile.encodeGeometry(newLineString(40, 0, 50, 0)),
Map.of("highway", "tag1")
));

var result = LinkSimplify.linkSimplify(items, "highway", "tag1", "tag1_link");
assertEquals(5, result.size());
}


}

0 comments on commit 2c55a60

Please sign in to comment.