commit 5b4edae20a0e87f46fb9726e4e1158e2653ffed0 Author: falk Date: Mon May 23 14:07:44 2022 +0200 bachelor thesis submission diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a090e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,96 @@ +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Files generated by invoking Julia with --code-coverage +*.jl.cov +*.jl.*.cov + +# Files generated by invoking Julia with --track-allocation +*.jl.mem + +# System-specific files and directories generated by the BinaryProvider and BinDeps packages +# They contain absolute paths specific to the host computer, and so should not be committed +deps/deps.jl +deps/build.log +deps/downloads/ +deps/usr/ +deps/src/ + +# Build artifacts for creating documentation generated by the Documenter package +docs/build/ +docs/site/ + +# File generated by Pkg, the package manager, based on a corresponding Project.toml +# It records a fixed state of all packages used by the project. As such, it should not be +# committed for packages, but should be committed for applications that require a static +# environment. +Manifest.toml + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# the following file-extensions are created by the prototype. Therefore they will be ignored +*.osm +*.pdf +*.yaml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2525619 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License (ISC) + +Copyright 2022 Falk Centner + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9427abf --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ + +WARNING: This is a prototype which is still under development. + +TODO: See source + +# Required packages + + - LightOSM.jl + - LightXML.jl + - Graphs.jl + - Cairo.jl + - MetaGraphs.jl + - HTTP.jl + - Plots.jl + - GraphPlot.jl + - GraphRecipes.jl + - Fontconfiq.jl + - Compose.jl + - YAML.jl + +# Usage and Contributing + + If there is an urgent usecase, here is a very short instruction: + Start the OSMTrainPath or DataGraph-class. + 1.) Use the "addRelation(id)"/"addWay(id)"-function to download data from overpass. + 2.) After you completed all downloads call the "createGraph()"-function. + 3.) To see the data call "plotGraph()" and search for the "Buffer.pdf"-file in which the plot will be saved. + 4.) Use "filterOnedirectional(startpoint-id, destinationpoint-id)"-function to get one specific directed graph. + 5.) Plot again to see the filtered path. + 6.) If there are maxspeeds "UNKNOWN" you must correct them with the "changeWaySpeed(way-id, newspeed)"-function. + 7.) If there are more possible ways than one for your whished export, you must remove one way so there is no other possible way. + Therefore use the "removeWay(way-id)"-function. + 8.) After you completed all maxspeeds and only one possibly way, you can export data to a YAML-file. + Therefore use the exportPathToYAML(description, filename, startpoint-id, destinationpoint-id). + Note that you have to name the filename like *name*.yaml. + +# License + + ISC-License + Copyright 2022 Falk Centner \ No newline at end of file diff --git a/src/DataGraph.jl b/src/DataGraph.jl new file mode 100644 index 0000000..4360ff7 --- /dev/null +++ b/src/DataGraph.jl @@ -0,0 +1,239 @@ +using Graphs, MetaGraphs, GraphPlot, Plots, GraphRecipes, Fontconfig, Cairo, Compose + +include("./input.jl") +include("./output.jl") + +nodeDict = Dict() +g = Graphs.SimpleDiGraph(1) +mdg = MetaDiGraph(g) +nodeXPositions = Float64[] +nodeYPositions = Float64[] +vecXCoordinates, vecYCoordinates = spring_layout(mdg) # This set the layout for the Graph-Packge, witch is used for plotting +nodeColorArray = [] +nodeLabelArray = [] + + +# +# The createGraph-function set a all data from the input-class in a MetaDiGraph. +# Therefore are some nested functions implemented. +# +# addDataToDiGraph creates the vertices and the edges in the MetaDiGraph. The data from the NamedTuples will be stored in these graph-objects. +# +# getNodeColorIndex returns an index-number which will be used later to color the nodes. +# +# getCartesianX and getCartesianY are used to transfer the lat&lon-coordinates in cartesian coordinates which are used for a plot of the graph (function->plotGraph()). +# +function createGraph() + g = Graphs.SimpleDiGraph(length(getFilteredNodeArray())) + global mdg=MetaDiGraph(g) + + function addDatasToDiGraph(filteredNodeArray::Vector{Any},filteredWayArray::Vector{Any}) + lonCorrection=0 + for node in filteredNodeArray + lonCorrection=lonCorrection+node.lon + end + lonCorrection=lonCorrection/length(filteredNodeArray) + for node in filteredNodeArray + push!(nodeDict,node.nodeID=>(length(nodeDict)+1)) + set_prop!(mdg,get(nodeDict,node.nodeID,"default value"),:id, node.nodeID) + push!(nodeColorArray,getNodeColorIndex(node.issignal,node.isswitch)) + append!(nodeXPositions,getCartesianX(node.lon-lonCorrection,node.lat)) + append!(nodeYPositions,getCartesianY(node.lon-lonCorrection,node.lat)) + push!(nodeLabelArray,node.nodeID) + end + + for way in filteredWayArray + bufferNode = first(way.containedNodeIDs) + for node in way.containedNodeIDs + if(node == first(way.containedNodeIDs)) + else + Graphs.add_edge!(mdg,get(nodeDict,bufferNode.nodeID,"default value"),get(nodeDict,node.nodeID,"default value")) + set_prop!(mdg,Graphs.Edge(get(nodeDict,bufferNode.nodeID,"default value"),get(nodeDict,node.nodeID,"default value")), :maxspeed,way.vmax.forward) + set_prop!(mdg,Graphs.Edge(get(nodeDict,bufferNode.nodeID,"default value"),get(nodeDict,node.nodeID,"default value")), :wayID, way.wayID) + set_prop!(mdg,Graphs.Edge(get(nodeDict,bufferNode.nodeID,"default value"),get(nodeDict,node.nodeID,"default value")), :length, getEdgelength(node.lat,node.lon,bufferNode.lat,bufferNode.lon)) + set_prop!(mdg,Graphs.Edge(get(nodeDict,bufferNode.nodeID,"default value"),get(nodeDict,node.nodeID,"default value")), :incline, way.incline.forward) + + Graphs.add_edge!(mdg,get(nodeDict,node.nodeID,"default value"),get(nodeDict,bufferNode.nodeID,"default value")) + set_prop!(mdg,Graphs.Edge(get(nodeDict,node.nodeID,"default value"),get(nodeDict,bufferNode.nodeID,"default value")), :maxspeed,way.vmax.backward) + set_prop!(mdg,Graphs.Edge(get(nodeDict,node.nodeID,"default value"),get(nodeDict,bufferNode.nodeID,"default value")), :wayID, way.wayID) + set_prop!(mdg,Graphs.Edge(get(nodeDict,node.nodeID,"default value"),get(nodeDict,bufferNode.nodeID,"default value")), :length, getEdgelength(node.lat,node.lon,bufferNode.lat,bufferNode.lon)) + set_prop!(mdg,Graphs.Edge(get(nodeDict,node.nodeID,"default value"),get(nodeDict,bufferNode.nodeID,"default value")), :incline, way.incline.backward) + bufferNode = node + end + end + end + end + + function getNodeColorIndex(issignal::Bool,isswitch::Bool) + if(issignal==false&&isswitch==false) + return 1 + elseif(issignal==true&&isswitch==false) + return 2 + elseif(issignal==false&&isswitch==true) + return 3 + else error("signal and switch in one node") + end + end + + function getCartesianY(lon::Float64,lat::Float64) + return cos(deg2rad(lon))*cos(deg2rad(lat))*6371000 + end + + function getCartesianX(lon::Float64,lat::Float64) + return cos(deg2rad(lat))*sin(deg2rad(lon))*6371000 + end + addDatasToDiGraph(getFilteredNodeArray(),getFilteredWayArray()) + println("Graph created") +end + + +# +# The plotGraph-function is used by the user to plot the Graph in it's current data status. +# Therefore the allready filled Arrays with the Node-Coordinates will be converted to vectors (necessary for GraphPlot-Package). +# Afterwards the function starts to find the horizontal an vertical size for the plot to distort it to an satellite-like view. +# Then the function fills the wayLabelArray and the wayColorArray to set them in the graph. The data for the Nodes are allready setted in the nodeColorArray and the nodeLabelArray. +# Finaly the function starts the gplot-function from the GraphPlot-Package. +# You can change all plot-settings in the gplot-function as it's descripted in the GraphPlot-Package: https://github.com/JuliaGraphs/GraphPlot.jl +# +function plotGraph() + println("start building vectors") + vecXCoordinates = vec(nodeXPositions) + vecYCoordinates = vec(nodeYPositions) + horizontal = (maximum(vecXCoordinates)-minimum(vecXCoordinates)) + vertical = (maximum(vecYCoordinates)-minimum(vecYCoordinates)) + wayLabelArray = [] + wayColorArray = [] + graphEdges = collect(Graphs.edges(mdg)) + # println(Graphs.ne(mdg)) # print's the actuell numbers of edges in the Graph + for graphEdge in graphEdges + push!(wayLabelArray,string(get_prop(mdg,graphEdge,:length))*" m, Way-ID="*string(get_prop(mdg,graphEdge,:wayID))*", maxspeed="*string(get_prop(mdg,graphEdge,:maxspeed))*", incline="*string(round(get_prop(mdg,graphEdge,:incline),digits=4))) + push!(wayColorArray, getEdgeColorIndex(get_prop(mdg,graphEdge,:maxspeed))) + end + nodeColor = [colorant"blue",colorant"red",colorant"orange"] + nodefillc = nodeColor[nodeColorArray] + edgestrokec = wayColorArray + println("start plotting") + draw(PDF("DataGraph-Plot.pdf",horizontal, vertical), gplot(mdg, vecXCoordinates, vecYCoordinates,nodelabel=nodeLabelArray, edgelabel=wayLabelArray, edgestrokec = edgestrokec, EDGELINEWIDTH = 1.0 / sqrt(Graphs.nv(g)),nodefillc = nodefillc ,arrowlengthfrac=0.0006 / sqrt(Graphs.nv(g)),NODESIZE = 0.0002 / sqrt(Graphs.nv(g)))) + # draw(PDF("DataGraph-Plot.pdf"), gplot(mdg, vecXCoordinates, vecYCoordinates,nodelabel=nodeLabelArray, edgelabel=wayLabelArray, edgestrokec = edgestrokec, EDGELINEWIDTH = 1.0 / sqrt(Graphs.nv(g)),nodefillc = nodefillc ,arrowlengthfrac=0.0006 / sqrt(Graphs.nv(g)),NODESIZE = 0.0002 / sqrt(Graphs.nv(g)))) + # draw(PDF("PlotSaveTestBS-UELZEN.pdf",horizontal, vertical), gplot(mdg, vecXN, vecYN, edgelabel=waylabel, edgestrokec = edgestrokec, EDGELINEWIDTH = 2.5 / sqrt(Graphs.nv(g)),nodefillc = nodefillc ,arrowlengthfrac=0.01 / sqrt(Graphs.nv(g)),NODESIZE = 0.005 / sqrt(Graphs.nv(g)))) + println("Plot finished. Saved to DataGraph-Plot.pdf") +end + + +# +# The getYAMLExportArray-function is normaly used by the exportPathToYAML-function. +# It first finds a route and saves the points of the route in "pointsOfRoute". +# After that, the exportDataArray will be filled with the parameters for TrainRun. +# For the return this exportDataArray will be converted to a vector. +# +function getYAMLExportArray(startpoint::Int,destinationpoint::Int) + exportDataArray=[] + sumlength=0 + pointsOfRoute = Graphs.enumerate_paths(Graphs.dijkstra_shortest_paths(mdg,get(nodeDict,string(startpoint),"default value")),get(nodeDict,string(destinationpoint),"default value")) + bufferNode = first(pointsOfRoute) + for node in pointsOfRoute + if(node==first(pointsOfRoute)) + else + push!(exportDataArray,[round(get_prop(mdg,Graphs.Edge(bufferNode,node), :length)+sumlength,digits=2),parse(Int,get_prop(mdg,Graphs.Edge(bufferNode,node), :maxspeed)),round(get_prop(mdg,Graphs.Edge(bufferNode,node),:incline),digits=4)]) + sumlength=sumlength+get_prop(mdg,Graphs.Edge(bufferNode,node), :length) + bufferNode=node + end + end + dataVector=vec(exportDataArray) + println("export-vector created") + return dataVector +end + + +# +# The exportPathToYAML-function calls an other function from the "output.jl"-class. Therefore it needs a description (saved in the yaml-file), a filename and the startpoint and the destinationpoint of the route. +# It doesn't matter if you've allready filtered the Path onedirectional or not. +# Note that it's possible to export only a part of the path of the plot. +# +function exportPathToYAML(description::String, filename::String, startpoint::Int,destinationpoint::Int) + createYAML(description, filename, getYAMLExportArray(startpoint,destinationpoint)) +end + + +# +# The getEdgelength-function returns the length between two coordinate-positions. +# The length is calculated by a formula for spherical geometry, cause the standard coordinates (latitude & longitude) are for a globe and not a typical x-y-chart (cartesian). +# It returns a value in meter rounded to two positions after decimal point (cm). +# +function getEdgelength(lat1,lon1,lat2,lon2) + c=acos(sin(lat1*pi/180)*sin(lat2*pi/180)+cos(lat1*pi/180)*cos(lat2*pi/180)*cos(lon1*pi/180-lon2*pi/180)) + length = round(c*6371000,digits=2) + if(length==0.00) + return 0.01 #This is necessary because TrainRun is unable to work with an edgelength below 0.01m + else return length + end +end + + +# +# The getEdgeColorIndex-function returns a color for a given String. It's normaly used by the plotGraph()-function. +# +function getEdgeColorIndex(maxspeed::String) + if(maxspeed=="UNKNOWN") + return colorant"orange" + elseif(maxspeed!="UNKNOWN") + return colorant"green" + end +end + + +# +# filterOnedirectional needs a startpoint and an endpoint to remove all unused edges. +# CAUTION!: This function is seraching for the shortest path in the graph. +# If there are multiple possiblitys to connect startpoint and endpoint, this function may not find the astimated path. +# Prepare the Graph with the "removeWayFromGraph()"-function so there is only one possible path in the graph. +# +function filterOnedirectional(startpoint::Int,destinationpoint::Int) + pointsOfRoute = Graphs.enumerate_paths(Graphs.dijkstra_shortest_paths(mdg,get(nodeDict,string(destinationpoint),"default value")),get(nodeDict,string(startpoint),"default value")) + remainingEdges = [] + bufferNode = first(pointsOfRoute) + for node in pointsOfRoute + if(node==first(pointsOfRoute)) + else + push!(remainingEdges,Graphs.Edge(node,bufferNode)) + bufferNode=node + end + end + + for edge in collect(MetaGraphs.edges(mdg)) + if(in(edge,remainingEdges)) + else MetaGraphs.rem_edge!(mdg,edge) + end + end + println("path filtered between "*string(startpoint)*" and "*string(destinationpoint)) +end + + +# +# The changeWaySpeed-function changes the speed of a way. It doesn't matter what the previous value was. +# NOTE!: If you haven't allready filtered the path onedirectional, you change the speed for both directions. +# It ist highly recommended to filter onedirectional bevore using this function. +# To filter onedirectional you can use the filterOnedirectional-function. +# +function changeWaySpeed(wayID::Int,newSpeed::Int) + for edge in MetaGraphs.edges(mdg) + if(get_prop(mdg,edge, :wayID)==string(wayID)) + set_prop!(mdg,edge, :maxspeed, string(newSpeed)) + end + end + println("set new Wayspeed "*string(newSpeed)*" for Way "*string(wayID)) +end + + +# +# The removeWayFromGraph-function removes a given way from the graph, if the given way (found by it's OSM-ID) is in the graph. +# +function removeWayFromGraph(wayID::Int) + for edge in collect(MetaGraphs.edges(mdg)) + if(get_prop(mdg,edge, :wayID)==string(wayID)) + clear_props!(mdg,edge) + Graphs.rem_edge!(mdg,edge) + end + end + println("Way "*string(wayID)*" was removed from the Graph") +end diff --git a/src/OSMTrainPath.jl b/src/OSMTrainPath.jl new file mode 100644 index 0000000..d07e2b1 --- /dev/null +++ b/src/OSMTrainPath.jl @@ -0,0 +1,12 @@ +# This is the main-module +# All relevant functions are callable from here +# This prototype can create path-files for the TrainRun-Tool (https://doi.org/10.5281/zenodo.6448563) +# Cause of some problems the typical structure with modules isn't yet setted + + +include("./input.jl") +include("./DataGraph.jl") +include("./output.jl") + + + diff --git a/src/download.jl b/src/download.jl new file mode 100644 index 0000000..904adea --- /dev/null +++ b/src/download.jl @@ -0,0 +1,53 @@ + +using LightOSM, HTTP, LightXML + + +# +# This function was created in the LightOSM-Package and is under the Copyright of this Package -> https://github.com/DeloitteDigitalAPAC/LightOSM.jl +# For the usage in this prototype the original LightOSM-function was addapted. +# The function checks if the overpass-server are available. +# Adaption of the original function: a print-information was removed. +# +function overpass_request(data::String)::String + LightOSM.check_overpass_server_availability() + return String(HTTP.post("http://overpass-api.de/api/interpreter",body=data).body) +end + + +# +# This function was created in the LightOSM-Package and is under the Copyright of this Package -> https://github.com/DeloitteDigitalAPAC/LightOSM.jl +# For the usage in this prototype the original LightOSM-function was addapted. +# It calls the overpass_request-function with the given string and a filepath for the datafile, which is also created with this function. +# The filetype is allways setted to "osm". +# Adaption of the original function: some given parameters were removed, additionally there usage in the function. +# +function download_osm_network(save_to_file_location,datas)::Union{XMLDocument,Dict{String,Any}} + data = overpass_request(datas) + #@info "Downloaded osm network data from $(["$k: $v" for (k, v) in download_kwargs]) in $download_format format" + + if !(save_to_file_location isa Nothing) + save_to_file_location = LightOSM.validate_save_location(save_to_file_location, "osm") + write(save_to_file_location, data) + @info "Saved osm network data to disk: $save_to_file_location" + end + + deserializer = LightOSM.string_deserializer(:osm) + return deserializer(data) +end + + +# +# This funtion is used to call the download_osm_network-function with a specific download-query-String, which is created by this function with the given OSM-ID. +# +function getOSMRelationXML(relationID::Int) + return download_osm_network("./Buffer.osm","[out:xml][timeout:25];relation("*string(relationID)*");(._;>>;);out;") +end + + +# +# This funtion is used to call the download_osm_network-function with a specific download-query-String, which is created by this function with the given OSM-ID. +# +function getOSMWayXML(wayID::Int) + return download_osm_network("./Buffer.osm","[out:xml][timeout:25];way("*string(wayID)*");(._;>>;);out;") +end + diff --git a/src/input.jl b/src/input.jl new file mode 100644 index 0000000..743a50a --- /dev/null +++ b/src/input.jl @@ -0,0 +1,438 @@ + +using LightXML +include("./download.jl") +# ` +# TODO: change variable names like Array(which are most time Any's). +# ` + +wayArray = [] +nodeArray = [] +filteredWayArray = [] +filteredNodeArray = [] + + +# +# This function returns the filteredWayArray (which is an Any). +# +function getFilteredWayArray() + return filteredWayArray +end + + +# +# This function returns the filteredNodeArray (which is an Any). +# +function getFilteredNodeArray() + return filteredNodeArray +end + + +# +# This function does a filtering of the given xroot and removes all members with a specific role (for example platform-data). +# To get the information which data are relevant, the function needs the relationID where the members are listet. +# +function filterAllWaysByDataFromRelation(relationID::Union{String, Int},xroot) + ID="" + if(typeof(relationID)==Int) + ID=string(relationID) + else + ID=relationID + end + xmlRelations=get_elements_by_tagname(xroot, "relation") + for relation in xmlRelations + if(attribute(relation,"id")==ID) + childnodesOfRelation = collect(child_nodes(relation)) + for elements in childnodesOfRelation + if is_elementnode(elements) + if name(elements) == "member" + g = XMLElement(elements) + if(attribute(g,"role")=="") + addToFilteredWayArray(getWayByID(attribute(g,"ref"))) + end + end + end + end + end + end +end + + +# +# This function checks the data-type of the given wayID and afterwards calls the addToFilteredWayArray-function with the ID. +# +function filterWayToFilteredWayArray(wayID::Union{String, Int}) + ID="" + if(typeof(wayID)==Int) + ID=string(wayID) + else + ID=wayID + end + addToFilteredWayArray(getWayByID(ID)) +end + + +# +# This function adds all nodes of all ways to the filteredNodeArray by calling the addToFilteredNodeArray-funtion. +# +function filterAllNodesByDataFromFilteredWayArray(filteredWayArray::Any) + for way in filteredWayArray + nodes = way.containedNodeIDs + for node in nodes + addToFilteredNodeArray(node) + end + end +end + + +# +# This function checks if a way is already saved in the filteredWayArray and returns the result with a Bool. +# +function checkIfAllreadyInFilteredWayArray(newway::NamedTuple) + for way in filteredWayArray + if(way==newway) + return true + end + end + return false +end + + +# +# This function checks if a node is already saved in the filteredNodeArray and returns the result with a Bool. +# +function checkIfAllreadyInFilteredNodeArray(newnode::NamedTuple) + for node in filteredNodeArray + if(node==newnode) + return true + end + end + return false +end + + +# +# This function adds a node to the filteredNodeArray if it's not already saved there. +# +function addToFilteredNodeArray(node::NamedTuple) + if(!checkIfAllreadyInFilteredNodeArray(node)) + push!(filteredNodeArray,node) + end +end + + +# +# This function adds a way to the filteredWayArray if it's not already saved there. +# +function addToFilteredWayArray(way::NamedTuple) + if(!checkIfAllreadyInFilteredWayArray(way)) + push!(filteredWayArray,way) + end +end + + +# +# This function returns a node-NamedTuple. Therefore the node-ID is required. +# +function getNodeByID(nodeID::Union{Int,String}) + if(typeof(nodeID)==Int) + for node in nodeArray + if(node.nodeID==string(nodeID)) + return node + end + end + else + for node in nodeArray + if(node.nodeID==nodeID) + return node + end + end + end + @debug "missed Node called. NodeID that is missed is: $nodeID" +end + + +# +# This function returns a way-NamedTuple from the wayArray by the wayID. +# +function getWayByID(wayID::Union{Int,String}) + if(typeof(wayID)==Int) + for way in wayArray + if(way.wayID==string(wayID)) + return way + end + end + else + for way in wayArray + if(way.wayID==wayID) + return way + end + end + end + @debug "missed Way called. WayID that is missed is: $wayID" +end + + +# +# This function returns a way in the filteredWayArray by the wayID. +# +function getWayFromFilteredWayArrayByID(wayID::Union{Int,String}) + if(typeof(wayID)==Int) + for way in filteredWayArray + if(way.wayID==string(wayID)) + return way + end + end + else + for way in filteredWayArray + if(way.wayID==wayID) + return way + end + end + end + @debug "missed Way called. WayID that is missed is: $wayID" +end + + +# +# The createNodes-function transfers the data from a given XMLElement to a NamedTuple and pushs this NamedTuple to the filteredNodeArray. +# In the transfer of the data the node-ID, the lat and lon coordinates and additional Bools for signal and switch information are stored in the NamedTuple. +# +function createNodes(xroot::XMLElement) + xmlNodes = get_elements_by_tagname(xroot, "node") + for node in xmlNodes + issignal = false + isswitch = false + if(has_children(node)) + element = child_nodes(node) + for c in element + if(is_elementnode(c)) + e= XMLElement(c) + if(attribute(e,"k")=="railway" && attribute(e,"v")=="signal") + issignal=true + end + if(attribute(e,"k")=="railway:switch"||(attribute(e,"k")=="railway"&&attribute(e,"v")=="switch")) + isswitch=true + end + end + end + end + nd = (nodeID = attribute(node,"id"), lat=parse(Float64,attribute(node,"lat")), lon=parse(Float64,attribute(node,"lon")), issignal=issignal, isswitch=isswitch) + push!(nodeArray,nd) + end +end + + +# +# The createWay-function transfers the data from a given XMLElement to a NamedTuple and pushs this NamedTuple to the filteredWayArray +# In the transfer of the data the way-ID, the containened Nodes (NamedTuples of the nodes), the maxspeed (NamedTuples because of direction-dependent maxspeeds) and the inclines (NamedTuples because of direction-dependent inclines) are stored in the NamedTuple. +# +function createWays(xroot::XMLElement) + xmlWays = get_elements_by_tagname(xroot, "way") + for way in xmlWays + w = (wayID = attribute(way,"id"), containedNodeIDs = getWayNodeIDs(way),vmax=getWayMaxspeed(way),incline=getWayIncline(way)) + push!(wayArray,w) + end +end + + +# +# This function returns a NamedTuple with the direction-dependent inclines of a given way-XMLElement. +# The values in the OSM are percent while in german train-tracks the unit perthousand is common. +# To avoid an unit-change-mistake, the units are converted in this function. +# +function getWayIncline(xWay::XMLElement) + elements = child_nodes(xWay.node) + incline = 0.0 + for e in elements + if(is_elementnode(e)) + x = XMLElement(e) + if(attribute(x,"k")=="incline") + value = attribute(x,"v") + if(value=="down"||value=="up") + else incline = parse(Float64,replace(attribute(x,"v"),"%"=>"")) + end + end + end + end + return (forward=incline*10,backward=incline*(-10)) # *10 necessary for change from % to per thousand -> (typically used for german-rail-incline) +end + + +# +# The getWayMaxspeed-function returns a NamedTuple with the OSM-maxspeed-data in which the maxspeed is listet with the direction for which the value is valid. +# If the OSM-data specify a direction with :forward and :backward, this will be safed in the NamedTuple. +# If there is no specification of the direction, both will be saved with the maxspeed-value. +# If there is no maxspeed in one or both directions, the value(s) will be saved with "UNKNOWN". +# +function getWayMaxspeed(xWay::XMLElement) + element = child_nodes(xWay.node) + maxspeedforward = "UNKNOWN" + maxspeedbackward = "UNKNOWN" + for c in element + if(is_elementnode(c)) + e = XMLElement(c) + if(attribute(e,"k")=="maxspeed") + return maxspeed = (forward = attribute(e,"v"), backward = attribute(e,"v")) + elseif (attribute(e,"k")=="maxspeed:forward") + maxspeedforward = attribute(e,"v") + elseif (attribute(e,"k")=="maxspeed:backward") + maxspeedbackward = attribute(e,"v") + end + end + end + if(maxspeedforward=="UNKNOWN"&&maxspeedbackward=="UNKNOWN") + return maxspeed=(forward = "UNKNOWN", backward = "UNKNOWN") + else return maxspeed = (forward = maxspeedforward, backward = maxspeedbackward) + end +end + + +# +# This function returns an Any which contains the NamedTuples of the Nodes from the given way-XMLElement. +# To get the Nodes for the Any, the getNodeByID-funtion is used. +# +function getWayNodeIDs(xWay::XMLElement) + nodeIDs = [] + element = child_nodes(xWay.node) + for c in element + if(is_elementnode(c)) + e = XMLElement(c) + if(name(e)=="nd") + #push!(nodeIDs,(attribute(e,"ref"))) + #or entweder nur die Nummer oder den ganzen Knoten + push!(nodeIDs,getNodeByID(attribute(e,"ref"))) + end + end + end + # println(attribute(xWay, "id")," = ", length(nodeIDs)) + return nodeIDs +end + + +# +# This function is used to add an OSM-Relation to the prototype memory. +# In contrast to the "addRelation"-funtion, this function reads the data from a given XML/OSM-file. +# Therefore the filepath and the OSM-Relation-ID is required. +# With these data the function calls the createWays/createNodes-functions and filters the NamedTuples with the filter***-functions. +# +function addRelationFromFile(id::Int,filepath::String) + xdoc = parse_file(filepath) + xroot = root(xdoc) + createNodes(xroot) + createWays(xroot) + println(length(nodeArray)," Nodes in nodeArray") + println(length(wayArray)," Ways in wayArray") + filterAllWaysByDataFromRelation(id,xroot) + filterAllNodesByDataFromFilteredWayArray(filteredWayArray) + println(length(filteredWayArray)," Ways in filteredWayArray") + println(length(filteredNodeArray)," Nodes in filteredNodeArray") +end + + +# +# This function is used to add an OSM-Way to the prototype memory. +# In contrast to the "addWay"-funtion this function reads the data from a given XML/OSM-file. +# Therefore the filepath and the OSM-Way-ID is required. +# With these data the function calls the createWays/createNodes-functions and filters the NamedTuples with the filter***-functions. +# +function addWayFromFile(id::Int,filepath::String) + xdoc = parse_file(filepath) + xroot = root(xdoc) + createNodes(xroot) + createWays(xroot) + println(length(nodeArray)," Nodes in nodeArray") + println(length(wayArray)," Ways in wayArray") + filterWayToFilteredWayArray(id) + filterAllNodesByDataFromFilteredWayArray(filteredWayArray) + println(length(filteredWayArray)," Ways in filteredWayArray") + println(length(filteredNodeArray)," Nodes in filteredNodeArray") +end + + +# +# This function is used to add an OSM-Relation to the prototype memory. +# It calls the Overpass-API via the download-class. Therefore the OSM-Relation-ID is required. +# After the download the function reads the Buffer.osm-file with the data and imports them. +# With these data the function calls the createWays/createNodes-functions and filters the NamedTuples with the filter***-functions. +# +function addRelation(id::Int) + getOSMRelationXML(id) + xdoc = parse_file("./Buffer.osm") + xroot = root(xdoc) + createNodes(xroot) + createWays(xroot) + println(length(nodeArray)," Nodes in nodeArray") + println(length(wayArray)," Ways in wayArray") + filterAllWaysByDataFromRelation(id,xroot) + filterAllNodesByDataFromFilteredWayArray(filteredWayArray) + println(length(filteredWayArray)," Ways in filteredWayArray") + println(length(filteredNodeArray)," Nodes in filteredNodeArray") +end + + +# +# This function is used to add an OSM-Way to the prototype memory. +# It calls the Overpass-API via the download-class. Therefore the OSM-Way-ID is required. +# After the download the function reads the Buffer.osm-file with the data and imports them. +# With these data the function calls the createWays/createNodes-functions and filters the NamedTuples with the filter***-functions. +# +function addWay(id::Int) + getOSMWayXML(id) + xdoc = parse_file("./Buffer.osm") + xroot = root(xdoc) + createNodes(xroot) + createWays(xroot) + println(length(nodeArray)," Nodes in nodeArray") + println(length(wayArray)," Ways in wayArray") + filterWayToFilteredWayArray(id) + filterAllNodesByDataFromFilteredWayArray(filteredWayArray) + println(length(filteredWayArray)," Ways in filteredWayArray") + println(length(filteredNodeArray)," Nodes in filteredNodeArray") +end + + +# +# This function is used to clear all collected data in any Arrays. +# +function clearAllArrays() + empty!(wayArray) + empty!(nodeArray) + empty!(filteredWayArray) + empty!(filteredNodeArray) +end + + +# +# This function prints the ways without maxspeed-information. +# +function checkWaySpeed() + waysWithoutSpeed=[] + for way in filteredWayArray + if(way.vmax.forward==""||way.vmax.backward=="") + push!(waysWithoutSpeed,way) + end + end + println("The following ways have none or one-directional only maxpeed") + for way in waysWithoutSpeed + println(way.wayID," maxspeed forward = ", way.vmax.forward, ", maxspeed backward = ",way.vmax.backward) + end +end + + +# +# This function can set the speed of a way. Therefore maxspeed-information for both direction and the OSM-ID of the Way is required. +# +function setWaySpeed(wayID::Union{Int,String}, maxspeedforward::Int, maxspeedbackward::Int) + w = getWayFromFilteredWayArrayByID(wayID) + newW = (wayID = w.wayID, containedNodeIDs = w.containedNodeIDs,vmax=(forward = string(maxspeedforward), backward = string(maxspeedbackward))) + replace!(filteredWayArray,w=>newW) +end + + +# +# This function can remove a way from the data. Therefore the OSM-ID of the Way is required. +# +function removeWay(id::Int) + println(indexin(getWayByID(id),filteredWayArray)) + println("removed the way") +end diff --git a/src/output.jl b/src/output.jl new file mode 100644 index 0000000..7cb09b8 --- /dev/null +++ b/src/output.jl @@ -0,0 +1,18 @@ +using YAML +# +# The createYAML-function produce the YAML-output-file of this prototype. +# The file is named with the given filename which has to be in a "*name*.yaml" style (so with the .yaml). +# A given description is needed and will be setted in the file. +# +# In this version there is no information of the data-source in the file. +# TODO: add data-source information (OpenStreetMap). +# +function createYAML(description::String, filename::String, datas::Any) + dataVector=datas + d=Dict() + d2=Dict(:name => description, :sectionStarts => nothing, :sectionStarts_kmh => dataVector) + push!(d,:path => d2) + YAML.write_file(filename, d) +end + +