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
VadereModelTests/**/legacy
VadereUtils/output/**
VadereModelTests/*_private
**/current_commit_hash.txt
# Operating system files
.DS_Store
......@@ -53,3 +54,7 @@ VadereModelTests/*_private
**/output/
**/legacy/
**/*_private/
# Vadere Cache
**/__cache__
**/vadere-server-output
\ No newline at end of file
......@@ -25,6 +25,7 @@ variables:
VADERE_DEPLOYMENT_BASE_URL: "http://www.vadere.org/builds"
VADERE_PACKAGE_NAME_BRANCHES: "vadere.${CI_COMMIT_REF_NAME}.${CI_RUNNER_TAGS}.zip"
VADERE_PACKAGE_NAME_RELEASES: "vadere.${CI_COMMIT_TAG}.${CI_RUNNER_TAGS}.zip"
EIKMESH_PACKAGE_NAME_BRANCHES: "eikmesh.${CI_COMMIT_REF_NAME}.${CI_RUNNER_TAGS}.zip"
# 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}
only:
refs:
- 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:
- `cacheType: [NO_CACHE|TXT_CACHE|BIN_CACHE]`
- `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
reasons.
- 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.
......
......@@ -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 diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -55,23 +55,180 @@ 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 = []
self.create_poly_points()
@classmethod
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)
lines_o1.append(lines[0])
lines_o2.append(lines[1])
path_o1 = self.offset_line(lines_o1)
path_o2 = self.offset_line(lines_o2)
self.polygon.extend(path_o1)
path_o2.reverse()
self.polygon.extend(path_o2)
if not self.polygon_closed():
self.polygon.append(self.polygon[0])
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
@staticmethod
def pairwise(iterable):
"""s -> (s0,s1), (s1,s2), (s2, s3), ..."""
a, b = iter.tee(iterable)
next(b, None)
return zip(a, b)
@staticmethod
def line_intersection(line1, line2):
"""
see https://stackoverflow.com/a/20677983
:param line1:
:param line2:
:return:
"""
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
else:
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.",
default="maps/map_hochschule_klein.osm",
)
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.")
# 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_converter.set_defaults(main_func=main_convert)
parser_way_to_polygon = subparser.add_parser('wayToPoly', help='convert given way id to a Vadere polygon')
parser_way_to_polygon.set_defaults(main_func=main_way_to_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()
return args
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
else:
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]
new_buildings.append(shifted_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")
assert_that_start_and_end_point_are_equal(node_references)
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,
assume_closed_path=True)
buildings_in_cartesian.append(cartesian_points)
if use_osm_id:
buildings_in_cartesian.append(PolyObjectWidthId(building.get('id'), cartesian_points))
else:
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)
list_of_vadere_obstacles_as_strings.append(vadere_obstacles_as_strings)
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:
b.shift_points(new_base)
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
"""
print(args)
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))
obstacles.append(vadere_obstacle)
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
args.main_func(args)
......@@ -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')
TEST_DATA="""{
"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 },
{ "x" : 148.44811333818495, "y" : 157.95187235651085 },
{ "x" : 143.3250868078719, "y" : 157.96442724530158 },
{ "x" : 143.42117346474575, "y" : 168.70012847277175 },
{ "x" : 143.92115343943564, "y" : 168.69565355275918 },
{ "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 },
{ "x" : 148.44811333818495, "y" : 157.95187235651085 },
{ "x" : 143.3250868078719, "y" : 157.96442724530158 },
{ "x" : 143.42117346474575, "y" : 168.70012847277175 },
{ "x" : 143.92115343943564, "y" : 168.69565355275918 } ]
},
"id" : 258139209
}
"""
class TestOsm2vadere(unittest.TestCase):
......@@ -24,7 +53,7 @@ class TestOsm2vadere(unittest.TestCase):
self.assertTrue(y_distance > 258 and y_distance < 275)
def test_extract_latitude_and_longitude_for_each_xml_node(self):
xml_tree = etree.parse("maps/map_for_testing.osm")