Skip to content
Snippets Groups Projects

Resolve "Change the processing of the `stories` rule to a `height` GEM Taxonomy tag"

All threads resolved!
Compare and
7 files
+ 381
75
Compare changes
  • Side-by-side
  • Inline
Files
7
#!/usr/bin/env python3
# Copyright (c) 2023-2024:
# Helmholtz-Zentrum Potsdam Deutsches GeoForschungsZentrum GFZ
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see http://www.gnu.org/licenses/.
class HeightAndFloorspaceRule:
def __call__(self, tags, area, *args, **kwargs):
"""
Find the `building:levels` tag in the attributes of the building or one of the building
relations and save this as the number of stories. Calculate the floorspace of the
building, based on the footprint size of the building and the number of stories.
Args:
tags (dict):
Building tags, such as the building levels or building type.
area (float):
Footprint size of the building.
Returns:
A dictionary with the number of stories and the floorspace of the building.
"""
gem_taxonomy_height = []
# The number of stories ideally comes from the `building:levels` tags in OpenStreetMap.
# 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
if stories:
gem_taxonomy_height.append(stories)
# If any of the number-of-stories and height tags are found in OpenStreetMap, they are
# returned.
if len(gem_taxonomy_height) > 0:
return {
"height": "+".join(gem_taxonomy_height),
"floorspace": floorspace,
}
return {"height": None, "floorspace": None}
def get_stories_and_floorspace_from_osm(self, tags: dict, area: float):
"""
Get the number of stories and the floorspace, based on the tags `building:levels`,
`roof:levels`, `building:levels:underground` and `building:min_level` and the footprint
area of the building. The information about parsing the tags comes from
https://wiki.openstreetmap.org/wiki/Key:building:levels.
Args:
tags (dict):
Building tags, such as the building levels or building type.
area (float):
Footprint size of the building.
Returns:
A string formatted according to the `height` tag in the GEM Taxonomy.
"""
from math import ceil
# Parse the story tags from OpenStreetMap.
main_stories = self.tag_to_float(tags, "building:levels")
roof_stories = self.tag_to_float(tags, "roof:levels")
underground_stories = self.tag_to_float(tags, "building:levels:underground")
min_stories = self.tag_to_float(tags, "building:min_level")
# Parse the number of main stories and roof stories.
stories = 0
floorspace = 0
if main_stories or roof_stories:
if main_stories:
stories += main_stories
floorspace += main_stories * area
if roof_stories:
stories += roof_stories
# Take only 50% of the floorspace for roof stories.
floorspace += roof_stories * area * 0.5
if min_stories:
stories -= min_stories
floorspace -= min_stories * area
# Ceil the number of stories.
stories = ceil(stories)
# Parse the underground story tag.
if underground_stories:
underground_stories = ceil(underground_stories)
floorspace += underground_stories * area
else:
underground_stories = 0
# Take a factor of 90% for the floorspace.
floorspace *= 0.9
# Check if anything is wrong with the tags.
if stories < 0 or underground_stories < 0:
return None, None
elif stories == 0 and underground_stories == 0:
return None, None
elif stories + underground_stories > 175:
return None, None
# Create the number-of-stories tag of the height attribute.
story_tags = []
if stories != 0:
story_tags.append(f"H:{stories}")
if underground_stories != 0:
story_tags.append(f"HBEX:{underground_stories}")
return "+".join(story_tags), floorspace
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
None.
Args:
tags (dict):
Building tags, such as the building levels or building type.
tag_key (str):
Name of the tag that should be converted.
Returns:
Tag value converted to a float.
"""
# Try to get the tag value.
tag_value = tags.get(tag_key, None)
# If the tag does not exist, return None.
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.
try:
return float(tag_value)
except ValueError:
return None
Loading