feat: PV workflow updated and modified

This commit is contained in:
Saeed Ranjbar 2024-11-13 12:11:11 +01:00
parent b6ec189207
commit 69a1af535b
13 changed files with 1508 additions and 1099 deletions

View File

@ -4,16 +4,16 @@ from shapely import Point
from pathlib import Path
def process_geojson(x, y, diff, expansion=False):
def process_geojson(x, y, diff, path, expansion=False):
selection_box = Polygon([[x + diff, y - diff],
[x - diff, y - diff],
[x - diff, y + diff],
[x + diff, y + diff]])
geojson_file = Path('./data/collinear_clean 2.geojson').resolve()
geojson_file = Path(path / 'data/collinear_clean 2.geojson').resolve()
if not expansion:
output_file = Path('./input_files/output_buildings.geojson').resolve()
output_file = Path(path / 'input_files/output_buildings.geojson').resolve()
else:
output_file = Path('./input_files/output_buildings_expanded.geojson').resolve()
output_file = Path(path / 'input_files/output_buildings_expanded.geojson').resolve()
buildings_in_region = []
with open(geojson_file, 'r') as file:

View File

@ -6,8 +6,8 @@ class HourlyElectricityDemand:
def calculate(self):
hourly_electricity_consumption = []
energy_systems = self.building.energy_systems
appliance = self.building.appliances_electrical_demand[cte.HOUR]
lighting = self.building.lighting_electrical_demand[cte.HOUR]
appliance = self.building.appliances_electrical_demand[cte.HOUR] if self.building.appliances_electrical_demand else 0
lighting = self.building.lighting_electrical_demand[cte.HOUR] if self.building.lighting_electrical_demand else 0
elec_heating = 0
elec_cooling = 0
elec_dhw = 0
@ -59,10 +59,12 @@ class HourlyElectricityDemand:
else:
cooling = self.building.cooling_consumption[cte.HOUR]
for i in range(len(self.building.heating_demand[cte.HOUR])):
for i in range(8760):
hourly = 0
hourly += appliance[i]
hourly += lighting[i]
if isinstance(appliance, list):
hourly += appliance[i]
if isinstance(lighting, list):
hourly += lighting[i]
if heating is not None:
hourly += heating[i]
if cooling is not None:

View File

