Refactoring building creation and bringing code up to date

This commit is contained in:
Guille Gutierrez 2022-11-23 10:20:33 -05:00
parent e9b50beea8
commit 1e24e43b64
21 changed files with 52396 additions and 106 deletions

View File

@ -20,8 +20,8 @@ class Building(CityObject):
""" """
Building(CityObject) class Building(CityObject) class
""" """
def __init__(self, name, lod, surfaces, year_of_construction, function, city_lower_corner, terrains=None): def __init__(self, name, lod, surfaces, year_of_construction, function, terrains=None):
super().__init__(name, lod, surfaces, city_lower_corner) super().__init__(name, lod, surfaces)
self._households = None self._households = None
self._basement_heated = None self._basement_heated = None
self._attic_heated = None self._attic_heated = None

View File

@ -17,8 +17,8 @@ class BusSystem(CityObject):
""" """
BusSystem(CityObject) class BusSystem(CityObject) class
""" """
def __init__(self, name, lod, surfaces, city_lower_corner): def __init__(self, name, lod, surfaces):
super().__init__(name, lod, surfaces, city_lower_corner) super().__init__(name, lod, surfaces)
self._bus_routes = None self._bus_routes = None
self._bus_network = None self._bus_network = None
self._buses = None self._buses = None

View File

@ -104,7 +104,9 @@ class City:
Get city name Get city name
:return: str :return: str
""" """
if self._name is None:
return self._get_location().city return self._get_location().city
return self._name
@property @property
def climate_reference_city(self) -> Union[None, str]: def climate_reference_city(self) -> Union[None, str]:

View File

@ -18,11 +18,10 @@ class CityObject:
""" """
class CityObject class CityObject
""" """
def __init__(self, name, lod, surfaces, city_lower_corner): def __init__(self, name, lod, surfaces):
self._name = name self._name = name
self._lod = lod self._lod = lod
self._surfaces = surfaces self._surfaces = surfaces
self._city_lower_corner = city_lower_corner
self._type = None self._type = None
self._city_object_lower_corner = None self._city_object_lower_corner = None
self._detailed_polyhedron = None self._detailed_polyhedron = None
@ -236,3 +235,5 @@ class CityObject:
:param value: [Sensor] :param value: [Sensor]
""" """
self._sensors = value self._sensors = value

View File

@ -21,7 +21,7 @@ class CityObjectsCluster(ABC, CityObject):
self._city_objects = city_objects self._city_objects = city_objects
self._sensors = [] self._sensors = []
self._lod = '' self._lod = ''
super().__init__(name, self._lod, None, None) super().__init__(name, self._lod, None)
@property @property
def name(self): def name(self):

View File

@ -16,8 +16,8 @@ class EnergySystem(CityObject):
EnergySystem(CityObject) class EnergySystem(CityObject) class
""" """
def __init__(self, name, lod, surfaces, city_lower_corner): def __init__(self, name, lod, surfaces):
super().__init__(name, lod, surfaces, city_lower_corner) super().__init__(name, lod, surfaces)
self._air_source_hp = None self._air_source_hp = None
self._water_to_water_hp = None self._water_to_water_hp = None
self._type = 'energy_system' self._type = 'energy_system'

View File

@ -12,7 +12,7 @@ class SubwayEntrance(CityObject):
SubwayEntrance(CityObject) class SubwayEntrance(CityObject) class
""" """
def __init__(self, name, latitude, longitude): def __init__(self, name, latitude, longitude):
super().__init__(0, [], name, []) super().__init__(name, 0, [])
self._name = name self._name = name
self._latitude = latitude self._latitude = latitude
self._longitude = longitude self._longitude = longitude

View File

@ -74,7 +74,7 @@ class UsPhysicsParameters(NrelPhysicsInterface):
if (str(function) == str(building_archetype.function)) and \ if (str(function) == str(building_archetype.function)) and \
(climate_zone == str(building_archetype.climate_zone)): (climate_zone == str(building_archetype.climate_zone)):
return building_archetype return building_archetype
return None raise KeyError('archetype not found')
@staticmethod @staticmethod
def _search_construction_in_archetype(archetype, construction_type): def _search_construction_in_archetype(archetype, construction_type):

View File

