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/Pipfile.lock b/Pipfile.lock index 3dc7f8266da11e8f1cce9a7c770e58a2d2185944..0c0a25072748985da1cf3cc07d0e1a42fc541763 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 4291eb5c988e4244ff624d4784ec7d4ae7b02420..c996a5c3075d6496cf1cd44b9f192529270139ca 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 d02ac3b7f9a3d7e9cfb449b3efe8695f984ba985..3b3f6265265ea164adefe810387e5e34de776a10 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 c08fa26f7d0eff80ebc998e9c3b0f77576345261..2e3c41dbce7398876a29c927c5c36b46a8b2194d 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 92e2eb4f0e51e2b416112875789bb6f7e21707e0..fc729854b02f6a25eb32bff9fddb2d21f4a29b8b 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]