@ -0,0 +1,235 @@
import math
import csv
import hub.helpers.constants as cte
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.electricity_demand_calculator import \
HourlyElectricityDemand
from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory
from hub.helpers.monthly_values import MonthlyValues
class PvSystemAssessment:
def __init__(self, building=None, pv_system=None, battery=None, tilt_angle=None, solar_angles=None,
system_type=None, pv_installation_type=None, simulation_model_type=None, module_model_name=None,
inverter_efficiency=None, system_catalogue_handler=None, roof_percentage_coverage=None,
facade_coverage_percentage=None, csv_output=False, output_path=None):
"""
:param building:
:param tilt_angle:
:param solar_angles:
:param system_type:
:param simulation_model_type:
:param module_model_name:
:param inverter_efficiency:
:param system_catalogue_handler:
:param roof_percentage_coverage:
:param facade_coverage_percentage:
"""
self.building = building
self.tilt_angle = tilt_angle
self.solar_angles = solar_angles
self.system_type = system_type
self.pv_installation_type = pv_installation_type
self.simulation_model_type = simulation_model_type
self.module_model_name = module_model_name
self.inverter_efficiency = inverter_efficiency
self.system_catalogue_handler = system_catalogue_handler
self.roof_percentage_coverage = roof_percentage_coverage
self.facade_coverage_percentage = facade_coverage_percentage
self.pv_hourly_generation = None
self.t_cell = None
self.results = {}
self.csv_output = csv_output
self.output_path = output_path
if pv_system is not None:
self.pv_system = pv_system
else:
for energy_system in self.building.energy_systems:
for generation_system in energy_system.generation_systems:
if generation_system.system_type == cte.PHOTOVOLTAIC:
self.pv_system = generation_system
if battery is not None:
self.battery = battery
else:
for energy_system in self.building.energy_systems:
for generation_system in energy_system.generation_systems:
if generation_system.system_type == cte.PHOTOVOLTAIC and generation_system.energy_storage_systems is not None:
for storage_system in generation_system.energy_storage_systems:
if storage_system.type_energy_stored == cte.ELECTRICAL:
self.battery = storage_system
@staticmethod
def explicit_model(standard_test_condition_maximum_power, standard_test_condition_radiation,
cell_temperature_coefficient, standard_test_condition_cell_temperature, nominal_radiation,
nominal_cell_temperature, nominal_ambient_temperature, inverter_efficiency, number_of_panels,
irradiance, outdoor_temperature):
inverter_efficiency = inverter_efficiency
stc_power = float(standard_test_condition_maximum_power)
stc_irradiance = float(standard_test_condition_radiation)
cell_temperature_coefficient = float(cell_temperature_coefficient) / 100 if (
cell_temperature_coefficient is not None) else None
stc_t_cell = float(standard_test_condition_cell_temperature)
nominal_condition_irradiance = float(nominal_radiation)
nominal_condition_cell_temperature = float(nominal_cell_temperature)
nominal_t_out = float(nominal_ambient_temperature)
g_i = irradiance
t_out = outdoor_temperature
t_cell = []
pv_output = []
for i in range(len(g_i)):
t_cell.append((t_out[i] + (g_i[i] / nominal_condition_irradiance) *
(nominal_condition_cell_temperature - nominal_t_out)))
pv_output.append((inverter_efficiency * number_of_panels * (stc_power * (g_i[i] / stc_irradiance) *
(1 - cell_temperature_coefficient *
(t_cell[i] - stc_t_cell)))))
return pv_output
def rooftop_sizing(self):
pv_system = self.pv_system
if self.module_model_name is not None:
self.system_assignation()
# System Sizing
module_width = float(pv_system.width)
module_height = float(pv_system.height)
roof_area = 0
for roof in self.building.roofs:
roof_area += roof.perimeter_area
pv_module_area = module_width * module_height
available_roof = (self.roof_percentage_coverage * roof_area)
# Inter-Row Spacing
winter_solstice = self.solar_angles[(self.solar_angles['AST'].dt.month == 12) &
(self.solar_angles['AST'].dt.day == 21) &
(self.solar_angles['AST'].dt.hour == 12)]
solar_altitude = winter_solstice['solar altitude'].values[0]
solar_azimuth = winter_solstice['solar azimuth'].values[0]
distance = ((module_height * math.sin(math.radians(self.tilt_angle)) * abs(math.cos(math.radians(solar_azimuth)))) / math.tan(math.radians(solar_altitude)))
distance = float(format(distance, '.2f'))
# Calculation of the number of panels
space_dimension = math.sqrt(available_roof)
space_dimension = float(format(space_dimension, '.2f'))
panels_per_row = math.ceil(space_dimension / module_width)
number_of_rows = math.ceil(space_dimension / distance)
total_number_of_panels = panels_per_row * number_of_rows
total_pv_area = total_number_of_panels * pv_module_area
self.building.roofs[0].installed_solar_collector_area = total_pv_area
return panels_per_row, number_of_rows
def system_assignation(self):
generation_units_catalogue = EnergySystemsCatalogFactory(self.system_catalogue_handler).catalog
catalog_pv_generation_equipments = [component for component in
generation_units_catalogue.entries('generation_equipments') if
component.system_type == 'photovoltaic']
selected_pv_module = None
for pv_module in catalog_pv_generation_equipments:
if self.module_model_name == pv_module.model_name:
selected_pv_module = pv_module
if selected_pv_module is None:
raise ValueError("No PV module with the provided model name exists in the catalogue")
for energy_system in self.building.energy_systems:
for idx, generation_system in enumerate(energy_system.generation_systems):
if generation_system.system_type == cte.PHOTOVOLTAIC:
new_system = selected_pv_module
# Preserve attributes that exist in the original but not in the new system
for attr in dir(generation_system):
# Skip private attributes and methods
if not attr.startswith('__') and not callable(getattr(generation_system, attr)):
if not hasattr(new_system, attr):
setattr(new_system, attr, getattr(generation_system, attr))
# Replace the old generation system with the new one
energy_system.generation_systems[idx] = new_system
def grid_tied_system(self):
building_hourly_electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in
HourlyElectricityDemand(self.building).calculate()]
rooftop_pv_output = [0] * 8760
facade_pv_output = [0] * 8760
rooftop_number_of_panels = 0
if 'rooftop' in self.pv_installation_type.lower():
np, ns = self.rooftop_sizing()
if self.simulation_model_type == 'explicit':
rooftop_number_of_panels = np * ns
rooftop_pv_output = self.explicit_model(standard_test_condition_maximum_power=
float(self.pv_system.standard_test_condition_maximum_power),
standard_test_condition_radiation=
float(self.pv_system.standard_test_condition_radiation),
cell_temperature_coefficient=
float(self.pv_system.cell_temperature_coefficient) / 100,
standard_test_condition_cell_temperature=
float(self.pv_system.standard_test_condition_cell_temperature),
nominal_radiation=float(self.pv_system.nominal_radiation),
nominal_cell_temperature=float(
self.pv_system.nominal_cell_temperature),
nominal_ambient_temperature=
float(self.pv_system.nominal_ambient_temperature),
inverter_efficiency=self.inverter_efficiency,
number_of_panels=rooftop_number_of_panels,
irradiance=self.building.roofs[0].global_irradiance_tilted[
cte.HOUR],
outdoor_temperature=self.building.external_temperature[
cte.HOUR])
total_hourly_pv_output = [rooftop_pv_output[i] + facade_pv_output[i] for i in range(8760)]
imported_electricity = [0] * 8760
exported_electricity = [0] * 8760
for i in range(8760):
transfer = total_hourly_pv_output[i] - building_hourly_electricity_demand[i]
if transfer > 0:
exported_electricity[i] = transfer
else:
imported_electricity[i] = abs(transfer)
results = {'building_name': self.building.name,
'total_floor_area_m2': self.building.thermal_zones_from_internal_zones[0].total_floor_area,
'roof_area_m2': self.building.roofs[0].perimeter_area, 'rooftop_panels': rooftop_number_of_panels,
'rooftop_panels_area_m2': self.building.roofs[0].installed_solar_collector_area,
'yearly_rooftop_ghi_kW/m2': self.building.roofs[0].global_irradiance[cte.YEAR][0] / 1000,
f'yearly_rooftop_tilted_radiation_{self.tilt_angle}_degree_kW/m2':
self.building.roofs[0].global_irradiance_tilted[cte.YEAR][0] / 1000,
'yearly_rooftop_pv_production_kWh': sum(rooftop_pv_output) / 1000,
'specific_pv_production_kWh/kWp': sum(rooftop_pv_output) / (
float(self.pv_system.standard_test_condition_maximum_power) * rooftop_number_of_panels),
'hourly_rooftop_poa_irradiance_W/m2': self.building.roofs[0].global_irradiance_tilted[cte.HOUR],
'hourly_rooftop_pv_output_W': rooftop_pv_output, 'T_out': self.building.external_temperature[cte.HOUR],
'building_electricity_demand_W': building_hourly_electricity_demand,
'total_hourly_pv_system_output_W': total_hourly_pv_output, 'import_from_grid_W': imported_electricity,
'export_to_grid_W': exported_electricity}
return results
def enrich(self):
if self.system_type.lower() == 'grid_tied':
self.results = self.grid_tied_system()
hourly_pv_output = self.results['total_hourly_pv_system_output_W']
self.building.onsite_electrical_production[cte.HOUR] = hourly_pv_output
self.building.onsite_electrical_production[cte.MONTH] = MonthlyValues.get_total_month(hourly_pv_output)
self.building.onsite_electrical_production[cte.YEAR] = [sum(hourly_pv_output)]
if self.csv_output:
self.save_to_csv(self.results, self.output_path, f'{self.building.name}_pv_system_analysis.csv')
@staticmethod
def save_to_csv(data, output_path, filename='rooftop_system_results.csv'):
# Separate keys based on whether their values are single values or lists
single_value_keys = [key for key, value in data.items() if not isinstance(value, list)]
list_value_keys = [key for key, value in data.items() if isinstance(value, list)]
# Check if all lists have the same length
list_lengths = [len(data[key]) for key in list_value_keys]
if not all(length == list_lengths[0] for length in list_lengths):
raise ValueError("All lists in the dictionary must have the same length")
# Get the length of list values (assuming all lists are of the same length, e.g., 8760 for hourly data)
num_rows = list_lengths[0] if list_value_keys else 1
# Open the CSV file for writing
with open(output_path / filename, mode='w', newline='') as csv_file:
writer = csv.writer(csv_file)
# Write single-value data as a header section
for key in single_value_keys:
writer.writerow([key, data[key]])
# Write an empty row for separation
writer.writerow([])
# Write the header for the list values
writer.writerow(list_value_keys)
# Write each row for the lists
for i in range(num_rows):
row = [data[key][i] for key in list_value_keys]
writer.writerow(row)

View File

@ -5,19 +5,18 @@ from hub.helpers.monthly_values import MonthlyValues
class RadiationTilted:
def __init__(self, building, solar_angles, tilt_angle, ghi, solar_constant=1366.1, maximum_clearness_index=1,
def __init__(self, building, solar_angles, tilt_angle, solar_constant=1366.1, maximum_clearness_index=1,
min_cos_zenith=0.065, maximum_zenith_angle=87):
self.building = building
self.ghi = ghi
self.tilt_angle = tilt_angle
self.zeniths = solar_angles['zenith'].tolist()[:-1]
self.incidents = solar_angles['incident angle'].tolist()[:-1]
self.date_time = solar_angles['DateTime'].tolist()[:-1]
self.ast = solar_angles['AST'].tolist()[:-1]
self.solar_azimuth = solar_angles['solar azimuth'].tolist()[:-1]
self.solar_altitude = solar_angles['solar altitude'].tolist()[:-1]
self.zeniths = solar_angles['zenith'].tolist()
self.incidents = solar_angles['incident angle'].tolist()
self.date_time = solar_angles['DateTime'].tolist()
self.ast = solar_angles['AST'].tolist()
self.solar_azimuth = solar_angles['solar azimuth'].tolist()
self.solar_altitude = solar_angles['solar altitude'].tolist()
data = {'DateTime': self.date_time, 'AST': self.ast, 'solar altitude': self.solar_altitude, 'zenith': self.zeniths,
'solar azimuth': self.solar_azimuth, 'incident angle': self.incidents, 'ghi': self.ghi}
'solar azimuth': self.solar_azimuth, 'incident angle': self.incidents}
self.df = pd.DataFrame(data)
self.df['DateTime'] = pd.to_datetime(self.df['DateTime'])
self.df['AST'] = pd.to_datetime(self.df['AST'])
@ -30,81 +29,84 @@ class RadiationTilted:
self.i_oh = []
self.k_t = []
self.fraction_diffuse = []
self.diffuse_horizontal = []
self.beam_horizontal = []
self.diffuse_hor = []
self.dni = []
self.beam_tilted = []
self.diffuse_tilted = []
self.total_radiation_tilted = []
self.calculate()
self.tilted_diffuse = []
self.tilted_beam = []
self.total_tilted = []
def dni_extra(self):
for i in range(len(self.df)):
self.i_on.append(self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * self.df.index.dayofyear[i] / 365))))
self.i_on.append(self.solar_constant * (1 + 0.033 *
math.cos(math.radians(360 * self.df.index.dayofyear[i] / 365))))
self.i_oh.append(self.i_on[i] * max(math.cos(math.radians(self.zeniths[i])), self.min_cos_zenith))
self.df['extraterrestrial normal radiation (Wh/m2)'] = self.i_on
def clearness_index(self):
for i in range(len(self.df)):
self.i_oh.append(self.i_on[i] * max(math.cos(math.radians(self.zeniths[i])), self.min_cos_zenith))
self.k_t.append(self.ghi[i] / self.i_oh[i])
self.k_t[i] = max(0, self.k_t[i])
self.k_t[i] = min(self.maximum_clearness_index, self.k_t[i])
self.df['extraterrestrial radiation on horizontal (Wh/m2)'] = self.i_oh
self.df['clearness index'] = self.k_t
def diffuse_fraction(self):
for i in range(len(self.df)):
if self.k_t[i] <= 0.22:
self.fraction_diffuse.append(1 - 0.09 * self.k_t[i])
elif self.k_t[i] <= 0.8:
self.fraction_diffuse.append(0.9511 - 0.1604 * self.k_t[i] + 4.388 * self.k_t[i] ** 2 -
16.638 * self.k_t[i] ** 3 + 12.336 * self.k_t[i] ** 4)
else:
self.fraction_diffuse.append(0.165)
if self.zeniths[i] > self.maximum_zenith_angle:
self.fraction_diffuse[i] = 1
def clearness_index(self, ghi, i_oh):
k_t = ghi / i_oh
k_t = max(0, k_t)
k_t = min(self.maximum_clearness_index, k_t)
return k_t
self.df['diffuse fraction'] = self.fraction_diffuse
def diffuse_fraction(self, k_t, zenith):
if k_t <= 0.22:
fraction_diffuse = 1 - 0.09 * k_t
elif k_t <= 0.8:
fraction_diffuse = (0.9511 - 0.1604 * k_t + 4.388 * k_t ** 2 - 16.638 * k_t ** 3 + 12.336 * k_t ** 4)
else:
fraction_diffuse = 0.165
if zenith > self.maximum_zenith_angle:
fraction_diffuse = 1
return fraction_diffuse
def radiation_components_horizontal(self):
for i in range(len(self.df)):
self.diffuse_horizontal.append(self.ghi[i] * self.fraction_diffuse[i])
self.beam_horizontal.append(self.ghi[i] - self.diffuse_horizontal[i])
self.dni.append((self.ghi[i] - self.diffuse_horizontal[i]) / math.cos(math.radians(self.zeniths[i])))
if self.zeniths[i] > self.maximum_zenith_angle or self.dni[i] < 0:
self.dni[i] = 0
def radiation_components_horizontal(self, ghi, fraction_diffuse, zenith):
diffuse_horizontal = ghi * fraction_diffuse
dni = (ghi - diffuse_horizontal) / math.cos(math.radians(zenith))
if zenith > self.maximum_zenith_angle or dni < 0:
dni = 0
return diffuse_horizontal, dni
self.df['diffuse horizontal (Wh/m2)'] = self.diffuse_horizontal
self.df['dni (Wh/m2)'] = self.dni
self.df['beam horizontal (Wh/m2)'] = self.beam_horizontal
def radiation_components_tilted(self):
for i in range(len(self.df)):
self.beam_tilted.append(self.dni[i] * math.cos(math.radians(self.incidents[i])))
self.beam_tilted[i] = max(self.beam_tilted[i], 0)
self.diffuse_tilted.append(self.diffuse_horizontal[i] * ((1 + math.cos(math.radians(self.tilt_angle))) / 2))
self.total_radiation_tilted.append(self.beam_tilted[i] + self.diffuse_tilted[i])
self.df['beam tilted (Wh/m2)'] = self.beam_tilted
self.df['diffuse tilted (Wh/m2)'] = self.diffuse_tilted
self.df['total radiation tilted (Wh/m2)'] = self.total_radiation_tilted
def calculate(self) -> pd.DataFrame:
self.dni_extra()
self.clearness_index()
self.diffuse_fraction()
self.radiation_components_horizontal()
self.radiation_components_tilted()
return self.df
def radiation_components_tilted(self, diffuse_horizontal, dni, incident_angle):
beam_tilted = dni * math.cos(math.radians(incident_angle))
beam_tilted = max(beam_tilted, 0)
self.tilted_beam.append(beam_tilted)
diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)
self.tilted_diffuse.append(diffuse_tilted)
total_radiation_tilted = beam_tilted + diffuse_tilted
return total_radiation_tilted
def enrich(self):
tilted_radiation = self.total_radiation_tilted
self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = tilted_radiation
self.dni_extra()
ghi = self.building.roofs[0].global_irradiance[cte.HOUR]
hourly_tilted_radiation = []
for i in range(len(ghi)):
k_t = self.clearness_index(ghi=ghi[i], i_oh=self.i_oh[i])
self.k_t.append(k_t)
fraction_diffuse = self.diffuse_fraction(k_t, self.zeniths[i])
self.fraction_diffuse.append(fraction_diffuse)
diffuse_horizontal, dni = self.radiation_components_horizontal(ghi=ghi[i],
fraction_diffuse=fraction_diffuse,
zenith=self.zeniths[i])
self.diffuse_hor.append(diffuse_horizontal)
self.dni.append(dni)
hourly_tilted_radiation.append(int(self.radiation_components_tilted(diffuse_horizontal=diffuse_horizontal,
dni=dni,
incident_angle=self.incidents[i])))
self.total_tilted.append(hourly_tilted_radiation[i])
self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = hourly_tilted_radiation
self.building.roofs[0].global_irradiance_tilted[cte.MONTH] = (
MonthlyValues.get_total_month(self.building.roofs[0].global_irradiance_tilted[cte.HOUR]))
self.building.roofs[0].global_irradiance_tilted[cte.YEAR] = \
[sum(self.building.roofs[0].global_irradiance_tilted[cte.MONTH])]
self.df['k_t'] = self.k_t
self.df['diffuse_frac'] = self.fraction_diffuse
self.df['diff_hor'] = self.diffuse_hor
self.df['dni'] = self.dni
self.df['diff_tilted'] = self.tilted_diffuse
self.df['diff_beam'] = self.tilted_beam
self.df['total_tilted'] = self.total_tilted
self.df['ghi'] = ghi
self.df.to_csv(f'{self.building.name}_old_radiation.csv')