@ -31,3 +31,10 @@ class ConstructionFactory:
:return: None :return: None
""" """
getattr(self, self._handler, lambda: None)() getattr(self, self._handler, lambda: None)()
def enrich_debug(self):
"""
Enrich the city given to the class using the class given handler
:return: None
"""
UsPhysicsParameters(self._city, self._base_path).enrich_buildings()

View File

@ -82,7 +82,7 @@ class AirSourceHeatPumpParameters:
heat_pump.heating_comp_power = h_data[1] heat_pump.heating_comp_power = h_data[1]
heat_pump.heating_capacity_coff = self._compute_coefficients(h_data) heat_pump.heating_capacity_coff = self._compute_coefficients(h_data)
energy_system = EnergySystem('{} capacity heat pump'.format(heat_pump.model), 0, [], None) energy_system = EnergySystem('{} capacity heat pump'.format(heat_pump.model), 0, [])
energy_system.air_source_hp = heat_pump energy_system.air_source_hp = heat_pump
self._city.add_city_object(energy_system) self._city.add_city_object(energy_system)
return self._city return self._city

View File

@ -129,7 +129,7 @@ class WaterToWaterHPParameters:
heat_pump.entering_water_temp = data['ewt'] heat_pump.entering_water_temp = data['ewt']
heat_pump.leaving_water_temp = data['lwt'] heat_pump.leaving_water_temp = data['lwt']
heat_pump.power_demand_coff = self._compute_coefficients(data) heat_pump.power_demand_coff = self._compute_coefficients(data)
energy_system = EnergySystem(heat_pump.model, 0, [], None) energy_system = EnergySystem(heat_pump.model, 0, [])
energy_system.water_to_water_hp = heat_pump energy_system.water_to_water_hp = heat_pump
self._city.add_city_object(energy_system) self._city.add_city_object(energy_system)
return self._city return self._city

View File

@ -77,7 +77,6 @@ class CityGml:
continue continue
envelope = bound['Envelope'] envelope = bound['Envelope']
self._srs_name = envelope['@srsName'] self._srs_name = envelope['@srsName']
lower_corner = None
upper_corner = None upper_corner = None
if '#text' in envelope['lowerCorner']: if '#text' in envelope['lowerCorner']:
lower_corner = np.fromstring(envelope['lowerCorner']['#text'], dtype=float, sep=' ') lower_corner = np.fromstring(envelope['lowerCorner']['#text'], dtype=float, sep=' ')
@ -120,7 +119,7 @@ class CityGml:
surfaces = CityGmlLod2(city_object).surfaces surfaces = CityGmlLod2(city_object).surfaces
else: else:
raise NotImplementedError("Not supported level of detail") raise NotImplementedError("Not supported level of detail")
return Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) return Building(name, lod, surfaces, year_of_construction, function, terrains=None)
def _create_parts_consisting_building(self, city_object): def _create_parts_consisting_building(self, city_object):
name = city_object['@id'] name = city_object['@id']

View File

