Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align setting of brunnel (and layer) tags for brunnel features depending on their length and zoom level #18

Merged
merged 20 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6a099de
clean-up: mvn spotless:apply
phanecak-maptiler Oct 2, 2023
afbd863
Long ferries (as per OMT PR 1486)
phanecak-maptiler Oct 2, 2023
3470989
add brunnel (and layer) attributes only for certain zoomlevels, depen…
phanecak-maptiler Oct 10, 2023
3876d6b
unit test testInterstateMotorway(): brunnel tag for test line no long…
phanecak-maptiler Oct 10, 2023
bca12e2
unit test testInterstateMotorway() clean-up: Z13 was tested twice
phanecak-maptiler Oct 10, 2023
6125d70
Merge branch 'omt_3_15_0' into omt3_15-long-ferries
phanecak-maptiler Oct 12, 2023
b7e7f4c
further adjustments to better match what is done with ferries in OMT
phanecak-maptiler Oct 17, 2023
00560b3
ferry minLength tweak + clean-up
phanecak-maptiler Oct 17, 2023
5d08720
mvn spotless:apply
phanecak-maptiler Oct 17, 2023
397efef
fixed minor typo
phanecak-maptiler Oct 17, 2023
dca9671
minor reformatting
phanecak-maptiler Oct 17, 2023
8654d86
ferry line length filter replaced with min. zoom calculation
phanecak-maptiler Oct 18, 2023
8e1703b
testFerry() adjusted to match previous commit
phanecak-maptiler Oct 18, 2023
6c05d7d
clea-up of unused stuff + mvn spotless:apply
phanecak-maptiler Oct 18, 2023
b6cda03
mvn spotless:apply
phanecak-maptiler Oct 18, 2023
338329a
Merge branch 'omt3_15-long-ferries' into omt3_15-frag-fix
phanecak-maptiler Oct 18, 2023
9b59981
added TODO node for follow-up pull-request/simplification
phanecak-maptiler Oct 18, 2023
a7bcfa9
clean-up: common getMinZoom() code moved to Utils
phanecak-maptiler Oct 18, 2023
30dab9f
minzoom clipping for brunnel was adjusted do Z9-Z12 -> test adjusted too
phanecak-maptiler Oct 18, 2023
1ddfb9a
clean-up
phanecak-maptiler Oct 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/main/java/org/openmaptiles/layers/Park.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ public void process(Tables.OsmParkPolygon element, FeatureCollector features) {
protectionTitle = protectionTitle.replace(' ', '_').toLowerCase(Locale.ROOT);
}
clazz = coalesce(
nullIfEmpty(protectionTitle),
nullIfEmpty(element.boundary()),
nullIfEmpty(element.leisure())
nullIfEmpty(protectionTitle),
nullIfEmpty(element.boundary()),
nullIfEmpty(element.leisure())
);
}

