summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Graph.pycbin0 -> 6081 bytes
-rw-r--r--README.md10
-rw-r--r--algorithms.py50
-rw-r--r--data/favicon.icobin0 -> 1150 bytes
-rw-r--r--lib.py78
-rw-r--r--osm_parser.py201
-rw-r--r--routes.py0
-rw-r--r--server.py57
-rw-r--r--store.py0
-rw-r--r--templates/index.css11
-rw-r--r--templates/index.html52
-rw-r--r--templates/index.js79
-rw-r--r--test_dijkstra.py39
14 files changed, 578 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ed8ebf5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__ \ No newline at end of file
diff --git a/Graph.pyc b/Graph.pyc
new file mode 100644
index 0000000..5f61861
--- /dev/null
+++ b/Graph.pyc
Binary files differ
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6d72bc5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+## Getting started
+
+Clone this repo and then remove the `.git` folder:
+```
+git clone https://gitlab.ida.liu.se/tdde25/ctf
+cd ctf
+rm -rf .git
+```
+> After you have cloned the repo, feel free to delete this `README.md` file.
+Next go to our [wiki](https://gitlab.ida.liu.se/tdde25/maps/wikis/home) and get started on the tutorials.
diff --git a/algorithms.py b/algorithms.py
new file mode 100644
index 0000000..75cd3b7
--- /dev/null
+++ b/algorithms.py
@@ -0,0 +1,50 @@
+import math
+from collections import defaultdict
+
+
+
+def disjoint_forest(nodes, edges):
+ """ Algorithm to test how many components there are
+ in our graph. There should optimally be few components
+ and one with the bulk of all the nodes."""
+ ranks = defaultdict(int)
+ parents = {}
+
+ for node in nodes.keys():
+ parents[node] = node
+
+ def find_parent(node):
+ if node != parents[node]:
+ node = find_parent(parents[node])
+ return node
+
+ def union (node1, node2):
+ node1, node2 = find_parent(node1), find_parent(node2)
+ rank1, rank2 = ranks[node1], ranks[node2]
+ if rank1 < rank2:
+ parents[node1] = node2
+ else:
+ parents[node2] = node1
+ if rank1 == rank2:
+ ranks[node1] += 1
+
+ for node, node2 in edges:
+ union(node, node2)
+
+ sets = defaultdict(set)
+ for node, parent in parents.items():
+ parent = find_parent(parent)
+ sets[parent].add(node)
+
+ print("Total Nodes: ", len(nodes))
+ print("Disjoint components: ", len(sets.keys()))
+
+ max_size = 0
+ for set_nodes in sets.values(): # Find biggest component
+ if len(set_nodes) > max_size:
+ max_size = len(set_nodes)
+
+ print("Size of biggest component:", max_size)
+
+ return sets, find_parent
+
diff --git a/data/favicon.ico b/data/favicon.ico
new file mode 100644
index 0000000..3678e16
--- /dev/null
+++ b/data/favicon.ico
Binary files differ
diff --git a/lib.py b/lib.py
new file mode 100644
index 0000000..66d2ccd
--- /dev/null
+++ b/lib.py
@@ -0,0 +1,78 @@
+import os
+import io
+import re
+
+
+posts = {}
+gets = {}
+
+def find_get(path):
+ try:
+ return gets[path]()
+ except KeyError:
+ return "404"
+
+
+def find_post(path, request):
+ try:
+ return posts[path](request)
+ except KeyError:
+ return "404"
+
+def get_relative_path():
+ return os.path.dirname(os.path.abspath(__file__))
+
+
+def read_html(relative_file_path):
+ """ Reads the html file on the file location provided
+ and injects any external files (marked with {{file_path}} )
+ that may be present. """
+ dir_path = get_relative_path()
+ file_path = os.path.join(dir_path, relative_file_path)
+ with open(file_path) as file:
+ html_content = file.read()
+ html_content = inject_external_files(html_content)
+ return html_content
+
+def inject_external_files(html_content):
+ """ Replaces {{ 'file_path' }} with the file content of that
+ file path. Useful for seperation of javascript and css files."""
+
+ pattern = r'{{([^<]+)}}'
+ external_files = re.findall(pattern, html_content)
+ new_html_content = html_content
+ for match in external_files:
+ external_file_path = match.replace(' ', '')
+ external_file = open(os.path.join(get_relative_path(), external_file_path))
+ file_content = external_file.read()
+ external_file.close()
+
+ if match.find('.css') != -1:
+ file_content = '<style>\n' + file_content + '</style>'
+ elif match.find('.js') != -1:
+ file_content = '<script>\n' + file_content + '</script>'
+
+ to_be_replaced = '{{' + match + '}}'
+ new_html_content = new_html_content.replace(to_be_replaced, file_content)
+ return new_html_content
+
+
+def post(route = '/'):
+ """ A decorator that takes in the route path as argument, and then
+ puts the post handler in the dictionary, with the route as a key. """
+
+ def decorator(handler_function):
+ posts[route] = handler_function
+ return handler_function
+
+ return decorator
+
+def get(route = '/'):
+ """ A decorator that takes in the route path as argument, and then
+ puts the get handler in the dictionary, with the route as a key. """
+
+ def decorator(handler_function):
+ gets[route] = handler_function
+ return handler_function
+
+ return decorator \ No newline at end of file
diff --git a/osm_parser.py b/osm_parser.py
new file mode 100644
index 0000000..60754c9
--- /dev/null
+++ b/osm_parser.py
@@ -0,0 +1,201 @@
+# Henrik 'catears' Adolfsson, henad221@student.liu.se
+# Date: 2014-11-09
+# Copyright (c) 2014 Henrik Adolfsson
+# Licensed under MIT License (see bottom of file)
+
+"""
+In this file you will find a class that can parse OpenStreetMap Node-data and OpenStreetMap Way-data.
+There is a helper function for retrieving a default parser called get_default_parser(fname)
+It provides you with a basic parser that can iterate over nodes and iterate over ways
+
+If you want to tweak the behaviour of the default parser you can change the
+tags that will be returned with the tuples DEFAULT_NODE/WAY_TAGS under the Module Config-section
+
+The default parser will return edges with tags if they exist in "DEFAULT_WAY_TAGS".
+You can tweak the behaviour of the default parser so that it will return all tags it encounters.
+You do this by calling 'get_default_parser(fname, allow_all=True)'
+
+The only dependency of the module is the xml.etree.ElementTree which
+is a part of the python library for both py2 and py3
+"""
+
+import xml.etree.ElementTree as ET
+FILEEXT = ".osm"
+
+
+##########################################
+# Module config #
+##########################################
+DEFAULT_NODE_TAGS = ('id', 'lat', 'lon')
+DEFAULT_WAY_TAGS = ('highway', 'name', 'oneway')
+
+
+##########################################
+# OSM Specific Keys #
+##########################################
+OSM_NODE = "node"
+OSM_WAY = "way"
+OSM_WAYNODE = "nd"
+OSM_NODE_REFERENCE = "ref"
+OSM_TAG = "tag"
+OSM_WAY_ID = "id"
+OSM_TAG_KEY = "k"
+OSM_TAG_VALUE = "v"
+
+
+##########################################
+# Ouput keys for returned dictionary #
+##########################################
+OUT_EDGE_ID = "id"
+OUT_EDGES_KEY = "road"
+OUT_TAGS_KEY = "tags"
+
+
+##########################################
+# Parser implementation #
+##########################################
+
+class OSMParser:
+ """
+ A Parser that parses OSM data and enables iterating over
+ Nodes and Ways as well as selecting which tags to show from the data.
+
+ One of the focuses of the parser has been to create something simple
+ that abstracts away as much object oriented features as possible from a normal user
+
+ Essentially the parser will parse the XML file and convert each element present
+ in the XML file into a python dictionary. (since this key-value stuff is basically what xml is)
+ """
+
+
+ def __init__(self, fname, all_way_tags):
+ """
+ Initialize. if all_way_tags is True then the parser will return ways
+ with all tags, otherwise it will only choose specific tags
+ """
+ if not FILEEXT in fname:
+ fname = fname + FILEEXT
+
+ self.tree = ET.parse(fname) # ET, parse home
+ self.node_tags = set()
+ self.way_tags = set()
+ self.allow_all = all_way_tags
+
+
+ def add_node_tag(self, tag):
+ """ Adds a tag to be searched for when looking at nodes """
+ self.node_tags.add(tag)
+
+
+ def add_way_tag(self, tag):
+ """ Adds a tag to be searched for when looking at ways (edges) """
+ self.way_tags.add(tag)
+
+
+ def iter_nodes(self):
+ """
+ Iterator-object for all nodes in osm-tree.
+ Returns a dictionary with the (manually) added tags
+ """
+ # Root of xml tree
+ root = self.tree.getroot()
+ for node in root.iter(OSM_NODE):
+
+ # yield a dictionary with all tags that are added to self.node_tags
+ yield { tag : node.attrib.get(tag, None)
+ for tag in self.node_tags
+ if tag in node.attrib }
+
+
+ def iter_ways(self):
+ """
+ Iterator-object for all ways (edges) in osm-tree.
+ Returns a dictionary with the ways
+ """
+ # Root of xml-tree
+ root = self.tree.getroot()
+ for way in root.iter(OSM_WAY):
+
+ # Take out roads and tags
+ road = tuple( node.attrib[OSM_NODE_REFERENCE]
+ for node in way.iter(OSM_WAYNODE) )
+ tags = { tag.attrib[OSM_TAG_KEY] : tag.attrib[OSM_TAG_VALUE]
+ for tag in way.iter(OSM_TAG)
+ if self.allow_all or tag.attrib[OSM_TAG_KEY] in self.way_tags}
+
+ # Yield the edge id, the road and the tags
+ yield {
+ OUT_EDGE_ID : way.attrib[OSM_WAY_ID],
+ OUT_EDGES_KEY : road,
+ OUT_TAGS_KEY : tags,
+ }
+
+
+##########################################
+# Example Usage and Default Getter #
+##########################################
+
+def get_default_parser(fname, allow_all=False, verbose=True):
+ """
+ Returns a default parser
+ The config used can be tweaked inside the Module Config section
+
+ allow_all sets if the parser should take all tags or if it should just
+ take the tags specified, in this case all tags inside DEFAULT_WAY_TAGS
+ """
+ if verbose:
+ print("Parsing OSM data from: {}".format(fname))
+
+ parser = OSMParser(fname, allow_all)
+
+ # Add default node and way tags
+ for tag in DEFAULT_NODE_TAGS:
+ parser.add_node_tag(tag)
+
+ for tag in DEFAULT_WAY_TAGS:
+ parser.add_way_tag(tag)
+
+ return parser
+
+
+def print_osm_data(fname):
+ """
+ Prints all the nodes and all the ways in the map
+ Good example of how to use the code =D
+ """
+ # Create parser
+ parser = get_default_parser(fname)
+
+ # Print all nodes
+ for node in parser.iter_nodes():
+ print (node)
+
+ # Print all ways that are tagged with highway
+ for way in parser.iter_ways():
+ if 'highway' in way['tags']:
+ print (way)
+
+
+"""
+The MIT License
+
+Copyright (c) 2014 Henrik Adolfsson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
diff --git a/routes.py b/routes.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/routes.py
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..13e5697
--- /dev/null
+++ b/server.py
@@ -0,0 +1,57 @@
+from http.server import HTTPServer, BaseHTTPRequestHandler
+import lib
+
+
+
+class Request():
+ def __init__(self, body):
+ self.body = body
+
+
+# Explanation: https://blog.anvileight.com/posts/simple-python-http-server/
+
+class Handler(BaseHTTPRequestHandler):
+ """ The class responsible for handling all the
+ different requests made to our server
+ """
+
+ def _set_headers(self):
+ """ Standard header stuff that has to be done """
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+
+ def do_GET(self):
+ """ Just serve up our one and only html page """
+ self._set_headers()
+ res = lib.find_get(self.path)
+ if hasattr(res, 'encode'):
+ self.wfile.write(res.encode())
+ else:
+ self.wfile.write(res)
+
+
+ def do_POST(self):
+ """ An async ajax request. Find the function from 'posts.py' to handle this
+ particular request.
+ """
+ self.send_response(200)
+ self.end_headers()
+ content_length = int(self.headers['Content-Length'])
+ body = self.rfile.read(content_length)
+ request = Request(body)
+ res = lib.find_post(self.path, request)
+ self.wfile.write(res.encode())
+
+
+def run (port = 8314):
+ server_adress = ('', port) # Should make the port a command line argument
+ server = HTTPServer(server_adress, Handler)
+ print(f'Starting server on port: {port}.')
+ server.serve_forever()
+
+
+
+if __name__ == '__main__':
+ run()
+
diff --git a/store.py b/store.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/store.py
diff --git a/templates/index.css b/templates/index.css
new file mode 100644
index 0000000..f8bab2c
--- /dev/null
+++ b/templates/index.css
@@ -0,0 +1,11 @@
+html { height: 100% }
+body { height: 80%; margin: 0; padding: 0 }
+#map_canvas { height: 100%; width: 100% }
+.row {
+ display:flex;
+}
+.col {
+ display: flex;
+ flex-direction: column;
+ margin: 2%;
+} \ No newline at end of file
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..71a0005
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,52 @@
+<html>
+ <head>
+ <meta name="viewport"content="initial-scale=1.0, user-scalable=no"/>
+
+ {{ templates/index.css }}
+
+ </head>
+
+ <body>
+
+ <div id="map_canvas"></div>
+
+ <form id="path-form">
+ <div class="row" >
+ <div class="col">
+ Place marker for:
+ <div>
+ <input type="radio" name="marker-point" id="marker-point-start" checked="true" />
+ <label for="marker-point-start">Start</label>
+ </div>
+ <div>
+ <input type="radio" name="marker-point" id="marker-point-end" />
+ <label for="marker-point-end">Destination</label>
+ </div>
+ </div>
+ <div class="col">
+ <div>
+ <label for="lat1">Start lat:</label>
+ <input name="lat1" id="lat1" />
+ </div>
+ <div>
+ <label for="lng1">Start lng:</label>
+ <input name="lng1" id="lng1" />
+ </div>
+ </div>
+ <div class="col">
+ <div>
+ <label for="lat2">End latitude:</label>
+ <input name="lat2" id="lat2" />
+ </div>
+ <div>
+ <label for="lng2">End longitude:</label>
+ <input name="lng2" id="lng2" />
+ </div>
+ </div>
+ <input type="submit" />
+ </div>
+ </form>
+ <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?key=AIzaSyBO7iAytSrxMTS21EovrsGI2TI73F3TyYg&sensor=false"></script>
+ {{ templates/index.js }}
+ </body>
+</html>
diff --git a/templates/index.js b/templates/index.js
new file mode 100644
index 0000000..bc05ed7
--- /dev/null
+++ b/templates/index.js
@@ -0,0 +1,79 @@
+var map;
+const form = document.querySelector('#path-form')
+const markerIsStart = document.querySelector("#marker-point-start")
+const markerIsEnd = document.querySelector("#marker-point-end")
+
+google.maps.event.addDomListener(window, 'load', initialize);
+form.addEventListener('submit', postShortestPath, false)
+
+
+function initialize() {
+ var mapOptions = {
+ center: new google.maps.LatLng(58.40384776751319, 15.578484535217285),
+ zoom: 15,
+ mapTypeId: google.maps.MapTypeId.ROADMAP
+ };
+ var mapCanvas = document.getElementById("map_canvas")
+ map = new google.maps.Map(mapCanvas, mapOptions);
+ google.maps.event.addListener(map, 'click', handleMapClick)
+}
+
+async function postShortestPath(event){
+ event.preventDefault()
+ var lat1 = parseFloat(document.querySelector('#lat1').value)
+ var lng1 = parseFloat(document.querySelector('#lng1').value)
+ var lat2 = parseFloat(document.querySelector('#lat2').value)
+ var lng2 = parseFloat(document.querySelector('#lng2').value)
+
+ // Check that they're not Nan (stops function if one is Nan)
+ if(!lat1 || !lng1 || !lat2 || !lng2)
+ return alert('Formatting Error: Coordinates are not float values.')
+
+ req = {lat1, lng1, lat2, lng2} // Dictionary auto-keys
+
+ res = await fetch('/shortest-path', {
+ method:'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify(req)
+ })
+
+ res = await res.json()
+ var points = new google.maps.MVCArray()
+ console.log(res.path)
+ res.path.map(coord=>{
+ var [lat, lng] = coord
+ var point = new google.maps.LatLng(lat, lng)
+ points.push(point)
+ })
+
+ createPolyline(points)
+}
+
+function createPolyline(points){
+ var polyline = new google.maps.Polyline({
+ path: points,
+ map: map
+ });
+}
+
+function handleMapClick ({latLng}){
+ createMarker(latLng, `Click: ${latLng}`)
+ console.log(latLng)
+ var {lat, lng} = latLng
+ var posNumber = markerIsStart.checked ? '1' : '2'
+ document.querySelector('#lat' + posNumber).value = lat()
+ document.querySelector('#lng' + posNumber).value = lng()
+ if(markerIsStart.checked)
+ markerIsEnd.checked = true
+
+
+}
+
+
+function createMarker (position, title){
+ return new google.maps.Marker({
+ map,
+ position,
+ title
+ })
+} \ No newline at end of file
diff --git a/test_dijkstra.py b/test_dijkstra.py
new file mode 100644
index 0000000..76799de
--- /dev/null
+++ b/test_dijkstra.py
@@ -0,0 +1,39 @@
+import Graph
+
+
+def dijkstra(adjacency_list, source, target):
+ """ To be implemented by students. Should return (distance, path).
+ Path should be a list of the vertices in the shortest path
+ including the source and target. """
+ return 6.433, ['A', 'C', 'D']
+
+
+def test_dijkstra():
+ vertices = [
+ ('A', 0, 0),
+ ('B', 2, 2),
+ ('C', 2, -2),
+ ('D', 4, 1),
+ ('E', 4, -2),
+ ('F', 6, 1)
+ ]
+
+ edges = [
+ ('A', 'B'),
+ ('A', 'C'),
+ ('C', 'D'),
+ ('B', 'C'),
+ ('D', 'F'),
+ ('B', 'E'),
+ ('A', 'F')
+ ]
+
+ graph = Graph.Dijkstra(vertices, edges, 'A', 'D')
+ dist1, path1 = graph.get_shortest_path()
+ dist2, path2 = dijkstra(graph.adjacency_list, graph.source, graph.target)
+ assert abs(dist1-dist2) < 1e-3
+ print('Correct distance')
+ assert path1 == path2
+ print('Correct path')
+
+