diff --git a/hub/imports/geometry/rhino.py b/hub/imports/geometry/rhino.py deleted file mode 100644 index b1f36138..00000000 --- a/hub/imports/geometry/rhino.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Rhino module parses rhino files and import the geometry into the city model structure -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.capip -""" -import numpy as np -from rhino3dm import * -from rhino3dm._rhino3dm import Extrusion, MeshType, File3dm - -from hub.city_model_structure.attributes.point import Point -from hub.city_model_structure.attributes.polygon import Polygon -from hub.city_model_structure.building import Building -from hub.city_model_structure.building_demand.surface import Surface as HubSurface -from hub.city_model_structure.city import City -from hub.helpers.configuration_helper import ConfigurationHelper -from hub.imports.geometry.helpers.geometry_helper import GeometryHelper - - -class Rhino: - """ - Rhino class - """ - def __init__(self, path): - self._model = File3dm.Read(str(path)) - max_float = float(ConfigurationHelper().max_coordinate) - min_float = float(ConfigurationHelper().min_coordinate) - self._min_x = self._min_y = self._min_z = max_float - self._max_x = self._max_y = self._max_z = min_float - - @staticmethod - def _in_perimeter(wall, corner): - res = wall.contains_point(Point(corner)) - return res - - @staticmethod - def _add_hole(solid_polygon, hole): - first = solid_polygon.points[0] - points = first + hole.points + solid_polygon.points - return Polygon(points) - - @staticmethod - def _solid_points(coordinates) -> np.ndarray: - solid_points = np.fromstring(coordinates, dtype=float, sep=' ') - solid_points = GeometryHelper.to_points_matrix(solid_points) - - result = [] - found = False - for row in solid_points: - for row2 in result: - if row[0] == row2[0] and row[1] == row2[1] and row[2] == row2[2]: - found = True - if not found: - result.append(row) - return solid_points - - def _corners(self, point): - if point.X < self._min_x: - self._min_x = point.X - if point.Y < self._min_y: - self._min_y = point.Y - if point.Z < self._min_z: - self._min_z = point.Z - if point.X > self._max_x: - self._max_x = point.X - if point.Y > self._max_y: - self._max_y = point.Y - if point.Z > self._max_z: - self._max_z = point.Z - - def _add_face(self, face): - hub_surfaces = [] - _mesh = face.GetMesh(MeshType.Default) - for i in range(0, len(_mesh.Faces)): - mesh_faces = _mesh.Faces[i] - _points = '' - faces = [] - for index in mesh_faces: - if index in faces: - continue - faces.append(index) - self._corners(_mesh.Vertices[index]) - _points = _points + f'{_mesh.Vertices[index].X} {_mesh.Vertices[index].Y} {_mesh.Vertices[index].Z} ' - polygon_points = Rhino._solid_points(_points.strip()) - hub_surfaces.append(HubSurface(Polygon(polygon_points), Polygon(polygon_points))) - return hub_surfaces - - @property - def city(self) -> City: - """ - Return a city based in the rhino file - :return: City - """ - buildings = [] - city_objects = [] # building and "windows" - windows = [] - _prev_name = '' - - for obj in self._model.Objects: - name = obj.Attributes.Id - hub_surfaces = [] - if isinstance(obj.Geometry, Extrusion): - surface = obj.Geometry - hub_surfaces = hub_surfaces + self._add_face(surface) - else: - for face in obj.Geometry.Faces: - if face is None: - break - hub_surfaces = hub_surfaces + self._add_face(face) - building = Building(name, 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) - city = City(lower_corner, upper_corner, 'EPSG:26918') - for building in city_objects: - if len(building.surfaces) <= 2: - # is not a building but a window! - for surface in building.surfaces: - # add to windows the "hole" with the normal inverted - windows.append(Polygon(surface.perimeter_polygon.inverse)) - else: - buildings.append(building) - - # todo: this method will be pretty inefficient - for hole in windows: - corner = hole.coordinates[0] - for building in buildings: - for surface in building.surfaces: - plane = surface.perimeter_polygon.plane - # todo: this is a hack for dompark project it should not be done this way windows should be correctly modeled - # if the distance between the wall plane and the window is less than 2m - # and the window Z coordinate it's between the wall Z, it's a window of that wall - if plane.distance_to_point(corner) <= 2: - # check if the window is in the right high. - if surface.upper_corner[2] >= corner[2] >= surface.lower_corner[2]: - if surface.holes_polygons is None: - surface.holes_polygons = [] - surface.holes_polygons.append(hole) - - for building in buildings: - city.add_city_object(building) - building.level_of_detail.geometry = 3 - city.level_of_detail.geometry = 3 - return city diff --git a/hub/imports/geometry_factory.py b/hub/imports/geometry_factory.py index 5c13b6ee..06c07180 100644 --- a/hub/imports/geometry_factory.py +++ b/hub/imports/geometry_factory.py @@ -12,7 +12,6 @@ from hub.imports.geometry.citygml import CityGml from hub.imports.geometry.geojson import Geojson from hub.imports.geometry.gpandas import GPandas from hub.imports.geometry.obj import Obj -from hub.imports.geometry.rhino import Rhino class GeometryFactory: @@ -80,14 +79,6 @@ class GeometryFactory: self._function_field, self._function_to_hub).city - @property - def _rhino(self) -> City: - """ - Enrich the city by using Rhino information as data source - :return: City - """ - return Rhino(self._path).city - @property def city(self) -> City: """ diff --git a/hub/persistence/db_control.py b/hub/persistence/db_control.py index 2d8dd399..74526c7d 100644 --- a/hub/persistence/db_control.py +++ b/hub/persistence/db_control.py @@ -74,19 +74,31 @@ class DBControl: """ return self._city_object.get_by_name_or_alias_and_city(name, city_id) - def results(self, user_id, application_id, cities, result_names=None) -> Dict: + def buildings_info(self, request_values, city_id) -> [CityObject]: + """ + Retrieve the building info from the database + :param request_values: Building names + :param city_id: City ID + :return: [CityObject] + """ + buildings = [] + for name in request_values['names']: + buildings.append(self._city_object.get_by_name_or_alias_and_city(name, city_id)) + return buildings + + def results(self, user_id, application_id, request_values, result_names=None) -> Dict: """ Retrieve the simulation results for the given cities from the database :param user_id: the user id owning the results :param application_id: the application id owning the results - :param cities: dictionary containing the city and building names for the results + :param request_values: dictionary containing the scenario and building names to grab the results :param result_names: if given, filter the results to the selected names """ if result_names is None: result_names = [] results = {} - for city in cities['cities']: - scenario_name = next(iter(city)) + for scenario in request_values['scenarios']: + scenario_name = next(iter(scenario)) result_sets = self._city_repository.get_by_user_id_application_id_and_scenario( user_id, application_id, @@ -96,14 +108,13 @@ class DBControl: continue for result_set in result_sets: city_id = result_set[0].id - print('city ids', city_id) + results[scenario_name] = [] - for building_name in city[scenario_name]: + for building_name in scenario[scenario_name]: _building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id) if _building is None: continue city_object_id = _building.id - print('city object ids', city_object_id) _ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names( city_id, city_object_id, diff --git a/hub/persistence/repositories/city_object.py b/hub/persistence/repositories/city_object.py index ae27d134..6b82c6e4 100644 --- a/hub/persistence/repositories/city_object.py +++ b/hub/persistence/repositories/city_object.py @@ -104,15 +104,17 @@ class CityObject(Repository): :return: [CityObject] with the provided name or alias belonging to the city with id city_id """ try: - _city_objects = self.session.execute(select(Model).where( - or_(Model.name == name, Model.aliases.contains(f'{name}')), Model.city_id == city_id - )).all() - for city_object in _city_objects: - if city_object[0].name == name: - return city_object[0] + # search by name first + city_object = self.session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first() + if city_object is not None: + return city_object[0] + city_objects = self.session.execute( + select(Model).where(Model.aliases.contains(name), Model.city_id == city_id) + ).all() + # name not found, so search by alias instead + for city_object in city_objects: aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',') for alias in aliases: - print(alias, name) if alias == name: # force the name as the alias city_object[0].name = name diff --git a/requirements.txt b/requirements.txt index ea99fe66..93b455ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,6 @@ openpyxl networkx parseidf==1.0.0 ply -rhino3dm==7.7.0 scipy PyYAML pyecore==0.12.2 diff --git a/tests/test_db_factory.py b/tests/test_db_factory.py index 293f05d3..1b85a5ff 100644 --- a/tests/test_db_factory.py +++ b/tests/test_db_factory.py @@ -4,35 +4,34 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Peter Yefi peteryefi@gmail.com """ +import distutils.spawn import glob import json import logging import os import subprocess import unittest -from unittest import TestCase from pathlib import Path -import sqlalchemy.exc +from unittest import TestCase -from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction -from hub.imports.geometry_factory import GeometryFactory -from hub.imports.construction_factory import ConstructionFactory -from hub.imports.usage_factory import UsageFactory -from hub.imports.results_factory import ResultFactory -from hub.imports.weather_factory import WeatherFactory -from hub.imports.energy_systems_factory import EnergySystemsFactory +import sqlalchemy.exc +from sqlalchemy import create_engine +from sqlalchemy.exc import ProgrammingError + +import hub.helpers.constants as cte from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory from hub.exports.exports_factory import ExportsFactory +from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction +from hub.imports.construction_factory import ConstructionFactory +from hub.imports.energy_systems_factory import EnergySystemsFactory +from hub.imports.geometry_factory import GeometryFactory +from hub.imports.results_factory import ResultFactory +from hub.imports.usage_factory import UsageFactory +from hub.imports.weather_factory import WeatherFactory from hub.persistence.db_control import DBControl -from hub.persistence.repository import Repository -from sqlalchemy import create_engine from hub.persistence.models import City, Application, CityObject, SimulationResults from hub.persistence.models import User, UserRoles -from hub.helpers.dictionaries import Dictionaries -from sqlalchemy.exc import ProgrammingError -import uuid -import hub.helpers.constants as cte -import distutils.spawn +from hub.persistence.repository import Repository class Control: @@ -65,13 +64,13 @@ class Control: self._skip_test = True self._skip_reason = f'{operational_error}' return - """ + Application.__table__.create(bind=repository.engine, checkfirst=True) User.__table__.create(bind=repository.engine, checkfirst=True) City.__table__.create(bind=repository.engine, checkfirst=True) CityObject.__table__.create(bind=repository.engine, checkfirst=True) SimulationResults.__table__.create(bind=repository.engine, checkfirst=True) - """ + city_file = Path('tests_data/test.geojson').resolve() output_path = Path('tests_outputs/').resolve() self._city = GeometryFactory('geojson', @@ -81,6 +80,7 @@ class Control: aliases_field=['ID_UEV', 'CIVIQUE_DE', 'NOM_RUE'], function_field='CODE_UTILI', function_to_hub=MontrealFunctionToHubFunction().dictionary).city + ConstructionFactory('nrcan', self._city).enrich() UsageFactory('nrcan', self._city).enrich() WeatherFactory('epw', self._city).enrich() @@ -107,14 +107,13 @@ class Control: self._application_id = 1 self._user_id = 1 - """ self._application_id = self._database.persist_application( 'City_layers', 'City layers test user', self.application_uuid ) self._user_id = self._database.create_user('city_layers', self._application_id, 'city_layers', UserRoles.Admin) - """ + self._pickle_path = 'tests_data/pickle_path.bz2' @property diff --git a/tests/test_geometry_factory.py b/tests/test_geometry_factory.py index c4ade93b..a118bd58 100644 --- a/tests/test_geometry_factory.py +++ b/tests/test_geometry_factory.py @@ -110,15 +110,6 @@ class TestGeometryFactory(TestCase): self._check_surfaces(building) city = ConstructionFactory('nrel', city).enrich() - def test_import_rhino(self): - """ - Test rhino import - """ - file = 'dompark.3dm' - city = self._get_city(file, 'rhino') - self.assertIsNotNone(city, 'city is none') - self.assertTrue(len(city.buildings) == 36) - def test_import_obj(self): """ Test obj import diff --git a/tests/tests_data/dompark.3dm b/tests/tests_data/dompark.3dm deleted file mode 100644 index da86349e..00000000 Binary files a/tests/tests_data/dompark.3dm and /dev/null differ diff --git a/tests/tests_data/pickle_path.bz2 b/tests/tests_data/pickle_path.bz2 index 06e1d98f..28450f58 100644 Binary files a/tests/tests_data/pickle_path.bz2 and b/tests/tests_data/pickle_path.bz2 differ