Skip to content
Snippets Groups Projects
Commit 54686ed0 authored by Chengzhi Rao's avatar Chengzhi Rao :speech_balloon: Committed by Chengzhi Rao
Browse files

Change Rule attributes, add tests

parent 9fd18220
No related branches found
No related tags found
No related merge requests found
Pipeline #83744 passed
Showing
with 304 additions and 165 deletions
from rulelib import AbstractRule
class RelationIDRule(AbstractRule):
def __call__(self, relations, *args, **kwargs):
"""
Determine the building relation OSM ID.
Args:
relations:
List of the attributes of all relations that the building is member of.
Returns:
OSM ID of the first relation that the building is member of.
"""
for relation in relations:
osm_id = relation.get("osm_id", None)
if osm_id is not None:
return {"relation_id": osm_id}
return {"relation_id": None}
File moved
...@@ -16,27 +16,44 @@ ...@@ -16,27 +16,44 @@
# You should have received a copy of the GNU Affero General Public License # 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/. # along with this program. If not, see http://www.gnu.org/licenses/.
from rulelib import AbstractRule
class HeightAndFloorspaceRule:
def __call__(self, tags, area, ghsl_characteristics_type=None, *args, **kwargs):
class HeightAndFloorspaceRule(AbstractRule):
def __call__(
self,
tags: dict[str, any],
area: float,
ghsl_characteristics_type: int | None = None,
attributes: dict[str, any] | None = None,
*args,
**kwargs,
) -> dict[str, dict]:
""" """
Find the `building:levels` tag in the attributes of the building or one of the building 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 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. building, based on the footprint size of the building and the number of stories.
Args: Args:
tags (dict): tags (dict[str, any]):
Building tags, such as the building levels or building type. Building tags, such as the building levels or building type.
area (float): area (float):
Footprint size of the building. Footprint size of the building.
ghsl_characteristics_type (int): ghsl_characteristics_type (int | None, optional, default: None):
The GHSL characteristics type with the largest overlap with the building. The GHSL characteristics type with the largest overlap with the building.
attributes (dict[str, any], optional, default: None):
A dictionary of building attributes.
Returns: Returns:
A dictionary with the number of stories and the floorspace of the building. dict[str, dict]:
A dictionary with a single key `attributes` that stores the number of stories
and the floorspace of the building. If these values are not found, the provided
`attributes` dictionary is returned, which may remain unchanged or be empty.
""" """
if attributes is None:
attributes = {}
gem_taxonomy_height = [] gem_taxonomy_height = []
# The number of stories ideally comes from the `building:levels` tags in OpenStreetMap. # The number of stories ideally comes from the `building:levels` tags in OpenStreetMap.
...@@ -54,20 +71,24 @@ class HeightAndFloorspaceRule: ...@@ -54,20 +71,24 @@ class HeightAndFloorspaceRule:
# If any of the number-of-stories and height tags are found in OpenStreetMap, they are # If any of the number-of-stories and height tags are found in OpenStreetMap, they are
# returned. # returned.
if len(gem_taxonomy_height) > 0: if len(gem_taxonomy_height) > 0:
return { attributes.update({"height": "+".join(gem_taxonomy_height)})
"height": "+".join(gem_taxonomy_height), if floorspace and floorspace > 0:
"floorspace": floorspace, attributes.update({"floorspace": floorspace})
}
return {"attributes": attributes}
# If no information can be retrieved from OSM, we use the height estimation from GHSL # If no information can be retrieved from OSM, we use the height estimation from GHSL
# and do not estimate any floorspace. # and do not estimate any floorspace.
ghsl_height = self.get_stories_from_ghsl(ghsl_characteristics_type) ghsl_height = self.get_stories_from_ghsl(ghsl_characteristics_type)
if ghsl_height: if ghsl_height:
return {"height": ghsl_height, "floorspace": None} attributes["height"] = ghsl_height
return {"attributes": attributes}
return {"height": None, "floorspace": None} return {"attributes": attributes}
def get_stories_and_floorspace_from_osm(self, tags: dict, area: float): def get_stories_and_floorspace_from_osm(
self, tags: dict[str, any], area: float
) -> tuple[str | None, float | None]:
""" """
Get the number of stories and the floorspace, based on the tags `building:levels`, 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 `roof:levels`, `building:levels:underground` and `building:min_level` and the footprint
...@@ -81,7 +102,10 @@ class HeightAndFloorspaceRule: ...@@ -81,7 +102,10 @@ class HeightAndFloorspaceRule:
Footprint size of the building. Footprint size of the building.
Returns: Returns:
A string formatted according to the `height` tag in the GEM Taxonomy. str (tuple[str | None, float | None]):
A string formatted according to the `height` tag in the GEM Taxonomy, and the
floorspace based on the stories and footprint size. If the input
arguments are not sufficient, return None.
""" """
from math import ceil from math import ceil
...@@ -137,18 +161,20 @@ class HeightAndFloorspaceRule: ...@@ -137,18 +161,20 @@ class HeightAndFloorspaceRule:
return "+".join(story_tags), floorspace return "+".join(story_tags), floorspace
def get_height_from_osm(self, tags: dict): def get_height_from_osm(self, tags: dict[str, any]) -> str | None:
""" """
Get the height of a building, based on the `height` and `min_height` tags. The Get the height of a building, based on the `height` and `min_height` tags. The
information about parsing the tags comes from information about parsing the tags comes from
https://wiki.openstreetmap.org/wiki/Key:building:levels. https://wiki.openstreetmap.org/wiki/Key:building:levels.
Args: Args:
tags (dict): tags (dict[str, any]):
Building tags, such as the building levels or building type. Building tags, such as the building levels or building type.
Returns: Returns:
A string formatted according to the `height` tag in the GEM Taxonomy. str | None:
A string formatted according to the `height` tag in the GEM Taxonomy or
None if no valid height can be determined.
""" """
# Parse the height tag from OpenStreetMap. # Parse the height tag from OpenStreetMap.
...@@ -167,7 +193,7 @@ class HeightAndFloorspaceRule: ...@@ -167,7 +193,7 @@ class HeightAndFloorspaceRule:
return f"HHT:{height:.2f}" return f"HHT:{height:.2f}"
def get_stories_from_ghsl(self, ghsl_characteristics_type: int): def get_stories_from_ghsl(self, ghsl_characteristics_type: int) -> str | None:
""" """
Get the GEM Taxonomy `height` attribute, based on the type in the GHSL Get the GEM Taxonomy `height` attribute, based on the type in the GHSL
characteristics layer that has the largest overlap with the building. characteristics layer that has the largest overlap with the building.
...@@ -177,36 +203,39 @@ class HeightAndFloorspaceRule: ...@@ -177,36 +203,39 @@ class HeightAndFloorspaceRule:
The GHSL characteristics type with the largest overlap with the building. The GHSL characteristics type with the largest overlap with the building.
Returns: Returns:
A string formatted according to the `height` attribute in the GEM Taxonomy. str | None:
A string formatted according to the `height` attribute in the GEM Taxonomy
or None if no valid height tag can be determined.
""" """
ghsl_type_map = { ghsl_type_map = {
11: "HBET:1-2", # res_3 11: "HBET:1-2", # Residential 0m-3m
12: "HBET:1-2", # res_3_6 12: "HBET:1-2", # Residential 3m-6m
13: "HBET:1-5", # res_6_15 13: "HBET:1-5", # Residential 6m-15m
14: None, # res_15_30 14: None, # Residential 15m-30m
15: None, # res_30 15: None, # Residential 30m+
21: "HBET:1-2", # nonres_3 21: "HBET:1-2", # Non-residential 0m-3m
22: "HBET:1-2", # nonres_3_6 22: "HBET:1-2", # Non-residential 3m-6m
23: "HBET:1-5", # nonres_6_15 23: "HBET:1-5", # Non-residential 6m-15m
24: None, # nonres_15_30 24: None, # Non-residential 15m-30m
25: None, # nonres_30 25: None, # Non-residential 30m+
} }
return ghsl_type_map.get(ghsl_characteristics_type, None) return ghsl_type_map.get(ghsl_characteristics_type, None)
def tag_to_float(self, tags: dict, tag_key: str): def tag_to_float(self, tags: dict[str, any], tag_key: str) -> float | None:
""" """
Try to parse a tag as a floating point value. If the value cannot be parsed, return Try to parse a tag as a floating point value. If the value cannot be parsed, return
None. None.
Args: Args:
tags (dict): tags (dict[str, any]):
Building tags, such as the building levels or building type. Building tags, such as the building levels or building type.
tag_key (str): tag_key (str):
Name of the tag that should be converted. Name of the tag that should be converted.
Returns: Returns:
Tag value converted to a float. float | None:
Tag value converted to a float or None if conversion fails.
""" """
# Try to get the tag value. # Try to get the tag value.
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
</input> </input>
<function filepath="height_and_floorspace.py"/> <function filepath="height_and_floorspace.py"/>
<output> <output>
<param type="str">height</param> <param type="dict">attributes</param>
<param type="float">floorspace</param>
</output> </output>
</rule> </rule>
...@@ -7,6 +7,6 @@ ...@@ -7,6 +7,6 @@
</input> </input>
<function filepath="occupancy.py"/> <function filepath="occupancy.py"/>
<output> <output>
<param type="float">occupancy</param> <param type="dict">attributes</param>
</output> </output>
</rule> </rule>
File moved
#!/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/.
from rulelib import AbstractRule
class RelationIDRule(AbstractRule):
def __call__(
self, relations: list[dict], attributes: dict[str, dict] | None = None, *args, **kwargs
) -> dict[str, dict]:
"""
Determine the building relation OSM ID.
Given a list of relations that the building is a member of, this function will check
for the first relation that contains a valid OSM ID.
Args:
relations (list[dict]):
List of the attributes of all relations that the building is member of.
attributes (dict[str, dict], optional, default: None):
A dictionary of building attributes.
Returns:
dict[str, dict]:
A dictionary with a single key `attributes` that maps to another dictionary
containing the `relation_id`, which is the OSM ID of the first relation that
the building is member of as value. If no OSM ID is found, it returns the
provided `attributes` dictionary, which may remain unchanged, or an empty
dictionary.
"""
if attributes is None:
attributes = {}
for relation in relations:
osm_id = relation.get("osm_id", None)
attributes["relation_id"] = osm_id
if osm_id is not None:
return {"attributes": attributes}
return {"attributes": attributes}
...@@ -5,6 +5,6 @@ ...@@ -5,6 +5,6 @@
</input> </input>
<function filepath="relation_id.py"/> <function filepath="relation_id.py"/>
<output> <output>
<param type="int">relation_id</param> <param type="dict">attributes</param>
</output> </output>
</rule> </rule>
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