Merge pull request 'cesiumjs_tileset' (#49) from cesiumjs_tileset into main

Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/CERC/hub/pulls/49
This commit is contained in:
Guille Gutierrez 2023-10-19 02:22:40 -04:00
commit a102a201e6
18 changed files with 444 additions and 167 deletions

View File

@ -743,8 +743,13 @@ class Building(CityObject):
for key, item in self._distribution_systems_electrical_consumption.items():
for i in range(0, len(item)):
self._distribution_systems_electrical_consumption[key][i] += _peak_load * _consumption_fix_flow \
* _working_hours[key] * cte.WATTS_HOUR_TO_JULES
_working_hours_value = _working_hours[key]
if len(item) == 12:
_working_hours_value = _working_hours[key][i]
self._distribution_systems_electrical_consumption[key][i] += (
_peak_load * _consumption_fix_flow * _working_hours_value * cte.WATTS_HOUR_TO_JULES
)
return self._distribution_systems_electrical_consumption
def _calculate_consumption(self, consumption_type, demand):
@ -805,3 +810,17 @@ class Building(CityObject):
orientation_losses_factor[_key]['south'])]
self._onsite_electrical_production[_key] = _results
return self._onsite_electrical_production
@property
def lower_corner(self):
"""
Get building lower corner.
"""
return [self._min_x, self._min_y, self._min_z]
@property
def upper_corner(self):
"""
Get building upper corner.
"""
return [self._max_x, self._max_y, self._max_z]

View File

@ -7,9 +7,11 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
from pathlib import Path
from hub.exports.formats.glb import Glb
from hub.exports.formats.obj import Obj
from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
from hub.exports.formats.stl import Stl
from hub.exports.formats.cesiumjs_tileset import CesiumjsTileset
from hub.helpers.utils import validate_import_export_type
@ -17,7 +19,7 @@ class ExportsFactory:
"""
Exports factory class
"""
def __init__(self, handler, city, path, target_buildings=None, adjacent_buildings=None):
def __init__(self, handler, city, path, target_buildings=None, adjacent_buildings=None, base_uri=None):
self._city = city
self._handler = '_' + handler.lower()
validate_import_export_type(ExportsFactory, handler)
@ -26,6 +28,7 @@ class ExportsFactory:
self._path = path
self._target_buildings = target_buildings
self._adjacent_buildings = adjacent_buildings
self._base_uri = base_uri
@property
def _citygml(self):
@ -61,9 +64,26 @@ class ExportsFactory:
Export the city to Simplified Radiosity Algorithm xml format
:return: None
"""
return SimplifiedRadiosityAlgorithm(self._city,
(self._path / f'{self._city.name}_sra.xml'),
target_buildings=self._target_buildings)
return SimplifiedRadiosityAlgorithm(
self._city, (self._path / f'{self._city.name}_sra.xml'), target_buildings=self._target_buildings
)
@property
def _cesiumjs_tileset(self):
"""
Export the city to a cesiumJs tileset format
:return: None
"""
return CesiumjsTileset(
self._city,
(self._path / f'{self._city.name}.json'),
target_buildings=self._target_buildings,
base_uri=self._base_uri
)
@property
def _glb(self):
return Glb(self._city, self._path, target_buildings=self._target_buildings)
def export(self):
"""

View File

@ -0,0 +1,153 @@
import json
import math
import pyproj
from pyproj import Transformer
from hub.helpers.geometry_helper import GeometryHelper
class CesiumjsTileset:
def __init__(self, city, file_name, target_buildings=None, base_uri=None):
self._city = city
self._file_name = file_name
self._target_buildings = target_buildings
if base_uri is None:
base_uri = '.'
self._base_uri = base_uri
try:
srs_name = self._city.srs_name
if self._city.srs_name in GeometryHelper.srs_transformations:
srs_name = GeometryHelper.srs_transformations[self._city.srs_name]
input_reference = pyproj.CRS(srs_name) # Projected coordinate system from input data
except pyproj.exceptions.CRSError as err:
raise pyproj.exceptions.CRSError from err
self._to_gps = Transformer.from_crs(input_reference, pyproj.CRS('EPSG:4326'))
city_upper_corner = [
self._city.upper_corner[0] - self._city.lower_corner[0],
self._city.upper_corner[1] - self._city.lower_corner[1],
self._city.upper_corner[2] - self._city.lower_corner[2]
]
city_lower_corner = [0, 0, 0]
self._tile_set = {
'asset': {
'version': '1.1',
"tilesetVersion": "1.2.3"
},
'position': self._to_gps.transform(self._city.lower_corner[0], self._city.lower_corner[1]),
'schema': {
'id': "building",
'classes': {
'building': {
"properties": {
'name': {
'type': 'STRING'
},
'position': {
'type': 'SCALAR',
'array': True,
'componentType': 'FLOAT32'
},
'aliases': {
'type': 'STRING',
'array': True,
},
'volume': {
'type': 'SCALAR',
'componentType': 'FLOAT32'
},
'floor_area': {
'type': 'SCALAR',
'componentType': 'FLOAT32'
},
'max_height': {
'type': 'SCALAR',
'componentType': 'INT32'
},
'year_of_construction': {
'type': 'SCALAR',
'componentType': 'INT32'
},
'function': {
'type': 'STRING'
},
'usages_percentage': {
'type': 'STRING'
}
}
}
}
},
'geometricError': 500,
'root': {
'boundingVolume': {
'box': CesiumjsTileset._box_values(city_upper_corner, city_lower_corner)
},
'geometricError': 70,
'refine': 'ADD',
'children': []
}
}
self._export()
@staticmethod
def _box_values(upper_corner, lower_corner):
x = (upper_corner[0] - lower_corner[0]) / 2
x_center = ((upper_corner[0] - lower_corner[0]) / 2) + lower_corner[0]
y = (upper_corner[1] - lower_corner[1]) / 2
y_center = ((upper_corner[1] - lower_corner[1]) / 2) + lower_corner[1]
z = (upper_corner[2] - lower_corner[2]) / 2
return [x_center, y_center, z, x, 0, 0, 0, y, 0, 0, 0, z]
def _ground_coordinates(self, coordinates):
ground_coordinates = []
for coordinate in coordinates:
ground_coordinates.append(
(coordinate[0] - self._city.lower_corner[0], coordinate[1] - self._city.lower_corner[1])
)
return ground_coordinates
def _export(self):
for building in self._city.buildings:
upper_corner = [-math.inf, -math.inf, 0]
lower_corner = [math.inf, math.inf, 0]
lower_corner_coordinates = lower_corner
for surface in building.grounds: # todo: maybe we should add the terrain?
coordinates = self._ground_coordinates(surface.solid_polygon.coordinates)
lower_corner = [min([c[0] for c in coordinates]), min([c[1] for c in coordinates]), 0]
lower_corner_coordinates = [
min([c[0] for c in surface.solid_polygon.coordinates]),
min([c[1] for c in surface.solid_polygon.coordinates]),
0
]
upper_corner = [max([c[0] for c in coordinates]), max([c[1] for c in coordinates]), building.max_height]
tile = {
'boundingVolume': {
'box': CesiumjsTileset._box_values(upper_corner, lower_corner)
},
'geometricError': 250,
'metadata': {
'class': 'building',
'properties': {
'name': building.name,
'position': self._to_gps.transform(lower_corner_coordinates[0], lower_corner_coordinates[1]),
'aliases': building.aliases,
'volume': building.volume,
'floor_area': building.floor_area,
'max_height': building.max_height,
'year_of_construction': building.year_of_construction,
'function': building.function,
'usages_percentage': building.usages_percentage
}
},
'content': {
'uri': f'{self._base_uri}/{building.name}.glb'
}
}
self._tile_set['root']['children'].append(tile)
with open(self._file_name, 'w') as f:
json.dump(self._tile_set, f, indent=2)

View File