View File

@ -1,7 +1,6 @@
import math
import pandas as pd
from datetime import datetime
from pathlib import Path
class CitySolarAngles:
@ -28,7 +27,7 @@ class CitySolarAngles:
self.incidents = []
self.beam_tilted = []
self.factor = []
self.times = pd.date_range(start='2023-01-01', end='2024-01-01', freq='h', tz=self.timezone)
self.times = pd.date_range(start='2023-01-01', end='2023-12-31 23:00', freq='h', tz=self.timezone)
self.df = pd.DataFrame(index=self.times)
self.day_of_year = self.df.index.dayofyear
@ -44,8 +43,9 @@ class CitySolarAngles:
lst = lst_hour + lst_minute / 60 + lst_second / 3600
# Calculate Apparent Solar Time (AST) in decimal hours
ast_decimal = lst + eot / 60 + self.longitude_correction / 60
ast_hours = int(ast_decimal)
ast_hours = int(ast_decimal) % 24 # Adjust hours to fit within 023 range
ast_minutes = round((ast_decimal - ast_hours) * 60)
# Ensure ast_minutes is within valid range

View File

@ -0,0 +1,221 @@
import math
import pandas as pd
from datetime import datetime
import hub.helpers.constants as cte
from hub.helpers.monthly_values import MonthlyValues
class SolarCalculator:
def __init__(self, city, tilt_angle, surface_azimuth_angle, standard_meridian=-75,
solar_constant=1366.1, maximum_clearness_index=1, min_cos_zenith=0.065, maximum_zenith_angle=87):
"""
A class to calculate the solar angles and solar irradiance on a tilted surface in the City
:param city: An object from the City class -> City
:param tilt_angle: tilt angle of surface -> float
:param surface_azimuth_angle: The orientation of the surface. 0 is North -> float
:param standard_meridian: A standard meridian is the meridian whose mean solar time is the basis of the time of day
observed in a time zone -> float
:param solar_constant: The amount of energy received by a given area one astronomical unit away from the Sun. It is
constant and must not be changed
:param maximum_clearness_index: This is used to calculate the diffuse fraction of the solar irradiance -> float
:param min_cos_zenith: This is needed to avoid unrealistic values in tilted irradiance calculations -> float
:param maximum_zenith_angle: This is needed to avoid negative values in tilted irradiance calculations -> float
"""
self.city = city
self.location_latitude = city.latitude
self.location_longitude = city.longitude
self.location_latitude_rad = math.radians(self.location_latitude)
self.surface_azimuth_angle = surface_azimuth_angle
self.surface_azimuth_rad = math.radians(surface_azimuth_angle)
self.tilt_angle = tilt_angle
self.tilt_angle_rad = math.radians(tilt_angle)
self.standard_meridian = standard_meridian
self.longitude_correction = (self.location_longitude - standard_meridian) * 4
self.solar_constant = solar_constant
self.maximum_clearness_index = maximum_clearness_index
self.min_cos_zenith = min_cos_zenith
self.maximum_zenith_angle = maximum_zenith_angle
timezone_offset = int(-standard_meridian / 15)
self.timezone = f'Etc/GMT{"+" if timezone_offset < 0 else "-"}{abs(timezone_offset)}'
self.eot = []
self.ast = []
self.hour_angles = []
self.declinations = []
self.solar_altitudes = []
self.solar_azimuths = []
self.zeniths = []
self.incidents = []
self.i_on = []
self.i_oh = []
self.times = pd.date_range(start='2023-01-01', end='2023-12-31 23:00', freq='h', tz=self.timezone)
self.solar_angles = pd.DataFrame(index=self.times)
self.day_of_year = self.solar_angles.index.dayofyear
def solar_time(self, datetime_val, day_of_year):
b = (day_of_year - 81) * 2 * math.pi / 364
eot = 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b)
self.eot.append(eot)
# Calculate Local Solar Time (LST)
lst_hour = datetime_val.hour
lst_minute = datetime_val.minute
lst_second = datetime_val.second
lst = lst_hour + lst_minute / 60 + lst_second / 3600
# Calculate Apparent Solar Time (AST) in decimal hours
ast_decimal = lst + eot / 60 + self.longitude_correction / 60
ast_hours = int(ast_decimal) % 24 # Adjust hours to fit within 023 range
ast_minutes = round((ast_decimal - ast_hours) * 60)
# Ensure ast_minutes is within valid range
if ast_minutes == 60:
ast_hours += 1
ast_minutes = 0
elif ast_minutes < 0:
ast_minutes = 0
ast_time = datetime(year=datetime_val.year, month=datetime_val.month, day=datetime_val.day,
hour=ast_hours, minute=ast_minutes)
self.ast.append(ast_time)
return ast_time
def declination_angle(self, day_of_year):
declination = 23.45 * math.sin(math.radians(360 / 365 * (284 + day_of_year)))
declination_radian = math.radians(declination)
self.declinations.append(declination)
return declination_radian
def hour_angle(self, ast_time):
hour_angle = ((ast_time.hour * 60 + ast_time.minute) - 720) / 4
hour_angle_radian = math.radians(hour_angle)
self.hour_angles.append(hour_angle)
return hour_angle_radian
def solar_altitude(self, declination_radian, hour_angle_radian):
solar_altitude_radians = math.asin(math.cos(self.location_latitude_rad) * math.cos(declination_radian) *
math.cos(hour_angle_radian) + math.sin(self.location_latitude_rad) *
math.sin(declination_radian))
solar_altitude = math.degrees(solar_altitude_radians)
self.solar_altitudes.append(solar_altitude)
return solar_altitude_radians
def zenith(self, solar_altitude_radians):
solar_altitude = math.degrees(solar_altitude_radians)
zenith_degree = 90 - solar_altitude
zenith_radian = math.radians(zenith_degree)
self.zeniths.append(zenith_degree)
return zenith_radian
def solar_azimuth_analytical(self, hourangle, declination, zenith):
numer = (math.cos(zenith) * math.sin(self.location_latitude_rad) - math.sin(declination))
denom = (math.sin(zenith) * math.cos(self.location_latitude_rad))
if math.isclose(denom, 0.0, abs_tol=1e-8):
cos_azi = 1.0
else:
cos_azi = numer / denom
cos_azi = max(-1.0, min(1.0, cos_azi))
sign_ha = math.copysign(1, hourangle)
solar_azimuth_radians = sign_ha * math.acos(cos_azi) + math.pi
solar_azimuth_degrees = math.degrees(solar_azimuth_radians)
self.solar_azimuths.append(solar_azimuth_degrees)
return solar_azimuth_radians
def incident_angle(self, solar_altitude_radians, solar_azimuth_radians):
incident_radian = math.acos(math.cos(solar_altitude_radians) *
math.cos(abs(solar_azimuth_radians - self.surface_azimuth_rad)) *
math.sin(self.tilt_angle_rad) + math.sin(solar_altitude_radians) *
math.cos(self.tilt_angle_rad))
incident_angle_degrees = math.degrees(incident_radian)
self.incidents.append(incident_angle_degrees)
return incident_radian
def dni_extra(self, day_of_year, zenith_radian):
i_on = self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * day_of_year / 365)))
i_oh = i_on * max(math.cos(zenith_radian), self.min_cos_zenith)
self.i_on.append(i_on)
self.i_oh.append(i_oh)
return i_on, i_oh
def clearness_index(self, ghi, i_oh):
k_t = ghi / i_oh
k_t = max(0, k_t)
k_t = min(self.maximum_clearness_index, k_t)
return k_t
def diffuse_fraction(self, k_t, zenith):
if k_t <= 0.22:
fraction_diffuse = 1 - 0.09 * k_t
elif k_t <= 0.8:
fraction_diffuse = (0.9511 - 0.1604 * k_t + 4.388 * k_t ** 2 - 16.638 * k_t ** 3 + 12.336 * k_t ** 4)
else:
fraction_diffuse = 0.165
if zenith > self.maximum_zenith_angle:
fraction_diffuse = 1
return fraction_diffuse
def radiation_components_horizontal(self, ghi, fraction_diffuse, zenith):
diffuse_horizontal = ghi * fraction_diffuse
dni = (ghi - diffuse_horizontal) / math.cos(math.radians(zenith))
if zenith > self.maximum_zenith_angle or dni < 0:
dni = 0
return diffuse_horizontal, dni
def radiation_components_tilted(self, diffuse_horizontal, dni, incident_angle):
beam_tilted = dni * math.cos(math.radians(incident_angle))
beam_tilted = max(beam_tilted, 0)
diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)
total_radiation_tilted = beam_tilted + diffuse_tilted
return total_radiation_tilted
def solar_angles_calculator(self, csv_output=False):
for i in range(len(self.times)):
datetime_val = self.times[i]
day_of_year = self.day_of_year[i]
declination_radians = self.declination_angle(day_of_year)
ast_time = self.solar_time(datetime_val, day_of_year)
hour_angle_radians = self.hour_angle(ast_time)
solar_altitude_radians = self.solar_altitude(declination_radians, hour_angle_radians)
zenith_radians = self.zenith(solar_altitude_radians)
solar_azimuth_radians = self.solar_azimuth_analytical(hour_angle_radians, declination_radians, zenith_radians)
self.incident_angle(solar_altitude_radians, solar_azimuth_radians)
self.dni_extra(day_of_year=day_of_year, zenith_radian=zenith_radians)
self.solar_angles['DateTime'] = self.times
self.solar_angles['AST'] = self.ast
self.solar_angles['hour angle'] = self.hour_angles
self.solar_angles['eot'] = self.eot
self.solar_angles['declination angle'] = self.declinations
self.solar_angles['solar altitude'] = self.solar_altitudes
self.solar_angles['zenith'] = self.zeniths
self.solar_angles['solar azimuth'] = self.solar_azimuths
self.solar_angles['incident angle'] = self.incidents
self.solar_angles['extraterrestrial normal radiation (Wh/m2)'] = self.i_on
self.solar_angles['extraterrestrial radiation on horizontal (Wh/m2)'] = self.i_oh
if csv_output:
self.solar_angles.to_csv('solar_angles_new.csv')
def tilted_irradiance_calculator(self):
if self.solar_angles.empty:
self.solar_angles_calculator()
for building in self.city.buildings:
hourly_tilted_irradiance = []
roof_ghi = building.roofs[0].global_irradiance[cte.HOUR]
for i in range(len(roof_ghi)):
k_t = self.clearness_index(ghi=roof_ghi[i], i_oh=self.i_oh[i])
fraction_diffuse = self.diffuse_fraction(k_t, self.zeniths[i])
diffuse_horizontal, dni = self.radiation_components_horizontal(ghi=roof_ghi[i],
fraction_diffuse=fraction_diffuse,
zenith=self.zeniths[i])
hourly_tilted_irradiance.append(int(self.radiation_components_tilted(diffuse_horizontal=diffuse_horizontal,
dni=dni,
incident_angle=self.incidents[i])))
building.roofs[0].global_irradiance_tilted[cte.HOUR] = hourly_tilted_irradiance
building.roofs[0].global_irradiance_tilted[cte.MONTH] = (MonthlyValues.get_total_month(
building.roofs[0].global_irradiance_tilted[cte.HOUR]))
building.roofs[0].global_irradiance_tilted[cte.YEAR] = [sum(building.roofs[0].global_irradiance_tilted[cte.MONTH])]