Expand Down
3 changes: 1 addition & 2 deletions src/main/java/org/openmaptiles/layers/Poi.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.openmaptiles.OpenMapTilesProfile;
import org.openmaptiles.generated.OpenMapTilesSchema;
Expand Down Expand Up @@ -156,7 +155,7 @@ private int minzoom(String subclass, String mappingKey) {

public static int uniAreaToMinZoom(double areaWorld) {
double oneSideWorld = Math.sqrt(areaWorld);
// full(-er) formula (along with comments) is in PoiTest.testUniAreaToMinZoom(), here is simplified reverse of that
// adjusted formula from `Utils.getMinZoomForLength()`, given that 1/10 does not match `1 / (2^<something>)`
double zoom = -(Math.log(oneSideWorld * SQRT10) / LOG2);

// Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold,
Expand Down
72 changes: 63 additions & 9 deletions src/main/java/org/openmaptiles/layers/Transportation.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
import org.openmaptiles.OpenMapTilesProfile;
import org.openmaptiles.generated.OpenMapTilesSchema;
import org.openmaptiles.generated.Tables;
import org.openmaptiles.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -155,6 +156,8 @@ public class Transportation implements
.put(6, 100)
.put(5, 500)
.put(4, 1_000);
// "shipway_linestring_gen_z5: ... tolerance: ZRES6", etc. when recalculated from meters to pixels is always:
private static final double FERRY_TOLERANCE = 0.5;
// ORDER BY network_type, network, LENGTH(ref), ref)
private static final Comparator<RouteRelation> RELATION_ORDERING = Comparator
.<RouteRelation>comparingInt(
Expand All @@ -163,11 +166,11 @@ public class Transportation implements
.thenComparingInt(r -> r.ref().length())
.thenComparing(RouteRelation::ref);
private static final Set<Integer> ONEWAY_VALUES = Set.of(-1, 1);
private final Map<String, Integer> MINZOOMS;
private static final String LIMIT_MERGE_TAG = "__limit_merge";
private final AtomicBoolean loggedNoGb = new AtomicBoolean(false);
private final AtomicBoolean loggedNoIreland = new AtomicBoolean(false);
private final boolean z13Paths;
private final Map<String, Integer> MINZOOMS;
private final Stats stats;
private final PlanetilerConfig config;
private PreparedGeometry greatBritain = null;
Expand Down Expand Up @@ -449,6 +452,9 @@ public void process(Tables.OsmHighwayLinestring element, FeatureCollector featur
}
int minzoom = getMinzoom(element, highwayClass);

String brunnelValue = brunnel(element.isBridge(), element.isTunnel(), element.isFord());
int brunnelMinzoom = brunnelValue != null ? getBrunnelMinzoom(element) : minzoom;

if (minzoom > config.maxzoom()) {
return;
}
Expand All @@ -462,13 +468,11 @@ public void process(Tables.OsmHighwayLinestring element, FeatureCollector featur
.setAttr(Fields.CLASS, highwayClass)
.setAttr(Fields.SUBCLASS, highwaySubclass(highwayClass, element.publicTransport(), highway))
.setAttr(Fields.NETWORK, networkType != null ? networkType.name : null)
// TODO: including brunnel at low zooms leads to some large 300-400+kb z4-7 tiles, instead
// we should only set brunnel if the line is above a certain length
.setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()))
.setAttrWithMinzoom(Fields.BRUNNEL, brunnelValue, brunnelMinzoom)
// z8+
.setAttrWithMinzoom(Fields.EXPRESSWAY, element.expressway() && !"motorway".equals(highway) ? 1 : null, 8)
// z9+
.setAttrWithMinzoom(Fields.LAYER, nullIfLong(element.layer(), 0), 9)
.setAttrWithMinzoom(Fields.LAYER, nullIfLong(element.layer(), 0), brunnelValue == null ? 9 : brunnelMinzoom)
.setAttrWithMinzoom(Fields.BICYCLE, nullIfEmpty(element.bicycle()), 9)
.setAttrWithMinzoom(Fields.FOOT, nullIfEmpty(element.foot()), 9)
.setAttrWithMinzoom(Fields.HORSE, nullIfEmpty(element.horse()), 9)
Expand Down Expand Up @@ -534,6 +538,17 @@ int getMinzoom(Tables.OsmHighwayLinestring element, String highwayClass) {
return minzoom;
}

int getBrunnelMinzoom(Tables.OsmHighwayLinestring element) {
try {
return Utils.getClippedMinZoomForLength(element.source().length(), 6, 9, 12);
} catch (GeometryException e) {
e.log(stats, "omt_brunnel_minzoom",
"Unable to calculate brunnel minzoom for " + element.source().id());
// brunnel is optional (depends on feature size) for Z9-Z11, it is always present for Z12+, hence 12 as fallback
return 12;
}
}

private boolean isPierPolygon(Tables.OsmHighwayLinestring element) {
if ("pier".equals(element.manMade())) {
try {
Expand Down Expand Up @@ -609,7 +624,18 @@ public void process(Tables.OsmShipwayLinestring element, FeatureCollector featur
.setAttr(Fields.LAYER, nullIfLong(element.layer(), 0))
.setSortKey(element.zOrder())
.setMinPixelSize(0) // merge during post-processing, then limit by size
.setMinZoom(11);
.setMinZoom(getFerryMinzoom(element));
}

int getFerryMinzoom(Tables.OsmShipwayLinestring element) {
try {
return Utils.getClippedMinZoomForLength(element.source().length(), 3, 4, 11);
} catch (GeometryException e) {
e.log(stats, "omt_ferry_minzoom",
"Unable to calculate ferry minzoom for " + element.source().id());
// ferries are supposed to be included in Z4-Z10 depending on their length (=this min. zoom calculation), for Z11+ always, hence 11 as fallback
return 11;
}
}

@Override
Expand All @@ -632,9 +658,7 @@ public void process(Tables.OsmHighwayPolygon element, FeatureCollector features)
}
}

@Override
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
double tolerance = config.tolerance(zoom);
private List<VectorTile.Feature> postProcessItems(int zoom, List<VectorTile.Feature> items, double tolerance) {
double minLength = coalesce(MIN_LENGTH.apply(zoom), 0).doubleValue();

// don't merge road segments with oneway tag
Expand All @@ -655,6 +679,36 @@ public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> i
return merged;
}

private List<VectorTile.Feature> postProcessAllOrNonFerry(int zoom, List<VectorTile.Feature> items) {
// TODO: use same tolerance as for ferries (see tolerances in OMT `transportation/mapping.yaml`), hence no need to split ferries from the rest in postProcess()
return postProcessItems(zoom, items, config.tolerance(zoom));
}

private List<VectorTile.Feature> postProcessFerry(int zoom, List<VectorTile.Feature> items) {
return postProcessItems(zoom, items, FERRY_TOLERANCE);
}

@Override
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
if (zoom < 4 || zoom > 10) {
return postProcessAllOrNonFerry(zoom, items);
} else {
// ferries at Z4-Z10 need different treatment
List<VectorTile.Feature> ferryItems = new ArrayList<>();
List<VectorTile.Feature> otherItems = new ArrayList<>();
for (var item : items) {
if (FieldValues.CLASS_FERRY.equals(item.attrs().get(Fields.CLASS))) {
ferryItems.add(item);
} else {
otherItems.add(item);
}
}
var result = postProcessFerry(zoom, ferryItems);
result.addAll(postProcessAllOrNonFerry(zoom, otherItems));
return result;
}
}

enum RouteNetwork {

US_INTERSTATE("us-interstate"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ public void process(Tables.OsmHighwayLinestring element, FeatureCollector featur
}

if (brunnel) {
feature.setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()));
String brunnelValue = brunnel(element.isBridge(), element.isTunnel(), element.isFord());
int brunnelMinzoom = brunnelValue != null ? transportation.getBrunnelMinzoom(element) : minzoom;
feature.setAttrWithMinzoom(Fields.BRUNNEL, brunnelValue, brunnelMinzoom);
}

/*
Expand Down
13 changes: 3 additions & 10 deletions src/main/java/org/openmaptiles/layers/WaterName.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
import org.openmaptiles.generated.OpenMapTilesSchema;
import org.openmaptiles.generated.Tables;
import org.openmaptiles.util.OmtLanguageUtils;
import org.openmaptiles.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -82,7 +83,6 @@ public class WaterName implements
*/

private static final Logger LOGGER = LoggerFactory.getLogger(WaterName.class);
private static final double LOG2 = Math.log(2);
private static final Set<String> SEA_OR_OCEAN_PLACE = Set.of("sea", "ocean");
private final Translations translations;
// need to synchronize updates from multiple threads
Expand Down Expand Up @@ -227,14 +227,7 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) {

public static int areaToMinZoom(double areaWorld) {
double oneSideWorld = Math.sqrt(areaWorld);
// full(-er) formula (along with comments) is in WaterNameTest.testAreaToMinZoom(), here is simplified reverse of that
double zoom = -(Math.log(oneSideWorld) / LOG2) - 1;

// Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold,
// hence Z13.01 and Z13.00 will be rounded to Z14 and Z12.99 to Z13 (e.g. `floor() + 1`).
// And to accommodate for some precision errors (observed for Z9-Z11) we do also `- 0.1e-11`.
int result = (int) Math.floor(zoom - 0.1e-11) + 1;

return Math.min(14, Math.max(3, result));
// OMT does "feature area is 1/4 of tile area", which is same as "feature side is 1/2 of tile side"
return Utils.getClippedMinZoomForLength(oneSideWorld, 1, 3, 14);
}
}
41 changes: 41 additions & 0 deletions src/main/java/org/openmaptiles/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/
public class Utils {

private static final double LOG2 = Math.log(2);

public static <T> T coalesce(T a, T b) {
return a != null ? a : b;
}
Expand Down Expand Up @@ -75,4 +77,43 @@ public static String brunnel(boolean isBridge, boolean isTunnel, boolean isFord)
return isBridge ? "bridge" : isTunnel ? "tunnel" : isFord ? "ford" : null;
}

/**
* Calculate minzoom for a feature with given length based on threshold: if a feature's length is least
* {@code 1 / (2^threshold)} of a tile at certain zoom level, it will be shown at that and higher zoom levels, e.g.
* that particular zoom level is minzoom.
* <p>
* {@code threshold} is calculated in such way to avoid calculating log(2) os it during the runtime. Use 1 for 1/2, 2
* for 1/4, 3 for 1/8, etc.
*
* @param length length of the feature
* @param threshold threshold
* @return minzoom for a feature with given length and given threshold
*/
public static int getMinZoomForLength(double length, double threshold) {
// Say threshold is 1/8 (threshold variable = 8) of tile size, hence ...
// ... from pixels to world coord, for say Z14, the minimum length is:
// PORTION_OF_TILE_SIDE = (256d / 8) / Math.pow(2d, 14d + 8d);
// ... and then minimum length for some lower zoom:
// PORTION_OF_TILE_SIDE * Math.pow(2, 14 - zoom);
// all this then reversed and simplified to:
double zoom = -(Math.log(length) / LOG2) - threshold;

// Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold,
// hence Z13.01 and Z13.00 will be rounded to Z14 and Z12.99 to Z13 (e.g. `floor() + 1`).
// And to accommodate for some precision errors (observed for Z9-Z11) we do also `- 0.1e-10`.
return (int) Math.floor(zoom - 0.1e-10) + 1;
}

/**
* Same as {@link #getMinZoomForLength(double, double)} but with result within the given minimum and maximim.
*
* @param length length of the feature
* @param threshold threshold
* @param min clip the result to this value if lower
* @param max clip the result to this value if higher
* @return minzoom for a feature with given length and given threshold clipped to not exceed given minimum and maximum
*/
public static int getClippedMinZoomForLength(double length, double threshold, int min, int max) {
return Math.min(max, Math.max(min, getMinZoomForLength(length, threshold)));
}
}
10 changes: 10 additions & 0 deletions src/test/java/org/openmaptiles/layers/AbstractLayerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ SourceFeature lineFeature(Map<String, Object> props) {
);
}