@ -4,23 +4,15 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Guillermo Gutierrez Guillermo.GutierrezMorote@concordia.ca Project Coder Guillermo Gutierrez Guillermo.GutierrezMorote@concordia.ca
""" """
import geopandas import json
import trimesh
import trimesh.exchange.load
import trimesh.geometry
import trimesh.creation import trimesh.creation
import trimesh.repair from pyproj import Transformer
from shapely.geometry import Point from shapely.geometry import Polygon as ShapelyPolygon
from shapely.geometry import Polygon as ShapelyPoly
from trimesh import Scene
from city_model_structure.attributes.polygon import Polygon
from city_model_structure.building import Building
from city_model_structure.building_demand.surface import Surface
from city_model_structure.city import City
import helpers.constants as cte import helpers.constants as cte
from city_model_structure.city import City
from city_model_structure.attributes.polygon import Polygon
from city_model_structure.building_demand.surface import Surface
from city_model_structure.building import Building
class Geojson: class Geojson:
""" """
@ -28,72 +20,92 @@ class Geojson:
""" """
def __init__(self, path, extrusion_height=None, year_of_construction=None, function=None): def __init__(self, path, extrusion_height=None, year_of_construction=None, function=None):
self._transformer = Transformer.from_crs('epsg:4326', 'epsg:26911')
self._min_x = cte.MAX_FLOAT
self._min_y = cte.MAX_FLOAT
self._max_x = cte.MIN_FLOAT
self._max_y = cte.MIN_FLOAT
self._city = None self._city = None
self._extrusion_height = 0
if self._extrusion_height is not None:
self._extrusion_height = extrusion_height self._extrusion_height = extrusion_height
self._year_of_construction = year_of_construction self._year_of_construction = year_of_construction
self._function = function self._function = function
self._geo_dataframe = geopandas.read_file(path) with open(path) as json_file:
self._geojson = json.loads(json_file.read())
def _save_bounds(self, x, y):
print(x, y)
if x > self._max_x:
self._max_x = x
if x < self._min_x:
self._min_x = x
if y > self._max_y:
self._max_y = y
if y < self._min_y:
self._min_y = y
@staticmethod
def _create_building_lod0(name, year_of_construction, function, surfaces_coordinates):
surfaces = []
for surface_coordinates in surfaces_coordinates:
polygon = Polygon(surface_coordinates)
surfaces.append(Surface(polygon, polygon))
Building(name, 0, surfaces, year_of_construction, function)
@staticmethod
def _create_building_lod1(name, year_of_construction, function, height, surfaces_coordinates):
surfaces = []
for surface_coordinates in surfaces_coordinates:
shapely_coordinates = [surface_coordinates[n:n + 3] for n in range(0, len(surface_coordinates), 3)]
shapely_polygon = ShapelyPolygon(shapely_coordinates)
mesh = trimesh.creation.extrude_polygon(shapely_polygon,height)
for face in mesh.faces:
points = []
for vertex_index in face:
points.append(mesh.vertices[vertex_index])
polygon = Polygon(points)
surface = Surface(polygon, polygon)
surfaces.append(surface)
Building(name, 1, surfaces, year_of_construction, function)
@property @property
def city(self) -> City: def city(self) -> City:
""" """
Get city out of a GeoPandas Table Get city out of a Geojson file
""" """
if self._city is None:
self._city = City(None, None, self._srs_name)
for scene_index, bldg in self._scene.iterrows():
geom = bldg.geom
polygon = ShapelyPoly(geom['coordinates'][0])
height = float(bldg['height_mean'])
building_mesh = trimesh.creation.extrude_polygon(polygon, height)
trimesh.repair.fill_holes(building_mesh)
trimesh.repair.fix_winding(building_mesh)
year_of_construction = int(bldg['year_built'])
name = str(scene_index)
lod = 1
if year_of_construction > 2000:
function = cte.RESIDENTIAL
else:
function = cte.INDUSTRY
surfaces = [] if self._city is None:
for face_index, face in enumerate(building_mesh.faces): buildings = []
points = [] for feature in self._geojson['features']:
for vertex_index in face: extrusion_height = float(feature['properties'][self._extrusion_height])
points.append(building_mesh.vertices[vertex_index]) year_of_construction = int(feature['properties'][self._year_of_construction])
solid_polygon = Polygon(points) function = feature['properties'][self._function]
perimeter_polygon = solid_polygon geometry = feature['geometry']
surface = Surface(solid_polygon, perimeter_polygon) building_name = feature['id']
surfaces.append(surface) surfaces_coordinates = []
building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) for coordinates_set in geometry['coordinates']:
self._city.add_city_object(building) surface_coordinates = []
for coordinates in coordinates_set:
print(coordinates)
if type(coordinates[0]) != float:
print(feature)
coordinates[0], coordinates[1] = self._transformer.transform(coordinates[0], coordinates[1])
self._save_bounds(coordinates[0], coordinates[1])
surface_coordinates = surface_coordinates + coordinates + [0.0]
surfaces_coordinates.append(surface_coordinates)
# todo: create building
if extrusion_height == 0:
buildings.append(Geojson._create_building_lod0(building_name,
year_of_construction,
function,
surfaces_coordinates))
else:
buildings.append(Geojson._create_building_lod1(building_name,
year_of_construction,
function,
extrusion_height,
surfaces_coordinates))
return self._city return self._city
@staticmethod
def resize_polygon(poly, factor=0.10, expand=False) -> ShapelyPoly:
"""
returns the shapely polygon which is smaller or bigger by passed factor.
Arguments:
poly {shapely.geometry.Polygon} -- an input geometry in shapely polygon format
Keyword Arguments:
factor {float} -- factor of expansion (default: {0.10})
expand {bool} -- If expand = True , then it returns bigger polygon, else smaller (default: {False})
Returns:
{shapely.geometry.Polygon} -- output geometry in shapely polygon format
"""
xs = list(poly.exterior.coords.xy[0])
ys = list(poly.exterior.coords.xy[1])
x_center = 0.5 * min(xs) + 0.5 * max(xs)
y_center = 0.5 * min(ys) + 0.5 * max(ys)
min_corner = Point(min(xs), min(ys))
center = Point(x_center, y_center)
shrink_distance = center.distance(min_corner) * factor
if expand:
poly_resized = poly.buffer(shrink_distance) # expand
else:
poly_resized = poly.buffer(-shrink_distance) # shrink
return poly_resized