View File

@ -29,19 +29,21 @@ residential_systems_percentage = {'system 1 gas': 15,
'system 8 electricity': 35}
residential_new_systems_percentage = {
'Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 100,
'Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0,
'Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 0,
'Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0,
'Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 0,
'Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0,
'Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating': 0,
'Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating': 0,
'Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating': 0,
'Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating': 0,
'Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating': 0,
'Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating': 0,
'Rooftop PV System': 0
'Central Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 50,
'Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0,
'Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0,
'Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW '
'and PV': 0,
'Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0,
'Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW '
'and PV': 0,
'Central Hydronic Air and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
'Central Hydronic Air and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
'Central Hydronic Ground and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
'Central Hydronic Ground and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
'Central Hydronic Water and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
'Central Hydronic Water and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
'Grid-Tied PV System': 50
}
non_residential_systems_percentage = {'system 1 gas': 0,

View File

View File

@ -0,0 +1,86 @@
from pathlib import Path
import subprocess
from building_modelling.ep_run_enrich import energy_plus_workflow
from energy_system_modelling_package import random_assignation
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_system_assessment import \
PvSystemAssessment
from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.solar_calculator import \
SolarCalculator
from hub.imports.energy_systems_factory import EnergySystemsFactory
from hub.imports.geometry_factory import GeometryFactory
from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
from hub.imports.results_factory import ResultFactory
from building_modelling.geojson_creator import process_geojson
from hub.exports.exports_factory import ExportsFactory
# Define paths for input and output directories, ensuring directories are created if they do not exist
main_path = Path(__file__).parent.parent.resolve()
input_files_path = (Path(__file__).parent.parent / 'input_files')
input_files_path.mkdir(parents=True, exist_ok=True)
output_path = (Path(__file__).parent.parent / 'out_files').resolve()
output_path.mkdir(parents=True, exist_ok=True)
# Define specific paths for outputs from EnergyPlus and SRA (Simplified Radiosity Algorith) and PV calculation processes
energy_plus_output_path = output_path / 'energy_plus_outputs'
energy_plus_output_path.mkdir(parents=True, exist_ok=True)
sra_output_path = output_path / 'sra_outputs'
sra_output_path.mkdir(parents=True, exist_ok=True)
pv_assessment_path = output_path / 'pv_outputs'
pv_assessment_path.mkdir(parents=True, exist_ok=True)
# Generate a GeoJSON file for city buildings based on latitude, longitude, and building dimensions
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, path=main_path, diff=0.0001)
geojson_file_path = input_files_path / 'output_buildings.geojson'
# Initialize a city object from the geojson file, mapping building functions using a predefined dictionary
city = GeometryFactory(file_type='geojson',
path=geojson_file_path,
height_field='height',
year_of_construction_field='year_of_construction',
function_field='function',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
# Enrich city data with construction, usage, and weather information specific to the location
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
# Execute the EnergyPlus workflow to simulate building energy performance and generate output
# energy_plus_workflow(city, energy_plus_output_path)
# Export the city data in SRA-compatible format to facilitate solar radiation assessment
ExportsFactory('sra', city, sra_output_path).export()
# Run SRA simulation using an external command, passing the generated SRA XML file path as input
sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve()
subprocess.run(['sra', str(sra_path)])
# Enrich city data with SRA simulation results for subsequent analysis
ResultFactory('sra', city, sra_output_path).enrich()
# Assign PV system archetype name to the buildings in city
random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage)
# Enrich city model with Montreal future systems parameters
EnergySystemsFactory('montreal_future', city).enrich()
# # Initialize solar calculation parameters (e.g., azimuth, altitude) and compute irradiance and solar angles
tilt_angle = 37
solar_parameters = SolarCalculator(city=city,
surface_azimuth_angle=180,
tilt_angle=tilt_angle)
solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis
solar_parameters.tilted_irradiance_calculator()
# # PV modelling building by building
for building in city.buildings:
PvSystemAssessment(building=building,
pv_system=None,
battery=None,
tilt_angle=tilt_angle,
solar_angles=solar_angles,
system_type='grid_tied',
pv_installation_type='rooftop',
simulation_model_type='explicit',
module_model_name=None,
inverter_efficiency=0.95,
system_catalogue_handler='montreal_future',
roof_percentage_coverage=0.75,
facade_coverage_percentage=0,
csv_output=True,
output_path=pv_assessment_path).enrich()
print('test')