@ -0,0 +1,49 @@
import os
import shutil
import subprocess
from hub.city_model_structure.city import City
from hub.exports.formats.obj import Obj
class GltExceptionError(Exception):
"""
Glt execution error
"""
class Glb:
def __init__(self, city, path, target_buildings=None):
self._city = city
self._path = path
if target_buildings is None:
target_buildings = [b.name for b in self._city.buildings]
self._target_buildings = target_buildings
self._export()
@property
def _obj2gtl(self):
"""
Get the SRA installation path
:return: str
"""
return shutil.which('obj2gltf')
def _export(self):
try:
for building in self._city.buildings:
city = City(self._city.lower_corner, self._city.upper_corner, self._city.srs_name)
city.add_city_object(building)
city.name = building.name
Obj(city, self._path)
glb = f'{self._path}/{building.name}.glb'
subprocess.run([
self._obj2gtl,
'-i', f'{self._path}/{building.name}.obj',
'-b',
'-o', f'{glb}'
])
os.unlink(f'{self._path}/{building.name}.obj')
os.unlink(f'{self._path}/{building.name}.mtl')
except (subprocess.SubprocessError, subprocess.TimeoutExpired, subprocess.CalledProcessError) as err:
raise GltExceptionError from err

View File

@ -4,7 +4,6 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import math
from pathlib import Path
import numpy as np
@ -27,7 +26,7 @@ class Obj:
def _to_vertex(self, coordinate):
x, y, z = self._ground(coordinate)
return f'v {x} {z} {y}\n'
return f'v {x} {z} -{y}\n' # to match opengl expectations
def _to_texture_vertex(self, coordinate):
u, v, _ = self._ground(coordinate)
@ -55,22 +54,22 @@ class Obj:
with open(mtl_file_path, 'w', encoding='utf-8') as mtl:
mtl.write("newmtl cerc_base_material\n")
mtl.write("Ka 1.0 1.0 1.0 # Ambient color (white)\n")
mtl.write("Kd 0.3 0.8 0.3 # Diffuse color (greenish)\n")
mtl.write("Kd 0.1 0.3 0.1 # Diffuse color (greenish)\n")
mtl.write("Ks 1.0 1.0 1.0 # Specular color (white)\n")
mtl.write("Ns 400.0 # Specular exponent (defines shininess)\n")
vertices = {}
normals_index = {}
faces = []
vertex_index = 0
normal_index = 0
with open(obj_file_path, 'w', encoding='utf-8') as obj:
obj.write("# cerc-hub export\n")
obj.write(f'mtllib {mtl_name}')
obj.write(f'mtllib {mtl_name}\n')
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 = []
@ -79,7 +78,6 @@ class Obj:
textures = []
for coordinate in surface.perimeter_polygon.coordinates:
vertex = self._to_vertex(coordinate)
if vertex not in vertices:
vertex_index += 1
vertices[vertex] = vertex_index
@ -88,8 +86,7 @@ class Obj:
textures.append(self._to_texture_vertex(coordinate)) # only append if non-existing
else:
current = vertices[vertex]
face.insert(0, f'{current}/{current}/{normal_index}') # insert counterclockwise
face.append(f'{current}/{current}/{normal_index}') # insert clockwise
obj.writelines(normal) # add the normal
obj.writelines(textures) # add the texture vertex

View File

@ -114,10 +114,7 @@ class DBControl:
result_names = []
results = {}
for scenario in request_values['scenarios']:
print('scenario', scenario, results)
for scenario_name in scenario.keys():
print('scenario name', scenario_name)
result_sets = self._city.get_by_user_id_application_id_and_scenario(
user_id,
application_id,
@ -143,7 +140,6 @@ class DBControl:
values = json.loads(value.values)
values["building"] = building_name
results[scenario_name].append(values)
print(scenario, results)
return results
def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):

View File

@ -7,9 +7,10 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
import datetime
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy import DateTime
from sqlalchemy.dialects.postgresql import UUID
from hub.persistence.configuration import Models

View File

@ -21,8 +21,8 @@ class City(Models):
pickle_path = Column(String, nullable=False)
name = Column(String, nullable=False)
scenario = Column(String, nullable=False)
application_id = Column(Integer, ForeignKey('application.id'), nullable=False)
user_id = Column(Integer, ForeignKey('user.id'), nullable=True)
application_id = Column(Integer, ForeignKey('application.id', ondelete='CASCADE'), nullable=False)
user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'), nullable=True)
hub_release = Column(String, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow)
updated = Column(DateTime, default=datetime.datetime.utcnow)

View File

