Skip to content
Snippets Groups Projects
year_of_construction.py 4.86 KiB
Newer Older
#!/usr/bin/env python3

# Copyright (c) 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 YearOfConstructionRule(AbstractRule):
    def __call__(
        self,
        tags: dict,
        relations: list,
        year_of_construction_cadaster: int | None = None,
        *args,
        **kwargs
    ) -> dict[str, int | None]:
        """
        Obtain the year of construction of a building from any available source, presently only
        OSM and cadaster data. In OSM, the time of construction is stored in the `start_date`
        tag of which only an explicit four-digit year with or without a leading `~` (signifying
        an approximation) are considered. Priority is given to exact years of construction from
        OSM, followed by those from the cadaster data and finally approximate dates from OSM.

        Args:
            tags (dict):
                Building tags, such as the building levels or building type.
            relations (list):
                List of the attributes of all relations that the building is member of.
            year_of_construction_cadaster (int, optional, default: None):
                Year of construction taken from cadaster data.

        Returns:
            dict[str, int | None]:
                A dictionary with the construction year of the building as an integer or None.
        """

        all_building_tags = [tags] + [building["tags"] for building in relations]

        year_of_construction_string_approximate = None

        for tags in all_building_tags:
            year_of_construction_string = self.get_start_date(tags)
            if year_of_construction_string is None:
                continue
            else:
                # Years with leading `~` are approximate dates, these values are set
                # last in the priority list if other sources are available.
                if (
                    year_of_construction_string[0] == "~"
                    and year_of_construction_string_approximate is None
                ):
                    year_of_construction_string_approximate = year_of_construction_string[1:5]
                    continue
                # Years from OSM without a leading `~` are at the top of the priority list,
                # and returned as soon as one is found.
                else:
                    year_of_construction_string = year_of_construction_string[:4]
                try:
                    year_of_construction = int(year_of_construction_string)
                    return {"year_of_construction": year_of_construction}
                except ValueError:
                    continue

        # If no unambiguous year is found from the OSM tags, the cadaster year of construction
        # is returned, second in the priority list.
        if year_of_construction_cadaster is not None:
            return {"year_of_construction": year_of_construction_cadaster}
        # If the there is no construction year from the cadaster data, the approximate year is
        # returned.
        elif year_of_construction_string_approximate is not None:
            try:
                year_of_construction = int(year_of_construction_string_approximate)
                return {"year_of_construction": year_of_construction}
            except ValueError:
                return {"year_of_construction": None}
        # If no source has a valid year of construction, None is returned.
        else:
            return {"year_of_construction": None}

    @staticmethod
    def get_start_date(tags: dict) -> str | None:
        """
        Get the building year of construction based on the year in the `start_date` tag from
        the OSM tags dictionary.

        Args:
            tags (dict):
                Dictionary with building tags from OSM, which may include `start_date`, `name`,
                `type`, etc.

        Returns:
            str | None:
                String value of the tag `start_date` which represents the date in which a
                building was completed, otherwise None. The date format may vary, as there are
                many variations within the OSM `start_date` tag, for this rule, only the year is
                relevant.
        """

        return tags.get("start_date", None)