Merge pull request 'optimization' (#10) from optimization into main

Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/CERC/hub/pulls/10
This commit is contained in:
Guille Gutierrez 2023-03-21 14:31:55 -04:00
commit 053a866443
10 changed files with 256164 additions and 52 deletions

View File

@ -26,8 +26,6 @@ class Polygon:
""" """
Polygon class Polygon class
""" """
# todo: review with @Guille: Points, Coordinates, Vertices, Faces
def __init__(self, coordinates): def __init__(self, coordinates):
self._area = None self._area = None
self._points = None self._points = None

View File

@ -60,6 +60,7 @@ class City:
self._stations = [] self._stations = []
self._lca_materials = None self._lca_materials = None
self._level_of_detail = LevelOfDetail() self._level_of_detail = LevelOfDetail()
self._city_objects_dictionary = {}
@property @property
def fuels(self) -> [Fuel]: def fuels(self) -> [Fuel]:
@ -198,9 +199,8 @@ class City:
:param name:str :param name:str
:return: None or CityObject :return: None or CityObject
""" """
for city_object in self.buildings: if name in self._city_objects_dictionary:
if str(city_object.name) == str(name): return self.buildings[self._city_objects_dictionary[name]]
return city_object
return None return None
def add_city_object(self, new_city_object): def add_city_object(self, new_city_object):
@ -213,6 +213,7 @@ class City:
if self._buildings is None: if self._buildings is None:
self._buildings = [] self._buildings = []
self._buildings.append(new_city_object) self._buildings.append(new_city_object)
self._city_objects_dictionary[new_city_object.name] = len(self._buildings) - 1
elif new_city_object.type == 'energy_system': elif new_city_object.type == 'energy_system':
if self._energy_systems is None: if self._energy_systems is None:
self._energy_systems = [] self._energy_systems = []
@ -233,6 +234,10 @@ class City:
else: else:
if city_object in self._buildings: if city_object in self._buildings:
self._buildings.remove(city_object) self._buildings.remove(city_object)
# regenerate hash map
self._city_objects_dictionary.clear()
for i, city_object in enumerate(self._buildings):
self._city_objects_dictionary[city_object.name] = i
@property @property
def srs_name(self) -> Union[None, str]: def srs_name(self) -> Union[None, str]:

View File

@ -6,9 +6,6 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from pathlib import Path from pathlib import Path
import trimesh.exchange.obj
from hub.exports.formats.triangular import Triangular
from hub.imports.geometry_factory import GeometryFactory
class Obj: class Obj:
@ -38,6 +35,8 @@ class Obj:
faces = [] faces = []
for building in self._city.buildings: for building in self._city.buildings:
obj.write(f'# building {building.name}\n') obj.write(f'# building {building.name}\n')
obj.write(f'g {building.name}\n')
obj.write('s off\n')
for surface in building.surfaces: for surface in building.surfaces:
obj.write(f'# surface {surface.name}\n') obj.write(f'# surface {surface.name}\n')
face = 'f ' face = 'f '
@ -56,6 +55,3 @@ class Obj:
faces.append(f'{face} {face.split(" ")[1]}\n') faces.append(f'{face} {face.split(" ")[1]}\n')
obj.writelines(faces) obj.writelines(faces)
faces = [] faces = []

View File

@ -9,6 +9,7 @@ import math
import numpy as np import numpy as np
import requests import requests
from PIL import Image
from trimesh import Trimesh from trimesh import Trimesh
from trimesh import intersections from trimesh import intersections
@ -16,8 +17,6 @@ from hub.city_model_structure.attributes.polygon import Polygon
from hub.city_model_structure.attributes.polyhedron import Polyhedron from hub.city_model_structure.attributes.polyhedron import Polyhedron
from hub.helpers.location import Location from hub.helpers.location import Location
from PIL import Image
class MapPoint: class MapPoint:
def __init__(self, x, y): def __init__(self, x, y):
@ -88,6 +87,7 @@ class GeometryHelper:
for ground in building.grounds: for ground in building.grounds:
length = len(ground.perimeter_polygon.coordinates) - 1 length = len(ground.perimeter_polygon.coordinates) - 1
for i, coordinate in enumerate(ground.perimeter_polygon.coordinates): for i, coordinate in enumerate(ground.perimeter_polygon.coordinates):
j = i + 1 j = i + 1
if i == length: if i == length:
j = 0 j = 0
@ -167,10 +167,53 @@ class GeometryHelper:
elif building not in neighbour.neighbours: elif building not in neighbour.neighbours:
neighbour.neighbours.append(building) neighbour.neighbours.append(building)
line += 1 line += 1
if plot: if plot:
img.show() img.show()
return lines_information return lines_information
@staticmethod
def fast_city_mapping(city, building_names=None):
lines_information = {}
if building_names is None:
building_names = [b.name for b in city.buildings]
x = int((city.upper_corner[0] - city.lower_corner[0]) * 0.5) + 1
y = int((city.upper_corner[1] - city.lower_corner[1]) * 0.5) + 1
city_map = [['' for _ in range(y + 1)] for _ in range(x + 1)]
for building_name in building_names:
building = city.city_object(building_name)
line = 0
for ground in building.grounds:
length = len(ground.perimeter_polygon.coordinates) - 1
for i, coordinate in enumerate(ground.perimeter_polygon.coordinates):
j = i + 1
if i == length:
j = 0
next_coordinate = ground.perimeter_polygon.coordinates[j]
point = GeometryHelper.coordinate_to_map_point(coordinate, city)
distance = int(GeometryHelper.distance_between_points(coordinate, next_coordinate))
if distance == 0:
continue
delta_x = (coordinate[0] - next_coordinate[0]) / (distance / 0.5)
delta_y = (coordinate[1] - next_coordinate[1]) / (distance / 0.5)
for k in range(0, distance):
x = MapPoint(point.x + (delta_x * k), point.y + (delta_y * k)).x
y = MapPoint(point.x + (delta_x * k), point.y + (delta_y * k)).y
if city_map[x][y] == '':
city_map[x][y] = building.name
elif city_map[x][y] != building.name:
neighbour = city.city_object(city_map[x][y])
if building.neighbours is None:
building.neighbours = [neighbour]
elif neighbour not in building.neighbours:
building.neighbours.append(neighbour)
if neighbour.neighbours is None:
neighbour.neighbours = [building]
elif building not in neighbour.neighbours:
neighbour.neighbours.append(building)
line += 1
return lines_information
@staticmethod @staticmethod
def segment_list_to_trimesh(lines) -> Trimesh: def segment_list_to_trimesh(lines) -> Trimesh:
""" """

View File

@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
import datetime
import sys import sys
import math import math
import numpy as np import numpy as np
@ -39,7 +40,7 @@ class StoreysGeneration:
number_of_storeys, height = self._calculate_number_storeys_and_height(self._building.average_storey_height, number_of_storeys, height = self._calculate_number_storeys_and_height(self._building.average_storey_height,
self._building.eave_height, self._building.eave_height,
self._building.storeys_above_ground) self._building.storeys_above_ground)
number_of_storeys = 1
if not self._divide_in_storeys or number_of_storeys == 1: if not self._divide_in_storeys or number_of_storeys == 1:
storey = Storey('storey_0', self._building.surfaces, [None, None], self._internal_zone.volume, storey = Storey('storey_0', self._building.surfaces, [None, None], self._internal_zone.volume,
self._internal_zone, self._floor_area) self._internal_zone, self._floor_area)
@ -55,7 +56,6 @@ class StoreysGeneration:
else: else:
thermal_zones = [storey.neighbours[1], storey.thermal_zone] thermal_zones = [storey.neighbours[1], storey.thermal_zone]
thermal_boundary.thermal_zones = thermal_zones thermal_boundary.thermal_zones = thermal_zones
return [storey.thermal_zone] return [storey.thermal_zone]
if number_of_storeys == 0: if number_of_storeys == 0:

View File

@ -4,7 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
import datetime
import math import math
import sys import sys
from hub.hub_logger import logger from hub.hub_logger import logger
@ -37,17 +37,21 @@ class NrcanPhysicsParameters:
for building in city.buildings: for building in city.buildings:
try: try:
function = Dictionaries().hub_function_to_nrcan_construction_function[building.function] function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone) archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone)
except KeyError: except KeyError:
logger.error(f'Building {building.name} has unknown construction archetype for building function: ' logger.error(f'Building {building.name} has unknown construction archetype for building function: '
f'{building.function}, building year of construction: {building.year_of_construction} ' f'{function} [{building.function}], building year of construction: {building.year_of_construction} '
f'and climate zone {self._climate_zone}\n') f'and climate zone {self._climate_zone}\n')
sys.stderr.write(f'Building {building.name} has unknown construction archetype for building function: ' sys.stderr.write(f'Building {building.name} has unknown construction archetype for building function: '
f'{building.function}, building year of construction: {building.year_of_construction} ' f'{function} [{building.function}], building year of construction: {building.year_of_construction} '
f'and climate zone {self._climate_zone}\n') f'and climate zone {self._climate_zone}\n')
continue continue
# if building has no thermal zones defined from geometry, and the building will be divided in storeys, # if building has no thermal zones defined from geometry, and the building will be divided in storeys,
# one thermal zone per storey is assigned # one thermal zone per storey is assigned
if len(building.internal_zones) == 1: if len(building.internal_zones) == 1:
if building.internal_zones[0].thermal_zones is None: if building.internal_zones[0].thermal_zones is None:
self._create_storeys(building, archetype, self._divide_in_storeys) self._create_storeys(building, archetype, self._divide_in_storeys)
@ -63,7 +67,6 @@ class NrcanPhysicsParameters:
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones: for thermal_zone in internal_zone.thermal_zones:
thermal_zone.total_floor_area = thermal_zone.footprint_area thermal_zone.total_floor_area = thermal_zone.footprint_area
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
self._assign_values(internal_zone.thermal_zones, archetype) self._assign_values(internal_zone.thermal_zones, archetype)
for thermal_zone in internal_zone.thermal_zones: for thermal_zone in internal_zone.thermal_zones:
@ -74,7 +77,7 @@ class NrcanPhysicsParameters:
nrcan_archetypes = nrcan_catalog.entries('archetypes') nrcan_archetypes = nrcan_catalog.entries('archetypes')
for building_archetype in nrcan_archetypes: for building_archetype in nrcan_archetypes:
construction_period_limits = building_archetype.construction_period.split('_') construction_period_limits = building_archetype.construction_period.split('_')
if int(construction_period_limits[0]) <= year_of_construction < int(construction_period_limits[1]): if int(construction_period_limits[0]) <= year_of_construction <= int(construction_period_limits[1]):
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