View File

@ -40,7 +40,6 @@ class GPandas:
self._scene = dataframe self._scene = dataframe
self._scene = self._scene.to_crs(self._srs_name) self._scene = self._scene.to_crs(self._srs_name)
min_x, min_y, max_x, max_y = self._scene.total_bounds min_x, min_y, max_x, max_y = self._scene.total_bounds
print(min_x)
self._lower_corner = [min_x, min_y, 0] self._lower_corner = [min_x, min_y, 0]
self._upper_corner = [max_x, max_y, 0] self._upper_corner = [max_x, max_y, 0]
@ -82,7 +81,7 @@ class GPandas:
perimeter_polygon = solid_polygon perimeter_polygon = solid_polygon
surface = Surface(solid_polygon, perimeter_polygon) surface = Surface(solid_polygon, perimeter_polygon)
surfaces.append(surface) surfaces.append(surface)
building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) building = Building(name, lod, surfaces, year_of_construction, function)
self._city.add_city_object(building) self._city.add_city_object(building)
return self._city return self._city

View File

@ -77,6 +77,6 @@ class Obj:
perimeter_polygon = solid_polygon perimeter_polygon = solid_polygon
surface = Surface(solid_polygon, perimeter_polygon) surface = Surface(solid_polygon, perimeter_polygon)
surfaces.append(surface) surfaces.append(surface)
building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) building = Building(name, lod, surfaces, year_of_construction, function)
self._city.add_city_object(building) self._city.add_city_object(building)
return self._city return self._city

View File

@ -101,7 +101,7 @@ class Rhino:
if face is None: if face is None:
break break
hub_surfaces = hub_surfaces + self._add_face(face) hub_surfaces = hub_surfaces + self._add_face(face)
building = Building(name, 3, hub_surfaces, 'unknown', 'unknown', (self._min_x, self._min_y, self._min_z), []) building = Building(name, 3, hub_surfaces, 'unknown', 'unknown', [])
city_objects.append(building) city_objects.append(building)
lower_corner = (self._min_x, self._min_y, self._min_z) lower_corner = (self._min_x, self._min_y, self._min_z)
upper_corner = (self._max_x, self._max_y, self._max_z) upper_corner = (self._max_x, self._max_y, self._max_z)

View File

@ -5,22 +5,27 @@ Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
import geopandas
from city_model_structure.city import City from city_model_structure.city import City
from imports.geometry.citygml import CityGml from imports.geometry.citygml import CityGml
from imports.geometry.obj import Obj from imports.geometry.obj import Obj
from imports.geometry.osm_subway import OsmSubway from imports.geometry.osm_subway import OsmSubway
from imports.geometry.rhino import Rhino from imports.geometry.rhino import Rhino
from imports.geometry.gpandas import GPandas from imports.geometry.gpandas import GPandas
import geopandas from imports.geometry.geojson import Geojson
class GeometryFactory: class GeometryFactory:
""" """
GeometryFactory class GeometryFactory class
""" """
def __init__(self, file_type, path=None, data_frame=None): def __init__(self, file_type, path=None, data_frame=None, height=None, year_of_construction=None, function=None):
self._file_type = '_' + file_type.lower() self._file_type = '_' + file_type.lower()
self._path = path self._path = path
self._data_frame = data_frame self._data_frame = data_frame
self._height = height
self._year_of_construction = year_of_construction
self._function = function
@property @property
def _citygml(self) -> City: def _citygml(self) -> City:
@ -54,9 +59,7 @@ class GeometryFactory:
Enrich the city by using Geojson information as data source Enrich the city by using Geojson information as data source
:return: City :return: City
""" """
if self._data_frame is None: return Geojson(self._path, self._height, self._year_of_construction, self._function).city
self._data_frame = geopandas.read_file(self._path)
return Geojson(self._data_frame).city
@property @property
def _osm_subway(self) -> City: def _osm_subway(self) -> City:
@ -88,4 +91,4 @@ class GeometryFactory:
Enrich the city given to the class using the class given handler Enrich the city given to the class using the class given handler
:return: City :return: City
""" """
return GPandas(geopandas.read_file(self._path)).city return Geojson(self._path, self._height, self._year_of_construction, self._function).city

