Compare commits

...

2 Commits

Author SHA1 Message Date
b0b5916f0b partial matsim implementation 2024-04-15 07:12:09 +02:00
c6f6498a23 partial matsim implementation 2024-04-03 12:34:09 +02:00
10 changed files with 544 additions and 103690 deletions

2
hub/data/traffic/osm/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.gitignore
!.gitignore

File diff suppressed because it is too large Load Diff

View File

@ -1,157 +0,0 @@
! Minimal.idf
! Basic file description: This is a minimal configuration necessary to run.
! Highlights: Illustrates minimal items necessary to perform run.
! BUILDING, SURFACEGEOMETRY, LOCATION and DESIGNDAY (or RUNPERIOD) are the absolute minimal required input objects.
! TIME STEP IN HOUR is included so as to not get warning error.
! Including two design days, Run Control object and RunPeriod to facilitate use.
! Although not incredibly useful, this could be used as a weather/solar calculator.
! Simulation Location/Run: Denver is included. Any could be used.
! Building: None.
!
! Internal gains description: None.
!
! HVAC: None.
!
Version,9.5;
Timestep,4;
Building,
None, !- Name
0.0000000E+00, !- North Axis {deg}
Suburbs, !- Terrain
0.04, !- Loads Convergence Tolerance Value {W}
0.40, !- Temperature Convergence Tolerance Value {deltaC}
FullInteriorAndExterior, !- Solar Distribution
25, !- Maximum Number of Warmup Days
6; !- Minimum Number of Warmup Days
GlobalGeometryRules,
UpperLeftCorner, !- Starting Vertex Position
CounterClockWise, !- Vertex Entry Direction
World; !- Coordinate System
Site:Location,
DENVER_STAPLETON_CO_USA_WMO_724690, !- Name
39.77, !- Latitude {deg}
-104.87, !- Longitude {deg}
-7.00, !- Time Zone {hr}
1611.00; !- Elevation {m}
! DENVER_STAPLETON_CO_USA Annual Heating Design Conditions Wind Speed=2.3m/s Wind Dir=180
! Coldest Month=December
! DENVER_STAPLETON_CO_USA Annual Heating 99.6%, MaxDB=-20°C
SizingPeriod:DesignDay,
DENVER_STAPLETON Ann Htg 99.6% Condns DB, !- Name
12, !- Month
21, !- Day of Month
WinterDesignDay, !- Day Type
-20, !- Maximum Dry-Bulb Temperature {C}
0.0, !- Daily Dry-Bulb Temperature Range {deltaC}
, !- Dry-Bulb Temperature Range Modifier Type
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
Wetbulb, !- Humidity Condition Type
-20, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
, !- Humidity Condition Day Schedule Name
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
, !- Daily Wet-Bulb Temperature Range {deltaC}
83411., !- Barometric Pressure {Pa}
2.3, !- Wind Speed {m/s}
180, !- Wind Direction {deg}
No, !- Rain Indicator
No, !- Snow Indicator
No, !- Daylight Saving Time Indicator
ASHRAEClearSky, !- Solar Model Indicator
, !- Beam Solar Day Schedule Name
, !- Diffuse Solar Day Schedule Name
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
0.00; !- Sky Clearness
! DENVER_STAPLETON Annual Cooling Design Conditions Wind Speed=4m/s Wind Dir=120
! Hottest Month=July
! DENVER_STAPLETON_CO_USA Annual Cooling (DB=>MWB) .4%, MaxDB=34.1°C MWB=15.8°C
SizingPeriod:DesignDay,
DENVER_STAPLETON Ann Clg .4% Condns DB=>MWB, !- Name
7, !- Month
21, !- Day of Month
SummerDesignDay, !- Day Type
34.1, !- Maximum Dry-Bulb Temperature {C}
15.2, !- Daily Dry-Bulb Temperature Range {deltaC}
, !- Dry-Bulb Temperature Range Modifier Type
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
Wetbulb, !- Humidity Condition Type
15.8, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
, !- Humidity Condition Day Schedule Name
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
, !- Daily Wet-Bulb Temperature Range {deltaC}
83411., !- Barometric Pressure {Pa}
4, !- Wind Speed {m/s}
120, !- Wind Direction {deg}
No, !- Rain Indicator
No, !- Snow Indicator
No, !- Daylight Saving Time Indicator
ASHRAEClearSky, !- Solar Model Indicator
, !- Beam Solar Day Schedule Name
, !- Diffuse Solar Day Schedule Name
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
1.00; !- Sky Clearness
RunPeriod,
Run Period 1, !- Name
1, !- Begin Month
1, !- Begin Day of Month
, !- Begin Year
12, !- End Month
31, !- End Day of Month
, !- End Year
Tuesday, !- Day of Week for Start Day
Yes, !- Use Weather File Holidays and Special Days
Yes, !- Use Weather File Daylight Saving Period
No, !- Apply Weekend Holiday Rule
Yes, !- Use Weather File Rain Indicators
Yes; !- Use Weather File Snow Indicators
SimulationControl,
No, !- Do Zone Sizing Calculation
No, !- Do System Sizing Calculation
No, !- Do Plant Sizing Calculation
No, !- Run Simulation for Sizing Periods
Yes, !- Run Simulation for Weather File Run Periods
No, !- Do HVAC Sizing Simulation for Sizing Periods
1; !- Maximum Number of HVAC Sizing Simulation Passes
Output:Table:SummaryReports, AnnualBuildingUtilityPerformanceSummary,
DemandEndUseComponentsSummary,
SensibleHeatGainSummary,
InputVerificationandResultsSummary,
AdaptiveComfortSummary,
Standard62.1Summary,
ClimaticDataSummary,
EquipmentSummary,
EnvelopeSummary,
LightingSummary,
HVACSizingSummary,
SystemSummary,
ComponentSizingSummary,
OutdoorAirSummary,
ObjectCountSummary,
EndUseEnergyConsumptionOtherFuelsMonthly,
PeakEnergyEndUseOtherFuelsMonthly;
OutputControl:Table:Style, CommaAndHTML,JtoKWH;
Output:Meter,DISTRICTHEATING:Facility,hourly;
Output:Meter,DISTRICTCOOLING:Facility,hourly;
Output:Meter,InteriorEquipment:Electricity,hourly;
Output:Meter,InteriorLights:Electricity,hourly;
OutputControl:IlluminanceMap:Style,
Comma; !- Column separator