@ -21,7 +21,7 @@ class CityObject(Models):
"""
__tablename__ = 'city_object'
id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=False)
name = Column(String, nullable=False)
aliases = Column(String, nullable=True)
type = Column(String, nullable=False)

View File

@ -19,8 +19,8 @@ class SimulationResults(Models):
"""
__tablename__ = 'simulation_results'
id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=True)
city_object_id = Column(Integer, ForeignKey('city_object.id'), nullable=True)
city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=True)
city_object_id = Column(Integer, ForeignKey('city_object.id', ondelete='CASCADE'), nullable=True)
name = Column(String, nullable=False)
values = Column(JSONB, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow)

View File

@ -10,6 +10,7 @@ import logging
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm.session import Session
from hub.persistence.repository import Repository
from hub.persistence.models import Application as Model
@ -48,10 +49,11 @@ class Application(Repository):
pass
try:
application = Model(name=name, description=description, application_uuid=application_uuid)
self.session.add(application)
self.session.commit()
self.session.refresh(application)
return application.id
with Session(self.engine) as session:
session.add(application)
session.commit()
session.refresh(application)
return application.id
except SQLAlchemyError as err:
logging.error('An error occurred while creating application %s', err)
raise SQLAlchemyError from err
@ -65,10 +67,11 @@ class Application(Repository):
:return: None
"""
try:
self.session.query(Model).filter(
Model.application_uuid == application_uuid
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
self.session.commit()
with Session(self.engine) as session:
session.query(Model).filter(
Model.application_uuid == application_uuid
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
session.commit()
except SQLAlchemyError as err:
logging.error('Error while updating application %s', err)
raise SQLAlchemyError from err
@ -80,9 +83,10 @@ class Application(Repository):
:return: None
"""
try:
self.session.query(Model).filter(Model.application_uuid == application_uuid).delete()
self.session.flush()
self.session.commit()
with Session(self.engine) as session:
session.query(Model).filter(Model.application_uuid == application_uuid).delete()
session.flush()
session.commit()
except SQLAlchemyError as err:
logging.error('Error while deleting application %s', err)
raise SQLAlchemyError from err
@ -94,10 +98,11 @@ class Application(Repository):
:return: Application with the provided application_uuid
"""
try:
result_set = self.session.execute(select(Model).where(
Model.application_uuid == application_uuid)
).first()
return result_set[0]
with Session(self.engine) as session:
result_set = session.execute(select(Model).where(
Model.application_uuid == application_uuid)
).first()
return result_set[0]
except SQLAlchemyError as err:
logging.error('Error while fetching application by application_uuid %s', err)
raise SQLAlchemyError from err

View File

@ -54,18 +54,18 @@ class City(Repository):
application_id,
user_id,
__version__)
self.session.add(db_city)
self.session.flush()
self.session.commit()
for building in city.buildings:
db_city_object = CityObject(db_city.id,
building)
self.session.add(db_city_object)
self.session.flush()
self.session.commit()
self.session.refresh(db_city)
return db_city.id
with Session(self.engine) as session:
session.add(db_city)
session.flush()
session.commit()
for building in city.buildings:
db_city_object = CityObject(db_city.id,
building)
session.add(db_city_object)
session.flush()
session.commit()
session.refresh(db_city)
return db_city.id
except SQLAlchemyError as err:
logging.error('An error occurred while creating a city %s', err)
raise SQLAlchemyError from err
@ -79,8 +79,9 @@ class City(Repository):
"""
try:
now = datetime.datetime.utcnow()
self.session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
self.session.commit()
with Session(self.engine) as session:
session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
session.commit()
except SQLAlchemyError as err:
logging.error('Error while updating city %s', err)
raise SQLAlchemyError from err
@ -92,9 +93,10 @@ class City(Repository):
:return: None
"""
try:
self.session.query(CityObject).filter(CityObject.city_id == city_id).delete()
self.session.query(Model).filter(Model.id == city_id).delete()
self.session.commit()
with Session(self.engine) as session:
session.query(CityObject).filter(CityObject.city_id == city_id).delete()
session.query(Model).filter(Model.id == city_id).delete()
session.commit()
except SQLAlchemyError as err:
logging.error('Error while fetching city %s', err)
raise SQLAlchemyError from err
@ -108,13 +110,12 @@ class City(Repository):
:return: [ModelCity]
"""
try:
result_set = self.session.execute(select(Model).where(Model.user_id == user_id,
Model.application_id == application_id,
Model.scenario == scenario
)).all()
self.session.close()
self.session = Session(self.engine)
return result_set
with Session(self.engine) as session:
result_set = session.execute(select(Model).where(Model.user_id == user_id,
Model.application_id == application_id,
Model.scenario == scenario
)).all()
return result_set
except SQLAlchemyError as err:
logging.error('Error while fetching city by name %s', err)
raise SQLAlchemyError from err
@ -127,10 +128,11 @@ class City(Repository):
:return: ModelCity
"""
try:
result_set = self.session.execute(
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
)
return [r[0] for r in result_set]
with Session(self.engine) as session:
result_set = session.execute(
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
)
return [r[0] for r in result_set]
except SQLAlchemyError as err:
logging.error('Error while fetching city by name %s', err)
raise SQLAlchemyError from err

View File

@ -46,10 +46,11 @@ class CityObject(Repository):
try:
city_object = Model(city_id=city_id,
building=building)
self.session.add(city_object)
self.session.flush()
self.session.commit()
self.session.refresh(city_object)
with Session(self.engine) as session:
session.add(city_object)
session.flush()
session.commit()
session.refresh(city_object)
return city_object.id
except SQLAlchemyError as err:
logging.error('An error occurred while creating city_object %s', err)
@ -68,17 +69,18 @@ class CityObject(Repository):
for usage in internal_zone.usages:
object_usage = f'{object_usage}{usage.name}_{usage.percentage} '
object_usage = object_usage.rstrip()
self.session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
{'name': building.name,
'alias': building.alias,
'object_type': building.type,
'year_of_construction': building.year_of_construction,
'function': building.function,
'usage': object_usage,
'volume': building.volume,
'area': building.floor_area,
'updated': datetime.datetime.utcnow()})
self.session.commit()
with Session(self.engine) as session:
session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
{'name': building.name,
'alias': building.alias,
'object_type': building.type,
'year_of_construction': building.year_of_construction,
'function': building.function,
'usage': object_usage,
'volume': building.volume,
'area': building.floor_area,
'updated': datetime.datetime.utcnow()})
session.commit()
except SQLAlchemyError as err:
logging.error('Error while updating city object %s', err)
raise SQLAlchemyError from err
@ -91,8 +93,9 @@ class CityObject(Repository):
:return: None
"""
try:
self.session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
self.session.commit()
with Session(self.engine) as session:
session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
session.commit()
except SQLAlchemyError as err:
logging.error('Error while deleting application %s', err)
raise SQLAlchemyError from err
@ -106,15 +109,14 @@ class CityObject(Repository):
"""
try:
# 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]
# name not found, so search by alias instead
city_objects = self.session.execute(
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
).all()
self.session.close()
self.session = Session(self.engine)
with Session(self.engine) as session:
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
if city_object is not None:
return city_object[0]
# name not found, so search by alias instead
city_objects = session.execute(
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
).all()
for city_object in city_objects:
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
for alias in aliases:

View File

@ -10,6 +10,7 @@ import logging
from sqlalchemy import or_
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from hub.persistence.repository import Repository
from hub.persistence.models import City
@ -52,11 +53,12 @@ class SimulationResults(Repository):
values=values,
city_id=city_id,
city_object_id=city_object_id)
self.session.add(simulation_result)
self.session.flush()
self.session.commit()
self.session.refresh(simulation_result)
return simulation_result.id
with Session(self.engine) as session:
session.add(simulation_result)
session.flush()
session.commit()
session.refresh(simulation_result)
return simulation_result.id
except SQLAlchemyError as err:
logging.error('An error occurred while creating city_object %s', err)
raise SQLAlchemyError from err
@ -71,22 +73,23 @@ class SimulationResults(Repository):
:return: None
"""
try:
if city_id is not None:
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
with Session(self.engine) as session:
if city_id is not None:
session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
{
'values': values,
'updated': datetime.datetime.utcnow()
})
self.session.commit()
elif city_object_id is not None:
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
{
'values': values,
'updated': datetime.datetime.utcnow()
})
self.session.commit()
else:
raise NotImplementedError('Missing either city_id or city_object_id')
session.commit()
elif city_object_id is not None:
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
{
'values': values,
'updated': datetime.datetime.utcnow()
})
session.commit()
else:
raise NotImplementedError('Missing either city_id or city_object_id')
except SQLAlchemyError as err:
logging.error('Error while updating city object %s', err)
raise SQLAlchemyError from err
@ -100,14 +103,15 @@ class SimulationResults(Repository):
:return: None
"""
try:
if city_id is not None:
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
self.session.commit()
elif city_object_id is not None:
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
self.session.commit()
else:
raise NotImplementedError('Missing either city_id or city_object_id')
with Session(self.engine) as session:
if city_id is not None:
session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
session.commit()
elif city_object_id is not None:
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
session.commit()
else:
raise NotImplementedError('Missing either city_id or city_object_id')
except SQLAlchemyError as err:
logging.error('Error while deleting application: %s', err)
raise SQLAlchemyError from err
@ -119,7 +123,8 @@ class SimulationResults(Repository):
:return: [City] with the provided city_id
"""
try:
return self.session.execute(select(City).where(City.id == city_id)).first()
with Session(self.engine) as session:
return session.execute(select(City).where(City.id == city_id)).first()
except SQLAlchemyError as err:
logging.error('Error while fetching city by city_id: %s', err)
raise SQLAlchemyError from err
@ -131,7 +136,8 @@ class SimulationResults(Repository):
:return: [CityObject] with the provided city_object_id
"""
try:
return self.session.execute(select(CityObject).where(CityObject.id == city_object_id)).first()
with Session(self.engine) as session:
return session.execute(select(CityObject).where(CityObject.id == city_object_id)).first()
except SQLAlchemyError as err:
logging.error('Error while fetching city by city_id: %s', err)
raise SQLAlchemyError from err
@ -145,18 +151,19 @@ class SimulationResults(Repository):
:return: [SimulationResult]
"""
try:
result_set = self.session.execute(select(Model).where(or_(
Model.city_id == city_id,
Model.city_object_id == city_object_id
)))
results = [r[0] for r in result_set]
if not result_names:
return results
filtered_results = []
for result in results:
if result.name in result_names:
filtered_results.append(result)
return filtered_results
with Session(self.engine) as session:
result_set = session.execute(select(Model).where(or_(
Model.city_id == city_id,
Model.city_object_id == city_object_id
)))
results = [r[0] for r in result_set]
if not result_names:
return results
filtered_results = []
for result in results:
if result.name in result_names:
filtered_results.append(result)
return filtered_results
except SQLAlchemyError as err:
logging.error('Error while fetching city by city_id: %s', err)
raise SQLAlchemyError from err