View File

@ -4,6 +4,7 @@ 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 datetime
import json import json
import numpy as np import numpy as np
@ -67,7 +68,8 @@ class Geojson:
points = igh.invert_points(points) points = igh.invert_points(points)
polygon = Polygon(points) polygon = Polygon(points)
polygon.area = igh.ground_area(points) polygon.area = igh.ground_area(points)
surfaces.append(Surface(polygon, polygon)) surface = Surface(polygon, polygon)
surfaces.append(surface)
buildings.append(Building(f'{name}_zone_{zone}', surfaces, year_of_construction, function)) buildings.append(Building(f'{name}_zone_{zone}', surfaces, year_of_construction, function))
return buildings return buildings
@ -79,6 +81,7 @@ class Geojson:
for zone, lod0_building in enumerate(lod0_buildings): for zone, lod0_building in enumerate(lod0_buildings):
for surface in lod0_building.grounds: for surface in lod0_building.grounds:
volume = surface.solid_polygon.area * height volume = surface.solid_polygon.area * height
surfaces.append(surface) surfaces.append(surface)
roof_coordinates = [] roof_coordinates = []
@ -88,6 +91,7 @@ class Geojson:
# insert the roof rotated already # insert the roof rotated already
roof_coordinates.insert(0, roof_coordinate) roof_coordinates.insert(0, roof_coordinate)
polygon = Polygon(roof_coordinates) polygon = Polygon(roof_coordinates)
polygon.area = surface.solid_polygon.area
roof = Surface(polygon, polygon) roof = Surface(polygon, polygon)
surfaces.append(roof) surfaces.append(roof)
# adding a wall means add the point coordinates and the next point coordinates with Z's height and 0 # adding a wall means add the point coordinates and the next point coordinates with Z's height and 0
@ -177,6 +181,7 @@ class Geojson:
Get city out of a Geojson file Get city out of a Geojson file
""" """
if self._city is None: if self._city is None:
start = datetime.datetime.now()
missing_functions = [] missing_functions = []
buildings = [] buildings = []
building_id = 0 building_id = 0
@ -225,12 +230,18 @@ class Geojson:
[polygon]) [polygon])
self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911') self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911')
print(f'features: {datetime.datetime.now()-start}')
for building in buildings: for building in buildings:
self._city.add_city_object(building) self._city.add_city_object(building)
self._city.level_of_detail.geometry = lod self._city.level_of_detail.geometry = lod
if lod == 1: if lod == 1:
lines_information = GeometryHelper.city_mapping(self._city) start = datetime.datetime.now()
lines_information = GeometryHelper.city_mapping(self._city, plot=False)
print(f'mapping: {datetime.datetime.now() - start}')
start = datetime.datetime.now()
self._store_shared_percentage_to_walls(self._city, lines_information) self._store_shared_percentage_to_walls(self._city, lines_information)
print(f'shared_walls: {datetime.datetime.now() - start}')
if len(missing_functions) > 0: if len(missing_functions) > 0:
print(f'There are unknown functions {missing_functions}') print(f'There are unknown functions {missing_functions}')
return self._city return self._city