View File

View File

View File

View File

@ -0,0 +1,25 @@
import logging
class Osm:
_osm_file = {
'CA.02.5935': 'https://download.geofabrik.de/north-america/canada/british-columbia-latest.osm.pbf',
'CA.10.06': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
'CA.10.13': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
'CA.10.14': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
'CA.10.16': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
'DE.01.082': 'https://download.geofabrik.de/europe/germany/baden-wuerttemberg-latest.osm.pbf',
'US.NY.047': 'https://download.geofabrik.de/north-america/us/new-york-latest.osm.pbf',
'CA.10.12': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
'IL.01.': 'https://download.geofabrik.de/asia/israel-and-palestine-latest.osm.pbf',
'ES.07.PM': 'https://download.geofabrik.de/europe/spain/islas-baleares-latest.osm.pbf'
}
def __init__(self):
pass
def pfb_file(self, region_code):
if region_code in self._osm_file.keys():
return self._osm_file[region_code]
logging.error('Specific osm data unknown for %s', region_code)
raise NotImplementedError('Specific osm data unknown for %s', region_code)

View File

@ -0,0 +1,476 @@
import gzip
import math
import shutil
import subprocess
from pathlib import Path
import geopandas as gpd
import requests
from lxml import etree
from shapely import Polygon
from helpers.osm import Osm
import hub.helpers.constants as cte
class Matsim:
_x = 0
_y = 1
_z = 2
def __init__(self, city, output_file_path):
self._city = city
self._output_file_path = output_file_path
self._crs = city.c
self._facilities = {
'name': self._city.name + ' Facilities',
'facility': []
}
self._export()
def _export(self):
self._export_facilities()
self._export_network()
self._export_population()
self._export_config()
@property
def _jar_path(self):
"""
Get the matsim-network-from-osm installation path
:return: str
"""
return shutil.which('matsim-network-from-osm.jar')
@property
def _java_path(self):
"""
Get the matsim-network-from-osm installation path
:return: str
"""
return shutil.which('java')
def _export_facilities(self):
"""
Exports the city's facilities data to XML and shapefile formats.
"""
facilities_xml = etree.Element('facilities', name=self._facilities['name'])
for building in self._city.buildings:
try:
facility = {
'id': building.name,
'x': str(building.centroid[0]),
'y': str(building.centroid[1]),
'activity': []
}
if len(building.thermal_zones_from_internal_zones) > 1:
raise NotImplementedError('multi-zone buildings aren\'t yet supported')
building_schedules = []
capacity = 0
for thermal_zone in building.thermal_zones_from_internal_zones:
capacity = thermal_zone.occupancy.occupancy_density * building.floor_area * building.storeys_above_ground
for schedule in thermal_zone.occupancy.occupancy_schedules:
building_schedules.append(schedule)
activity_info = {
'type': building.function,
'capacity': math.ceil(capacity),
'opentime': Matsim._convert_schedules(building_schedules)
}
facility_xml = etree.SubElement(facilities_xml, 'facility', {
'id': f'{facility["id"]}',
'x': f'{facility["x"]}',
'y': f'{facility["y"]}',
})
activity_xml = etree.SubElement(facility_xml, 'activity', {
'type': f'{activity_info["type"]}'
})
etree.SubElement(activity_xml, 'capacity', {
'value': f'{activity_info["capacity"]}'
})
etree.SubElement(activity_xml, 'opentime', {
'day': f'{activity_info["opentime"][0]["day"]}',
'start_time': f'{activity_info["opentime"][0]["start_time"]}',
'end_time': f'{activity_info["opentime"][0]["end_time"]}'
})
facility['activity'].append(activity_info)
self._facilities['facility'].append(facility)
except Exception as ex:
print('error: ', ex)
viewport = Polygon([
(self._city.lower_corner[self._x], self._city.upper_corner[self._y]),
(self._city.upper_corner[self._x], self._city.upper_corner[self._y]),
(self._city.upper_corner[self._x], self._city.lower_corner[self._y]),
(self._city.lower_corner[self._x], self._city.lower_corner[self._y])])
gdf = gpd.GeoDataFrame(
geometry=[viewport],
crs=self._city.srs_name
)
gdf.to_file(f'{self._output_file_path}/buildings_shapefile.shp')
# Write xml content to file
xml_content = etree.tostring(facilities_xml, pretty_print=True, encoding='UTF-8').decode('utf-8')
output_file = f'{self._output_file_path}/{self._city.name}_facilities.xml'
xml_dtd = '<!DOCTYPE facilities SYSTEM "http://www.matsim.org/files/dtd/facilities_v1.dtd">\n'
Matsim._save_xml(output_file, xml_content, xml_dtd)
@staticmethod
def _convert_schedules(building_schedules):
"""
Converts building schedules into a format suitable for facilities.xml.
:param building_schedules: A list of building schedule objects to be converted.
:return: A list of dictionaries, each representing the converted schedule for a building.
"""
converted_schedules = []
opening_hour = 0
closing_hour = 0
schedule = building_schedules[0]
for i, value in enumerate(schedule.values):
if value > 0:
opening_hour = i
break
for i, value in reversed(list(enumerate(schedule.values))):
if value > 0:
closing_hour = i
break
for day in schedule.day_types:
if day[0:3] != 'hol':
converted_schedules.append({
'day': day[0:3],
'start_time': f'{opening_hour:02}:00:00',
'end_time': f'{closing_hour:02}:00:00'
})
return converted_schedules
@staticmethod
def _save_xml(output_file, xml_content, xml_dtd=None):
"""
Saves XML content to a file, optionally adding a DOCTYPE declaration and compressing the file.
:param output_file: The path where the XML file will be saved.
:param xml_content: The XML content to be written to the file.
:param xml_dtd: An optional DOCTYPE declaration to be included at the beginning of the file.
"""
if xml_dtd is None:
xml_dtd = ''
with open(output_file, 'w') as file:
file.write('<?xml version="1.0" encoding="utf-8"?>\n')
file.write(xml_dtd)
file.write(xml_content)
with open(output_file, 'rb') as f_in:
with gzip.open(output_file + '.gz', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
def _export_network(self):
"""
Generates a transportation network file from the city's OpenStreetMap data and buildings shapefile.
"""
url = Osm().pfb_file(self._city.location.region_code)
path = (Path(__file__).parent.parent.parent / f'data/weather/epw/{url.rsplit("/", 1)[1]}').resolve()
if not path.exists():
with open(path, 'wb') as pbf_file:
pbf_file.write(requests.get(url, allow_redirects=True).content)
command = [
self._java_path,
'-jar', self._jar_path,
path,
f'{self._output_file_path}/buildings_shapefile.shp',
f'{self._output_file_path}/{self._city.name}_network.xml.gz',
self._crs
]
subprocess.run(command)
def _export_population(self):
"""
Generates and exports the city's population data to an XML file.
"""
population = etree.Element('population')
person_id = 0
try:
# Generate work facilities
work = []
for facility in self._facilities['facility']:
if facility['activity'][0]['type'] != cte.RESIDENTIAL:
work.append({
'type': facility['activity'][0]['type'],
'capacity': int(facility['activity'][0]['capacity']),
'facility': facility['id'],
'x': facility['x'],
'y': facility['y'],
'start_time': '08:00:00',
'end_time': '18:00:00'
})
# Generate the population from residential places first
current_work = 0
for facility in self._facilities['facility']:
if facility['activity'][0]['type'] == cte.RESIDENTIAL:
max_capacity = int(facility['activity'][0]['capacity'])
for i in range(max_capacity):
person = etree.SubElement(population, 'person', {
'id': str(person_id),
'sex': 'm',
'age': '32',
'car_avail': 'always',
'employed': 'yes',
})
plan = etree.SubElement(person, 'plan', {'selected': 'yes'})
# Residential activity
etree.SubElement(plan, 'act', {
'type': f'{facility["activity"][0]["type"]}',
'facility': f'{facility["id"]}',
'x': f'{facility["x"]}',
'y': f'{facility["y"]}',
'end_time': '7:30:00'
})
# Leg to work
etree.SubElement(plan, 'leg', {'mode': 'car'})
# Work activity
etree.SubElement(plan, 'act', {
'type': f'{work[current_work]["type"]}',
'facility': f'{work[current_work]["facility"]}',
'x': f'{work[current_work]["x"]}',
'y': f'{work[current_work]["y"]}',
'start_time': f'{work[current_work]["start_time"]}',
'end_time': f'{work[current_work]["end_time"]}',
})
# Leg to home
etree.SubElement(plan, 'leg', {'mode': 'car'})
# Residential activity (return)
etree.SubElement(plan, 'act', {
'type': f'{facility["activity"][0]["type"]}',
'facility': f'{facility["id"]}',
'x': f'{facility["x"]}',
'y': f'{facility["y"]}',
})
work[current_work]['capacity'] -= 1
if work[current_work]['capacity'] == 0:
current_work += 1
person_id += 1
except Exception as ex:
print('error: ', ex)
# Write xml content to file
xml_content = etree.tostring(population, pretty_print=True, encoding='UTF-8').decode('utf-8')
output_file = f'{self._output_file_path}/{self._city.name}_population.xml'
self._save_xml(
output_file,
xml_content,
f'<!DOCTYPE population SYSTEM "http://www.matsim.org/files/dtd/population_v5.dtd">\n'
)
@staticmethod
def _add_param(parent, name, value):
"""
Helper function to add a parameter to an XML element.
:param parent: The parent XML element to which the parameter should be added.
:param name: The name of the parameter.
:param value: The value of the parameter.
"""
etree.SubElement(parent, 'param', {
'name': name,
'value': value
})
@staticmethod
def _add_parameterset(parent, attribute_type, parameters):
"""
Helper function to add a set of parameters to an XML element.
:param parent: The parent XML element to which the parameterset should be added.
:param attribute_type: The attribute type for the parameterset.
:param parameters: A list of tuples, each containing the name and value of a parameter.
"""
parameterset = etree.SubElement(parent, 'parameterset', {
'type': attribute_type
})
for name, value in parameters:
Matsim._add_param(parameterset, name, value)
def _export_config(self):
"""
Creates and exports the simulation configuration settings to an XML file.
"""
root = etree.Element('config')
# ======== NETWORK ========= #
network_path_module = etree.SubElement(root, 'module', {
'name': 'network'
})
Matsim._add_param(network_path_module, 'inputNetworkFile', f'{self._city.name}_network.xml.gz/')
# ======== POPULATION ========= #
population_path_module = etree.SubElement(root, 'module', {
'name': 'plans'
})
Matsim._add_param(population_path_module, 'inputPlansFile', f'{self._city.name}_population.xml.gz')
# ======== FACILITIES ========= #
facilities_path_module = etree.SubElement(root, 'module', {
'name': 'facilities'
})
Matsim._add_param(facilities_path_module, 'inputFacilitiesFile', f'{self._city.name}_facilities.xml.gz')
Matsim._add_param(facilities_path_module, 'facilitiesSource', 'fromFile')
# ======== CONTROLER ========= #
controler_module = etree.SubElement(root, 'module', {
'name': 'controler'
})
controler_params = [
('writeEventsInterval', '1000'),
('writePlansInterval', '1000'),
('eventsFileFormat', 'xml'),
('outputDirectory', f"{self._output_file_path}/Montreal"),
('firstIteration', '0'),
('lastIteration', '10'),
('mobsim', 'qsim'),
]
for param in controler_params:
Matsim._add_param(controler_module, param[0], param[1])
# ======== QSIM ========= #
qsim_module = etree.SubElement(root, 'module', {
'name': 'qsim'
})
qsim_params = [
('startTime', '00:00:00'),
('endTime', '30:00:00'),
('flowCapacityFactor', '1.00'),
('storageCapacityFactor', '1.00'),
('numberOfThreads', '1'),
('snapshotperiod', '00:00:01'),
('removeStuckVehicles', 'false'),
('stuckTime', '3600.0'),
('timeStepSize', '00:00:01'),
('trafficDynamics', 'queue'),
]
for param in qsim_params:
Matsim._add_param(qsim_module, param[0], param[1])
# ======== SCORING ========= #
score_module = etree.SubElement(root, 'module', {
'name': 'planCalcScore'
})
Matsim._add_param(score_module, 'BrainExpBeta', '1.0')
Matsim._add_param(score_module, 'learningRate', '1.0')
scoring_paramset = etree.SubElement(score_module, 'parameterset', {
'type': 'scoringParameters'
})
scoring_paramset_params = [
('earlyDeparture', '0.0'),
('lateArrival', '0.0'),
('marginalUtilityOfMoney', '0.062'),
('performing', '0.96'),
('utilityOfLineSwitch', '0.0'),
('waitingPt', '-0.18'),
]
for param in scoring_paramset_params:
Matsim._add_param(scoring_paramset, param[0], param[1])
mode_paramsets = [
('car',
[
('marginalUtilityOfTraveling_util_hr', '0.0'),
('constant', '-0.562'),
('monetaryDistanceRate', '-0.0004')
]),
('walk',
[
('marginalUtilityOfTraveling_util_hr', '-1.14'),
('constant', '0.0'),
('marginalUtilityOfDistance_util_m', '0.0')
])
]
for mode, parameters in mode_paramsets:
Matsim._add_parameterset(scoring_paramset, 'modeParams', [('mode', mode)] + parameters)
activity_paramsets = [
('residential',[
('priority', '1'),
('typicalDuration', '13:00:00'),
('minimalDuration', '01:00:00'),
]),
]
for facility in self._facilities['facility']:
if not any(activity[0] == facility['activity'][0]['type'] for activity in activity_paramsets):
activity_paramsets.append(
(facility['activity'][0]['type'], [
('priority', '1'),
('typicalDuration', '09:00:00'),
('minimalDuration', '08:00:00'),
('openingTime', '08:00:00'),
('earliestEndTime', '17:00:00'),
('latestStartTime', '09:00:00'),
('closingTime', '18:00:00'),
])
)
for activity_type, parameters in activity_paramsets:
Matsim._add_parameterset(scoring_paramset, "activityParams", [("activityType", activity_type)] + parameters)
# ======== STRATEGY ========= #
strategy_module = etree.SubElement(root, 'module', {
'name': 'strategy'
})
Matsim._add_param(strategy_module, 'maxAgentPlanMemorySize', '6')
strategy_paramsets = [
('ChangeExpBeta',[
('weight', '0.7'),
]),
('ReRoute',[
('disableAfterIteration', '2900'),
('weight', '0.01'),
]),
('SubtourModeChoice', [
('disableAfterIteration', '2900'),
('weight', '0.01'),
]),
('TimeAllocationMutator', [
('disableAfterIteration', '2900'),
('weight', '0.01'),
]),
]
for name, parameters in strategy_paramsets:
Matsim._add_parameterset(strategy_module, 'strategysettings', [("strategyName", name)] + parameters)
# ======== SUBTOUR MODE CHOICE ========= #
subtour_module = etree.SubElement(root, 'module', {
'name': 'subtourModeChoice'
})
# Defines the chain-based modes, seperated by commas
Matsim._add_param(subtour_module, 'chainBasedModes', 'car')
# Defines whether car availability must be considered or not. An agent has no car only if it has no license, or never access to a car
Matsim._add_param(subtour_module, 'considerCarAvailability', 'true')
# Defines all the modes available, including chain-based modes, seperated by commas
Matsim._add_param(subtour_module, 'modes', 'car,walk')
# Write xml content to file
xml_content = etree.tostring(root, pretty_print=True, encoding='UTF-8').decode('utf-8')
output_file = f"{self._output_file_path}/{self._city.name}_config.xml"
self._save_xml(output_file, xml_content, f'<!DOCTYPE config SYSTEM "http://www.matsim.org/files/dtd/config_v2.dtd">\n')

View File

@ -0,0 +1,39 @@
"""
Traffic factory export a city traffic information into several formats
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from pathlib import Path
from hub.exports.traffic.matsim import Matsim
from hub.helpers.utils import validate_import_export_type
class TrafficFactory:
"""
Exports factory class
"""
def __init__(self, handler, city, path):
self._city = city
self._handler = '_' + handler.lower()
validate_import_export_type(TrafficFactory, handler)
if isinstance(path, str):
path = Path(path)
self._path = path
@property
def _matsim(self):
"""
Export the city traffic information to Matsim
:return: None
"""
return Matsim(self._city, self._path)
def export(self):
"""
Export the city given to the class using the given export type handler
:return: None
"""
return getattr(self, self._handler, lambda: None)

View File

@ -23,4 +23,5 @@ geopandas
triangle triangle
psycopg2-binary psycopg2-binary
Pillow Pillow
pathlib pathlib
lxml