""" City module SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ from __future__ import annotations import sys import pickle from typing import List, Union import pyproj from pyproj import Transformer from city_model_structure.building import Building from city_model_structure.city_object import CityObject from helpers.geometry_helper import GeometryHelper import math class City: """ City class """ def __init__(self, lower_corner, upper_corner, srs_name, buildings=None): self._name = None self._lower_corner = lower_corner self._upper_corner = upper_corner self._buildings = buildings self._srs_name = srs_name self._geometry = GeometryHelper() # todo: right now extracted at city level, in the future should be extracted also at building level if exist self._location = None self._country_code = None self._latitude = None self._longitude = None self._time_zone = None @property def _get_location(self): if self._location is None: gps = pyproj.CRS('EPSG:4326') # LatLon with WGS84 datum used by GPS units and Google Earth try: input_reference = pyproj.CRS(self.srs_name) # Projected coordinate system from input data except pyproj.exceptions.CRSError: sys.stderr.write('Invalid projection reference system, please check the input data. ' '(e.g. in CityGML files: srs_name)\n') sys.exit() transformer = Transformer.from_crs(input_reference, gps) coordinates = transformer.transform(self.lower_corner[0], self.lower_corner[1]) self._location = GeometryHelper.get_location(coordinates[0], coordinates[1]) return self._location @property def country_code(self): """ City country code :return: str """ return self._get_location[0] @property def name(self): """ City name :return: str """ return self._get_location[1] @property def city_objects(self) -> Union[List[CityObject], None]: """ City objects belonging to the city :return: None or [CityObject] """ return self.buildings @property def buildings(self) -> Union[List[Building], None]: """ Buildings belonging to the city :return: None or [Building] """ return self._buildings @property def trees(self) -> NotImplementedError: """ Trees belonging to the city :return: NotImplementedError """ raise NotImplementedError @property def bixi_features(self) -> NotImplementedError: """ Bixi features belonging to the city :return: NotImplementedError """ raise NotImplementedError @property def composting_plants(self) -> NotImplementedError: """ Composting plants belonging to the city :return: NotImplementedError """ raise NotImplementedError @property def lower_corner(self): """ City lower corner :return: [x,y,z] """ return self._lower_corner @property def upper_corner(self): """ City upper corner :return: [x,y,z] """ return self._upper_corner def city_object(self, name) -> Union[CityObject, None]: """ Retrieve the city CityObject with the given name :param name:str :return: None or CityObject """ for city_object in self.buildings: if city_object.name == name: return city_object return None def add_city_object(self, new_city_object): """ Add a CityObject to the city :param new_city_object:CityObject :return: None """ if new_city_object.type != 'building': raise NotImplementedError(new_city_object.type) if self._buildings is None: self._buildings = [] self._buildings.append(new_city_object) @property def srs_name(self): """ srs name :return: str """ return self._srs_name @name.setter def name(self, value): """ Set the city name :param value:str :return: None """ self._name = value @staticmethod def load(city_filename) -> City: """ Load a city saved with city.save(city_filename) :param city_filename: city filename :return: City """ with open(city_filename, 'rb') as f: return pickle.load(f) def save(self, city_filename): """ Save a city into the given filename :param city_filename: destination city filename :return: """ with open(city_filename, 'wb') as f: pickle.dump(self, f) def region(self, center, radius): """ Save a city into the given filename :param center: specific point in space [x, y, z] :param radius: distance to center of the sphere selected in meters :return: selected_region_city """ selected_region_lower_corner = [center[0] - radius, center[1] - radius, center[2] - radius] selected_region_upper_corner = [center[0] + radius, center[1] + radius, center[2] + radius] selected_region_city = City(selected_region_lower_corner, selected_region_upper_corner, srs_name=self.srs_name) for city_object in self.city_objects: location = city_object.centroid distance = math.sqrt(math.pow(location[0]-center[0], 2) + math.pow(location[1]-center[1], 2) + math.pow(location[2]-center[2], 2)) if distance < radius: selected_region_city.add_city_object(city_object) return selected_region_city @property def latitude(self): """ city latitude in degrees :return: real """ return self._latitude