forked from s_ranjbar/city_retrofit
Refactoring building creation and bringing code up to date
This commit is contained in:
parent
e9b50beea8
commit
1e24e43b64
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
52255
unittests/tests_data/concordia.geojson
Normal file
52255
unittests/tests_data/concordia.geojson
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user