View File

@ -9,6 +9,7 @@ import logging
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from hub.helpers.auth import Auth
from hub.persistence.repository import Repository
@ -49,10 +50,11 @@ class User(Repository):
pass
try:
user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id)
self.session.add(user)
self.session.flush()
self.session.commit()
self.session.refresh(user)
with Session(self.engine) as session:
session.add(user)
session.flush()
session.commit()
session.refresh(user)
return user.id
except SQLAlchemyError as err:
logging.error('An error occurred while creating user %s', err)
@ -68,13 +70,14 @@ class User(Repository):
:return: None
"""
try:
self.session.query(Model).filter(Model.id == user_id).update({
'name': name,
'password': Auth.hash_password(password),
'role': role,
'updated': datetime.datetime.utcnow()
})
self.session.commit()
with Session(self.engine) as session:
session.query(Model).filter(Model.id == user_id).update({
'name': name,
'password': Auth.hash_password(password),
'role': role,
'updated': datetime.datetime.utcnow()
})
session.commit()
except SQLAlchemyError as err:
logging.error('Error while updating user: %s', err)
raise SQLAlchemyError from err
@ -86,8 +89,9 @@ class User(Repository):
:return: None
"""
try:
self.session.query(Model).filter(Model.id == user_id).delete()
self.session.commit()
with Session(self.engine) as session:
session.query(Model).filter(Model.id == user_id).delete()
session.commit()
except SQLAlchemyError as err:
logging.error('Error while fetching user: %s', err)
raise SQLAlchemyError from err
@ -100,10 +104,12 @@ class User(Repository):
:return: User matching the search criteria or None
"""
try:
user = self.session.execute(
select(Model).where(Model.name == name, Model.application_id == application_id)
).first()
return user[0]
with Session(self.engine) as session:
user = session.execute(
select(Model).where(Model.name == name, Model.application_id == application_id)
).first()
session.commit()
return user[0]
except SQLAlchemyError as err:
logging.error('Error while fetching user by name and application: %s', err)
raise SQLAlchemyError from err
@ -120,12 +126,13 @@ class User(Repository):
:return: User
"""
try:
user = self.session.execute(
select(Model).where(Model.name == name, Model.application_id == application_id)
).first()
if user:
if Auth.check_password(password, user[0].password):
return user[0]
with Session(self.engine) as session:
user = session.execute(
select(Model).where(Model.name == name, Model.application_id == application_id)
).first()
if user:
if Auth.check_password(password, user[0].password):
return user[0]
except SQLAlchemyError as err:
logging.error('Error while fetching user by name: %s', err)
raise SQLAlchemyError from err
@ -140,10 +147,11 @@ class User(Repository):
:return: User
"""
try:
application = self.session.execute(
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
).first()
return self.get_by_name_application_id_and_password(name, password, application[0].id)
with Session(self.engine) as session:
application = session.execute(
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
).first()
return self.get_by_name_application_id_and_password(name, password, application[0].id)
except SQLAlchemyError as err:
logging.error('Error while fetching user by name: %s', err)
raise SQLAlchemyError from err

View File

@ -6,7 +6,6 @@ Project Coder Peter Yefi peteryefi@gmail.com
"""
import logging
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from hub.persistence.configuration import Configuration
@ -19,6 +18,5 @@ class Repository:
try:
self.configuration = Configuration(db_name, dotenv_path, app_env)
self.engine = create_engine(self.configuration.connection_string)
self.session = Session(self.engine)
except ValueError as err:
logging.error('Missing value for credentials: %s', err)