View File

@ -193,3 +193,12 @@ class TestConstructionFactory(TestCase):
self.assertIsNotNone(thermal_boundary.layers, 'layers is none') self.assertIsNotNone(thermal_boundary.layers, 'layers is none')
self._check_thermal_openings(thermal_boundary) self._check_thermal_openings(thermal_boundary)
self._check_surfaces(thermal_boundary) self._check_surfaces(thermal_boundary)
def test_archetype_not_found(self):
file = 'pluto_building.gml'
city = self._get_citygml(file)
for building in city.buildings:
building.year_of_construction = 1990
building.function = 'office'
ConstructionFactory('nrel', city).enrich()

View File

@ -33,7 +33,6 @@ class TestBuildings(TestCase):
ConstructionFactory('nrel', city).enrich() ConstructionFactory('nrel', city).enrich()
UsageFactory('comnet', city).enrich() UsageFactory('comnet', city).enrich()
ExportsFactory('idf', city, output_path).export() ExportsFactory('idf', city, output_path).export()
self.assertEqual(1, len(city.buildings)) self.assertEqual(1, len(city.buildings))
for building in city.buildings: for building in city.buildings:
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:

View File

@ -28,9 +28,13 @@ class TestGeometryFactory(TestCase):
self._city = None self._city = None
self._example_path = (Path(__file__).parent / 'tests_data').resolve() self._example_path = (Path(__file__).parent / 'tests_data').resolve()
def _get_city(self, file, file_type): def _get_city(self, file, file_type, height=None, year_of_construction=None, function=None):
file_path = (self._example_path / file).resolve() file_path = (self._example_path / file).resolve()
self._city = GeometryFactory(file_type, path=file_path).city self._city = GeometryFactory(file_type,
path=file_path,
height=height,
year_of_construction=year_of_construction,
function=function).city
self.assertIsNotNone(self._city, 'city is none') self.assertIsNotNone(self._city, 'city is none')
return self._city return self._city
@ -128,7 +132,6 @@ class TestGeometryFactory(TestCase):
""" """
file = 'kelowna.obj' file = 'kelowna.obj'
city = self._get_city(file, 'obj') city = self._get_city(file, 'obj')
self.assertIsNotNone(city, 'city is none')
self.assertTrue(len(city.buildings) == 1) self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city) self._check_buildings(city)
for building in city.buildings: for building in city.buildings:
@ -140,7 +143,6 @@ class TestGeometryFactory(TestCase):
""" """
file = 'sample.geojson' file = 'sample.geojson'
city = self._get_city(file, 'gpandas') city = self._get_city(file, 'gpandas')
self.assertIsNotNone(city, 'city is none')
self.assertTrue(len(city.buildings) == 1) self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city) self._check_buildings(city)
for building in city.buildings: for building in city.buildings:
@ -152,9 +154,11 @@ class TestGeometryFactory(TestCase):
""" """
Test geojson import Test geojson import
""" """
file = 'sample.geojson' file = 'concordia.geojson'
city = self._get_city(file, 'geojson') city = self._get_city(file, 'geojson',
self.assertIsNotNone(city, 'city is none') height='citygml_me',
year_of_construction='ANNEE_CONS',
function='LIBELLE_UT')
self.assertTrue(len(city.buildings) == 1) self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city) self._check_buildings(city)

File diff suppressed because it is too large Load Diff