diff --git a/Pipfile b/Pipfile index 17c4b913cdd89e63c8c2a3286acc7438a76f7dd0..1cf969a7bf9df7e9bfcb4c9f2521ba339635f40a 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,7 @@ pandas = "*" networkx = "*" network2tikz = {editable = true,path = "/home/mark/Projekte/network2tikz"} requests = "*" +rtree = "*" [requires] python_version = "3.7" diff --git a/evrouting/osm/imports.py b/evrouting/osm/imports.py index e14d10cfab9099160aee3afeeeafd9d9db732437..16c598d0a9fc3125f7c8970f0d50e001fcfa5ac9 100644 --- a/evrouting/osm/imports.py +++ b/evrouting/osm/imports.py @@ -15,15 +15,16 @@ Added : import copy import xml.sax import logging +import itertools from collections import namedtuple import networkx as nx -import requests +import rtree -from evrouting.graph_tools import CHARGING_COEFFICIENT_KEY +from evrouting.graph_tools import CHARGING_COEFFICIENT_KEY, CONSUMPTION_KEY, DISTANCE_KEY from evrouting.osm.const import ms_to_kmh from evrouting.osm.profiles import speed -from evrouting.osm.routing import find_nearest, haversine_distance +from evrouting.osm.routing import point, haversine_distance logger = logging.getLogger(__name__) @@ -38,40 +39,54 @@ def query_url(service, coordinates, osrm_config: OsrmConf): f'{";".join([f"{lon},{lat}" for lat, lon in coordinates])}' -def insert_charging_stations(G, charging_stations): - """Insert Charging Stations""" - S = set() - for s in charging_stations: - lon = s['lon'] - lat = s['lat'] - n = find_nearest(G, (lat, lon), distance_limit=500) - if n: - G.nodes[n][CHARGING_COEFFICIENT_KEY] = s['power'] - S.add(n) - - return S - - -class OsrmDistance: +class OSMGraph(nx.DiGraph): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.charging_stations = set() + self._rtree = rtree.index.Index() + self._int_index = itertools.count() + + def update_rtree(self, node): + info = self.nodes[node] + lat, lon = info['lat'], info['lon'] + self._rtree.insert(next(self._int_index), (lon, lat, lon, lat), obj=node) + + def insert_charging_stations(self, charging_stations): + """Insert Charging Stations""" + S = set() + for s in charging_stations: + lon = s['lon'] + lat = s['lat'] + n = self.find_nearest((lat, lon), distance_limit=500) + if n is not None: + self.nodes[n][CHARGING_COEFFICIENT_KEY] = s['power'] + S.add(n) + + self.charging_stations = S + + def find_nearest(self, v: point, distance_limit=None): + """ + Find nearest point to location v within radius + of distance_limit. + """ + lat_v, lon_v = v - def __init__(self, osrm_config: OsrmConf = None): - self.osrm_config = osrm_config or OsrmConf( - server='localhost', - port=5000 - ) + n = next(self._rtree.nearest( + (lon_v, lat_v, lon_v, lat_v), 1, objects=True + )).object - def __call__(self, G, u, v): - """Calc distance between u and v on osrm.""" - coordinates = [ - (G.nodes[u]['lat'], G.nodes[u]['lon']), - (G.nodes[v]['lat'], G.nodes[v]['lon']) - ] - url = query_url('route', coordinates, self.osrm_config) + if distance_limit is not None: + d = haversine_distance( + self.nodes[n]['lat'], + self.nodes[n]['lon'], + lat_v, + lon_v, + unit_m=True + ) + if d > distance_limit: + n = None - resp = requests.get(url, timeout=0.1) - resp.raise_for_status() - resp = resp.json() - return resp['routes'][0]['duration'] + return n def read_osm(osm_xml_data, profile) -> nx.DiGraph: @@ -82,7 +97,7 @@ def read_osm(osm_xml_data, profile) -> nx.DiGraph: """ osm = OSM(osm_xml_data) - G = nx.DiGraph() + G = OSMGraph() # Add ways for w in osm.ways.values(): @@ -101,11 +116,17 @@ def read_osm(osm_xml_data, profile) -> nx.DiGraph: if w.tags.get('oneway', 'no') == 'yes': # ONLY ONE DIRECTION - G.add_edge(u_id, v_id, weight=d) + G.add_edge(u_id, v_id, **{ + DISTANCE_KEY: d + }) else: # BOTH DIRECTION - G.add_edge(u_id, v_id, weight=d) - G.add_edge(v_id, u_id, weight=d) + G.add_edge(u_id, v_id,**{ + DISTANCE_KEY: d + }) + G.add_edge(v_id, u_id, **{ + DISTANCE_KEY: d + }) # Complete the used nodes' information for n_id in G.nodes(): @@ -113,6 +134,7 @@ def read_osm(osm_xml_data, profile) -> nx.DiGraph: G.nodes[n_id]['lat'] = n.lat G.nodes[n_id]['lon'] = n.lon G.nodes[n_id]['id'] = n.id + G.update_rtree(n_id) return G diff --git a/evrouting/osm/routing.py b/evrouting/osm/routing.py index fbf877a085c12fcf543feea745e49ed7633f3180..fe636d4ca8ff54cbc17f70bbfb48661a271daa26 100644 --- a/evrouting/osm/routing.py +++ b/evrouting/osm/routing.py @@ -30,38 +30,10 @@ def haversine_distance(lon1, lat1, lon2, lat2, unit_m=True): return c * r -def find_nearest(G, v: point, distance_limit=None): - """ - Find nearest point to location v within radius - of distance_limit. - """ - min_dist = None - closest_node = None - - lat_v, lon_v = v - - for n in G.nodes: - d = haversine_distance( - G.nodes[n]['lat'], - G.nodes[n]['lon'], - lat_v, - lon_v, - unit_m=True - ) - if min_dist is None or d < min_dist: - closest_node = n - min_dist = d - - if distance_limit: - return closest_node if min_dist <= distance_limit else None - else: - return closest_node - - def shortest_path(G, s: point, t: point, profile): """Calc A* shortest path.""" - _s = find_nearest(G, s) - _t = find_nearest(G, t) + _s = G.find_nearest(s) + _t = G.find_nearest(t) def dist(u, v): return haversine_distance( @@ -82,6 +54,7 @@ def to_coordinates(G, path): Note: Coordinates are (lon, lat) to conform to geojson. """ + def get_coordinates(n): lat = G.nodes[n]['lat'] lon = G.nodes[n]['lon'] diff --git a/setup.py b/setup.py index 0e84d01775835dce914c80a0db40f9c54dbf9a3d..5d2f8bc989bd61ae9ab58aa73de56a6544b49f3f 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ setup( python_requires='>=3.4.*, <4', install_requires=[ 'networkx', - 'requests' + 'requests', + 'rtree' ] ) diff --git a/tests/osm/test_osm_charge.py b/tests/osm/test_osm_charge.py index 5a2bbb0161d305c87bd5871b16f99d0751fa7b6f..becf5d5e595a27c52bc46f5365c842c3359dad98 100644 --- a/tests/osm/test_osm_charge.py +++ b/tests/osm/test_osm_charge.py @@ -1,10 +1,8 @@ import os import pytest -import networkx as nx -import rtree -from evrouting.osm.imports import read_osm, insert_charging_stations +from evrouting.osm.imports import read_osm, OSMGraph from evrouting.osm.profiles import car from evrouting.osm.routing import shortest_path from evrouting.graph_tools import CHARGING_COEFFICIENT_KEY @@ -12,8 +10,7 @@ from evrouting.graph_tools import CHARGING_COEFFICIENT_KEY @pytest.fixture def graph(): - G = nx.DiGraph() - G.rtree = rtree.index.Index() + G = OSMGraph() node_coordinates = [ (51.7705832, 7.0002595), @@ -24,7 +21,7 @@ def graph(): lat, lon = coordinates # Add two nodes, that exist in osm test map G.add_node(n_id, lat=lat, lon=lon) - G.rtree.insert(n_id, (lon, lat, lon, lat)) + G.update_rtree(n_id) yield G del G @@ -42,7 +39,7 @@ def test_insert_charging_stations_close(graph): # Close two node 1 S = [{"lon": 7.0002593, "lat": 51.7705832, "power": 22.0}] - graph = insert_charging_stations(graph, S) + graph.insert_charging_stations(S) assert graph.nodes[0][CHARGING_COEFFICIENT_KEY] == 22.0 assert CHARGING_COEFFICIENT_KEY not in graph.nodes[1] @@ -52,7 +49,7 @@ def test_insert_charging_stations_eq(graph): # Close exactly at node 1 S = [{"lon": 7.0002595, "lat": 51.7705832, "power": 22.0}] - graph = insert_charging_stations(graph, S) + graph.insert_charging_stations(S) assert graph.nodes[0][CHARGING_COEFFICIENT_KEY] == 22.0 assert CHARGING_COEFFICIENT_KEY not in graph.nodes[1]