Skip to content
Snippets Groups Projects
Commit 932db180 authored by Laurens Oostwegel's avatar Laurens Oostwegel
Browse files

Add height from the OpenStreetMap height-related tags

parent c3875d3c
No related branches found
No related tags found
No related merge requests found
Pipeline #75331 passed
......@@ -40,16 +40,14 @@ class HeightAndFloorspaceRule:
# If they are present, the floorspace of a building can be calculated. OpenStreetMap
# buildings can also contain a `height` tag but this tag is only parsed without the
# floorspace being estimated.
try:
stories, floorspace = self.get_stories_and_floorspace_from_osm(tags, area)
# OSM can have unexpected data types and unexpected content in their tagging scheme,
# therefore we do not raise an exception in case of a ValueError, but instead ignore
# the values.
except ValueError:
stories, floorspace = None, None
stories, floorspace = self.get_stories_and_floorspace_from_osm(tags, area)
if stories:
gem_taxonomy_height.append(stories)
height = self.get_height_from_osm(tags)
if height:
gem_taxonomy_height.append(height)
# If any of the number-of-stories and height tags are found in OpenStreetMap, they are
# returned.
if len(gem_taxonomy_height) > 0:
......@@ -130,6 +128,34 @@ class HeightAndFloorspaceRule:
return "+".join(story_tags), floorspace
def get_height_from_osm(self, tags: dict):
"""
Get the heightb of a building, based on the `height` and `min_height` tags.
Args:
tags (dict):
Building tags, such as the building levels or building type.
Returns:
A string formatted according to the `height` tag in the GEM Taxonomy.
"""
# Parse the height tag from OpenStreetMap.
height = self.tag_to_float(tags, "height")
if not height:
return None
# Check if there is a `min_height` tag.
min_height = self.tag_to_float(tags, "min_height")
if min_height:
height -= min_height
# Check if the height is higher than 0.
if height < 0:
return None
return f"HHT:{height:.2f}"
def tag_to_float(self, tags: dict, tag_key: str):
"""
Try to parse a tag as a floating point value. If the value cannot be parsed, return
......@@ -152,8 +178,9 @@ class HeightAndFloorspaceRule:
if tag_value is None or tag_value == "":
return None
# If the tag does exist, try to convert it to float. If that raises a `ValueError`,
# return None.
# OSM can have unexpected data types and unexpected content in their tagging scheme,
# therefore we do not raise an exception in case of a ValueError, but instead ignore
# the values.
try:
return float(tag_value)
except ValueError:
......
......@@ -29,26 +29,61 @@ def test_height_and_floorspace_rule(height_and_floorspace_rule):
OSM and GHSL.
"""
building_information = {
"tags": {
"building:levels": 5,
"roof:levels": 2,
"building:min_level": 1,
"building:levels:underground": 0,
},
"area": 300.0,
}
result = height_and_floorspace_rule(**building_information)
message = (
f"The `height` attribute is not correct. "
f"Expected `H:6` but estimated {result['height']}"
)
assert result["height"] == "H:6", message
message = (
f"The `floorspace` attribute is not correct. "
f"Expected `1350` but estimated {result['floorspace']}"
)
assert result["floorspace"] == pytest.approx(1350.0), message
test_list = [
# Test values related to building stories
[
{
"tags": {
"building:levels": "5",
"roof:levels": "2",
"building:min_level": "1",
"building:levels:underground": "0",
},
"area": 300.0,
},
"H:6",
1350.0,
],
# Test values related to building height
[
{
"tags": {
"height": "5",
"min_height": "2",
},
"area": 300.0,
},
"HHT:3.00",
None,
],
# Test values related to building levels and height
[
{
"tags": {
"building:levels": "5",
"building:levels:underground": "1",
"height": "5",
"min_height": "2",
},
"area": 300.0,
},
"H:5+HBEX:1+HHT:3.00",
1620.0,
],
]
for building_information, correct_height, correct_floorspace in test_list:
result = height_and_floorspace_rule(**building_information)
message = (
f"The `height` attribute is not correct. "
f"Expected `{correct_height}` but estimated `{result['height']}`."
)
assert result["height"] == correct_height, message
message = (
f"The `floorspace` attribute is not correct. "
f"Expected `{correct_floorspace}` but estimated `{result['floorspace']}`"
)
assert result["floorspace"] == pytest.approx(correct_floorspace), message
def test_get_stories_and_floorspace_from_osm(height_and_floorspace_rule):
......@@ -59,17 +94,21 @@ def test_get_stories_and_floorspace_from_osm(height_and_floorspace_rule):
rule_function = height_and_floorspace_rule.function.get_stories_and_floorspace_from_osm
test_list = [
# Check with only building levels.
[{"tags": {"building:levels": 5}, "area": 100.0}, "H:5", 450.0],
[{"tags": {"building:levels": "5"}, "area": 100.0}, "H:5", 450.0],
# Check with only roof levels.
[{"tags": {"roof:levels": 5}, "area": 100.0}, "H:5", 225.0],
[{"tags": {"roof:levels": "5"}, "area": 100.0}, "H:5", 225.0],
# Check with only levels underground.
[{"tags": {"building:levels:underground": 5}, "area": 100.0}, "HBEX:5", 450.0],
[{"tags": {"building:levels:underground": "5"}, "area": 100.0}, "HBEX:5", 450.0],
# Check with a wrong type as input.
[{"tags": {"building:levels": "no"}, "area": 100.0}, None, None],
# Check with a negative value.
[{"tags": {"building:levels:underground": -1}, "area": 100.0}, None, None],
[{"tags": {"building:levels:underground": "-1"}, "area": 100.0}, None, None],
# Check with a `building:min_level` value higher than the `building_levels` value.
[{"tags": {"building:levels": 5, "building:min_level": 6}, "area": 100.0}, None, None],
[
{"tags": {"building:levels": "5", "building:min_level": "6"}, "area": 100.0},
None,
None,
],
# Check without any tags.
[{"tags": {}, "area": 100.0}, None, None],
# Check with all tags.
......@@ -106,6 +145,43 @@ def test_get_stories_and_floorspace_from_osm(height_and_floorspace_rule):
assert floorspace == correct_floorspace, message
def test_get_height_from_osm(height_and_floorspace_rule):
"""
Test the function `tag_to_float` of the `HeightAndFloorspaceRule.
"""
rule_function = height_and_floorspace_rule.function.get_height_from_osm
test_list = [
# Check a valid height.
[{"tags": {"height": "8"}}, "HHT:8.00"],
# Check a building with a floating point height value.
[{"tags": {"height": "8.2"}}, "HHT:8.20"],
# Check a building that is above ground.
[{"tags": {"height": "8", "min_height": "4"}}, "HHT:4.00"],
# Check a building with an invalid `min_height` attribute.
[{"tags": {"height": "8", "min_height": "no"}}, "HHT:8.00"],
# Check a building with a `min_height` higher than the `height` attribute.
[{"tags": {"height": "4", "min_height": "5"}}, None],
# Check a building with a negative height.
[{"tags": {"height": "-4"}}, None],
# Check a building with an invalid `height` attribute.
[{"tags": {"height": "no", "min_height": "5"}}, None],
# Check a building without tags.
[{"tags": {}}, None],
]
for input_values, correct_height in test_list:
height = rule_function(**input_values)
# Check the height attribute
message = (
f"The `height` attribute is not correct. Expected `{correct_height}` but estimated "
f"`{height}` using {input_values} as input."
)
assert height == correct_height, message
def test_tag_to_float(height_and_floorspace_rule):
"""
Test the function `tag_to_float` of the `HeightAndFloorspaceRule.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment