From 7c813ea7b872cbba20b257d8595a51893dba9de5 Mon Sep 17 00:00:00 2001 From: "niehues.mark@gmail.com" <niehues.mark@gmail.com> Date: Wed, 22 Apr 2020 21:05:18 +0200 Subject: [PATCH] nearest mapping --- Pipfile | 1 + Pipfile.lock | 9 ++++- data.py | 21 +++------- evrouting/charge/routing.py | 2 +- evrouting/osm.py | 77 +++++++++++++++++++++++------------- tests/osm/test_osm_charge.py | 40 +++++++++++++++++-- 6 files changed, 103 insertions(+), 47 deletions(-) diff --git a/Pipfile b/Pipfile index 17c4b91..1cf969a 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/Pipfile.lock b/Pipfile.lock index 3dc7f82..0c0a250 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e32e5dcd04aab442923fb70d6bf8cc3b630fbbd8a936b3a45011d74810328e23" + "sha256": "cf774cb2531efccdb033c22ff2dd3a54f1ffdb0ec6dd51eeba5cb001619cd864" }, "pipfile-spec": 6, "requires": { @@ -90,6 +90,13 @@ "index": "pypi", "version": "==2.23.0" }, + "rtree": { + "hashes": [ + "sha256:cae327e2c03b3da4ea40d0fdf68f3e55fe9f302c56b9f31e1bfeb36dbea73f44" + ], + "index": "pypi", + "version": "==0.9.4" + }, "urllib3": { "hashes": [ "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", diff --git a/data.py b/data.py index 4291eb5..c996a5c 100644 --- a/data.py +++ b/data.py @@ -4,7 +4,6 @@ Get OSM Data and merge with charging stops. import argparse from typing import Tuple -import networkx as nx import pandas as pd long = float @@ -19,7 +18,7 @@ def parse_charging_stations_csv(file: str, nw_corner: coordinate = None, se_corn return float(s.replace(',', '.')) converters = { - 'long': lambda s: float(s.replace(',', '.')), + 'lon': lambda s: float(s.replace(',', '.')), 'lat': lambda s: float(s.replace(',', '.')), 'p1': float_converter, 'p2': float_converter, @@ -45,25 +44,17 @@ def parse_charging_stations_csv(file: str, nw_corner: coordinate = None, se_corn del df['p4'] if nw_corner is not None and se_corner is not None: - lat_max, long_min = nw_corner - lat_min, long_max = se_corner + lat_max, lon_min = nw_corner + lat_min, lon_max = se_corner - df = df[(df['long'] <= long_max) - & (df['long'] >= long_min) + df = df[(df['lon'] <= lon_max) + & (df['lon'] >= lon_min) & (df['lat'] <= lat_max) & (df['lat'] >= lat_min) ] return df -def fetch_osm_data(nw_corner: coordinate, se_corner: coordinate) -> nx.DiGraph: - pass - - -def merge_osm_with_chtarging_stations(osm_data, charging_stations) -> nx.DiGraph: - pass - - if __name__ == '__main__': parser = argparse.ArgumentParser(description="Merge OSM map data with" "charging station data.") @@ -84,7 +75,7 @@ if __name__ == '__main__': '--cut', type=float, nargs=4, - metavar=('lat_nw', 'long_nw', 'lat_se', 'long_se'), + metavar=('lat_nw', 'lon_nw', 'lat_se', 'lon_se'), default=[None, None, None, None], help='Use only those charging stations that are located with in a ' 'rectangle defined by the north-west and south-east coordinates.' diff --git a/evrouting/charge/routing.py b/evrouting/charge/routing.py index d02ac3b..3b3f626 100644 --- a/evrouting/charge/routing.py +++ b/evrouting/charge/routing.py @@ -23,7 +23,7 @@ from evrouting.charge.factories import ( ) -def shortest_path(G: nx.Graph, charging_stations: Set[Node], s: Node, t: Node, +def shortest_path(G: nx.DiGraph, charging_stations: Set[Node], s: Node, t: Node, initial_soc: SoC, final_soc: SoC, capacity: SoC) -> Result: """ Calculates shortest path using the CHarge algorithm. diff --git a/evrouting/osm.py b/evrouting/osm.py index c08fa26..2e3c41d 100644 --- a/evrouting/osm.py +++ b/evrouting/osm.py @@ -20,13 +20,45 @@ from collections import namedtuple import networkx as nx import requests +import rtree + +from evrouting.graph_tools import CHARGING_COEFFICIENT_KEY logger = logging.getLogger(__name__) -OsrmConf = namedtuple('OsrmConf', - ['server', 'port', 'version', 'profile'], - defaults=('v1', 'driving') - ) +OsrmConf = namedtuple('OsrmConf', ['server', 'port', 'version', 'profile'], + defaults=('v1', 'driving')) + + +def query_url(service, coordinates, osrm_config: OsrmConf): + """Construct query url.""" + return f'http://{osrm_config.server}:{osrm_config.port}' \ + f'/{service}/{osrm_config.version}/{osrm_config.profile}/' \ + f'{";".join([f"{lon},{lat}" for lat, lon in coordinates])}' + + +def insert_charging_stations(G, charging_stations, osrm_conf=None): + osrm_conf = osrm_conf or OsrmConf( + server='localhost', + port=5000 + ) + + index = rtree.index.Index() + + for n in G.nodes: + lon = G.nodes[n]['lon'] + lat = G.nodes[n]['lat'] + + # insert point + index.insert(n, (lon, lat, lon, lat)) + + for s in charging_stations: + lon = s['lon'] + lat = s['lat'] + n = list(index.nearest((lon, lat, lon, lat), 1))[0] + G.nodes[n][CHARGING_COEFFICIENT_KEY] = s['power'] + + return G def haversine_distance(lon1, lat1, lon2, lat2, unit_m=True): @@ -51,32 +83,23 @@ def haversine_distance(lon1, lat1, lon2, lat2, unit_m=True): class OsrmDistance: - def __init__(self, G, osrm_config: OsrmConf = OsrmConf(server='localhost', port=5000)): - self.G = G - self.osrm_config = osrm_config - - def query_url(self, service, coordinates): - return f'http://{self.osrm_config.server}:{self.osrm_config.port}' \ - f'/{service}/{self.osrm_config.version}/{self.osrm_config.profile}/' \ - f'{";".join([f"{lon},{lat}" for lat, lon in coordinates])}' + def __init__(self, osrm_config: OsrmConf = None): + self.osrm_config = osrm_config or OsrmConf( + server='localhost', + port=5000 + ) - def __call__(self, u, v): + def __call__(self, G, u, v): """Calc distance between u and v on osrm.""" - url = self.query_url('route', - [ - (self.G.nodes[u]['lat'], self.G.nodes[u]['lon']), - (self.G.nodes[v]['lat'], self.G.nodes[v]['lon']) - ]) - resp = requests.get(url, timeout=0.1) - - try: - resp.raise_for_status() - except requests.HTTPError as e: - logger.error(f'Error {e}: {resp}') - raise - else: - resp = resp.json() + 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) + resp = requests.get(url, timeout=0.1) + resp.raise_for_status() + resp = resp.json() return resp['routes'][0]['duration'] diff --git a/tests/osm/test_osm_charge.py b/tests/osm/test_osm_charge.py index 92e2eb4..fc72985 100644 --- a/tests/osm/test_osm_charge.py +++ b/tests/osm/test_osm_charge.py @@ -2,8 +2,42 @@ import os import networkx as nx -from evrouting.osm import read_osm +from evrouting.osm import read_osm, insert_charging_stations +from evrouting.graph_tools import CHARGING_COEFFICIENT_KEY -def test_read_osm(): - G: nx.DiGraph = read_osm(os.path.join(os.path.dirname(__file__), 'static/map.osm')) +def _test_read_osm(): + """Just check if it runs. Todo: Delete.""" + assert read_osm(os.path.join(os.path.dirname(__file__), 'static/map.osm')) + + +def test_insert_charging_stations_close(): + G = nx.DiGraph() + + # Add two nodes, that exist in osm test map + G.add_node(0, lat=51.7705832, lon=7.0002595) + G.add_node(1, lat=51.7696529, lon=6.9568520) + + # Close two node 1 + S = [{"lon": 7.0002593, "lat": 51.7705832, "power": 22.0}] + + G = insert_charging_stations(G, S) + + assert G.nodes[0][CHARGING_COEFFICIENT_KEY] == 22.0 + assert CHARGING_COEFFICIENT_KEY not in G.nodes[1] + + +def test_insert_charging_stations_eq(): + G = nx.DiGraph() + + # Add two nodes, that exist in osm test map + G.add_node(0, lat=51.7705832, lon=7.0002595) + G.add_node(1, lat=51.7696529, lon=6.9568520) + + # Close exactly at node 1 + S = [{"lon": 7.0002595, "lat": 51.7705832, "power": 22.0}] + + G = insert_charging_stations(G, S) + + assert G.nodes[0][CHARGING_COEFFICIENT_KEY] == 22.0 + assert CHARGING_COEFFICIENT_KEY not in G.nodes[1] -- GitLab