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
"""
def __init__(self, name, lod, surfaces, year_of_construction, function, city_lower_corner, terrains=None):
super().__init__(name, lod, surfaces, city_lower_corner)
def __init__(self, name, lod, surfaces, year_of_construction, function, terrains=None):
super().__init__(name, lod, surfaces)
self._households = None
self._basement_heated = None
self._attic_heated = None

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,3 +31,10 @@ class ConstructionFactory:
:return: 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_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
self._city.add_city_object(energy_system)
return self._city

View File

@ -129,7 +129,7 @@ class WaterToWaterHPParameters:
heat_pump.entering_water_temp = data['ewt']
heat_pump.leaving_water_temp = data['lwt']
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
self._city.add_city_object(energy_system)
return self._city

View File

@ -77,7 +77,6 @@ class CityGml:
continue
envelope = bound['Envelope']
self._srs_name = envelope['@srsName']
lower_corner = None
upper_corner = None
if '#text' in envelope['lowerCorner']:
lower_corner = np.fromstring(envelope['lowerCorner']['#text'], dtype=float, sep=' ')
@ -120,7 +119,7 @@ class CityGml:
surfaces = CityGmlLod2(city_object).surfaces
else:
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):
name = city_object['@id']

View File

@ -4,23 +4,15 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guillermo Gutierrez Guillermo.GutierrezMorote@concordia.ca
"""
import geopandas
import trimesh
import trimesh.exchange.load
import trimesh.geometry
import json
import trimesh.creation
import trimesh.repair
from shapely.geometry import Point
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
from pyproj import Transformer
from shapely.geometry import Polygon as ShapelyPolygon
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:
"""
@ -28,72 +20,92 @@ class Geojson:
"""
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._extrusion_height = 0
if self._extrusion_height is not None:
self._extrusion_height = extrusion_height
self._year_of_construction = year_of_construction
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
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 = []
for face_index, face in enumerate(building_mesh.faces):
points = []
for vertex_index in face:
points.append(building_mesh.vertices[vertex_index])
solid_polygon = Polygon(points)
perimeter_polygon = solid_polygon
surface = Surface(solid_polygon, perimeter_polygon)
surfaces.append(surface)
building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None)
self._city.add_city_object(building)
if self._city is None:
buildings = []
for feature in self._geojson['features']:
extrusion_height = float(feature['properties'][self._extrusion_height])
year_of_construction = int(feature['properties'][self._year_of_construction])
function = feature['properties'][self._function]
geometry = feature['geometry']
building_name = feature['id']
surfaces_coordinates = []
for coordinates_set in geometry['coordinates']:
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
@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 = self._scene.to_crs(self._srs_name)
min_x, min_y, max_x, max_y = self._scene.total_bounds
print(min_x)
self._lower_corner = [min_x, min_y, 0]
self._upper_corner = [max_x, max_y, 0]
@ -82,7 +81,7 @@ class GPandas:
perimeter_polygon = solid_polygon
surface = Surface(solid_polygon, perimeter_polygon)
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)
return self._city

View File

@ -77,6 +77,6 @@ class Obj:
perimeter_polygon = solid_polygon
surface = Surface(solid_polygon, perimeter_polygon)
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)
return self._city

View File

@ -101,7 +101,7 @@ class Rhino:
if face is None:
break
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)
lower_corner = (self._min_x, self._min_y, self._min_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
"""
import geopandas
from city_model_structure.city import City
from imports.geometry.citygml import CityGml
from imports.geometry.obj import Obj
from imports.geometry.osm_subway import OsmSubway
from imports.geometry.rhino import Rhino
from imports.geometry.gpandas import GPandas
import geopandas
from imports.geometry.geojson import Geojson
class GeometryFactory:
"""
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._path = path
self._data_frame = data_frame
self._height = height
self._year_of_construction = year_of_construction
self._function = function
@property
def _citygml(self) -> City:
@ -54,9 +59,7 @@ class GeometryFactory:
Enrich the city by using Geojson information as data source
:return: City
"""
if self._data_frame is None:
self._data_frame = geopandas.read_file(self._path)
return Geojson(self._data_frame).city
return Geojson(self._path, self._height, self._year_of_construction, self._function).city
@property
def _osm_subway(self) -> City:
@ -88,4 +91,4 @@ class GeometryFactory:
Enrich the city given to the class using the class given handler
: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._check_thermal_openings(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()
UsageFactory('comnet', city).enrich()
ExportsFactory('idf', city, output_path).export()
self.assertEqual(1, len(city.buildings))
for building in city.buildings:
for internal_zone in building.internal_zones:

View File

@ -28,9 +28,13 @@ class TestGeometryFactory(TestCase):
self._city = None
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()
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')
return self._city
@ -128,7 +132,6 @@ class TestGeometryFactory(TestCase):
"""
file = 'kelowna.obj'
city = self._get_city(file, 'obj')
self.assertIsNotNone(city, 'city is none')
self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city)
for building in city.buildings:
@ -140,7 +143,6 @@ class TestGeometryFactory(TestCase):
"""
file = 'sample.geojson'
city = self._get_city(file, 'gpandas')
self.assertIsNotNone(city, 'city is none')
self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city)
for building in city.buildings:
@ -152,9 +154,11 @@ class TestGeometryFactory(TestCase):
"""
Test geojson import
"""
file = 'sample.geojson'
city = self._get_city(file, 'geojson')
self.assertIsNotNone(city, 'city is none')
file = 'concordia.geojson'
city = self._get_city(file, 'geojson',
height='citygml_me',
year_of_construction='ANNEE_CONS',
function='LIBELLE_UT')
self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city)

File diff suppressed because it is too large Load Diff