diff --git a/city_model_structure/city.py b/city_model_structure/city.py
index 02b095f0..3b4582df 100644
--- a/city_model_structure/city.py
+++ b/city_model_structure/city.py
@@ -1,5 +1,8 @@
from city_model_structure.city_object import CityObject
from typing import List, Union
+import pyproj
+from pyproj import Transformer
+import reverse_geocoder as rg
class City:
@@ -10,6 +13,33 @@ class City:
self._upper_corner = upper_corner
self._city_objects = city_objects
self._srs_name = srs_name
+ # todo: right now extracted at city level, in the future should be extracted also at building level if exist
+ self._location = None
+
+ @property
+ def location(self):
+ if self._location is None:
+ gps = pyproj.CRS('EPSG:4326') # LatLon with WGS84 datum used by GPS units and Google Earth
+ input_reference = None
+ try:
+ input_reference = pyproj.CRS(self.srs_name) # Projected coordinate system from input data
+ except pyproj.exceptions.CRSError:
+ print('Invalid projection reference system, please check the input data. (e.g. in CityGML files: srs_name)')
+ quit()
+ transformer = Transformer.from_crs(input_reference, gps)
+ coordinates = transformer.transform(self.lower_corner[0], self.lower_corner[1])
+ self._location = rg.search(coordinates)
+ return self._location
+
+ @property
+ def country_code(self):
+ return self.location[0]['cc']
+
+ @property
+ def name(self):
+ if self._name is None:
+ self._name = self.location[0]['name']
+ return self._name
@property
def city_objects(self) -> Union[List[CityObject], None]:
@@ -30,10 +60,9 @@ class City:
return None
def add_city_object(self, new_city_object):
- if self.city_objects is None:
+ if self._city_objects is None:
self._city_objects = []
for city_object in self.city_objects:
- # ToDo: Check for shared walls.
for surface in city_object.surfaces:
for surface2 in new_city_object.surfaces:
surface.shared(surface2)
@@ -43,10 +72,6 @@ class City:
def srs_name(self):
return self._srs_name
- @property
- def name(self):
- return self._name
-
@name.setter
def name(self, value):
self._name = value
diff --git a/city_model_structure/city_object.py b/city_model_structure/city_object.py
index 0e9ca95a..47355115 100644
--- a/city_model_structure/city_object.py
+++ b/city_model_structure/city_object.py
@@ -14,7 +14,8 @@ from typing import Union, List
class CityObject:
- def __init__(self, name, lod, surfaces, terrains, year_of_construction, function, attic_heated=0, basement_heated=0):
+ def __init__(self, name, lod, surfaces, terrains, year_of_construction, function, lower_corner, attic_heated=0,
+ basement_heated=0):
self._name = name
self._lod = lod
self._surfaces = surfaces
@@ -24,6 +25,7 @@ class CityObject:
self._terrains = terrains
self._year_of_construction = year_of_construction
self._function = function
+ self._lower_corner = lower_corner
self._geometry = Geometry()
self._average_storey_height = None
self._storeys_above_ground = None
@@ -42,6 +44,7 @@ class CityObject:
t.bounded = [ThermalBoundary(s, [t]) for s in t.surfaces]
surface_id = 0
for s in self._surfaces:
+ s.lower_corner = self._lower_corner
s.parent(self, surface_id)
surface_id += 1
diff --git a/city_model_structure/material.py b/city_model_structure/material.py
index 2473c9ad..9a247003 100644
--- a/city_model_structure/material.py
+++ b/city_model_structure/material.py
@@ -1,6 +1,5 @@
class Material:
def __init__(self):
- # ToDo: construct this class
self._conductivity_wm_k = None
self._specific_heat_jkg_k = None
self._density_kg_m3 = None
diff --git a/city_model_structure/surface.py b/city_model_structure/surface.py
index 10b17e76..675c417d 100644
--- a/city_model_structure/surface.py
+++ b/city_model_structure/surface.py
@@ -2,6 +2,7 @@ from __future__ import annotations
import numpy as np
import pyny3d.geoms as pn
from helpers.geometry import Geometry
+from typing import Union
class Surface:
@@ -14,8 +15,10 @@ class Surface:
self._is_projected = is_projected
self._geometry = Geometry()
self._polygon = None
+ self._ground_polygon = None
self._area = None
self._points = None
+ self._ground_points = None
self._points_list = None
self._normal = None
self._azimuth = None
@@ -25,9 +28,13 @@ class Surface:
self._parent = None
self._shapely = None
self._projected_surface = None
- self._shared_surfaces = None
+ self._min_x = None
+ self._min_y = None
+ self._min_z = None
+ self._shared_surfaces = []
self._global_irradiance_hour = np.zeros(8760)
self._global_irradiance_month = np.zeros(12)
+ self._ground_coordinates = (self.min_x, self.min_y, self.min_z)
def parent(self, parent, surface_id):
self._parent = parent
@@ -48,21 +55,69 @@ class Surface:
self._swr = value
@property
- def points(self):
+ def points(self) -> np.ndarray:
if self._points is None:
self._points = np.fromstring(self._coordinates, dtype=float, sep=' ')
self._points = Geometry.to_points_matrix(self._points, self._remove_last)
return self._points
+ def _min_coord(self, axis):
+ if axis == 'x':
+ axis = 0
+ elif axis == 'y':
+ axis = 1
+ else:
+ axis = 2
+ min_coordinate = ''
+ for point in self.points:
+ if min_coordinate == '':
+ min_coordinate = point[axis]
+ elif min_coordinate > point[axis]:
+ min_coordinate = point[axis]
+ return min_coordinate
+
@property
- def points_list(self):
+ def min_x(self):
+ if self._min_x is None:
+ self._min_x = self._min_coord('x')
+ return self._min_x
+
+ @property
+ def min_y(self):
+ if self._min_y is None:
+ self._min_y = self._min_coord('y')
+ return self._min_y
+
+ @property
+ def min_z(self):
+ if self._min_z is None:
+ self._min_z = self._min_coord('z')
+ return self._min_z
+
+ @property
+ def ground_points(self) -> np.ndarray:
+ if self._ground_points is None:
+ coordinates = ''
+ for point in self.points:
+ x = point[0] - self._ground_coordinates[0]
+ y = point[1] - self._ground_coordinates[1]
+ z = point[2] - self._ground_coordinates[2]
+ if coordinates != '':
+ coordinates = coordinates + ' '
+ coordinates = coordinates + str(x) + ' ' + str(y) + ' ' + str(z)
+ self._ground_points = np.fromstring(coordinates, dtype=float, sep=' ')
+ self._ground_points = Geometry.to_points_matrix(self._ground_points, False)
+ return self._ground_points
+
+ @property
+ def points_list(self) -> np.ndarray:
if self._points_list is None:
s = self.points
self._points_list = np.reshape(s, len(s) * 3)
return self._points_list
@property
- def polygon(self):
+ def polygon(self) -> pn.Polygon:
if self._polygon is None:
try:
self._polygon = pn.Polygon(self.points)
@@ -71,15 +126,22 @@ class Surface:
self._polygon = None
return self._polygon
+ @property
+ def ground_polygon(self) -> Union[pn.Polygon, None]:
+ if self._ground_polygon is None:
+ try:
+ self._ground_polygon = pn.Polygon(self.ground_points)
+ except ValueError:
+ # is not really a polygon but a line so just return none
+ self._ground_polygon = None
+ return self._ground_polygon
+
@property
def area(self):
if self._area is None:
self._area = self.polygon.get_area()
return self._area
- def _is_shared(self, surface):
- return False
-
def _is_almost_same_terrain(self, terrain_points, ground_points):
equal = 0
for t in terrain_points:
@@ -110,7 +172,7 @@ class Surface:
return self._area_below_ground
@property
- def normal(self):
+ def normal(self) -> np.ndarray:
if self._normal is None:
points = self.points
n = np.cross(points[1] - points[0], points[2] - points[0])
@@ -142,12 +204,18 @@ class Surface:
self._type = 'Roof'
return self._type
+ def add_shared(self, surface, intersection_area):
+ percent = intersection_area / self.area
+ self._shared_surfaces.append((percent, surface))
+
def shared(self, surface):
- if self.type is not 'Wall':
+ if self.type is not 'Wall' or surface.type is not 'Wall':
return
- if self._is_shared(surface):
- self._shared_surfaces.append((100, surface))
- surface.shared(self)
+ if self._geometry.is_almost_same_surface(self, surface):
+ intersection_area = self.intersect(surface).area
+ percent = intersection_area / self.area
+ self._shared_surfaces.append((percent, surface))
+ surface.add_shared(self, intersection_area)
@property
def global_irradiance_hour(self):
@@ -173,15 +241,53 @@ class Surface:
self._shapely = self.polygon.get_shapely()
return self._shapely
+ @staticmethod
+ def _polygon_to_surface(polygon):
+ coordinates = ''
+ for coordinate in polygon.exterior.coords:
+ if coordinates != '':
+ coordinates = coordinates + ' '
+ coordinates = coordinates + str(coordinate[0]) + ' ' + str(coordinate[1]) + ' 0.0'
+ return Surface(coordinates, remove_last=False)
+
@property
def projection(self) -> Surface:
if self._is_projected:
return self
if self._projected_surface is None:
+ self._projected_surface = self._polygon_to_surface(self.shapely)
+ return self._projected_surface
+
+ def intersect(self, surface) -> Union[Surface, None]:
+ min_x = min(self.min_x, surface.min_x)
+ min_y = min(self.min_y, surface.min_y)
+ min_z = min(self.min_z, surface.min_z)
+ self._ground_coordinates = (min_x, min_y, min_z)
+ surface._ground_coordinates = (min_x, min_y, min_z)
+ origin = (0, 0, 0)
+ azimuth = self.azimuth - (np.pi / 2)
+ while azimuth < 0:
+ azimuth += (np.pi / 2)
+ inclination = self.inclination - np.pi
+ while inclination < 0:
+ inclination += np.pi
+ polygon1 = self.ground_polygon.rotate(azimuth, 'z', origin).rotate(inclination, 'x', origin)
+ polygon2 = surface.ground_polygon.rotate(azimuth, 'z', origin).rotate(inclination, 'x', origin)
+ try:
coordinates = ''
- for coordinate in self.shapely.exterior.coords:
+ intersection = pn.Surface([polygon1]).intersect_with(polygon2)
+ if len(intersection) == 0:
+ return None
+ for coordinate in pn.Surface([polygon1]).intersect_with(polygon2)[0]:
if coordinates != '':
coordinates = coordinates + ' '
coordinates = coordinates + str(coordinate[0]) + ' ' + str(coordinate[1]) + ' 0.0'
- self._projected_surface = Surface(coordinates)
- return self._projected_surface
+ if coordinates == '':
+ return None
+ intersect_surface = Surface(coordinates, remove_last=False)
+ if intersect_surface.polygon is None:
+ return None
+ return Surface(coordinates, remove_last=False)
+ except Exception as err:
+ print('Error', err)
+ return None
diff --git a/city_model_structure/thermal_zone.py b/city_model_structure/thermal_zone.py
index c9749db0..b9efbc30 100644
--- a/city_model_structure/thermal_zone.py
+++ b/city_model_structure/thermal_zone.py
@@ -1,5 +1,6 @@
from typing import List
from city_model_structure.thermal_boundary import ThermalBoundary
+from city_model_structure.usage_zone import UsageZone
class ThermalZone:
@@ -86,10 +87,9 @@ class ThermalZone:
self._infiltration_rate_system_off = value
@property
- def usage_zones(self):
+ def usage_zones(self) -> List[UsageZone]:
return self._usage_zones
@usage_zones.setter
def usage_zones(self, values):
self._usage_zones = values
-
diff --git a/city_model_structure/window.py b/city_model_structure/window.py
index 271a1aa9..36085538 100644
--- a/city_model_structure/window.py
+++ b/city_model_structure/window.py
@@ -1,6 +1,5 @@
class Window:
def __init__(self):
- # ToDo: construct this class
self._conductivity_wm_k = None
self._solar_transmittance_at_normal_incidence = None
self._front_side_solar_reflectance_at_normal_incidence = None
diff --git a/geometry/geometry_feeders/city_gml.py b/geometry/geometry_feeders/city_gml.py
index 57ee84f6..ad7c3104 100644
--- a/geometry/geometry_feeders/city_gml.py
+++ b/geometry/geometry_feeders/city_gml.py
@@ -98,7 +98,8 @@ class CityGml:
year_of_construction = o['Building']['yearOfConstruction']
if 'function' in o['Building']:
function = o['Building']['function']
- self._city.add_city_object(CityObject(name, lod, surfaces, terrains, year_of_construction, function))
+ self._city.add_city_object(CityObject(name, lod, surfaces, terrains, year_of_construction, function,
+ self._lower_corner))
return self._city
diff --git a/helpers/assumptions.py b/helpers/assumptions.py
new file mode 100644
index 00000000..13115d60
--- /dev/null
+++ b/helpers/assumptions.py
@@ -0,0 +1,9 @@
+# These values are intended as configurable assumptions
+# ToDo: these values need to be changed into configurable parameters
+
+# convective fluxes
+h_i = 10 # W/m2K
+h_e = 25 # W/m2K
+
+# windows' default values
+frame_ratio = 0
diff --git a/helpers/geometry.py b/helpers/geometry.py
index 5474b531..c99dabff 100644
--- a/helpers/geometry.py
+++ b/helpers/geometry.py
@@ -7,13 +7,42 @@ class Geometry:
self._delta = delta
def almost_equal(self, v1, v2):
- delta = math.sqrt(pow((v1[0]-v2[0]), 2) + pow((v1[1]-v2[1]), 2) + pow((v1[2]-v2[2]), 2))
+ delta = math.sqrt(pow((v1[0] - v2[0]), 2) + pow((v1[1] - v2[1]), 2) + pow((v1[2] - v2[2]), 2))
return delta <= self._delta
+ def is_almost_same_surface(self, s1, s2):
+ # delta is grads an need to be converted into radians
+ delta = np.rad2deg(self._delta)
+ difference = (s1.inclination - s2.inclination) % math.pi
+ if abs(difference) > delta:
+ return False
+ # s1 and s2 are at least almost parallel surfaces
+ # calculate distance point to plane using all the vertex
+ # select surface1 value for the point (X,Y,Z) where two of the values are 0
+ minimum_distance = self._delta + 1
+ parametric = s2.polygon.get_parametric()
+ n2 = s2.normal
+ for point in s1.points:
+ distance = abs(
+ (point[0] * parametric[0]) + (point[1] * parametric[1]) + (point[2] * parametric[2]) + parametric[3])
+ normal_module = math.sqrt(pow(n2[0], 2) + pow(n2[1], 2) + pow(n2[2], 2))
+
+ if normal_module == 0:
+ continue
+ distance = distance / normal_module
+ if distance < minimum_distance:
+ minimum_distance = distance
+ if minimum_distance <= self._delta:
+ break
+ if minimum_distance > self._delta or s1.intersect(s2) is None:
+ return False
+ else:
+ return True
+
@staticmethod
def to_points_matrix(points, remove_last=False):
- rows = points.size//3
+ rows = points.size // 3
points = points.reshape(rows, 3)
if remove_last:
- points = np.delete(points, rows-1, 0)
+ points = np.delete(points, rows - 1, 0)
return points
diff --git a/physics/physics_factory.py b/physics/physics_factory.py
index 7ac3a327..af7c89ea 100644
--- a/physics/physics_factory.py
+++ b/physics/physics_factory.py
@@ -3,16 +3,17 @@ from physics.physics_feeders.us_physics_parameters import UsPhysicsParameters
class PhysicsFactory:
- def __init__(self, handler, city):
+ def __init__(self, handler, city, base_path='data/physics'):
self._handler = handler.lower().replace(' ', '_')
self._city = city
+ self._base_path = base_path
self.factory()
def us_new_york_city(self):
- UsNewYorkCityPhysicsParameters(self._city)
+ UsNewYorkCityPhysicsParameters(self._city, self._base_path)
def us(self):
- UsPhysicsParameters(self._city)
+ UsPhysicsParameters(self._city, self._base_path)
def ca(self):
raise Exception('Not implemented')
diff --git a/physics/physics_feeders/us_base_physics_parameters.py b/physics/physics_feeders/us_base_physics_parameters.py
index bfd00443..b261e4f4 100644
--- a/physics/physics_feeders/us_base_physics_parameters.py
+++ b/physics/physics_feeders/us_base_physics_parameters.py
@@ -6,17 +6,17 @@ from physics.physics_feeders.helpers.us_to_library_types import UsToLibraryTypes
class UsBasePhysicsParameters:
- def __init__(self, climate_zone, city_objects, function_to_type):
+ def __init__(self, climate_zone, city_objects, function_to_type, base_path):
self._climate_zone = climate_zone
self._city_objects = city_objects
# load US Library
- path = str(Path.cwd() / 'data/physics/us_constructions.xml')
+ path = str(Path.cwd() / base_path / 'us_constructions.xml')
with open(path) as xml:
self._library = xmltodict.parse(xml.read(), force_list='layer')
# load US Archetypes
- path = str(Path.cwd() / 'data/physics/us_archetypes.xml')
+ path = str(Path.cwd() / base_path / 'us_archetypes.xml')
with open(path) as xml:
self._archetypes = xmltodict.parse(xml.read(), force_list='layer')
for city_object in self._city_objects:
diff --git a/physics/physics_feeders/us_new_york_city_physics_parameters.py b/physics/physics_feeders/us_new_york_city_physics_parameters.py
index 67b667ff..9bb5549d 100644
--- a/physics/physics_feeders/us_new_york_city_physics_parameters.py
+++ b/physics/physics_feeders/us_new_york_city_physics_parameters.py
@@ -3,7 +3,7 @@ from physics.physics_feeders.helpers.us_pluto_to_function import UsPlutoToFuncti
class UsNewYorkCityPhysicsParameters(UsBasePhysicsParameters):
- def __init__(self, city):
+ def __init__(self, city, base_path):
self._city = city
climate_zone = 'ASHRAE_2004:4A'
- super().__init__(climate_zone, self._city.city_objects, Pf.function)
+ super().__init__(climate_zone, self._city.city_objects, Pf.function, base_path)
diff --git a/physics/physics_feeders/us_physics_parameters.py b/physics/physics_feeders/us_physics_parameters.py
index 79946968..930bc879 100644
--- a/physics/physics_feeders/us_physics_parameters.py
+++ b/physics/physics_feeders/us_physics_parameters.py
@@ -3,8 +3,8 @@ from physics.physics_feeders.helpers.us_to_library_types import UsToLibraryTypes
class UsPhysicsParameters(UsBasePhysicsParameters):
- def __init__(self, city):
+ def __init__(self, city, base_path):
self._city = city
- self._climate_zone = UsToLibraryTypes.city_to_climate_zone(city.city_name)
- super().__init__(self._climate_zone, self._city.city_objects, lambda function: function)
+ self._climate_zone = UsToLibraryTypes.city_to_climate_zone(city.name)
+ super().__init__(self._climate_zone, self._city.city_objects, lambda function: function, base_path)
diff --git a/tests/test_geometry_factory.py b/tests/test_geometry_factory.py
new file mode 100644
index 00000000..bbd36ac4
--- /dev/null
+++ b/tests/test_geometry_factory.py
@@ -0,0 +1,147 @@
+from unittest import TestCase
+from pathlib import Path
+from geometry.geometry_factory import GeometryFactory
+import os
+from city_model_structure.surface import Surface
+
+
+class TestGeometryFactory(TestCase):
+ def setUp(self) -> None:
+ self._city_gml = None
+ self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve()
+
+ def get_citygml(self):
+ if self._city_gml is None:
+ file_path = (self._example_path / 'buildings.gml').resolve()
+ self._city_gml = GeometryFactory('citygml', file_path).city
+ self.assertIsNotNone(self._city_gml, 'city is none')
+ return self._city_gml
+
+ def test_citygml_city(self):
+ city = self.get_citygml()
+ self.assertIsNotNone(city.city_objects, 'city_objects is none')
+ for city_object in city.city_objects:
+ self.assertIsNotNone(city.city_object(city_object.name), 'city_object return none')
+ self.assertIsNotNone(city.srs_name, 'srs_name is none')
+ self.assertIsNotNone(city.lower_corner, 'lower_corner is none')
+ self.assertIsNotNone(city.upper_corner, 'upper_corner is none')
+ self.assertIsNotNone(city.name, 'name is none')
+ self.assertIsNotNone(city.country_code, 'country code is none')
+ self.assertIsNotNone(city.location, 'location is none')
+
+ def test_citygml_city_objects(self):
+ city = self.get_citygml()
+ for city_object in city.city_objects:
+ self.assertIsNotNone(city_object.name, 'city_object name is none')
+ self.assertIsNotNone(city_object.lod, 'city_object lod is none')
+ self.assertIsNotNone(city_object.year_of_construction, 'city_object year_of_construction is none')
+ self.assertIsNotNone(city_object.function, 'city_object function is none')
+ self.assertIsNotNone(city_object.volume, 'city_object volume is none')
+ self.assertIsNotNone(city_object.surfaces, 'city_object surfaces is none')
+ self.assertIsNotNone(city_object.surfaces[0].name, 'surface not found')
+ self.assertIsNotNone(city_object.basement_heated, 'city_object basement_heated is none')
+ self.assertIsNotNone(city_object.attic_heated, 'city_object attic_heated is none')
+ self.assertIsNotNone(city_object.terrains, 'city_object terrains is none')
+ self.assertIsNotNone(city_object.foot_print, 'city_object foot_print is none')
+ self.assertIsNotNone(city_object.usage_zones, 'city_object usage_zones is none')
+ self.assertIsNone(city_object.average_storey_height, 'city_object average_storey_height is not none')
+ self.assertIsNone(city_object.storeys_above_ground, 'city_object storeys_above_ground is not none')
+ self.assertIsNotNone(city_object.heated_volume, 'city_object heated_volume is none')
+ self.assertIsNotNone(city_object.thermal_zones, 'city_object thermal_zones is none')
+ self.assertIsNotNone(city_object.type, 'city_object type is none')
+ self.assertIsNotNone(city_object.max_height, 'city_object max_height is none')
+ city_object.stl_export(self._example_path)
+ os.remove(Path(self._example_path, city_object.name + '.stl').resolve())
+
+ def test_citygml_surfaces(self):
+ city = self.get_citygml()
+ for city_object in city.city_objects:
+ for surface in city_object.surfaces:
+ self.assertIsNotNone(surface.name, 'surface name is none')
+ self.assertIsNotNone(surface.area, 'surface area is none')
+ self.assertIsNotNone(surface.type, 'surface type is none')
+ self.assertIsNotNone(surface.azimuth, 'surface azimuth is none')
+ self.assertIsNotNone(surface.inclination, 'surface inclination is none')
+ self.assertIsNotNone(surface.area_below_ground, 'surface area_below_ground is none')
+ self.assertIsNotNone(surface.area_above_ground, 'surface area_above_ground is none')
+ self.assertIsNotNone(surface.points, 'surface points is none')
+ self.assertIsNotNone(surface.points_list, 'surface points_list is none')
+ self.assertIsNotNone(surface.polygon, 'surface polygon is none')
+ self.assertIsNotNone(surface.shapely, 'surface shapely is none')
+ self.assertIsNotNone(surface.global_irradiance_hour, 'surface global_irradiance_hour is none')
+ self.assertIsNotNone(surface.global_irradiance_month, 'surface global_irradiance_month is none')
+ self.assertIsNotNone(surface.normal, 'surface normal is none')
+ self.assertIsNotNone(surface.projection, 'surface projection is none')
+ self.assertIsNotNone(surface.swr, 'surface swr is none')
+ self.assertIsNotNone(surface.min_x, 'surface min_x is none')
+ self.assertIsNotNone(surface.min_y, 'surface min_y is none')
+ self.assertIsNotNone(surface.min_z, 'surface min_z is none')
+ self.assertIsNotNone(surface.ground_polygon, 'surface ground_polygon is none')
+ self.assertIsNotNone(surface.ground_points, 'surface ground_points is none')
+ self.assertIsNotNone(surface.intersect(surface), 'self intersection is none')
+
+ def test_citygml_thermal_zone(self):
+ city = self.get_citygml()
+ for city_object in city.city_objects:
+ for thermal_zone in city_object.thermal_zones:
+ self.assertIsNotNone(thermal_zone.surfaces, 'thermal_zone surfaces is none')
+ self.assertIsNotNone(thermal_zone.bounded, 'thermal_zone bounded is none')
+ self.assertIsNotNone(thermal_zone.floor_area, 'thermal_zone floor_area is none')
+ self.assertIsNotNone(thermal_zone.heated, 'thermal_zone heated is none')
+ self.assertIsNotNone(thermal_zone.cooled, 'thermal_zone cooled is none')
+ self.assertIsNone(thermal_zone.additional_thermal_bridge_u_value,
+ 'thermal_zone additional_thermal_bridge_u_value is not none')
+ self.assertIsNone(thermal_zone.effective_thermal_capacity,
+ 'thermal_zone effective_thermal_capacity is not none')
+ self.assertIsNone(thermal_zone.indirectly_heated_area_ratio
+ , 'thermal_zone indirectly_heated_area_ratio is not none')
+ self.assertIsNone(thermal_zone.infiltration_rate_system_off,
+ 'thermal_zone infiltration_rate_system_off is not none')
+ self.assertIsNone(thermal_zone.infiltration_rate_system_on,
+ 'thermal_zone infiltration_rate_system_on is not none')
+ self.assertIsNone(thermal_zone.usage_zones,
+ 'thermal_zone usage_zones are not none')
+
+ def test_citygml_thermal_boundary(self):
+ city = self.get_citygml()
+ for city_object in city.city_objects:
+ for thermal_zone in city_object.thermal_zones:
+ for thermal_boundary in thermal_zone.bounded:
+ self.assertIsNotNone(thermal_boundary.type, 'thermal_boundary type is none')
+ self.assertIsNotNone(thermal_boundary.area, 'thermal_boundary area is none')
+ self.assertIsNotNone(thermal_boundary.area_above_ground, 'thermal_boundary area_above_ground is none')
+ self.assertIsNotNone(thermal_boundary.area_below_ground, 'thermal_boundary area_below_ground is none')
+ self.assertIsNotNone(thermal_boundary.azimuth, 'thermal_boundary azimuth is none')
+ self.assertIsNotNone(thermal_boundary.delimits, 'thermal_boundary delimits is none')
+ self.assertIsNotNone(thermal_boundary.inclination, 'thermal_boundary inclination is none')
+ self.assertRaises(Exception, lambda: thermal_boundary.u_value, 'thermal_boundary u_value was initialized')
+ self.assertIsNone(thermal_boundary.layers, 'thermal_boundary layers was initialized')
+ self.assertRaises(Exception, lambda: thermal_boundary.outside_solar_absorptance,
+ 'thermal_boundary outside_solar_absorptance was initialized')
+ self.assertIsNone(thermal_boundary.outside_thermal_absorptance,
+ 'thermal_boundary outside_thermal_absorptance was initialized')
+ self.assertIsNone(thermal_boundary.outside_visible_absorptance,
+ 'thermal_boundary outside_visible_absorptance was initialized')
+ self.assertRaises(Exception, lambda: thermal_boundary.shortwave_reflectance,
+ 'thermal_boundary shortwave_reflectance was initialized')
+ self.assertRaises(Exception, lambda: thermal_boundary.window_area,
+ 'thermal_boundary window_area was initialized')
+ self.assertIsNone(thermal_boundary.window_ratio, 'thermal_boundary window_ratio was initialized')
+
+ def test_citygml_thermal_opening(self):
+ city = self.get_citygml()
+ for city_object in city.city_objects:
+ for thermal_zone in city_object.thermal_zones:
+ for thermal_boundary in thermal_zone.bounded:
+ for thermal_opening in thermal_boundary.thermal_openings:
+ self.assertIsNone(thermal_opening.area, 'thermal_opening area was initialized')
+ self.assertTrue(thermal_opening.frame_ratio == 0, 'thermal_opening frame_ratio was not 0')
+ self.assertIsNone(thermal_opening.g_value, 'thermal_opening g_value was initialized')
+ self.assertIsNone(thermal_opening.conductivity_w_mk, 'thermal_opening conductivity_w_mk was initialized')
+ self.assertIsNone(thermal_opening.inside_reflectance, 'thermal_opening inside_reflectance was initialized')
+ self.assertRaises(Exception, lambda: thermal_opening.openable_ratio,
+ 'thermal_opening openable_ratio is not raising an exception')
+ self.assertIsNone(thermal_opening.outside_reflectance,
+ 'thermal_opening outside_reflectance was initialized')
+ self.assertIsNone(thermal_opening.thickness_m, 'thermal_opening thickness_m was initialized')
+ self.assertRaises(Exception, lambda: thermal_opening.u_value, 'thermal_opening u_value was initialized')
diff --git a/tests/test_physics_factory.py b/tests/test_physics_factory.py
new file mode 100644
index 00000000..889d6690
--- /dev/null
+++ b/tests/test_physics_factory.py
@@ -0,0 +1,27 @@
+from unittest import TestCase
+from pathlib import Path
+from geometry.geometry_factory import GeometryFactory
+from physics.physics_factory import PhysicsFactory
+
+
+class TestPhysicsFactory(TestCase):
+ def setUp(self) -> None:
+ self._city_gml = None
+ self._nyc_with_physics = None
+ self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve()
+
+ def get_citygml(self):
+ if self._city_gml is None:
+ file_path = (self._example_path / 'buildings.gml').resolve()
+ self._city_gml = GeometryFactory('citygml', file_path).city
+ self.assertIsNotNone(self._city_gml, 'city is none')
+ return self._city_gml
+
+ def get_city_with_physics(self):
+ if self._nyc_with_physics is None:
+ self._nyc_with_physics = self.get_citygml()
+ PhysicsFactory('us_new_york_city', self._nyc_with_physics, base_path=self._example_path)
+ return self._nyc_with_physics
+
+ def test_city_with_physics(self):
+ city = self.get_city_with_physics()
\ No newline at end of file
diff --git a/tests_data/buildings.gml b/tests_data/buildings.gml
new file mode 100644
index 00000000..a86229e2
--- /dev/null
+++ b/tests_data/buildings.gml
@@ -0,0 +1,626 @@
+
+
+ Gowanus 2050 Best Practice Scenario
+
+
+ 301067.2312223603 55182.50627018605 -15.371661394834565
+ 301852.9778043915 57588.63517643605 75.67673757672314
+
+
+
+
+
+ 1965
+
+
+ I1
+
+
+
+
+
+
+
+
+
+ 301010.4314176728 57301.3749225298 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727
+
+
+
+
+
+
+
+
+ 301014.183859079 57308.78849674855 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727
+
+
+
+
+
+
+
+
+ 301004.1125700165 57288.87345768605 10.786276534199727 300992.0398161103 57285.56779362355 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727
+
+
+
+
+
+
+
+
+ 301024.4275114228 57311.0624225298 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727
+
+
+
+
+
+
+
+
+ 301000.3254606415 57281.3758990923 10.786276534199727 300997.2820036103 57275.3758990923 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727
+
+
+
+
+
+
+
+
+ 301004.1125700165 57288.87345768605 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727
+
+
+
+
+
+
+
+
+ 301017.183859079 57314.7147662798 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727 301017.183859079 57314.7147662798 10.786276534199727
+
+
+
+
+
+
+
+
+ 301005.9055387665 57312.9716022173 10.786276534199727 301002.1530973603 57305.55900456105 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727 301005.9055387665 57312.9716022173 10.786276534199727
+
+
+
+
+
+
+
+
+ 300995.8337614228 57293.0555865923 10.786276534199727 300992.0398161103 57285.56779362355 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727 300995.8337614228 57293.0555865923 10.786276534199727
+
+
+
+
+
+
+
+
+ 301014.183859079 57308.78849674855 10.786276534199727 301002.1530973603 57305.55900456105 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727
+
+
+
+
+
+
+
+
+ 301005.9055387665 57312.9716022173 10.786276534199727 301005.9055387665 57312.9716022173 0.0 301002.1530973603 57305.55900456105 10.786276534199727 301005.9055387665 57312.9716022173 10.786276534199727
+
+
+
+
+
+
+
+
+ 301002.1530973603 57305.55900456105 10.786276534199727 301005.9055387665 57312.9716022173 0.0 301002.1530973603 57305.55900456105 0.0 301002.1530973603 57305.55900456105 10.786276534199727
+
+
+
+
+
+
+
+
+ 301017.183859079 57314.7147662798 0.0 301024.4275114228 57311.0624225298 0.0 301014.183859079 57308.78849674855 0.0 301017.183859079 57314.7147662798 0.0
+
+
+
+
+
+
+
+
+ 301005.9055387665 57312.9716022173 0.0 301014.183859079 57308.78849674855 0.0 301002.1530973603 57305.55900456105 0.0 301005.9055387665 57312.9716022173 0.0
+
+
+
+
+
+
+
+
+ 300995.8337614228 57293.0555865923 0.0 301004.1125700165 57288.87345768605 0.0 300992.0398161103 57285.56779362355 0.0 300995.8337614228 57293.0555865923 0.0
+
+
+
+
+
+
+
+
+ 301014.183859079 57308.78849674855 0.0 301010.4314176728 57301.3749225298 0.0 301002.1530973603 57305.55900456105 0.0 301014.183859079 57308.78849674855 0.0
+
+
+
+
+
+
+
+
+ 301010.4314176728 57301.3749225298 0.0 301024.4275114228 57311.0624225298 0.0 301004.1125700165 57288.87345768605 0.0 301010.4314176728 57301.3749225298 0.0
+
+
+
+
+
+
+
+
+ 301014.183859079 57308.78849674855 0.0 301024.4275114228 57311.0624225298 0.0 301010.4314176728 57301.3749225298 0.0 301014.183859079 57308.78849674855 0.0
+
+
+
+
+
+
+
+
+ 301024.4275114228 57311.0624225298 0.0 301004.5266325165 57271.70548893605 0.0 301004.1125700165 57288.87345768605 0.0 301024.4275114228 57311.0624225298 0.0
+
+
+
+
+
+
+
+
+ 301004.1125700165 57288.87345768605 0.0 301000.3254606415 57281.3758990923 0.0 300992.0398161103 57285.56779362355 0.0 301004.1125700165 57288.87345768605 0.0
+
+
+
+
+
+
+
+
+ 301000.3254606415 57281.3758990923 0.0 301004.5266325165 57271.70548893605 0.0 300997.2820036103 57275.3758990923 0.0 301000.3254606415 57281.3758990923 0.0
+
+
+
+
+
+
+
+
+ 301004.1125700165 57288.87345768605 0.0 301004.5266325165 57271.70548893605 0.0 301000.3254606415 57281.3758990923 0.0 301004.1125700165 57288.87345768605 0.0
+
+
+
+
+
+
+
+
+ 301014.183859079 57308.78849674855 10.786276534199727 301014.183859079 57308.78849674855 0.0 301005.9055387665 57312.9716022173 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727
+
+
+
+
+
+
+
+
+ 301005.9055387665 57312.9716022173 10.786276534199727 301014.183859079 57308.78849674855 0.0 301005.9055387665 57312.9716022173 0.0 301005.9055387665 57312.9716022173 10.786276534199727
+
+
+
+
+
+
+
+
+ 301017.183859079 57314.7147662798 10.786276534199727 301017.183859079 57314.7147662798 0.0 301014.183859079 57308.78849674855 10.786276534199727 301017.183859079 57314.7147662798 10.786276534199727
+
+
+
+
+
+
+
+
+ 301014.183859079 57308.78849674855 10.786276534199727 301017.183859079 57314.7147662798 0.0 301014.183859079 57308.78849674855 0.0 301014.183859079 57308.78849674855 10.786276534199727
+
+
+
+
+
+
+
+
+ 301002.1530973603 57305.55900456105 10.786276534199727 301002.1530973603 57305.55900456105 0.0 301010.4314176728 57301.3749225298 10.786276534199727 301002.1530973603 57305.55900456105 10.786276534199727
+
+
+
+
+
+
+
+
+ 301010.4314176728 57301.3749225298 10.786276534199727 301002.1530973603 57305.55900456105 0.0 301010.4314176728 57301.3749225298 0.0 301010.4314176728 57301.3749225298 10.786276534199727
+
+
+
+
+
+
+
+
+ 301024.4275114228 57311.0624225298 10.786276534199727 301024.4275114228 57311.0624225298 0.0 301017.183859079 57314.7147662798 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727
+
+
+
+
+
+
+
+
+ 301017.183859079 57314.7147662798 10.786276534199727 301024.4275114228 57311.0624225298 0.0 301017.183859079 57314.7147662798 0.0 301017.183859079 57314.7147662798 10.786276534199727
+
+
+
+
+
+
+
+
+ 301004.5266325165 57271.70548893605 10.786276534199727 301004.5266325165 57271.70548893605 0.0 301024.4275114228 57311.0624225298 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727
+
+
+
+
+
+
+
+
+ 301024.4275114228 57311.0624225298 10.786276534199727 301004.5266325165 57271.70548893605 0.0 301024.4275114228 57311.0624225298 0.0 301024.4275114228 57311.0624225298 10.786276534199727
+
+
+
+
+
+
+
+
+ 300997.2820036103 57275.3758990923 10.786276534199727 300997.2820036103 57275.3758990923 0.0 301004.5266325165 57271.70548893605 10.786276534199727 300997.2820036103 57275.3758990923 10.786276534199727
+
+
+
+
+
+
+
+
+ 301004.5266325165 57271.70548893605 10.786276534199727 300997.2820036103 57275.3758990923 0.0 301004.5266325165 57271.70548893605 0.0 301004.5266325165 57271.70548893605 10.786276534199727
+
+
+
+
+
+
+
+
+ 301010.4314176728 57301.3749225298 10.786276534199727 301010.4314176728 57301.3749225298 0.0 301004.1125700165 57288.87345768605 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727
+
+
+
+
+
+
+
+
+ 301004.1125700165 57288.87345768605 10.786276534199727 301010.4314176728 57301.3749225298 0.0 301004.1125700165 57288.87345768605 0.0 301004.1125700165 57288.87345768605 10.786276534199727
+
+
+
+
+
+
+
+
+ 301004.1125700165 57288.87345768605 10.786276534199727 301004.1125700165 57288.87345768605 0.0 300995.8337614228 57293.0555865923 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727
+
+
+
+
+
+
+
+
+ 300995.8337614228 57293.0555865923 10.786276534199727 301004.1125700165 57288.87345768605 0.0 300995.8337614228 57293.0555865923 0.0 300995.8337614228 57293.0555865923 10.786276534199727
+
+
+
+
+
+
+
+
+ 301000.3254606415 57281.3758990923 10.786276534199727 301000.3254606415 57281.3758990923 0.0 300997.2820036103 57275.3758990923 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727
+
+
+
+
+
+
+
+
+ 300997.2820036103 57275.3758990923 10.786276534199727 301000.3254606415 57281.3758990923 0.0 300997.2820036103 57275.3758990923 0.0 300997.2820036103 57275.3758990923 10.786276534199727
+
+
+
+
+
+
+
+
+ 300995.8337614228 57293.0555865923 10.786276534199727 300995.8337614228 57293.0555865923 0.0 300992.0398161103 57285.56779362355 10.786276534199727 300995.8337614228 57293.0555865923 10.786276534199727
+
+
+
+
+
+
+
+
+ 300992.0398161103 57285.56779362355 10.786276534199727 300995.8337614228 57293.0555865923 0.0 300992.0398161103 57285.56779362355 0.0 300992.0398161103 57285.56779362355 10.786276534199727
+
+
+
+
+
+
+
+
+ 300992.0398161103 57285.56779362355 10.786276534199727 300992.0398161103 57285.56779362355 0.0 301000.3254606415 57281.3758990923 10.786276534199727 300992.0398161103 57285.56779362355 10.786276534199727
+
+
+
+
+
+
+
+
+ 301000.3254606415 57281.3758990923 10.786276534199727 300992.0398161103 57285.56779362355 0.0 301000.3254606415 57281.3758990923 0.0 301000.3254606415 57281.3758990923 10.786276534199727
+
+
+
+
+
+
+
+
+ 1965
+ I1
+
+
+
+
+
+ 2045
+
+
+ I1
+
+
+
+
+
+
+
+
+
+ 300906.4538786103 56181.20939518605 7.9999997168779355 300897.539327829 56167.5155475298 7.9999997168779355 300906.4538786103 56181.20939518605 0.0 300906.4538786103 56181.20939518605 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300897.539327829 56167.5155475298 7.9999997168779355 300897.539327829 56167.5155475298 0.0 300906.4538786103 56181.20939518605 0.0 300897.539327829 56167.5155475298 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300883.334249704 56196.25919987355 7.9999997168779355 300861.299093454 56191.1053912798 7.9999997168779355 300897.539327829 56167.5155475298 7.9999997168779355 300883.334249704 56196.25919987355 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300906.4538786103 56181.20939518605 7.9999997168779355 300883.334249704 56196.25919987355 7.9999997168779355 300897.539327829 56167.5155475298 7.9999997168779355 300906.4538786103 56181.20939518605 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300896.0696012665 56215.82365299855 7.9999997168779355 300882.9489957978 56224.3641803423 7.9999997168779355 300883.334249704 56196.25919987355 7.9999997168779355 300896.0696012665 56215.82365299855 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300882.9489957978 56224.3641803423 7.9999997168779355 300861.299093454 56191.1053912798 7.9999997168779355 300883.334249704 56196.25919987355 7.9999997168779355 300882.9489957978 56224.3641803423 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300883.334249704 56196.25919987355 7.9999997168779355 300883.334249704 56196.25919987355 0.0 300896.0696012665 56215.82365299855 7.9999997168779355 300883.334249704 56196.25919987355 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300896.0696012665 56215.82365299855 7.9999997168779355 300883.334249704 56196.25919987355 0.0 300896.0696012665 56215.82365299855 0.0 300896.0696012665 56215.82365299855 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300883.334249704 56196.25919987355 7.9999997168779355 300906.4538786103 56181.20939518605 7.9999997168779355 300883.334249704 56196.25919987355 0.0 300883.334249704 56196.25919987355 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300906.4538786103 56181.20939518605 7.9999997168779355 300906.4538786103 56181.20939518605 0.0 300883.334249704 56196.25919987355 0.0 300906.4538786103 56181.20939518605 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300896.0696012665 56215.82365299855 0.0 300883.334249704 56196.25919987355 0.0 300882.9489957978 56224.3641803423 0.0 300896.0696012665 56215.82365299855 0.0
+
+
+
+
+
+
+
+
+ 300882.9489957978 56224.3641803423 0.0 300883.334249704 56196.25919987355 0.0 300861.299093454 56191.1053912798 0.0 300882.9489957978 56224.3641803423 0.0
+
+
+
+
+
+
+
+
+ 300883.334249704 56196.25919987355 0.0 300897.539327829 56167.5155475298 0.0 300861.299093454 56191.1053912798 0.0 300883.334249704 56196.25919987355 0.0
+
+
+
+
+
+
+
+
+ 300906.4538786103 56181.20939518605 0.0 300897.539327829 56167.5155475298 0.0 300883.334249704 56196.25919987355 0.0 300906.4538786103 56181.20939518605 0.0
+
+
+
+
+
+
+
+
+ 300882.9489957978 56224.3641803423 7.9999997168779355 300896.0696012665 56215.82365299855 7.9999997168779355 300882.9489957978 56224.3641803423 0.0 300882.9489957978 56224.3641803423 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300896.0696012665 56215.82365299855 7.9999997168779355 300896.0696012665 56215.82365299855 0.0 300882.9489957978 56224.3641803423 0.0 300896.0696012665 56215.82365299855 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300882.9489957978 56224.3641803423 7.9999997168779355 300882.9489957978 56224.3641803423 0.0 300861.299093454 56191.1053912798 7.9999997168779355 300882.9489957978 56224.3641803423 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300861.299093454 56191.1053912798 7.9999997168779355 300882.9489957978 56224.3641803423 0.0 300861.299093454 56191.1053912798 0.0 300861.299093454 56191.1053912798 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300897.539327829 56167.5155475298 7.9999997168779355 300861.299093454 56191.1053912798 7.9999997168779355 300897.539327829 56167.5155475298 0.0 300897.539327829 56167.5155475298 7.9999997168779355
+
+
+
+
+
+
+
+
+ 300861.299093454 56191.1053912798 7.9999997168779355 300861.299093454 56191.1053912798 0.0 300897.539327829 56167.5155475298 0.0 300861.299093454 56191.1053912798 7.9999997168779355
+
+
+
+
+
+
+
+
+ 2045
+ I1
+
+
+
\ No newline at end of file
diff --git a/tests_data/us_archetypes.xml b/tests_data/us_archetypes.xml
new file mode 100644
index 00000000..bbe49e54
--- /dev/null
+++ b/tests_data/us_archetypes.xml
@@ -0,0 +1,786 @@
+
+
+
+
+
+ 0.21
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 2
+ 130
+ 0.15
+ 0.15
+ 0.5
+ 0
+
+
+
+
+ 0.33
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.34
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.38
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.34
+ 3
+ 130
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.35
+ 4
+
+
+ 0
+
+
+
+ 0.0019
+ 2
+
+
+ 3.96
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.33
+ 4
+
+
+ 0
+
+
+
+ 0.0068
+ 2
+
+
+ 3.96
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.07
+ 4
+
+
+ 0
+
+
+
+ 0.0064
+ 2
+
+
+ 6.1
+ 3
+ 130
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.11
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 5.18
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.11
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 6.1
+ 1
+ 130
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.14
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.17
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.11
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.3
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 130
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.15
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 4.27
+ 3
+ 130
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.2
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.006
+ 4
+
+
+ 0
+
+
+
+ .0032
+ 2
+
+
+ 8.53
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.15
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.3
+ 4
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.15
+ 0.15
+ 0.50
+ 0
+
+
+
+
+ 0.21
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 2
+ 130
+ 0.05
+ 0.15
+ 0.1
+ 0
+
+
+
+
+ 0.33
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.34
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.1
+ 0
+
+
+
+
+ 0.38
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.34
+ 3
+ 130
+ 0.05
+ 0.15
+ 0.1
+ 0
+
+
+
+
+ 0.35
+ 3
+
+
+ 0
+
+
+
+ 0.0019
+ 1
+
+
+ 3.96
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.1
+ 0
+
+
+
+
+ 0.33
+ 3
+
+
+ 0
+
+
+
+ 0.0068
+ 1
+
+
+ 3.96
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.1
+ 0
+
+
+
+
+ 0.07
+ 3
+
+
+ 0
+
+
+
+ 0.0064
+ 1
+
+
+ 6.1
+ 3
+ 130
+ 0.05
+ 0.15
+ 0.1
+ 0
+
+
+
+
+ 0.11
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 5.18
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.1
+ 0
+
+
+
+
+ 0.11
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 6.1
+ 1
+ 130
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.14
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.17
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.11
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.3
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 130
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.15
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 4.27
+ 3
+ 130
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.2
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.006
+ 3
+
+
+ 0
+
+
+
+ .0032
+ 1
+
+
+ 8.53
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.15
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
+
+ 0.3
+ 3
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 3.05
+ 3
+ 90
+ 0.05
+ 0.15
+ 0.10
+ 0
+
+
+
diff --git a/tests_data/us_constructions.xml b/tests_data/us_constructions.xml
new file mode 100644
index 00000000..c18ec613
--- /dev/null
+++ b/tests_data/us_constructions.xml
@@ -0,0 +1,606 @@
+
+
+
+
+ 0.32
+ 0
+ 0.003
+ 0.301483
+ 0.648517
+ 0.648517
+ 0.0133144
+
+
+ 0.49
+ 0
+ 0.003
+ 0.481761
+ 0.468239
+ 0.468239
+ 0.03026
+
+
+ 0.35
+ 0
+ 0.003
+ 0.328881
+ 0.621119
+ 0.621119
+ 0.0071399
+
+
+ 0.36
+ 0
+ 0.003
+ 0.354957
+ 0.595043
+ 0.595043
+ 0.0134755
+
+
+
+
+ 1.311
+ 2240
+ 836.8
+ 0.9
+ 0.7
+ 0.7
+
+
+ true
+ 0.21648
+ 0.9
+ 0.7
+ 0.8
+
+
+ 0.045
+ 265
+ 836.8
+ 0.9
+ 0.7
+ 0.7
+
+
+ 0.6918
+ 1858
+ 837
+ 0.9
+ 0.92
+ 0.92
+
+
+ 1.7296
+ 2243
+ 837
+ 0.9
+ 0.65
+ 0.65
+
+
+ 0.0432
+ 91
+ 837
+ 0.9
+ 0.5
+ 0.5
+
+
+ 0.16
+ 784.9
+ 830
+ 0.9
+ 0.92
+ 0.92
+
+
+ 0.11
+ 544.62
+ 1210
+ 0.9
+ 0.78
+ 0.78
+
+
+ 0.115
+ 513
+ 1255
+ 0.9
+ 0.78
+ 0.78
+
+
+ 0.1211
+ 593
+ 2510
+ 0.9
+ 0.78
+ 0.78
+
+
+ 0.049
+ 265
+ 836.8
+ 0.9
+ 0.7
+ 0.7
+
+
+ true
+ 0.36256
+ 0.9
+ 0.7
+ 0.7
+
+
+ true
+ 0.36256
+ 0.9
+ 0.7
+ 0.7
+
+
+ 45.006
+ 7680
+ 418.4
+ 0.9
+ 0.7
+ 0.3
+
+
+ 0.16
+ 1121.29
+ 1460
+ 0.9
+ 0.7
+ 0.7
+
+
+ true
+ 0.21648
+ 0.9
+ 0.7
+ 0.8
+
+
+ true
+ 0.36256
+ 0.9
+ 0.7
+ 0.7
+
+
+ true
+ 0.21648
+ 0.9
+ 0.7
+ 0.8
+
+
+ true
+ 0.36256
+ 0.9
+ 0.7
+ 0.7
+
+
+ true
+ 0.21648
+ 0.9
+ 0.7
+ 0.8
+
+
+ 0.045
+ 265
+ 836.8
+ 0.9
+ 0.7
+ 0.7
+
+
+ 44.96
+ 7688.86
+ 410
+ 0.9
+ 0.2
+ 0.2
+
+
+
+
+
+
+
+ 3
+ 0.0795397
+
+
+ 1
+ 0.20321
+
+
+ 2
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 4
+ 0.0253
+
+
+ 5
+ 0.2033
+
+
+ 6
+ 0.0680962
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 8
+ 0.01
+
+
+ 3
+ 0.0746874
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.78
+ 0.9
+ 0.78
+
+
+ 9
+ 0.0178
+
+
+ 10
+ 0.0254
+
+
+ 11
+ 0.375211
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.78
+ 0.9
+ 0.78
+
+
+ 9
+ 0.0178
+
+
+ 10
+ 0.0254
+
+
+ 11
+ 0.221604
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 12
+
+
+ 3
+ 0.118387
+
+
+ 7
+ 0.01271
+
+
+
+
+
+
+ 1
+ 0.20321
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 13
+
+
+ 3
+ 0.0373223
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.7
+ 0.9
+ 0.7
+
+
+ 15
+ 0.0095
+
+
+ 11
+ 0.210538
+
+
+ 14
+ 0.001524
+
+
+
+
+
+
+ 1
+ 0.1016
+
+
+
+
+ 0.7
+ 0.9
+ 0.3
+
+
+ 14
+ 0.001524
+
+
+ 11
+ 0.23578
+
+
+
+
+
+
+ 1
+ 0.1016
+
+
+ 16
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 4
+ 0.0253
+
+
+ 5
+ 0.2033
+
+
+ 6
+ 0.0338606
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.7
+ 0.9
+ 0.3
+
+
+ 14
+ 0.001524
+
+
+ 11
+ 0.123533
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 17
+
+
+ 3
+ 0.118387
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 8
+ 0.01
+
+
+ 3
+ 0.110422
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.7
+ 0.9
+ 0.7
+
+
+ 15
+ 0.0095
+
+
+ 11
+ 0.124958
+
+
+ 14
+ 0.001524
+
+
+
+
+
+
+ 3
+ 0.0463846
+
+
+ 1
+ 0.20321
+
+
+ 18
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 19
+
+
+ 3
+ 0.0971136
+
+
+ 7
+ 0.01271
+
+
+
+
+
+
+ 1
+ 0.20321
+
+
+
+
+
+
+ 1
+ 0.1016
+
+
+
+
+
+
+ 1
+ 0.1016
+
+
+ 20
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 22
+ 0.0015
+
+
+ 21
+ 0.139618
+
+
+ 7
+ 0.01271
+
+
+
+
+ 0.92
+ 0.9
+ 0.92
+
+
+ 22
+ 0.0015
+
+
+ 21
+ 0.0598725
+
+
+ 7
+ 0.01271
+
+
+
+
+
\ No newline at end of file