Quickstart ========== The intended and most convenient way of running *Kalithea* jobs with *Perion* is by using the web service *ApoPerion*. It is (by default) accessible at the localhost at port 5000: ``127.0.0.1:5000``. ApoPerion is a kind of ReST-API and communicates in JSON. For processing larger amounts of requests or recurrent tasks it might be helpful to write a short script to handle those in- and outputs. An example in Python is given :ref:`below `. .. note:: Further on in this guide, the paradigmatic HTTP requests to communicate with *ApoPerion* are sent via the ``curl`` command-line tool. On Windows, it seems to be necessary to use double quotation marks -- and only those -- with ``curl``. Hence, in strings containing double quotes (e.g. JSON data) they need to be escaped: ``\"``. Creating and Removing Jobs -------------------------- ApoPerion stores all user-sent information and visibility outputs generated by Kalithea. To keep the information belonging to a certain task together, a unique *token* needs to be created. Via this token the data can be accessed and updated. Creating a Job ^^^^^^^^^^^^^^ To generate a token -- and with it a new job -- send the command ``new`` to the service: .. code-block:: console $ curl "127.0.0.1:5000/new" {"token":"b2afc4dc-bf91-4899-8654-edc6f48f47ba"} It responds with the newly generated token, which is needed for all further operations concerning the job. .. note:: It is not necessary to put the URL for the ``curl`` command in quotation marks. But it helps later on when it comes to more complex URLs containing special charaters as ``?`` and ``&``. Removing a Job ^^^^^^^^^^^^^^ If all calculations are done and all data is fetched, make sure to remove the created job with the ``remove`` command followed by the token to free the occupied memory. .. warning:: Keep in mind that removal of a job cannot be undone! Make sure that all generated data is retrieved before removing a job. .. code:: console $ curl "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/remove" {"status":200} Uploading Layout and Emitter Information ---------------------------------------- .. note:: Instead of uploading model, DEM and emitter information separately, it is possible to give URLs to those files in the run parameters file (see :ref:`allinone`). That way, only one input file has to be provided. *Kalithea* calculates the visibility based on a 3D model. This model needs to be provided by the user either in form of an `STL file `_ (ASCII or binary) or as an area layout given in `GeoJSON format `_. The detailed specifications of the GeoJSON elements that can or need to be used are documented in :doc:`abmlayout`. Repeated upload of an STL file or a GeoJSON layout to the same job (same token) overwrites a possibly already existing one. Since the upload of a layout triggers the creation of an STL file, it is not necessary to provide both. Each job can only have a single 3D model regardless in which format it was provided. If both a GeoJSON layout and a detailed STL model are available, it is advisable to first upload the GeoJSON file and then the STL file. This way, the outer area boundaries of the layout are available to *Perion* and the detailed 3D model can still be used. Note, however, that it is the user's responsibility to correctly align the coordinates of the layout and the STL model. STL files do not store meta data such as the coordinate reference systems (CRS). The coordinates in the STL file should be given in a metric, rectilinear CRS. For later transformation of the obtained results in the target CRS, the city model's CRS can be specified in the :ref:`run parameters `. .. warning:: The user has to make sure, that all uploaded files containing georeferenced data will be stored using the **same coordinate reference system**. A common reason for not getting the expected results is a mismatch in the object's positions (e.g. emitters lying outside the 3D model's boundaries). STL Upload ^^^^^^^^^^ To upload an STL model, send the respective data as multipart/form-data (``curl -F``) with the parameter `stl` to ``Host:Port//stl``. If the data exists in a file (which is most likely the case), precede the file name with an @ sign. .. code:: console $ curl -F stl="@path/to/model.stl" "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/stl" {"status":200} Successful upload is acknowledged with return status 200. .. _layoutupload: Layout Upload ^^^^^^^^^^^^^ GeoJSON layout data can be sent to the service via the POST method. The receiving address has the form ``Host:Port//layout``. .. code:: console $ curl -X POST -H "Content-Type: application/json" -d "@path/to/file.geojson" \ "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/layout" {"status":200} *Perion* will create a 3D model from the layout data and save it in STL format. The extrusion height of the model can be controlled with the key ``"extrusion-height"`` followed by a ``float`` value added to the topmost object in the uploaded GeoJSON data. If the key:value pair ``"extrusion-at": [x, y]`` (where `x` and `y` are of type ``float``) is added, the centroid of the layout is moved to `x,y` and the extrusion is performed there. This is helpful to reduce numerical instabilities in the coordinates. In the latter case, the origial position of the layout's centroid is returned after successful processing. .. code:: console $ curl -X POST -H "Content-Type: application/json" -d "{\"type\": \"FeatureCollection\", \ \"features\": [...], \"extrusion-height\": 5.2, \"extrusion-at\": [0, 0]}" \ "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/layout" {"centroid":[..., ...],"status":200} The key ``"target-crs"`` determines the CRS of the created 3D model (e.g. as `EPSG code `_). Omitting this key keeps the CRS given in the layout's feature collection. In case of doubt, EPSG 3857 (``"target-crs": 3857``) should be a good choice here. Emitter Upload ^^^^^^^^^^^^^^ .. note:: Emitters only need to be provided if the perceptibility of warning devices is to be calculated. For visibility/audibility fields, it is sufficient to upload a layout or STL model. Analogously to the layout, JSON or GeoJSON emitter data can be sent to the service via the POST method. The receiving address prototype is ``Host:Port//emitter``. There are two main ways to format the data. First, a JSON list can be given, which contains a list of ten ``floats`` for each emitter. Those ten numbers specify the coordinates (1-3), the direction (4-6), the width and height (7, 8), the opening angle in degree (9) and the itensity/power (10) of the warning device. The coordinates in this list will be used as given and have to match the 3D model. This is the user's responsibility. .. code:: console $ curl -X POST -H "Content-Type: application/json" -d "[[182,8,2,0,1,0,1.2,3.2,72.5,550.0], ...]" \ "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/emitter" {"status":200} Second, a GeoJSON ``FeatureColletion`` containing ``Points`` can be provided. Each warning device is represented by two points. One for its position and one for its view direction. Dimensions and other properties are retrieved from the *Warning Device Data Base* using the ``"name"`` and ``"type"`` fields of the position points as selector. The section :ref:`wdpoints` exemplifies such a feature colletion. To change the CRS, the key ``"target-crs"`` can be given on the topmost level of the GeoJSON data just as for the layout. If the key is omitted, the CRS of the 3D model will be used when available. Otherwise the CRS will be kept as given. .. code:: console $ curl -X POST -H "Content-Type: application/json" -d "@warning_devices.geojson" \ "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/emitter" {"status":200} .. _runningcalc: Running Calculations -------------------- Once the necessary data are successfully uploaded, the simulation can be started sending the run parameters as a JSON object to ``Host:Port//run``. .. code:: console $ curl -X POST -H "Content-Type: application/json" -d "{\"general\": ..., \"perion\": ...}" \ "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/run" {"status":200} Some of the run parameters are mandatory, other are optional. The run paramters are passed on to *Kalithea* except for the (optional) ``"perion"`` object. A detailed overview of all available parameters is given :ref:`here `. Below is a minimal example for a JSON run parameter object. It contains only the mandatory fields. For all other parameters the default settings are used. .. code:: javascript { "general": { "generator": "cpu", "type": "visrad" }, "analysis": { "cell-width": 1.0, "evaluation-height": 2.0, "max-visibility": 400.0, "angle-resolution": 360.0 } } The calculation itself runs in a separat process. Its status is available via ``Host:Port//status``. .. code:: console $ curl "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/status" {"kalithea-status":"running","progress":0.0,"stdout":["Creating Scenario"],"stderr":[]} As soon as *Kalithea* has ended, the job status changes to ``"finished"``. Then, the results can be queried. .. _allinone: All-in-one Run File ^^^^^^^^^^^^^^^^^^^ To reduce the number of file uploads for one-shot jobs, it is possible to provide URLs for STL, DEM and/or emitter files in an additional ``"scenario"`` object in the run parameters. URLs have to be given in the form ``http://host:port/path/to/file.ext``. Indication of the ``port`` is optional. The recognized keys in the ``"scenario"`` object are ``"city-model"`` (STL file), ``"elevation-model"`` (DEM file) and ``"emitter-file"`` (GeoJSON file). .. code:: javascript { "general": { ... }, "scenario": { "city-model": "http://localhost:5000/my_data/mesh.stl", "elevation-model": "http://www.some-domain.com/elevations/DEM.xyz", "emitter-file": "http://127.0.0.1:5000/project/emitters.geojson" } "analysis": { ... } } Retrieving Results ------------------ All produced outputs of finalized jobs can be requested at ``Host:Port//results``. For this URL two optional GET arguments are available. The ``format`` argument defines the :ref:`output format ` of the retrieved data. Allowed values are one of the plain text formats ``geojson``, ``geojson-grid``, ``geojson-abm``, and ``csv``, or one of the output types defined by the user in the ``"output-types"`` object of the *Kalithea* run parameters (i.e., ``png``, ``vtk``, or ``blob``). The ``id`` argument requsts download of a specific data set. It is only meaningful for perceptibility calculations of warning devices. Each warning device is assigned an ID, either automatically (ascending integers starting with 0) or according to the feature IDs in the feature collection defining the device positions and directions (see :ref:`wdpoints`). If a job comprises several perceptibility simulation and no ID is specified, all results are returned. When using the ``curl`` command-line tool, it is advisable to add the ``-o`` option to redirect the respons to a file. .. note:: When special characters like the question mark (?) or ampersand (&) are part of the URL, it is necessary to write it in double quotes (at least on Windows systems). .. code:: console $ curl "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/results?format=geojson" \ -o visibility_field.geojson % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 8602k 100 8602k 0 0 748k 0 0:00:11 0:00:11 --:--:-- 2026k .. code:: console $ curl "127.0.0.1:5000/b2afc4dc-bf91-4899-8654-edc6f48f47ba/results?format=geojson-abm&id=2" \ -o display2_visibility.geojson % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 2671k 100 2671k 0 0 754k 0 0:00:03 0:00:03 --:--:-- 754k .. _python_web_example: Job Management via Python Script -------------------------------- The following Python snippet exemplifies the communication with the ApoPerion API using the *apopyrion* module. At the moment, the module is only available from the Docker container (/perion/apopyrion.py). .. code-block:: python import asyncio from apopyrion.client import ApoperionJob, ApoperionJobError stl_file = "myCityModel.stl" emitters_file = "myEmitters.json" run_params_file = "myRunParams.json" results_file = "myResult.blob" try: with ApoperionJob() as job: print("uploading STL...") job.assignSTL(stl_file) print("uploading emitters...") job.assignEmitters(emitters_file) print("running job...") job.run(run_params_file) asyncio.run(job.wait()) # wait() is an asynchronous function print("fetching results...") job.saveResults(results_file, format="blob") except ApoperionJobError as err: print(err) When creating an instance of ``ApoperionJob``, the host address (``host``) and port number (``port``) can be specified. They default to ``localhost:5000``. Hence, the script can either connect to the local ApoPerion service running in the Docker container or to any other remote ApoPerion instance. Command Line Interface ---------------------- Based on the Python API *apopyrion*, the following script works as a command line interface that manages the communication with the ApoPerion web service. .. code-block:: python import argparse import asyncio from apopyrion.client import ApoperionJob, ApoperionJobError from pathlib import Path parser = argparse.ArgumentParser() parser.add_argument("params", type=Path, help="Path to a set of run parameters in JSON format") parser.add_argument("-s", "--stl", type=Path, help="Path to a city model in STL format") parser.add_argument("-d", "--dem", type=Path, help="Path to a elevation model in XYZ format") parser.add_argument("-e", "--emitters", type=Path, metavar="ETR", help="Path to a set of emitter specifications in JSON format") parser.add_argument("-o", "--output", type=Path, metavar="OUT", help="Path to the output file") parser.add_argument("-i", "--ids", type=int, metavar="ID", nargs="+", help="List of emitter IDs to get output for") parser.add_argument("-a", "--host", type=str, default="localhost", help="Host name of ApoPerion service (default: localhost)") parser.add_argument("-p", "--port", type=int, default=5000, help="Port of ApoPerion service (default: 5000)") args = parser.parse_args() blob_file = args.output if args.output else args.params.with_suffix(".blob") try: with ApoperionJob(args.host, args.port) as job: if args.stl: print("uploading STL...") job.assignSTL(str(args.stl)) if args.dem: print("uploading DEM...") job.assignDEM(str(args.dem)) if args.emitters: print("uploading emitters...") job.assignEmitters(str(args.emitters)) print("running job...") job.run(str(args.params)) asyncio.run(job.wait()) print("fetching results") if args.ids: for i in args.ids: print(f" --> ID={i}") curr_blob_file = blob_file.with_name(blob_file.stem + f"_{i:02}" + blob_file.suffix) job.saveResults(curr_blob_file, format="blob", id=i) else: job.saveResults(blob_file, format="blob") except ApoperionJobError as err: print(err) When saved in a file "apoperion_cli.py", it can be used like this: .. code:: console $ python apoperion_cli.py my_params.json -s city-model.stl -o my_result.blob