16.12.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

Commit 89ca9c72 authored by Benedikt Kleinmeier's avatar Benedikt Kleinmeier
Browse files

Merge branch 'master' into salient_behavior

parents ca7a6d16 c5e9ad8e
Pipeline #137534 passed with stages
in 154 minutes and 50 seconds
......@@ -44,6 +44,7 @@ VadereModelCalibration/**/output
# Operating system files
......@@ -53,3 +54,7 @@ VadereModelTests/*_private
# Vadere Cache
\ No newline at end of file
......@@ -25,6 +25,7 @@ variables:
VADERE_DEPLOYMENT_BASE_URL: "http://www.vadere.org/builds"
# Stage Definitions
# Watch out: integration tests and the seed tests run after deployment, because
......@@ -81,6 +82,8 @@ stages:
- mvn -Dmaven.test.skip=true package
- python3 -m zipfile -c ${VADERE_PACKAGE_NAME_BRANCHES} CHANGELOG.md README.md VadereModelTests/ VadereGui/target/vadere-gui.jar VadereSimulator/target/vadere-console.jar
- scp ${VADERE_PACKAGE_NAME_BRANCHES} di49mur@webdev-mwn.lrz.de:~/webserver/htdocs/builds/master/${VADERE_PACKAGE_NAME_BRANCHES}
- python3 -m zipfile -c ${EIKMESH_PACKAGE_NAME_BRANCHES} VadereMeshing/README.md VadereMeshing/target/meshing-0.1-SNAPSHOT.jar
- scp ${EIKMESH_PACKAGE_NAME_BRANCHES} di49mur@webdev-mwn.lrz.de:~/webserver/htdocs/builds/master/eikmesh/${EIKMESH_PACKAGE_NAME_BRANCHES}
- master
......@@ -2,10 +2,69 @@
**Note:** Before writing into this file, read the guidelines in [Writing Changelog Entries.md](Documentation/contributing/Writing Changelog Entries.md).
## In Progress: v0.7
## In Progress:
### Added
### Changed
## v1.2 (2019-07-13)
### Added
- VadereServer:
- Introducing TraCI server implementation for Vadere to allow remote control
of Vaderes simulation loop.
- VadereManager/target/vadere-server.jar will open a TCP socket and waits
for connection request.
- FloorField Caching:
- CellGrid based floorfields can be loaded from a persisted cache file.
- Added attributes:
- `cacheDir: ""` relative path
- Cache files will be saved in a `__cache__` directory beside (sibling) the
scenario file. With `cacheDir` it is possible to create some structure within
the cache directory. Important: If `cacheDir` is an absolute path the cache
file will not be placed in `__cache__`.
- A TXT_CACHE type will save the CellGrid in a human readable form (CSV) and
BIN_CACHE will use a simple binary format for better performance and space
- TikzGenerator:
- add configuration to show all traces of pedestrians, even if they
left the simulation. Config: PostVis -> `Show all Trajectories on Snapshot`
- add named coordinate for each scenario element. The coordinate represents the
centroid point of the polygon and can be used as a point for relative placement
of labels.
- introduced id for reference: Source `src_XXX`, Target `trg_XXX`, AbsorbingArea `absorb_XXX`
Obstacels `obs_XXX`, Stairs `str_XXX`, MeasurementArea `mrmtA_XXX`
- single step mode in GUI: Allows the user to step through the simulation one
step at a time to identify bugs.
- simplify obstacles (with undo support): Merge multiple obstacles based on the
convex hull their points create. The merge can be undon
- add features to open street map (osm) importer:
- import 'open' paths as polygons with a specified width. With this it is
possible to create walls or subway entrance
- add option to include osm ids into each obstacle created
`PostVis` added functionalities:
- the PostVis works now on the basis of simulation time instead of time steps. Agents' position will be interpolated.
- the user can jump to a specific simulation time
- the user can step forward by steps as small as 0.01s
- the user can make videos using also this new feature which results in very smooth movement
- the frames per seconds (FPS) is now more accurate
### Changed
- version migration ensures that new attributes in the scenario file will be added
with default values if no specific actions are defined in a Transformation class.
- TikzGenerator: style information for pedestrians are moved to dedicated style
classes to simplify changes in generated tikz files.
## v1.0 (2019-06-13)
### Added
- Open a trajectory file in the stand-alone application `vadere-postvis.jar` via drag and drop from file explorer.
- Scenario elements like obstacles and targets can now be resized using the mouse in the topography creator tab (first, select element via left-click and then move mouse to one of its edges to get the resize handles).
- Draw also Voronoi diagram in `TikzGenerator`.
- Added new scenario element `AbsorbingArea` to absorb agents immediately.
......@@ -33,7 +92,7 @@
- `VadereConsole`: Add option `--logname <filename>` to specify the name for the log file.
Please note that the log file `log.out` is always written (because this file is configured
in the `log4j.properties` of each Vadere module (i.e., "gui", "meshing", ...). (c61a3946: Simulator)
- New outputprocessors
- New outputprocessors
* mainly for the BHM: QueueWidthProcessor (to evaluate queueWidth) and PedestrianBehaviorProcessor (evaluate behavior: step / tangential step / sideways step / wait)
* solely for the OSM: PedestrianFootStepProcessor (logs every step instead of the positions at each time step )
......@@ -61,7 +120,7 @@
- `GUI`:
- improved coloring
- `OutputFile`: Distinguish between indices (rows) and headers (columns), in code and with a new checkbox that when enables allow to write meta-data into output files. Furthermore, resolve naming conflicts (if they occur) in headers of output files (see #201).
- `OutputFile`: Distinguish between indices (rows) and headers (columns), in code and with a new checkbox that when enables allow to write meta-data into output files. Furthermore, resolve naming conflicts (if they occur) in headers of output files (see #201).
### Changed
......@@ -72,7 +131,7 @@
- Header in output file have now the following form "[NAME]-PID[ID]". This avoids name conflicts and makes mapping to the relevant processor easy and fast.
- Migration to Java 11 (OpenJDK).
- Removed directory `Documentation/version-control` which contained the Git hooks. The Git hooks are not required anymore. Instead, added `git rev-parse HEAD` to file `VadereSimulator/pom.xml` to create `VadereSimulator/resources/current_commit_hash.txt` during each build via `mvn compile`.
**Note:** The file `current_commit_hash.txt` is created during Maven's validation phase, i.e., before the actual build.
**Note:** The file `current_commit_hash.txt` is created during Maven's validation phase, i.e., before the actual build.
## v0.6 (2018-09-07)
......@@ -26,7 +26,7 @@ The Vadere framework includes a mesh generator for unstructured high-quality 2D
### Dependencies
* Java 11 (OpenJDK recommended)
* Java 11 or above (OpenJDK recommended -> see the official [Java website](https://jdk.java.net/))
* OpenCL (optional but recommended -> see the [install instructions](https://gitlab.lrz.de/vadere/vadere/tree/master/Documentation/installation/OpenCL-Installation.md) for details)
### Pre-Built Releases
......@@ -46,23 +46,22 @@ The ZIP file contains:
### Run the Application
1. Start the Application: After building the application, you can start Vadere by running `java -jar VadereGui/target/vadere-gui.jar`.
2. (If you only want to use the Postvisualization-Tool you can do so by running `java -jar VadereGui/target/postvis.jar`).
Open a terminal and enter `path/to/openjdk/java -jar vadere-gui.jar`.
### Run Built-In Examples
With the following steps, you can run a simulation with one of the built-in examples from [VadereModelTests](VadereModelTests):
- start Vadere
- *Project* > *Open*
- choose `vadere.project` of one of the projects e.g. [TestOSM](https://gitlab.lrz.de/vadere/vadere/tree/master/VadereModelTests/TestOSM) and click *open*
- select the scenario on the left and press *run selected scenario*
- Start Vadere
- Click *Project* > *Open*
- Choose `vadere.project` of one of the test projects, e.g. [TestOSM](https://gitlab.lrz.de/vadere/vadere/tree/master/VadereModelTests/TestOSM) and click *open*
- Select the scenario on the left and press *run selected scenario*
## Build from Source
### Dependencies
* Java 11 (OpenJDK recommended)
* Java 11 or above (OpenJDK recommended)
* Maven 3.0
* Git
* OpenCL (optional but recommended)
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -55,15 +55,172 @@ from string import Template
import argparse
import utm
import math
import numpy as np
import itertools as iter
from typing import List
vadere_obstacle_string = """{
"shape" : {
"type" : "POLYGON",
"points" : [ $points ]
"id" : $id
class PathToPolygon:
def __init__(self, list_of_tuples, dist):
self.points = [np.array([p[0], p[1]]) for p in list_of_tuples]
self.dist = dist
self.rotPositive = np.array(((0, -1), (1, 0)))
self.rotNegative = np.array(((0, 1), (-1, 0)))
self.polygon = []
def get_poly_object(cls, list_of_tuples, dist, id):
ptp = cls(list_of_tuples, dist)
return PolyObjectWidthId(id, ptp.create_poly_points())
def create_poly_points(self):
lines_o1 = []
lines_o2 = []
for a, b in self.pairwise(self.points):
lines = self.parallel_lines([a, b], self.dist)
path_o1 = self.offset_line(lines_o1)
path_o2 = self.offset_line(lines_o2)
if not self.polygon_closed():
return self.polygon
def polygon_closed(self):
if len(self.polygon) < 2:
return False
return np.array_equal(self.polygon[0], self.polygon[-1])
def offset_line(self, lines):
points = [lines[0][0]]
for l1, l2 in self.pairwise(lines):
points.append(self.line_intersection(l1, l2))
points.append(lines[-1][-1]) # last line last point
return points
def pairwise(iterable):
"""s -> (s0,s1), (s1,s2), (s2, s3), ..."""
a, b = iter.tee(iterable)
next(b, None)
return zip(a, b)
def line_intersection(line1, line2):
see https://stackoverflow.com/a/20677983
:param line1:
:param line2:
x_diff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
y_diff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
div = det(x_diff, y_diff)
if div == 0:
raise Exception('lines do not intersect')
d = (det(*line1), det(*line2))
x = det(d, x_diff) / div
y = det(d, y_diff) / div
return np.array([x, y])
def parallel_lines(self, line, dist):
create parallel lines moved dist amount away in +-90 deg.
:param line: [p1, p2] with p1 = [x1, y1]
:return: (line_pos, line_neg) at dist from line
p1, p2 = line[0], line[1]
v = p2 - p1
v_normalized = v / np.linalg.norm(v)
# +90 and strech by dist
o1 = dist * np.matmul(self.rotPositive, v_normalized)
line_o1 = [o1 + p1, o1 + p2]
# -90 and strech by dist
o2 = dist * np.matmul(self.rotNegative, v_normalized)
line_o2 = [o2 + p1, o2 + p2]
return [line_o1, line_o2]
class PolyObjectWidthId:
Simple wrapper class around a list of points (order is important!) withn an identifier field (id)
def __init__(self, id, utm_points):
self.id = id
self.cartesian_points = utm_points
self.base = None
def shift_points(self, base: list):
shift_in_x = -base[0]
shift_in_y = -base[1]
self.cartesian_points = [(point[0] + shift_in_x, point[1] + shift_in_y) for point in self.cartesian_points]
self.base = base
def str2bool(v):
# see https://stackoverflow.com/a/43357954
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
raise argparse.ArgumentTypeError('Boolean value expected.')
def parse_command_line_arguments():
parser = argparse.ArgumentParser(description="Convert and OpenStreetMap file to a Vadere topology description.")
parser.add_argument("filename", type=str, nargs="?",
parser.add_argument("osm_file", type=str, nargs="?",
help="An OSM map in XML format.",
parser.add_argument("--use-osm-id", dest='use_osm_id', type=str2bool, const=True, nargs="?",
default=True, help="Set to use osm ids for obstacles")
parser.add_argument("-o", "--output", type=str, nargs="?",
help="Specify filename if you want the output in a file.")
help="Specify filename if you want the output in a file.")
# parser.add_argument("-o", "--output", type=str, nargs="?",
# help="Specify filename if you want the output in a file.")
subparser = parser.add_subparsers(help="sub-command help")
parser_converter = subparser.add_parser('convert', help='convert given osm file to Vadere topography')
parser_way_to_polygon = subparser.add_parser('wayToPoly', help='convert given way id to a Vadere polygon')
parser_way_to_polygon.add_argument("-d", "--path-width", dest="d", type=float, help="Specify filename if you want the output in a file.")
parser_way_to_polygon.add_argument("-w", "--way", type=int, nargs="+")
args = parser.parse_args()
......@@ -71,7 +228,7 @@ def parse_command_line_arguments():
def extract_latitude_and_longitude_for_each_xml_node(xml_tree):
# Select all nodes (not only buildings).
# Select all convertnodes (not only buildings).
nodes = xml_tree.xpath("/osm/node")
nodes_dictionary_with_lat_and_lon = {node.get("id"): (node.get("lat"), node.get("lon")) for node in nodes}
......@@ -86,6 +243,24 @@ def filter_for_buildings(xml_tree):
return buildings
def filter_for_barrier(xml_tree, type='wall'):
barrier = xml_tree.xpath("/osm/way[./tag/@k='barrier']")
return barrier
def get_way(xml_tree, id):
way = xml_tree.xpath(f"/osm/way[@id='{id}']")
return way
def convert_way_to_cartesian(way, lookup_table_latitude_and_longitude, assume_closed_path=True):
node_references = way.xpath("./nd")
return convert_nodes_to_cartesian_points(node_references, lookup_table_latitude_and_longitude, assume_closed_path)
def filter_for_buildings_in_relations(xml_tree):
# Note: A relation describes a shape with "cutting holes".
......@@ -115,11 +290,16 @@ def assert_that_start_and_end_point_are_equal(node_references):
assert node_references[0].get("ref") == node_references[-1].get("ref")
def convert_nodes_to_cartesian_points(nodes, lookup_table_latitude_and_longitude):
def convert_nodes_to_cartesian_points(nodes, lookup_table_latitude_and_longitude, assume_closed_path):
cartesian_points = []
# Omit last node because it should be the same as the first one.
for node in nodes[:len(nodes) - 1]:
# Omit last node becausenodes it should be the same as the first one.
if assume_closed_path:
max_node = len(nodes) - 1
max_node = len(nodes)
for node in nodes[:max_node]:
reference = node.get("ref")
latitude, longitude = lookup_table_latitude_and_longitude[reference]
......@@ -132,23 +312,16 @@ def convert_nodes_to_cartesian_points(nodes, lookup_table_latitude_and_longitude
return cartesian_points
def create_vadere_obstacles_from_points(cartesian_points):
vadere_obstacle_string = """{
"shape" : {
"type" : "POLYGON",
"points" : [ $points ]
"id" : -1
vadere_point_string = '{ "x" : $x, "y" : $y }'
def create_vadere_obstacles_from_building(building: PolyObjectWidthId):
vadere_point_string = ' { "x" : $x, "y" : $y }'
obstacle_string_template = Template(vadere_obstacle_string)
point_string_template = Template(vadere_point_string)
points_as_string = [point_string_template.substitute(x=x, y=y) for x, y in cartesian_points]
points_as_string = [point_string_template.substitute(x=x, y=y) for x, y in building.cartesian_points]
points_as_string_concatenated = ",\n".join(points_as_string)
vadere_obstacle_as_string = obstacle_string_template.substitute(points=points_as_string_concatenated)
vadere_obstacle_as_string = obstacle_string_template.substitute(points=points_as_string_concatenated, id=building.id)
return vadere_obstacle_as_string
......@@ -178,62 +351,63 @@ def print_output(outputfile, output):
print(output, file=text_file)
def find_width_and_height(buildings_cartesian):
def find_width_and_height(buildings: List[PolyObjectWidthId]):
# search for the highest x- and y-coordinates within the points
width = 0
height = 0
for cartesian_points in buildings_cartesian:
for point in cartesian_points:
for cartesian_points in buildings:
for point in cartesian_points.cartesian_points:
width = max(width, point[0])
height = max(height, point[1])
return math.ceil(width), math.ceil(height)
def find_new_basepoint(buildings_cartesian):
def find_new_basepoint(buildings: List[PolyObjectWidthId]):
# "buildings_cartesian" is a list of lists!!!
# The inner list contains the (x,y) tuples!
# search for the lowest x- and y-coordinates within the points
all_points = [point for building in buildings_cartesian for point in building]
all_points = [point for building in buildings for point in building.cartesian_points]
tuple_with_min_x = min(all_points, key=lambda point: point[0])
tuple_with_min_y = min(all_points, key=lambda point: point[1])
return (tuple_with_min_x[0], tuple_with_min_y[1])
return tuple_with_min_x[0], tuple_with_min_y[1]
def shift_points(buildings_utm, shift_in_x_direction, shift_in_y_direction):
new_buildings = []
for cartesian_points in buildings_utm:
shifted_cartesian_points = \
[(point[0] + shift_in_x_direction, point[1] + shift_in_y_direction) for point in cartesian_points]
return new_buildings
def shift_way(way, base):
shift_in_x_direction = -base[0]
shift_in_y_direction = -base[1]
shift_cartesian_points = [(point[0] + shift_in_x_direction, point[1] + shift_in_y_direction) for point in way]
return shift_cartesian_points
def convert_buildings_to_cartesian(buildings_as_xml_nodes):
def convert_buildings_to_cartesian(buildings_as_xml_nodes, nodes_dictionary_with_lat_and_lon, use_osm_id: bool):
buildings_in_cartesian = []
for building in buildings_as_xml_nodes:
# Collect nodes that belong to the current building.
node_references = building.xpath("./nd")
cartesian_points = convert_nodes_to_cartesian_points(node_references, nodes_dictionary_with_lat_and_lon)
cartesian_points = convert_nodes_to_cartesian_points(node_references, nodes_dictionary_with_lat_and_lon,
if use_osm_id:
buildings_in_cartesian.append(PolyObjectWidthId(building.get('id'), cartesian_points))
buildings_in_cartesian.append(PolyObjectWidthId(-1, cartesian_points))
return buildings_in_cartesian
def convert_buildings_as_cartesian_to_buildings_as_vadere_obstacles(buildings_as_cartesian):
def convert_buildings_as_cartesian_to_buildings_as_vadere_obstacles(buildings: List[PolyObjectWidthId]):
list_of_vadere_obstacles_as_strings = []
for cartesian_points in buildings_as_cartesian:
vadere_obstacles_as_strings = create_vadere_obstacles_from_points(cartesian_points)
for building in buildings:
vadere_obstacles_as_strings = create_vadere_obstacles_from_building(building)
return list_of_vadere_obstacles_as_strings
if __name__ == "__main__":
args = parse_command_line_arguments()
xml_tree = etree.parse(args.filename)
def init(args):
xml_tree = etree.parse(args.osm_file)
nodes_dictionary_with_lat_and_lon = extract_latitude_and_longitude_for_each_xml_node(xml_tree)
......@@ -241,13 +415,28 @@ if __name__ == "__main__":
complex_buildings = filter_for_buildings_in_relations(xml_tree)
extracted_base_point = extract_base_point(xml_tree)
print_xml_parsing_statistics(args.filename, nodes_dictionary_with_lat_and_lon, simple_buildings, complex_buildings, extracted_base_point)
print_xml_parsing_statistics(args.osm_file, nodes_dictionary_with_lat_and_lon, simple_buildings, complex_buildings, extracted_base_point)
buildings_as_cartesian = convert_buildings_to_cartesian(simple_buildings + complex_buildings)
buildings_as_cartesian = convert_buildings_to_cartesian(simple_buildings + complex_buildings, nodes_dictionary_with_lat_and_lon, args.use_osm_id)
# make sure everything lies within the topography
new_base = find_new_basepoint(buildings_as_cartesian)
buildings_as_cartesian = shift_points(buildings_as_cartesian, -new_base[0], -new_base[1])
for b in buildings_as_cartesian:
return xml_tree, nodes_dictionary_with_lat_and_lon, buildings_as_cartesian, new_base
def main_convert(args):
osm2vadere.pu mf.osm convert -h // for sub command specific help
osm2vadere.py mf.osm convert --output map.json
xml_tree, _, buildings_as_cartesian, _ = init(args)
# make sure everything lies within the topography
width_topography, height_topography = find_width_and_height(buildings_as_cartesian)
list_of_vadere_obstacles_as_strings = convert_buildings_as_cartesian_to_buildings_as_vadere_obstacles(buildings_as_cartesian)
......@@ -256,3 +445,32 @@ if __name__ == "__main__":
vadere_topography_output = build_vadere_topography_input_with_obstacles(obstacles_joined, width_topography, height_topography)
print_output(args.output, vadere_topography_output)
def main_way_to_polygon(args):
osm2vadere.py mf.osm wayToPoly -h // for sub command specific help
osm2vadere.py mf.osm wayToPoly --way 116531500 --path-width 0.25
print('way_to_polygon:', args)
xml_tree, node_dict, buildings_as_cartesian, new_base = init(args)
obstacles = []
for w in args.way:
way = get_way(xml_tree, w)
assert len(way) == 1
path_list = convert_way_to_cartesian(way[0], node_dict, assume_closed_path=False)
path_list = shift_way(path_list, new_base)
vadere_obstacle = create_vadere_obstacles_from_building(PathToPolygon.get_poly_object(path_list, args.d, w))
print_output(args.output, '\n'.join(obstacles))
return obstacles
if __name__ == "__main__":
args = parse_command_line_arguments()
# map_mf_small.osm wayToPoly -w 258139211 -d 0.5
......@@ -3,6 +3,35 @@ from lxml import etree
import osm2vadere
import unittest
import utm
import os
TEST_DATA_LON_LAT = os.path.join(os.path.dirname(__file__), 'maps/map_for_testing.osm')
TEST_DATA_2 = os.path.join(os.path.dirname(__file__), 'maps/map_mf_small.osm')
"shape" : {
"type" : "POLYGON",
"points" : [ { "x" : 143.92115343943564, "y" : 168.69565355275918 },
{ "x" : 143.8295708812843, "y" : 158.46319241869656 },
{ "x" : 147.9524496438901, "y" : 158.453088570111 },
{ "x" : 148.04042161765545, "y" : 168.57734735060853 },
{ "x" : 148.54040274306163, "y" : 168.57300290155786 },