run_simstadt_workflow.py 7.06 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
r"""Tries to find simstadt, find a workflowstep, and run it with the specified
CityGMLs.

It could be useful in order to run the same workflows with many different CityGMLs, or with slightly different
parameters.

User config part will probably need to be updated.
Corresponding workflows can be defined and saved in the SimStadt GUI.


Example output with the current configuration:

    # SimStadt found in C:\Users\eric.duminil\Desktop\SimStadt_0.10.0-SNAPSHOT_master_20230601_607b426

    # Preparing 'Gruenbuehl_PhotovoltaicPotential&Financial' workflow (a.step):
    * Adding Gruenbuehl_LOD2_ALKIS_1010.gml
    * Adding Gruenbuehl_LOD2_ALKIS_1010_2buildings.gml

    # Launching Gruenbuehl_PhotovoltaicPotential&Financial:
    Workflow finished succesfuly!

    # Listing written files :
    * 'Gruenbuehl.proj\a.step\a.step\a.step\a.step\a.step\hourly_GHI_DHI_Ta.prn'
    * 'Gruenbuehl.proj\a.step\a.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_2buildings_pvgis_SARAH_2005_2016_Hay_pv_potential.csv'
    * 'Gruenbuehl.proj\a.step\a.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_pvgis_SARAH_2005_2016_Hay_pv_potential.csv'


    # Preparing 'Gruenbuehl_HeatDemand' workflow (b.step):
    * Adding Gruenbuehl_LOD2_ALKIS_1010.gml
    * Adding Gruenbuehl_LOD2_ALKIS_1010_2buildings.gml

    # Launching Gruenbuehl_HeatDemand:
    Workflow finished succesfuly!

    # Listing written files :
    * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\hourly_GHI_DHI_Ta.prn'
    * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_2buildings_DIN18599_HEATING.csv'
    * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_2buildings_DIN18599_HEATING.log'
    * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_DIN18599_HEATING.csv'
    * 'Gruenbuehl.proj\b.step\a.step\a.step\a.step\a.step\a.step\a.step\Gruenbuehl_LOD2_ALKIS_1010_DIN18599_HEATING.log'
"""

from pathlib import Path
import logging
import platform
import subprocess
import os
import sys
from xml.etree import ElementTree as et

logging.basicConfig(format='%(message)s')
# NOTE: Can set to logging.DEBUG for more info
logging.getLogger().setLevel(logging.INFO)

SCRIPT_DIR = Path(__file__).parent

# TODO: Possibility to change param in params.xml
# TODO: Possibility to change attribute in physics or usage library
# TODO: Possibility to change attribute in CityGML
# TODO: Parse CSV output

##############################################
# User config

SIMSTADT_GLOB = 'Desktop/SimStadt_0.*/'

67
REPO_PATH = Path.home() / 'git' / 'simstadt' / 'TestRepository'
68
69
70
71
72
73
74
75
76
77
78
PROJECT_NAME = 'Gruenbuehl'
WORKFLOW_FOLDERS = ['a.step', 'b.step']
CITYGMLS = ['Gruenbuehl_LOD2_ALKIS_1010.gml', 'Gruenbuehl_LOD2_ALKIS_1010_2buildings.gml']


##############################################

PARAMS = 'params.xml'


# TODO: write tests
79
def find_simstadt(simstadt_glob: str) -> Path:
80
81
82
83
84
85
86
87
    try:
        found = next(Path.home().glob(simstadt_glob))
        logging.info("# SimStadt found in %s\n", found)
        return found
    except StopIteration:
        sys.exit(f"Sorry, no SimStadt installation could be found in {simstadt_glob}")


88
def check_paths(repo_path: Path, workflow_path: Path):
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
    if not repo_path.exists():
        sys.exit(f"Sorry, no Repository could be found in {repo_path}")

    if not workflow_path.exists():
        sys.exit(f"Sorry, no workflow could be found in {workflow_path}")

    if not (workflow_path / PARAMS).exists():
        sys.exit(f"Sorry, no workflow is defined in {workflow_path / PARAMS}")

    proj_path = workflow_path.parent

    for gml in proj_path.glob('*.gml'):
        logging.debug("  * Available citygml : %s", gml.name)


Eric Duminil's avatar
Eric Duminil committed
104
def prepare_workflow(workflow_path: Path, citygmls: list[str]):
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    tree = et.parse(workflow_path / PARAMS)
    root = tree.getroot()

    name = root.find(".//void[@property='name']/string").text
    logging.info("# Preparing '%s' workflow (%s):", name, workflow_path.name)

    citygml_node = root.find(".//void[@property='cityGmlFileNames']")

    if citygml_node:
        logging.debug('  Removing old citygmls')
        children = list(citygml_node)

        for add_node in children:
            citygml_node.remove(add_node)
    else:
        logging.debug('  Add new XML node')
        workflow_node = root.find(".//object[@class='eu.simstadt.workflows.CityGmlWorkflow']")
        citygml_node = et.SubElement(workflow_node, 'void')
        citygml_node.set('property', 'cityGmlFileNames')

    for citygml in citygmls:
        logging.info("  * Adding %s", citygml)
        add_node = et.SubElement(citygml_node, 'void')
        add_node.set('method', 'add')
        gml = et.SubElement(add_node, 'string')
        gml.text = citygml

    tree.write(workflow_path / PARAMS)

    return name


Eric Duminil's avatar
Eric Duminil committed
137
138
def get_all_files(folder: Path) -> dict[Path, float]:
    files = [f for f in folder.glob('**/*') if f.is_file() and f.name != PARAMS]
139
140
141
142
143
144
145
146
147
148
    return {f: f.stat().st_mtime for f in files}


def simstadt_script():
    if platform.system().lower() == 'windows':
        return 'SimStadt.bat'
    else:
        return './SimStadt.sh'


Eric Duminil's avatar
Eric Duminil committed
149
def run_simstadt(simstadt_path: Path, workflow_path:Path , name: str):
150
151
152
    os.chdir(simstadt_path)
    try:
        logging.info("\n# Launching %s:", name)
Eric Duminil's avatar
Eric Duminil committed
153
        result = subprocess.run([simstadt_script(), str(workflow_path)],
154
155
156
157
158
159
160
161
162
163
164
165
                                text=True,
                                capture_output=True,
                                check=True
                                )
        logging.debug(result.stderr)
        logging.info("  Workflow finished succesfuly!\n")
    except subprocess.CalledProcessError:
        # NOTE: Current SimStadt actually never raises an error.
        logging.warning("  Workflow failed!\n")
        logging.info(result.stderr)


Eric Duminil's avatar
Eric Duminil committed
166
167
def compare_written_files(repo_path: Path, before, after) -> list[Path]:
    modified_files = []
168
169
170
171
172
    logging.info("# Listing written files :")
    for result_file, modification_time in after.items():
        before_time = before.get(result_file, 0)
        if modification_time > before_time:
            logging.info("  * '%s'", result_file.relative_to(repo_path))
Eric Duminil's avatar
Eric Duminil committed
173
            modified_files.append(result_file)
174
    logging.info("\n")
Eric Duminil's avatar
Eric Duminil committed
175
    return modified_files
176
177


Eric Duminil's avatar
Eric Duminil committed
178
def run_workflow(simstadt_path: Path, workflow_path: Path, citygmls: list[str]) -> list[Path]:
179
    repo_path = workflow_path.parent.parent
180

181
    check_paths(repo_path, workflow_path)
182
183
184
185
186
187
188

    name = prepare_workflow(workflow_path, citygmls)

    before = get_all_files(workflow_path)
    run_simstadt(simstadt_path, workflow_path, name)
    after = get_all_files(workflow_path)

Eric Duminil's avatar
Eric Duminil committed
189
    return compare_written_files(repo_path, before, after)
190
191


192
def main(glob: str, repo: Path, project_name: str, workflows: list[str], citygmls: list[str]):
193
    simstadt_path = find_simstadt(glob)
194
    project_path = repo / f'{project_name}.proj'
195
196

    for workflow_folder in workflows:
197
        run_workflow(simstadt_path, project_path / workflow_folder, citygmls)
198
199
200


if __name__ == '__main__':
201
    main(SIMSTADT_GLOB, REPO_PATH, PROJECT_NAME, WORKFLOW_FOLDERS, CITYGMLS)