View File

@ -105,11 +105,3 @@ class GeometryHelper:
cosine = -1 cosine = -1
alpha = math.acos(cosine) alpha = math.acos(cosine)
return alpha return alpha
@staticmethod
def invert_points(points):
res = []
for point in points:
res.insert(0,point)
return res

View File

@ -4,14 +4,12 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
import datetime
from pathlib import Path from pathlib import Path
from unittest import TestCase from unittest import TestCase
from hub.helpers.geometry_helper import GeometryHelper
from numpy import inf
import hub.exports.exports_factory import hub.exports.exports_factory
from hub.helpers.dictionaries import MontrealFunctionToHubFunction
from hub.helpers.geometry_helper import GeometryHelper
from hub.imports.construction_factory import ConstructionFactory from hub.imports.construction_factory import ConstructionFactory
from hub.imports.geometry_factory import GeometryFactory from hub.imports.geometry_factory import GeometryFactory
@ -21,6 +19,7 @@ class TestGeometryFactory(TestCase):
Non-functional TestGeometryFactory Non-functional TestGeometryFactory
Load testing Load testing
""" """
def setUp(self) -> None: def setUp(self) -> None:
""" """
Test setup Test setup
@ -36,7 +35,8 @@ class TestGeometryFactory(TestCase):
path=file_path, path=file_path,
height_field=height_field, height_field=height_field,
year_of_construction_field=year_of_construction_field, year_of_construction_field=year_of_construction_field,
function_field=function_field).city function_field=function_field,
).city
self.assertIsNotNone(self._city, 'city is none') self.assertIsNotNone(self._city, 'city is none')
return self._city return self._city
@ -135,19 +135,13 @@ class TestGeometryFactory(TestCase):
Test geojson import Test geojson import
""" """
file = '2000_buildings.geojson' file = '2000_buildings.geojson'
start = datetime.datetime.now() city = GeometryFactory('geojson',
city = self._get_city(file, 'geojson', path=(self._example_path / file).resolve(),
height_field='building_height', height_field='building_height',
year_of_construction_field='ANNEE_CONS', year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI') function_field='CODE_UTILI',
end = datetime.datetime.now() function_to_hub=MontrealFunctionToHubFunction().dictionary).city
print(f'geometry load in {end-start} s')
start = datetime.datetime.now()
hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export()
end = datetime.datetime.now()
print(f'geometry export in {end - start} s')
self.assertEqual(2356, len(city.buildings), 'wrong number of buildings') self.assertEqual(2356, len(city.buildings), 'wrong number of buildings')
self._check_buildings(city)
def test_map_neighbours(self): def test_map_neighbours(self):
""" """
@ -162,15 +156,16 @@ class TestGeometryFactory(TestCase):
city = self._get_city(file, 'geojson', city = self._get_city(file, 'geojson',
year_of_construction_field='ANNEE_CONS', year_of_construction_field='ANNEE_CONS',
function_field='LIBELLE_UT') function_field='LIBELLE_UT')
info_lod0 = GeometryHelper.city_mapping(city, plot=False) info_lod0 = GeometryHelper.city_mapping(city, plot=False)
hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export() hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export()
self.assertEqual(info_lod0, info_lod1) self.assertEqual(info_lod0, info_lod1)
for building in city.buildings: for building in city.buildings:
self.assertEqual(2, len(building.neighbours)) self.assertEqual(2, len(building.neighbours))
self.assertEqual('2_part_0_zone_0',city.city_object('1_part_0_zone_0').neighbours[0].name) self.assertEqual('2_part_0_zone_0', city.city_object('1_part_0_zone_0').neighbours[0].name)
self.assertEqual('3_part_0_zone_0',city.city_object('1_part_0_zone_0').neighbours[1].name) self.assertEqual('3_part_0_zone_0', city.city_object('1_part_0_zone_0').neighbours[1].name)
self.assertEqual('1_part_0_zone_0',city.city_object('2_part_0_zone_0').neighbours[0].name) self.assertEqual('1_part_0_zone_0', city.city_object('2_part_0_zone_0').neighbours[0].name)
self.assertEqual('3_part_0_zone_0',city.city_object('2_part_0_zone_0').neighbours[1].name) self.assertEqual('3_part_0_zone_0', city.city_object('2_part_0_zone_0').neighbours[1].name)
self.assertEqual('1_part_0_zone_0', city.city_object('3_part_0_zone_0').neighbours[0].name) self.assertEqual('1_part_0_zone_0', city.city_object('3_part_0_zone_0').neighbours[0].name)
self.assertEqual('2_part_0_zone_0', city.city_object('3_part_0_zone_0').neighbours[1].name) self.assertEqual('2_part_0_zone_0', city.city_object('3_part_0_zone_0').neighbours[1].name)

File diff suppressed because one or more lines are too long