summer_course_2024/city_model_structure/city.py

289 lines
7.4 KiB
Python

"""
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
from helpers.location import Location
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._climate_reference_city = None
self._climate_file = None
self._latitude = None
self._longitude = None
self._time_zone = None
self._generic_city_object = None
def _get_location(self) -> Location:
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().country
@property
def name(self):
"""
City name
:return: str
"""
return self._get_location().city
@property
def climate_reference_city(self):
return self._climate_reference_city
@climate_reference_city.setter
def climate_reference_city(self, value):
self._climate_reference_city = value
@property
def climate_file(self):
return self._climate_file
@climate_file.setter
def climate_file(self, value):
self._climate_file = value
@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':
if self._buildings is None:
self._buildings = []
self._buildings.append(new_city_object)
else:
if self._generic_city_object is None:
self._generic_city_object = []
self._generic_city_object.append(new_city_object)
def remove_city_object(self, city_object):
"""
Remove a CityObject from the city
:param city_object:CityObject
:return: None
"""
if city_object.type != 'building':
raise NotImplementedError(city_object.type)
if self._buildings is None or self._buildings == []:
sys.stderr.write('Warning: impossible to remove city_object, the city is empty\n')
else:
if city_object in self._buildings:
self._buildings.remove(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)
selected_region_city.climate_file = self.climate_file
# selected_region_city.climate_reference_city = self.climate_reference_city
for city_object in self.city_objects:
location = city_object.centroid
if location is not None:
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
@latitude.setter
def latitude(self, value):
"""
city latitude in degrees
:parameter value: real
"""
self._latitude = value
@property
def longitude(self):
"""
city longitude in degrees
:return: real
"""
return self._longitude
@longitude.setter
def longitude(self, value):
"""
city longitude in degrees
:parameter value: real
"""
self._longitude = value
@property
def time_zone(self):
"""
city time_zone
:return: real
"""
return self._time_zone
@time_zone.setter
def time_zone(self, value):
"""
city time_zone
:parameter value: real
"""
self._time_zone = value