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