SourceFeature lineFeatureWithLength(double length, Map<String, Object> props) {
return SimpleFeature.create(
GeoUtils.worldToLatLonCoords(newLineString(0, 0, 0, length)),
new HashMap<>(props),
OpenMapTilesProfile.OSM_SOURCE,
null,
0
);
}

SourceFeature closedWayFeature(Map<String, Object> props) {
return SimpleFeature.createFakeOsmFeature(
newLineString(0, 0, 1, 0, 1, 1, 0, 1, 0, 0),
Expand Down
32 changes: 16 additions & 16 deletions src/test/java/org/openmaptiles/layers/ParkTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,24 @@ void testNationalPark() {
@Test
void testAbotiginalLand() {
assertFeatures(13, List.of(Map.of(
"_layer", "park",
"_type", "polygon",
"class", "aboriginal_lands",
"name", "Hualapai Tribe",
"_minpixelsize", 2d,
"_minzoom", 4,
"_maxzoom", 14
"_layer", "park",
"_type", "polygon",
"class", "aboriginal_lands",
"name", "Hualapai Tribe",
"_minpixelsize", 2d,
"_minzoom", 4,
"_maxzoom", 14
), Map.of(
"_layer", "park",
"_type", "point",
"class", "aboriginal_lands",
"name", "Hualapai Tribe",
"_minzoom", 5,
"_maxzoom", 14
"_layer", "park",
"_type", "point",
"class", "aboriginal_lands",
"name", "Hualapai Tribe",
"_minzoom", 5,
"_maxzoom", 14
)), process(polygonFeature(Map.of(
"boundary", "aboriginal_lands",
"name", "Hualapai Tribe",
"protection_title", "National Park"
"boundary", "aboriginal_lands",
"name", "Hualapai Tribe",
"protection_title", "National Park"
))));
}

Expand Down
Loading