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
27 changes: 25 additions & 2 deletions tiles/src/main/java/com/protomaps/basemap/layers/Buildings.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,29 @@ public record Height(Double height, Double min_height) {}

static final Pattern pattern = Pattern.compile("^\\d+(\\.\\d)?$");

static final Pattern COMMA_DECIMAL_PATTERN = Pattern.compile(",(?=\\d{1,2}(\\s*[a-zA-Z]*)?$)");

/**
* Sanitizes height values by fixing common OSM tagging mistakes.
* Specifically handles commas used as decimal separators (e.g., "89,10" -> "89.10").
*
* @param value the raw height value from OSM
* @return sanitized value
*/
static String sanitizeHeightValue(String value) {
if (value == null || value.isEmpty()) {
return value;
}

// Replace comma with period if followed by 1 or 2 digits (optional unit suffix)
// This handles cases like: "89,1", "89,10", "89,1 m", "89,1m"
// Pattern explanation:
// - Match a comma followed by 1-2 digits
// - Optionally followed by whitespace and/or unit characters (like 'm')
// - Leave other commas unchanged (e.g., thousands separators like "1,000")
return COMMA_DECIMAL_PATTERN.matcher(value).replaceFirst(".");
}

static Double parseWellFormedDouble(String s) {
if (pattern.matcher(s).matches()) {
return parseDoubleOrNull(s);
Expand All @@ -44,15 +67,15 @@ static Double parseWellFormedDouble(String s) {
}

static Height parseHeight(String osmHeight, String osmLevels, String osmMinHeight) {
var height = parseDoubleOrNull(osmHeight);
var height = parseDoubleOrNull(sanitizeHeightValue(osmHeight));
if (height == null) {
Double levels = parseDoubleOrNull(osmLevels);
if (levels != null) {
height = Math.max(levels, 1) * 3 + 2;
}
}

return new Height(height, parseDoubleOrNull(osmMinHeight));
return new Height(height, parseDoubleOrNull(sanitizeHeightValue(osmMinHeight)));
}

static int quantizeVal(double val, int step) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ void parseBuildingHeights() {
assertEquals(1, result.min_height());
}

@Test
void parseBuildingHeightsWithCommaDecimalSeparator() {
var result = Buildings.parseHeight("89,10", null, "3,5");
assertEquals(89.10, result.height());
assertEquals(3.5, result.min_height());

result = Buildings.parseHeight("89,1 m", null, "3,5 m");
assertEquals(89.1, result.height());
assertEquals(3.5, result.min_height());

result = Buildings.parseHeight("89,100 m", null, "3,500");
assertEquals(89100, result.height());
assertEquals(3500, result.min_height());

result = Buildings.parseHeight("invalid", null, " ");
assertNull(result.height());
assertNull(result.min_height());
}

@Test
void parseBuildingHeightsFromLevels() {
var result = Buildings.parseHeight(null, "3", null);
Expand All @@ -59,6 +78,20 @@ void parseBuildingHeightsFromLevels() {
assertEquals(5, result.height());
}

@Test
void sanitizeHeightValue() {
assertEquals("89.1", Buildings.sanitizeHeightValue("89,1"));
assertEquals("89.10", Buildings.sanitizeHeightValue("89,10"));
assertEquals("89.1 m", Buildings.sanitizeHeightValue("89,1 m"));
assertEquals("89.1 meters", Buildings.sanitizeHeightValue("89,1 meters"));
assertEquals("89.1m", Buildings.sanitizeHeightValue("89,1m"));
assertEquals("89.10", Buildings.sanitizeHeightValue("89.10"));
assertNull(Buildings.sanitizeHeightValue(null));
assertEquals("", Buildings.sanitizeHeightValue(""));
assertEquals("1,234", Buildings.sanitizeHeightValue("1,234"));
assertEquals("3,500", Buildings.sanitizeHeightValue("3,500"));
assertEquals("89,100", Buildings.sanitizeHeightValue("89,100"));
}

@Test
void railwayTagIsBuilding() {
Expand Down