View File

@ -840,53 +840,55 @@ class Building(CityObject):
Get energy consumption of different sectors
return: dict
"""
fuel_breakdown = {cte.ELECTRICITY: {cte.LIGHTING: self.lighting_electrical_demand[cte.YEAR][0],
cte.APPLIANCES: self.appliances_electrical_demand[cte.YEAR][0]}}
fuel_breakdown = {cte.ELECTRICITY: {cte.LIGHTING: self.lighting_electrical_demand[cte.YEAR][0] if self.lighting_electrical_demand else 0,
cte.APPLIANCES: self.appliances_electrical_demand[cte.YEAR][0] if self.appliances_electrical_demand else 0}}
energy_systems = self.energy_systems
for energy_system in energy_systems:
demand_types = energy_system.demand_types
generation_systems = energy_system.generation_systems
for demand_type in demand_types:
for generation_system in generation_systems:
if generation_system.system_type != cte.PHOTOVOLTAIC:
if generation_system.fuel_type not in fuel_breakdown:
fuel_breakdown[generation_system.fuel_type] = {}
if demand_type in generation_system.energy_consumption:
fuel_breakdown[f'{generation_system.fuel_type}'][f'{demand_type}'] = (
generation_system.energy_consumption)[f'{demand_type}'][cte.YEAR][0]
storage_systems = generation_system.energy_storage_systems
if storage_systems:
for storage_system in storage_systems:
if storage_system.type_energy_stored == 'thermal' and storage_system.heating_coil_capacity is not None:
fuel_breakdown[cte.ELECTRICITY][f'{demand_type}'] += storage_system.heating_coil_energy_consumption[f'{demand_type}'][cte.YEAR][0]
#TODO: When simulation models of all energy system archetypes are created, this part can be removed
heating_fuels = []
dhw_fuels = []
for energy_system in self.energy_systems:
if cte.HEATING in energy_system.demand_types:
for generation_system in energy_system.generation_systems:
heating_fuels.append(generation_system.fuel_type)
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
for generation_system in energy_system.generation_systems:
dhw_fuels.append(generation_system.fuel_type)
for key in fuel_breakdown:
if key == cte.ELECTRICITY and cte.COOLING not in fuel_breakdown[key]:
for energy_system in energy_systems:
if cte.COOLING in energy_system.demand_types and cte.COOLING not in fuel_breakdown[key]:
for generation_system in energy_system.generation_systems:
fuel_breakdown[generation_system.fuel_type][cte.COOLING] = self.cooling_consumption[cte.YEAR][0]
for fuel in heating_fuels:
if cte.HEATING not in fuel_breakdown[fuel]:
if energy_systems is not None:
for energy_system in energy_systems:
demand_types = energy_system.demand_types
generation_systems = energy_system.generation_systems
for demand_type in demand_types:
for generation_system in generation_systems:
if generation_system.system_type != cte.PHOTOVOLTAIC:
if generation_system.fuel_type not in fuel_breakdown:
fuel_breakdown[generation_system.fuel_type] = {}
if demand_type in generation_system.energy_consumption:
fuel_breakdown[f'{generation_system.fuel_type}'][f'{demand_type}'] = (
generation_system.energy_consumption)[f'{demand_type}'][cte.YEAR][0]
storage_systems = generation_system.energy_storage_systems
if storage_systems:
for storage_system in storage_systems:
if storage_system.type_energy_stored == 'thermal' and storage_system.heating_coil_energy_consumption:
fuel_breakdown[cte.ELECTRICITY][f'{demand_type}'] += (
storage_system.heating_coil_energy_consumption)[f'{demand_type}'][cte.YEAR][0]
#TODO: When simulation models of all energy system archetypes are created, this part can be removed
heating_fuels = []
dhw_fuels = []
for energy_system in self.energy_systems:
if cte.HEATING in energy_system.demand_types:
for generation_system in energy_system.generation_systems:
heating_fuels.append(generation_system.fuel_type)
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
for generation_system in energy_system.generation_systems:
dhw_fuels.append(generation_system.fuel_type)
for key in fuel_breakdown:
if key == cte.ELECTRICITY and cte.COOLING not in fuel_breakdown[key]:
for energy_system in energy_systems:
if cte.HEATING in energy_system.demand_types:
for generation_system in energy_system.generation_systems:
fuel_breakdown[generation_system.fuel_type][cte.HEATING] = self.heating_consumption[cte.YEAR][0]
for fuel in dhw_fuels:
if cte.DOMESTIC_HOT_WATER not in fuel_breakdown[fuel]:
for energy_system in energy_systems:
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
for generation_system in energy_system.generation_systems:
fuel_breakdown[generation_system.fuel_type][cte.DOMESTIC_HOT_WATER] = self.domestic_hot_water_consumption[cte.YEAR][0]
if cte.COOLING in energy_system.demand_types and cte.COOLING not in fuel_breakdown[key]:
if self.cooling_consumption:
fuel_breakdown[energy_system.generation_systems[0].fuel_type][cte.COOLING] = self.cooling_consumption[cte.YEAR][0]
for fuel in heating_fuels:
if cte.HEATING not in fuel_breakdown[fuel]:
for energy_system in energy_systems:
if cte.HEATING in energy_system.demand_types:
if self.heating_consumption:
fuel_breakdown[energy_system.generation_systems[0].fuel_type][cte.HEATING] = self.heating_consumption[cte.YEAR][0]
for fuel in dhw_fuels:
if cte.DOMESTIC_HOT_WATER not in fuel_breakdown[fuel]:
for energy_system in energy_systems:
if cte.DOMESTIC_HOT_WATER in energy_system.demand_types:
if self.domestic_hot_water_consumption:
fuel_breakdown[energy_system.generation_systems[0].fuel_type][cte.DOMESTIC_HOT_WATER] = self.domestic_hot_water_consumption[cte.YEAR][0]
self._fuel_consumption_breakdown = fuel_breakdown
return self._fuel_consumption_breakdown

File diff suppressed because it is too large Load Diff

View File

@ -1,863 +0,0 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56769087843276,
45.49251875903776
],
[
-73.56765050367694,
45.492560280202284
],
[
-73.5677794213865,
45.49262188364245
],
[
-73.56781916241786,
45.49258006136105
],
[
-73.56769087843276,
45.49251875903776
]
]
]
},
"id": 173347,
"properties": {
"name": "01044617",
"address": "rue Victor-Hugo (MTL) 1666",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56765050367694,
45.492560280202284
],
[
-73.56761436875776,
45.49259744179384
],
[
-73.5676075694645,
45.49260454199484
],
[
-73.56773226889548,
45.49266394156485
],
[
-73.56773726906921,
45.49266624130272
],
[
-73.5677794213865,
45.49262188364245
],
[
-73.56765050367694,
45.492560280202284
]
]
]
},
"id": 173348,
"properties": {
"name": "01044619",
"address": "rue Victor-Hugo (MTL) 1670",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56829026835214,
45.492524742569145
],
[
-73.56849646900322,
45.49262354174874
],
[
-73.56861067001111,
45.492505541343576
],
[
-73.56864076915663,
45.492519941474434
],
[
-73.56866246900178,
45.49249754209202
],
[
-73.56867696946317,
45.49250454136644
],
[
-73.56867726964143,
45.49250414255471
],
[
-73.56881486931461,
45.492362042624144
],
[
-73.56881686903772,
45.492359941181455
],
[
-73.5688004699483,
45.49235084193039
],
[
-73.56882097012145,
45.4923320417195
],
[
-73.56879846891101,
45.49232034109352
],
[
-73.56883736970825,
45.492284841271946
],
[
-73.56886806888434,
45.492256240993704
],
[
-73.56885337003277,
45.49224914198001
],
[
-73.56890226932418,
45.49219894164121
],
[
-73.56851866897392,
45.49201434154299
],
[
-73.56837326884313,
45.492163841620254
],
[
-73.56864696910176,
45.49229554163243
],
[
-73.5685268682051,
45.49241904187041
],
[
-73.56825396962694,
45.49228824183907
],
[
-73.56810906858335,
45.49243794104013
],
[
-73.56829026835214,
45.492524742569145
]
]
]
},
"id": 173403,
"properties": {
"name": "01044334",
"address": "rue Saint-Jacques (MTL) 1460",
"function": "1000",
"height": 15,
"year_of_construction": 1985
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.5683896684674,
45.491800342137736
],
[
-73.56838616878639,
45.49180414157881
],
[
-73.56850686988925,
45.49185994152571
],
[
-73.56851286844197,
45.4918626410622
],
[
-73.56855549071014,
45.49181750806087
],
[
-73.56842962331187,
45.49175738300567
],
[
-73.5683896684674,
45.491800342137736
]
]
]
},
"id": 174898,
"properties": {
"name": "01044590",
"address": "rue Victor-Hugo (MTL) 1600",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.5680637695714,
45.49212884162544
],
[
-73.56802228176146,
45.49217205619571
],
[
-73.56815668696326,
45.49223626189717
],
[
-73.56815766959974,
45.49223524178655
],
[
-73.56818746886172,
45.49224944155107
],
[
-73.56822816806918,
45.49220694186927
],
[
-73.5680637695714,
45.49212884162544
]
]
]
},
"id": 175785,
"properties": {
"name": "01044602",
"address": "rue Victor-Hugo (MTL) 1630",
"function": "1000",
"height": 12,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56850793693103,
45.49167318076048
],
[
-73.56846877951091,
45.4917152818903
],
[
-73.56859506290321,
45.491775605518725
],
[
-73.56863463503653,
45.491733702062774
],
[
-73.56850793693103,
45.49167318076048
]
]
]
},
"id": 175910,
"properties": {
"name": "01044586",
"address": "rue Victor-Hugo (MTL) 1590",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56817543449134,
45.49201384773851
],
[
-73.56813497596143,
45.49205532773507
],
[
-73.56826745951075,
45.492118613912375
],
[
-73.56830763251781,
45.49207699906335
],
[
-73.56817543449134,
45.49201384773851
]
]
]
},
"id": 176056,
"properties": {
"name": "01044599",
"address": "rue Victor-Hugo (MTL) 1620",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56772876855176,
45.49247194194522
],
[
-73.56773406949068,
45.492474341387755
],
[
-73.56773125185198,
45.492477239659124
],
[
-73.56785890467093,
45.492538239964624
],
[
-73.56789966910456,
45.49249534173201
],
[
-73.56776616865103,
45.49243264153464
],
[
-73.56772876855176,
45.49247194194522
]
]
]
},
"id": 176261,
"properties": {
"name": "01044613",
"address": "rue Victor-Hugo (MTL) 1656",
"function": "1000",
"height": 10,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56802228176146,
45.49217205619571
],
[
-73.56798225825526,
45.492213743742184
],
[
-73.56811660206223,
45.49227791893211
],
[
-73.56815668696326,
45.49223626189717
],
[
-73.56802228176146,
45.49217205619571
]
]
]
},
"id": 176293,
"properties": {
"name": "01044604",
"address": "rue Victor-Hugo (MTL) 1636",
"function": "1000",
"height": 12,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56790222258577,
45.49229712328457
],
[
-73.56785996900595,
45.49234104192853
],
[
-73.56799446861396,
45.49240484193282
],
[
-73.56803643080562,
45.49236123475947
],
[
-73.56790222258577,
45.49229712328457
]
]
]
},
"id": 176296,
"properties": {
"name": "01044611",
"address": "rue Victor-Hugo (MTL) 1650",
"function": "1000",
"height": 10,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56798225825526,
45.492213743742184
],
[
-73.56794223597048,
45.4922554321734
],
[
-73.56807651582375,
45.49231957685336
],
[
-73.56811660206223,
45.49227791893211
],
[
-73.56798225825526,
45.492213743742184
]
]
]
},
"id": 176298,
"properties": {
"name": "01044607",
"address": "rue Victor-Hugo (MTL) 1640",
"function": "1000",
"height": 12,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56742736898599,
45.49184704208998
],
[
-73.56761256873325,
45.491896142437554
],
[
-73.56766926915839,
45.4917902412014
],
[
-73.56766956853903,
45.49179024192391
],
[
-73.56792966911675,
45.49183254222432
],
[
-73.56793006788594,
45.491831141828406
],
[
-73.56794526884076,
45.49174634219527
],
[
-73.56794516904765,
45.49174634225465
],
[
-73.56753896905731,
45.491638642248425
],
[
-73.56742736898599,
45.49184704208998
]
]
]
},
"id": 176918,
"properties": {
"name": "01097185",
"address": "rue Victor-Hugo (MTL) 1591",
"function": "1000",
"height": 10,
"year_of_construction": 1987
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56773125185198,
45.492477239659124
],
[
-73.56769087843276,
45.49251875903776
],
[
-73.56781916241786,
45.49258006136105
],
[
-73.56785890467093,
45.492538239964624
],
[
-73.56773125185198,
45.492477239659124
]
]
]
},
"id": 178164,
"properties": {
"name": "01044615",
"address": "rue Victor-Hugo (MTL) 1660",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56846877951091,
45.4917152818903
],
[
-73.56842962331187,
45.49175738300567
],
[
-73.56855549071014,
45.49181750806087
],
[
-73.56859506290321,
45.491775605518725
],
[
-73.56846877951091,
45.4917152818903
]
]
]
},
"id": 179679,
"properties": {
"name": "01044588",
"address": "rue Victor-Hugo (MTL) 1596",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56825635009473,
45.49193088860213
],
[
-73.56821589168355,
45.491972368627906
],
[
-73.5683477837006,
45.4920353716151
],
[
-73.56838787594006,
45.49199371809223
],
[
-73.56825635009473,
45.49193088860213
]
]
]
},
"id": 179789,
"properties": {
"name": "01044595",
"address": "rue Victor-Hugo (MTL) 1610",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56821589168355,
45.491972368627906
],
[
-73.56817543449134,
45.49201384773851
],
[
-73.56830763251781,
45.49207699906335
],
[
-73.5683477837006,
45.4920353716151
],
[
-73.56821589168355,
45.491972368627906
]
]
]
},
"id": 181310,
"properties": {
"name": "01044597",
"address": "rue Victor-Hugo (MTL) 1616",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56809506939487,
45.49209624228538
],
[
-73.56809246893268,
45.4920988416879
],
[
-73.56821287000538,
45.49216124158406
],
[
-73.56822186852654,
45.49216584161625
],
[
-73.56826745951075,
45.492118613912375
],
[
-73.56813497596143,
45.49205532773507
],
[
-73.56809506939487,
45.49209624228538
]
]
]
},
"id": 182393,
"properties": {
"name": "01044601",
"address": "rue Victor-Hugo (MTL) 1626",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56790756893894,
45.492291541967774
],
[
-73.56790222258577,
45.49229712328457
],
[
-73.56803643080562,
45.49236123475947
],
[
-73.56807651582375,
45.49231957685336
],
[
-73.56794223597048,
45.4922554321734
],
[
-73.56790756893894,
45.492291541967774
]
]
]
},
"id": 182442,
"properties": {
"name": "01044609",
"address": "rue Victor-Hugo (MTL) 1646",
"function": "1000",
"height": 11,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56829706912258,
45.49188914205178
],
[
-73.56825635009473,
45.49193088860213
],
[
-73.56838787594006,
45.49199371809223
],
[
-73.56842846901456,
45.49195154234486
],
[
-73.56829706912258,
45.49188914205178
]
]
]
},
"id": 182546,
"properties": {
"name": "01044592",
"address": "rue Victor-Hugo (MTL) 1606",
"function": "1000",
"height": 8,
"year_of_construction": 1986
}
}
]
}

View File

@ -41,9 +41,9 @@ class TestSystemsCatalog(TestCase):
archetypes = catalog.names()
self.assertEqual(13, len(archetypes['archetypes']))
systems = catalog.names('systems')
self.assertEqual(17, len(systems['systems']))
self.assertEqual(24, len(systems['systems']))
generation_equipments = catalog.names('generation_equipments')
self.assertEqual(27, len(generation_equipments['generation_equipments']))
self.assertEqual(46, len(generation_equipments['generation_equipments']))
with self.assertRaises(ValueError):
catalog.names('unknown')
@ -54,4 +54,6 @@ class TestSystemsCatalog(TestCase):
with self.assertRaises(IndexError):
catalog.get_entry('unknown')
print(catalog.entries())
catalog_pv_generation_equipments = [component for component in
catalog.entries('generation_equipments') if
component.system_type == cte.PHOTOVOLTAIC]