Commit 1a4f7b86 authored by Benedikt Kleinmeier's avatar Benedikt Kleinmeier
Browse files

Added script "osm2vadere.py" to "Tools" folder, to convert OpenStreetMap maps...

Added script "osm2vadere.py" to "Tools" folder, to convert OpenStreetMap maps to a Vadere topography.
parent 74db29fa
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
# Convert OpenStreetMap XML file exported from https://www.openstreetmap.org/
# to a Vadere topology (in Cartesian coordinates).
#
# Steps to run this script:
#
# 1. Go to https://www.openstreetmap.org/.
# 2. Click "Export" and adjust region of intereset and zoom level.
# 3. Call script and pass exported file from (2):
#
# python3 <script> <exported_file>
#
# 4. Insert output into "topography" tab of Vadere.
#
# Note: currently, the scripts converts only buildings to Vadere obstacles.
#
# Watch out: before running this script, install its dependencies using pip:
#
# pip install -r requirements.txt
#
# An OpenStreetMap XML file has following structure:
#
# 1. The boundary box of the exported tile.
# 2. List of all nodes in the tile containing latitude and longitude.
# 3. Paths are formed using references to (2).
#
# Example OSM file:
#
# <?xml version="1.0" encoding="UTF-8"?>
# <osm version="0.6" ...>
# <bounds minlat="47.8480100" minlon="11.8207100" maxlat="47.8495600" maxlon="11.8249200"/>
# <node id="31413334" ... lat="47.8563764" lon="11.8186396"/>
# ...
# <way id="192686406" ...>
# <nd ref="31413334"/>
# <nd ref="3578052113"/>
# <nd ref="394769402"/>
# <nd ref="3828186058"/>
# <tag k="highway" v="unclassified"/>
# </way>
# ...
#
# A Vadere obstacle looks like this:
#
# {
# "shape" : {
# "type" : "POLYGON",
# "points" : [ { "x" : 43.7, "y" : 3.4 }, ... ]
# },
# "id" : -1
# }
from lxml import etree
from string import Template
import argparse
import utm
import math
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="?",
help="An OSM map in XML format.",
default="maps/map_hochschule_klein.osm",
)
parser.add_argument("-o", "--output", type=str, nargs="?",
help="Specify filename if you want the output in a file.")
args = parser.parse_args()
return args
def extract_latitude_and_longitude_for_each_xml_node(xml_tree):
# Select all nodes (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}
return nodes_dictionary_with_lat_and_lon
def filter_for_buildings(xml_tree):
# Select "way" nodes with a child node "tag" annotated with attribute "k='building'".
buildings = xml_tree.xpath("/osm/way[./tag/@k='building']")
return buildings
def filter_for_buildings_in_relations(xml_tree):
# Note: A relation describes a shape with "cutting holes".
# Select "relation" nodes with a child node "tag" annotated with attribute "k='building'".
buildings = xml_tree.xpath("/osm/relation[./tag/@k='building']")
# We only want the shapes and only the outer parts. role='inner' is for "cutting holes" in the shape.
members_in_the_relations = [building.xpath("./member[./@type='way' and ./@role='outer']") for building in buildings]
way_ids = []
for element in members_in_the_relations:
for way in element:
way_ids.append(way.get("ref"))
ways = xml_tree.xpath("/osm/way")
ways_as_dict_with_id_key = {way.get("id"): way for way in ways}
buildings_from_relations = [ways_as_dict_with_id_key[id] for id in way_ids]
return buildings_from_relations
def extract_base_point(xml_tree):
base_node = xml_tree.xpath("/osm/bounds")[0]
base_point = (base_node.get("minlat"), base_node.get("minlon"))
return base_point
def extract_end_point(xml_tree):
end_node = xml_tree.xpath("/osm/bounds")[0]
end_point = (end_node.get("maxlat"), end_node.get("maxlon"))
return end_point
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, base_point):
cartesian_points = []
# Use base point to normalize coordinates to (0,0).
(baseX, baseY, base_zone_number, base_zone_letter) = utm.from_latlon(float(base_point[0]), float(base_point[1]))
# Omit last node because it should be the same as the first one.
for node in nodes[:len(nodes) - 1]:
reference = node.get("ref")
latitude, longitude = lookup_table_latitude_and_longitude[reference]
(x, y, zone_number, zone_letter) = utm.from_latlon(float(latitude), float(longitude))
# TODO: handle coordinates from different segments properly.
assert base_zone_number == zone_number, "Overstepped UTM boundary(zone number)"
assert base_zone_letter == zone_letter, "Overstepped UTM boundary(zone letter)"
point_to_add = (x-baseX, y - baseY)
cartesian_points.append(point_to_add)
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 }'
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_concatenated = ", ".join(points_as_string)
vadere_obstacle_as_string = obstacle_string_template.substitute(points=points_as_string_concatenated)
return vadere_obstacle_as_string
def build_vadere_topography_input_with_obstacles(obstacles, base_point, end_point):
with open("vadere_topography_default.txt", "r") as myfile:
vadere_topography_input = myfile.read().replace('\n', '')
base_point_cartesian = utm.from_latlon(float(base_point[0]), float(base_point[1]))
end_point_cartesian = utm.from_latlon(float(end_point[0]), float(end_point[1]))
width = math.ceil(end_point_cartesian[0] - base_point_cartesian[0])
height = math.ceil(end_point_cartesian[1] - base_point_cartesian[1])
vadere_topography_output = Template(vadere_topography_input).substitute(width=width, height=height, obstacles=obstacles)
return vadere_topography_output
def print_xml_parsing_statistics(filename, nodes_dictionary, simple_buildings, complex_buildings, base_point):
print("File: {}".format(filename))
print(" Nodes: {}".format(len(nodes_dictionary)))
print(" Simple buildings: {}".format(len(simple_buildings)))
print(" Complex buildings: {}".format(len(complex_buildings)))
print(" Base point: {}".format(base_point))
def print_output(outputfile, output):
if outputfile == None:
print(output)
else:
with open(outputfile, "w") as text_file:
print(output, file=text_file)
if __name__ == "__main__":
args = parse_command_line_arguments()
xml_tree = etree.parse(args.filename)
nodes_dictionary_with_lat_and_lon = extract_latitude_and_longitude_for_each_xml_node(xml_tree)
simple_buildings = filter_for_buildings(xml_tree)
complex_buildings = filter_for_buildings_in_relations(xml_tree)
base_point = extract_base_point(xml_tree)
end_point = extract_end_point(xml_tree)
print_xml_parsing_statistics(args.filename, nodes_dictionary_with_lat_and_lon, simple_buildings, complex_buildings, base_point)
list_of_vadere_obstacles_as_strings = []
for building in simple_buildings + complex_buildings:
# 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, base_point)
vadere_obstacles_as_strings = create_vadere_obstacles_from_points(cartesian_points)
list_of_vadere_obstacles_as_strings.append(vadere_obstacles_as_strings)
obstacles_joined = ",\n".join(list_of_vadere_obstacles_as_strings)
vadere_topography_output = build_vadere_topography_input_with_obstacles(obstacles_joined, base_point, end_point)
print_output(args.output, vadere_topography_output)
{
"attributes" : {
"bounds" : {
"x" : 0.0,
"y" : 0.0,
"width" : $width,
"height" : $height
},
"boundingBoxWidth" : 0.5,
"bounded" : true
},
"obstacles" : [ $obstacles],
"stairs" : [ ],
"targets" : [ ],
"sources" : [ ],
"dynamicElements" : [ ],
"attributesPedestrian" : {
"radius" : 0.195,
"densityDependentSpeed" : false,
"speedDistributionMean" : 1.34,
"speedDistributionStandardDeviation" : 0.26,
"minimumSpeed" : 0.5,
"maximumSpeed" : 2.2,
"acceleration" : 2.0
},
"attributesCar" : {
"id" : -1,
"radius" : 0.195,
"densityDependentSpeed" : false,
"speedDistributionMean" : 1.34,
"speedDistributionStandardDeviation" : 0.26,
"minimumSpeed" : 0.5,
"maximumSpeed" : 2.2,
"acceleration" : 2.0,
"length" : 4.5,
"width" : 1.7,
"direction" : {
"x" : 1.0,
"y" : 0.0
}
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment