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
"""
# todo: review with @Guille: Points, Coordinates, Vertices, Faces
def __init__(self, coordinates):
self._area = None
self._points = None

View File

@ -60,6 +60,7 @@ class City:
self._stations = []
self._lca_materials = None
self._level_of_detail = LevelOfDetail()
self._city_objects_dictionary = {}
@property
def fuels(self) -> [Fuel]:
@ -198,9 +199,8 @@ class City:
:param name:str
:return: None or CityObject
"""
for city_object in self.buildings:
if str(city_object.name) == str(name):
return city_object
if name in self._city_objects_dictionary:
return self.buildings[self._city_objects_dictionary[name]]
return None
def add_city_object(self, new_city_object):
@ -213,6 +213,7 @@ class City:
if self._buildings is None:
self._buildings = []
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':
if self._energy_systems is None:
self._energy_systems = []
@ -233,6 +234,10 @@ class City:
else:
if city_object in self._buildings:
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
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
import trimesh.exchange.obj
from hub.exports.formats.triangular import Triangular
from hub.imports.geometry_factory import GeometryFactory
class Obj:
@ -38,6 +35,8 @@ class Obj:
faces = []
for building in self._city.buildings:
obj.write(f'# building {building.name}\n')
obj.write(f'g {building.name}\n')
obj.write('s off\n')
for surface in building.surfaces:
obj.write(f'# surface {surface.name}\n')
face = 'f '
@ -56,6 +55,3 @@ class Obj:
faces.append(f'{face} {face.split(" ")[1]}\n')
obj.writelines(faces)
faces = []

View File

@ -9,6 +9,7 @@ import math
import numpy as np
import requests
from PIL import Image
from trimesh import Trimesh
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.helpers.location import Location
from PIL import Image
class MapPoint:
def __init__(self, x, y):
@ -88,6 +87,7 @@ class GeometryHelper:
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
@ -167,10 +167,53 @@ class GeometryHelper:
elif building not in neighbour.neighbours:
neighbour.neighbours.append(building)
line += 1
if plot:
img.show()
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
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
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import datetime
import sys
import math
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,
self._building.eave_height,
self._building.storeys_above_ground)
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,
self._internal_zone, self._floor_area)
@ -55,7 +56,6 @@ class StoreysGeneration:
else:
thermal_zones = [storey.neighbours[1], storey.thermal_zone]
thermal_boundary.thermal_zones = thermal_zones
return [storey.thermal_zone]
if number_of_storeys == 0:

View File

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

View File

@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guillermo Gutierrez Guillermo.GutierrezMorote@concordia.ca
"""
import datetime
import json
import numpy as np
@ -67,7 +68,8 @@ class Geojson:
points = igh.invert_points(points)
polygon = Polygon(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))
return buildings
@ -79,6 +81,7 @@ class Geojson:
for zone, lod0_building in enumerate(lod0_buildings):
for surface in lod0_building.grounds:
volume = surface.solid_polygon.area * height
surfaces.append(surface)
roof_coordinates = []
@ -88,6 +91,7 @@ class Geojson:
# insert the roof rotated already
roof_coordinates.insert(0, roof_coordinate)
polygon = Polygon(roof_coordinates)
polygon.area = surface.solid_polygon.area
roof = Surface(polygon, polygon)
surfaces.append(roof)
# 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
"""
if self._city is None:
start = datetime.datetime.now()
missing_functions = []
buildings = []
building_id = 0
@ -225,12 +230,18 @@ class Geojson:
[polygon])
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:
self._city.add_city_object(building)
self._city.level_of_detail.geometry = lod
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)
print(f'shared_walls: {datetime.datetime.now() - start}')
if len(missing_functions) > 0:
print(f'There are unknown functions {missing_functions}')
return self._city

View File

@ -105,11 +105,3 @@ class GeometryHelper:
cosine = -1
alpha = math.acos(cosine)
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
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import datetime
from pathlib import Path
from unittest import TestCase
from hub.helpers.geometry_helper import GeometryHelper
from numpy import inf
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.geometry_factory import GeometryFactory
@ -21,6 +19,7 @@ class TestGeometryFactory(TestCase):
Non-functional TestGeometryFactory
Load testing
"""
def setUp(self) -> None:
"""
Test setup
@ -36,7 +35,8 @@ class TestGeometryFactory(TestCase):
path=file_path,
height_field=height_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')
return self._city
@ -135,19 +135,13 @@ class TestGeometryFactory(TestCase):
Test geojson import
"""
file = '2000_buildings.geojson'
start = datetime.datetime.now()
city = self._get_city(file, 'geojson',
height_field='building_height',
year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI')
end = datetime.datetime.now()
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')
city = GeometryFactory('geojson',
path=(self._example_path / file).resolve(),
height_field='building_height',
year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI',
function_to_hub=MontrealFunctionToHubFunction().dictionary).city
self.assertEqual(2356, len(city.buildings), 'wrong number of buildings')
self._check_buildings(city)
def test_map_neighbours(self):
"""
@ -162,15 +156,16 @@ class TestGeometryFactory(TestCase):
city = self._get_city(file, 'geojson',
year_of_construction_field='ANNEE_CONS',
function_field='LIBELLE_UT')
info_lod0 = GeometryHelper.city_mapping(city, plot=False)
hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export()
self.assertEqual(info_lod0, info_lod1)
for building in city.buildings:
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('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('3_part_0_zone_0',city.city_object('2_part_0_zone_0').neighbours[1].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('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('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)

File diff suppressed because one or more lines are too long