View File

@ -103,16 +103,16 @@ class Control:
app_env='TEST',
dotenv_path=dotenv_path)
self._application_uuid = '60b7fc1b-f389-4254-9ffd-22a4cf32c7a3'
self._application_uuid = 'b9e0ce80-1218-410c-8a64-9d9b7026aad8'
self._application_id = 1
self._user_id = 1
self._application_id = self._database.persist_application(
'City_layers',
'City layers test user',
'test',
'test',
self.application_uuid
)
self._user_id = self._database.create_user('city_layers', self._application_id, 'city_layers', UserRoles.Admin)
self._user_id = self._database.create_user('test', self._application_id, 'test', UserRoles.Admin)
self._pickle_path = Path('tests_data/pickle_path.bz2').resolve()

View File

@ -5,7 +5,7 @@ Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import json
import logging.handlers
from pathlib import Path
from unittest import TestCase
@ -66,7 +66,7 @@ class TestExports(TestCase):
def _export(self, export_type, from_pickle=False):
self._complete_city = self._get_complete_city(from_pickle)
ExportsFactory(export_type, self._complete_city, self._output_path).export()
ExportsFactory(export_type, self._complete_city, self._output_path, base_uri='../glb').export()
def _export_building_energy(self, export_type, from_pickle=False):
self._complete_city = self._get_complete_city(from_pickle)
@ -78,6 +78,26 @@ class TestExports(TestCase):
"""
self._export('obj', False)
def test_cesiumjs_tileset_export(self):
"""
export to cesiumjs tileset
"""
self._export('cesiumjs_tileset', False)
tileset = Path(self._output_path / f'{self._city.name}.json')
self.assertTrue(tileset.exists())
with open(tileset, 'r') as f:
json_tileset = json.load(f)
self.assertEqual(1, len(json_tileset['root']['children']), "Wrong number of children")
def test_glb_export(self):
"""
export to glb format
"""
self._export('glb', False)
for building in self._city.buildings:
glb_file = Path(self._output_path / f'{building.name}.glb')
self.assertTrue(glb_file.exists(), f'{building.name} Building glb wasn\'t correctly generated')
def test_energy_ade_export(self):
"""
export to energy ADE