Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Tiles 4.14.4
------
- Continue expanding Overture support to lower zoom levels with Places and some infrastructure [#579]

Tiles 4.14.3
------
- Fix OSM place selection regression at zoom=7 [#576]
Expand Down
112 changes: 112 additions & 0 deletions app/src/examples.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,116 @@
[
{
"name": "oakland-downtown-z4",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.21885, 37.71324],
"zoom": 4
},
{
"name": "oakland-downtown-z5",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.21885, 37.71324],
"zoom": 5
},
{
"name": "oakland-downtown-z6",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.21885, 37.71324],
"zoom": 6
},
{
"name": "oakland-downtown-z7",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.2713, 37.8043],
"zoom": 7
},
{
"name": "oakland-downtown-z8",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.21885, 37.71324],
"zoom": 8
},
{
"name": "oakland-downtown-z9",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.2713, 37.8043],
"zoom": 9
},
{
"name": "oakland-downtown-z10",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.2713, 37.8043],
"zoom": 10
},
{
"name": "oakland-downtown-z11",
"description": "Area around downtown Oakland",
"tags": ["oakland", "oakland-downtown"],
"center": [-122.2713, 37.8043],
"zoom": 11
},
{
"name": "oakland-airport-z15",
"description": "Area around Oakland Airport terminals",
"tags": ["oakland", "oakland-airport"],
"center": [-122.21885, 37.71324],
"zoom": 15
},
{
"name": "oakland-airport-z14",
"description": "Area around Oakland Airport terminals",
"tags": ["oakland", "oakland-airport"],
"center": [-122.21885, 37.71324],
"zoom": 14
},
{
"name": "oakland-airport-z13",
"description": "Area around Oakland Airport terminals",
"tags": ["oakland", "oakland-airport"],
"center": [-122.21885, 37.71324],
"zoom": 13
},
{
"name": "oakland-airport-z12",
"description": "Area around Oakland Airport terminals",
"tags": ["oakland", "oakland-airport"],
"center": [-122.21885, 37.71324],
"zoom": 12
},
{
"name": "oakland-lake-merritt-z15",
"description": "Area around Oakland Lake Merritt",
"tags": ["oakland", "oakland-lake-merritt"],
"center": [-122.26467, 37.80661],
"zoom": 15
},
{
"name": "oakland-lake-merritt-z14",
"description": "Area around Oakland Lake Merritt",
"tags": ["oakland", "oakland-lake-merritt"],
"center": [-122.26467, 37.80661],
"zoom": 14
},
{
"name": "oakland-lake-merritt-z13",
"description": "Area around Oakland Lake Merritt",
"tags": ["oakland", "oakland-lake-merritt"],
"center": [-122.26467, 37.80661],
"zoom": 13
},
{
"name": "oakland-lake-merritt-z12",
"description": "Area around Oakland Lake Merritt",
"tags": ["oakland", "oakland-lake-merritt"],
"center": [-122.26467, 37.80661],
"zoom": 12
},
{
"name": "taiwan-z8",
"description": "Compare the appearance of highways",
Expand Down
2 changes: 1 addition & 1 deletion tiles/src/main/java/com/protomaps/basemap/Basemap.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public String description() {

@Override
public String version() {
return "4.14.3";
return "4.14.4";
}

@Override
Expand Down
92 changes: 45 additions & 47 deletions tiles/src/main/java/com/protomaps/basemap/layers/Places.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,42 +269,19 @@ public void processOsm(SourceFeature sf, FeatureCollector features) {
return;
}

Integer minZoom;
Integer maxZoom;
Integer kindRank;

var computedTags = makeTagMap(kind, kindDetail, population, populationFallback);
var sf2 = new Matcher.SourceFeatureWithComputedTags(sf, computedTags);
var zoomMatches = zoomsIndex.getMatches(sf2);

// Use populationFallback for sorting if no real population
if (population == 0 && populationFallback > 0) {
population = populationFallback;
}

minZoom = getInteger(sf2, zoomMatches, "pm:minzoom", 99);
maxZoom = getInteger(sf2, zoomMatches, "pm:maxzoom", 99);
kindRank = getInteger(sf2, zoomMatches, "pm:kindRank", 99);

int populationRank = 0;

for (int i = 0; i < popBreaks.length; i++) {
if (population >= popBreaks[i]) {
populationRank = i + 1;
}
}

if (WIKIDATA_CONFIGS.containsKey(sf.getString("wikidata"))) {
var wikidataConfig = WIKIDATA_CONFIGS.get(sf.getString("wikidata"));
if (kind.equals("country") || kind.equals("region")) {
minZoom = wikidataConfig.minZoom();
maxZoom = wikidataConfig.maxZoom();
}
if (kind.equals("locality")) {
minZoom = wikidataConfig.minZoom();
populationRank = wikidataConfig.rankMax();
}
}
var zp = getZoomsPops(sf2, kind, population);
int minZoom = zp.minZoom();
int maxZoom = zp.maxZoom();
int kindRank = zp.kindRank();
int populationRank = zp.populationRank();

var feat = features.point(this.name())
.setId(FeatureId.create(sf))
Expand Down Expand Up @@ -379,32 +356,19 @@ public void processOverture(SourceFeature sf, FeatureCollector features) {
}
}

// Overture always uses populationFallback for zoom calculations to get consistent behavior
// This ensures Overture places get the higher minzoom levels (8 for city, 9 for town, etc)
Integer populationFallback = 1; // Marker value to trigger fallback zoom levels

Integer minZoom;
Integer maxZoom;
Integer kindRank;
Integer populationFallback = (population > 0) ? 0 : 1;

var computedTags = makeTagMap(kind, kindDetail, population, populationFallback);
var sf2 = new Matcher.SourceFeatureWithComputedTags(sf, computedTags);
var zoomMatches = zoomsIndex.getMatches(sf2);

minZoom = getInteger(sf2, zoomMatches, "pm:minzoom", 99);
maxZoom = getInteger(sf2, zoomMatches, "pm:maxzoom", 99);
kindRank = getInteger(sf2, zoomMatches, "pm:kindRank", 99);

// Extract name
String name = sf.getString("names.primary");

int populationRank = 0;

for (int i = 0; i < popBreaks.length; i++) {
if (population >= popBreaks[i]) {
populationRank = i + 1;
}
}
var zp = getZoomsPops(sf2, kind, population);
int minZoom = zp.minZoom();
int maxZoom = zp.maxZoom();
int kindRank = zp.kindRank();
int populationRank = zp.populationRank();

var feat = features.point(this.name())
.setAttr("kind", kind)
Expand All @@ -418,6 +382,10 @@ public void processOverture(SourceFeature sf, FeatureCollector features) {
feat.setAttr("kind_detail", kindDetail);
}

if (sf.hasTag("wikidata")) {
feat.setAttr("wikidata", sf.getString("wikidata"));
}

int sortKey = getSortKey(minZoom, kindRank, population, name);
feat.setSortKey(sortKey);
feat.setAttr("sort_key", sortKey);
Expand All @@ -428,6 +396,36 @@ public void processOverture(SourceFeature sf, FeatureCollector features) {
feat.setBufferPixelOverrides(ZoomFunction.maxZoom(12, 64));
}

record ZoomsPops(int minZoom, int maxZoom, int kindRank, int populationRank) {}

private ZoomsPops getZoomsPops(Matcher.SourceFeatureWithComputedTags sf2, String kind, int population) {
var zoomMatches = zoomsIndex.getMatches(sf2);
int minZoom = getInteger(sf2, zoomMatches, "pm:minzoom", 99);
int maxZoom = getInteger(sf2, zoomMatches, "pm:maxzoom", 99);
int kindRank = getInteger(sf2, zoomMatches, "pm:kindRank", 99);

int populationRank = 0;
for (int i = 0; i < popBreaks.length; i++) {
if (population >= popBreaks[i]) {
populationRank = i + 1;
}
}

if (WIKIDATA_CONFIGS.containsKey(sf2.getString("wikidata"))) {
var wikidataConfig = WIKIDATA_CONFIGS.get(sf2.getString("wikidata"));
if (kind.equals("country") || kind.equals("region")) {
minZoom = wikidataConfig.minZoom();
maxZoom = wikidataConfig.maxZoom();
}
if (kind.equals("locality")) {
minZoom = wikidataConfig.minZoom();
populationRank = wikidataConfig.rankMax();
}
}

return new ZoomsPops(minZoom, maxZoom, kindRank, populationRank);
}

@Override
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
return items;
Expand Down
14 changes: 13 additions & 1 deletion tiles/src/main/java/com/protomaps/basemap/layers/Pois.java
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,13 @@ public void processOverture(SourceFeature sf, FeatureCollector features) {
if (kind.equals("pm:undefined"))
return;

// Drop low-confidence features. Below 0.65, features are dominated by uncertain data:
// real estate listings, auto repair, beauty salons, ATMs from low-quality sources.
double confidence = sf.getTag("confidence")instanceof Number n ? n.doubleValue() : 0.0;
if (confidence < 0.65) {
return;
}

// QRank may override minZoom entirely
String wikidata = sf.getString("wikidata");
long qrank = (wikidata != null) ? qrankDb.get(wikidata) : 0;
Expand All @@ -581,6 +588,10 @@ public void processOverture(SourceFeature sf, FeatureCollector features) {

String name = sf.getString("names.primary");

// Sort key: lower = higher rendering priority. Within the same minZoom bucket,
// higher confidence wins (subtract confidence*100 so 0.99 → -99, 0.65 → -65).
int sortKey = minZoom * 1000 - (int) (confidence * 100);

features.point(this.name())
// all POIs should receive their IDs at all zooms
// (there is no merging of POIs like with lines and polygons in other layers)
Expand All @@ -593,7 +604,8 @@ public void processOverture(SourceFeature sf, FeatureCollector features) {
.setAttr("min_zoom", minZoom + 1)
//
.setBufferPixels(8)
.setZoomRange(Math.min(minZoom, 15), 15);
.setZoomRange(Math.min(minZoom, 15), 15)
.setSortKey(sortKey);
}

@Override
Expand Down
35 changes: 35 additions & 0 deletions tiles/src/main/java/com/protomaps/basemap/layers/Roads.java
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,17 @@

)).index();

private static final MultiExpression.Index<Map<String, Object>> overtureAerowayKindsIndex =
MultiExpression.ofOrdered(List.of(
rule(use("pm:kind", "pm:undefined"), use("pm:kindDetail", "pm:undefined"), use("pm:highway", "pm:undefined")),
rule(with("class", "runway"), use("pm:kind", "aeroway"), use("pm:kindDetail", "runway"),
use("pm:highway", "aeroway")),
rule(with("class", "taxiway"), use("pm:kind", "aeroway"), use("pm:kindDetail", "taxiway"),
use("pm:highway", "aeroway")),
rule(with("class", "taxilane"), use("pm:kind", "aeroway"), use("pm:kindDetail", "taxiway"),
use("pm:highway", "aeroway"))
)).index();

// Protomaps kind/kind_detail to min_zoom mapping

private static final MultiExpression.Index<Map<String, Object>> highwayZoomsIndex = MultiExpression.ofOrdered(List.of(
Expand Down Expand Up @@ -504,7 +515,31 @@
}
}

public void processOverture(SourceFeature sf, FeatureCollector features) {

Check warning on line 518 in tiles/src/main/java/com/protomaps/basemap/layers/Roads.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

A "Brain Method" was detected. Refactor it to reduce at least one of the following metrics: LOC from 74 to 64, Complexity from 16 to 14, Nesting Level from 3 to 2, Number of Variables from 27 to 6.

See more on https://sonarcloud.io/project/issues?id=protomaps_basemaps&issues=AZzj_Ou29JS2QyFt9MGq&open=AZzj_Ou29JS2QyFt9MGq&pullRequest=579

Check failure on line 518 in tiles/src/main/java/com/protomaps/basemap/layers/Roads.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 24 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=protomaps_basemaps&issues=AZzj_Ou29JS2QyFt9MGp&open=AZzj_Ou29JS2QyFt9MGp&pullRequest=579
if ("base".equals(sf.getString("theme")) && "infrastructure".equals(sf.getString("type"))) {
if (!"airport".equals(sf.getString("subtype")))
return;

List<Map<String, Object>> kindMatches = overtureAerowayKindsIndex.getMatches(sf);
String kind = getString(sf, kindMatches, "pm:kind", "pm:undefined");
String kindDetail = getString(sf, kindMatches, "pm:kindDetail", "pm:undefined");
if ("pm:undefined".equals(kind))
return;

int minZoom = "runway".equals(kindDetail) ? 9 : 10;
String name = sf.getString("names.primary");

if (!sf.canBePolygon()) {
try {
LineString line = (LineString) sf.latLonGeometry();
emitOvertureFeature(features, sf, line, kind, kindDetail, name, "aeroway", minZoom,
new OvertureSegmentProperties());
} catch (GeometryException e) {
/* skip */ }
}
return;
}

// Filter by type field - Overture transportation theme
if (!"transportation".equals(sf.getString("theme"))) {
return;
Expand Down
Loading
Loading