first commit
commit
1c6724b7d0
|
|
@ -0,0 +1,10 @@
|
|||
*~
|
||||
venv/
|
||||
__pycache__/
|
||||
protos/*.py
|
||||
route_service_pb2.py
|
||||
route_service_pb2_grpc.py
|
||||
|
||||
*.o
|
||||
*.so
|
||||
*.cpp
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
FROM python:3.9-bullseye
|
||||
|
||||
MAINTAINER Yann Feunteun <yfe@protonmail>
|
||||
|
||||
RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.bz2
|
||||
|
||||
|
||||
RUN apt update && apt install -y python3.9-dev
|
||||
|
||||
RUN tar --bzip2 -xf boost_1_77_0.tar.bz2
|
||||
|
||||
RUN cd boost_1_77_0 && ./bootstrap.sh --with-python-version=3.9 && ./b2
|
||||
#apt-get install -y --no-install-recommends libboost-all-dev
|
||||
|
||||
RUN cp /boost_1_77_0/stage/lib/libboost_python39.so.1.77.0 /usr/lib
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY generate_graph.py generate_graph.py
|
||||
COPY generator.py generator.py
|
||||
COPY access.py access.py
|
||||
COPY export_clean_splitted.json data.json
|
||||
|
||||
|
||||
RUN python generate_graph.py
|
||||
#COPY test.cc test.cc
|
||||
|
||||
RUN g++ -Ofast -c -fPIC -I/boost_1_77_0/ -I/usr/include/python3.9/ foot.cpp -o c_foot.o
|
||||
RUN g++ -Ofast -c -fPIC -I/boost_1_77_0/ -I/usr/include/python3.9/ manual_wheelchair.cpp -o c_manual_wheelchair.o
|
||||
RUN g++ -Ofast -c -fPIC -I/boost_1_77_0/ -I/usr/include/python3.9/ electric_wheelchair.cpp -o c_electric_wheelchair.o
|
||||
|
||||
|
||||
|
||||
RUN g++ -Ofast -L/boost_1_77_0/stage/lib -shared -Wl,-soname,test.so -o c_foot.so c_foot.o -lboost_python39 -lpython3.9
|
||||
RUN g++ -Ofast -L/boost_1_77_0/stage/lib -shared -Wl,-soname,test.so -o c_manual_wheelchair.so c_manual_wheelchair.o -lboost_python39 -lpython3.9
|
||||
RUN g++ -Ofast -L/boost_1_77_0/stage/lib -shared -Wl,-soname,test.so -o c_electric_wheelchair.so c_electric_wheelchair.o -lboost_python39 -lpython3.9
|
||||
|
||||
COPY protos protos
|
||||
RUN python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=. ./protos/route_service.proto
|
||||
|
||||
COPY server.py /
|
||||
|
||||
EXPOSE 50051
|
||||
|
||||
ENTRYPOINT python server.py
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
* grpc-routing-microservice
|
||||
** Build with Docker
|
||||
#+begin_src sh
|
||||
docker build . -t routing
|
||||
#+end_src
|
||||
** Run with Docker
|
||||
#+begin_src sh
|
||||
docker run -it routing
|
||||
#+end_src
|
||||
** Benchmark (from within the container)
|
||||
#+begin_src sh
|
||||
python benchmark.py N_RUNS
|
||||
#+end_src
|
||||
=N_RUNS= denotes the number of time a random routing request will be
|
||||
made. Runtimes are then averaged and returned by the script.
|
||||
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
from andyamo import types
|
||||
|
||||
|
||||
def is_accessible(feature, profile: types.Profile):
|
||||
feature_type = feature["feature"]["properties"]["type"]
|
||||
feature_attributes = feature["attr"]
|
||||
|
||||
# |-------------- foot / crosswalk, sidewalk, stairs
|
||||
if profile.value == "foot":
|
||||
return True
|
||||
|
||||
elif profile.value == "manual_wheelchair":
|
||||
|
||||
# |-------------- manual_wheelchair / crosswalk
|
||||
if feature_type == "passage_pieton":
|
||||
predicates = [
|
||||
feature_attributes["Hauteur"] == "Non abaissé",
|
||||
feature_attributes["Marquage"] == "Non"
|
||||
and feature_attributes["Feu tricolore"] == "Non",
|
||||
feature_attributes["Feu tricolore"] == "Non"
|
||||
and feature_attributes["Croisement"]
|
||||
== "Piste cyclable bidirectionnelle",
|
||||
]
|
||||
not_accessible = sum(predicates) > 0
|
||||
if not_accessible:
|
||||
return False
|
||||
|
||||
# |-------------- manual_wheelchair / sidewalk
|
||||
elif feature_type == "segment":
|
||||
|
||||
predicates = [
|
||||
feature_attributes["Largeur"] == "Passe pas",
|
||||
feature_attributes["Largeur"] == "Passe seul"
|
||||
and feature_attributes["Obstacle"] == "Oui qui bloque le passage",
|
||||
feature_attributes["Type de revêtement"] == "Impraticable",
|
||||
feature_attributes["Type de revêtement"] == "Carrossable",
|
||||
feature_attributes["Etat"] == "Très abimé",
|
||||
feature_attributes["Inclinaison"] == "Très pentu",
|
||||
feature_attributes["Dévers"] == "Très pentu",
|
||||
feature_attributes["A vérifier"] == "En travaux",
|
||||
]
|
||||
not_accessible = sum(predicates) > 0
|
||||
if not_accessible:
|
||||
return False
|
||||
|
||||
# |-------------- manual_wheelchair / stairs
|
||||
elif feature_type == "escalier":
|
||||
return False
|
||||
|
||||
# |-------------- manual_wheelchair / catch all
|
||||
else:
|
||||
raise Exception(f"Unknown feature: {feature_type}")
|
||||
|
||||
return True
|
||||
|
||||
elif profile.value == "electric_wheelchair":
|
||||
|
||||
# |-------------- electric_wheelchair / crosswalk
|
||||
if feature_type == "passage_pieton":
|
||||
predicates = [
|
||||
feature_attributes["Hauteur"] == "Non abaissé",
|
||||
feature_attributes["Marquage"] == "Non"
|
||||
and feature_attributes["Feu tricolore"] == "Non",
|
||||
feature_attributes["Feu tricolore"] == "Non"
|
||||
and feature_attributes["Croisement"]
|
||||
== "Piste cyclable bidirectionnelle",
|
||||
]
|
||||
not_accessible = sum(predicates) > 0
|
||||
if not_accessible:
|
||||
return False
|
||||
|
||||
# |-------------- electric_wheelchair / sidewalk
|
||||
elif feature_type == "segment":
|
||||
|
||||
predicates = [
|
||||
feature_attributes["Largeur"] == "Passe pas",
|
||||
feature_attributes["Largeur"] == "Passe seul"
|
||||
and feature_attributes["Obstacle"] == "Oui qui bloque le passage",
|
||||
feature_attributes["Type de revêtement"] == "Impraticable",
|
||||
feature_attributes["Etat"] == "Très abimé",
|
||||
feature_attributes["Inclinaison"] == "Très pentu",
|
||||
feature_attributes["Dévers"] == "Très pentu",
|
||||
feature_attributes["A vérifier"] == "En travaux",
|
||||
]
|
||||
not_accessible = sum(predicates) > 0
|
||||
if not_accessible:
|
||||
return False
|
||||
|
||||
# |-------------- electric_wheelchair / stairs
|
||||
elif feature_type == "escalier":
|
||||
return False
|
||||
|
||||
# |-------------- electric_wheelchair / catch all
|
||||
else:
|
||||
raise Exception(f"Unknown feature: {feature_type}")
|
||||
|
||||
return True
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,57 @@
|
|||
import json
|
||||
import random
|
||||
from copy import deepcopy
|
||||
from collections import namedtuple
|
||||
|
||||
import access
|
||||
from andyamo import types
|
||||
|
||||
from generator import generate_graph
|
||||
from geopy import distance as geo_distance
|
||||
|
||||
|
||||
Edge = namedtuple("Edge", ["start_id", "end_id", "distance"])
|
||||
|
||||
with open("data.json", "r") as f:
|
||||
data = [json.loads(x.strip()) for x in f.readlines()]
|
||||
|
||||
|
||||
def get_graph(profile: types.Profile):
|
||||
node_set = set()
|
||||
edges = list()
|
||||
|
||||
for feature in data:
|
||||
try:
|
||||
if access.is_accessible(feature, profile):
|
||||
nodes = feature["feature"]["properties"]["nodes"]
|
||||
if len(nodes) == 2:
|
||||
coordinates = feature["feature"]["geometry"]["coordinates"]
|
||||
|
||||
for node in nodes:
|
||||
node_set.add(f"id_{node}")
|
||||
|
||||
distance = 0
|
||||
for i in range(len(coordinates)-1):
|
||||
distance += geo_distance.distance(
|
||||
coordinates[i], coordinates[i+1]
|
||||
).m
|
||||
|
||||
edges.append(Edge(f"id_{nodes[0]}", f"id_{nodes[1]}", distance))
|
||||
|
||||
|
||||
except Exception as e :
|
||||
print(feature)
|
||||
raise(e)
|
||||
|
||||
node_ids = list(node_set)
|
||||
return node_ids, edges
|
||||
|
||||
|
||||
node_ids, edges = get_graph(types.Profile("manual_wheelchair"))
|
||||
generate_graph("manual_wheelchair.cpp", "c_manual_wheelchair", node_ids, edges)
|
||||
|
||||
node_ids, edges = get_graph(types.Profile("electric_wheelchair"))
|
||||
generate_graph("electric_wheelchair.cpp", "c_electric_wheelchair", node_ids, edges)
|
||||
|
||||
node_ids, edges = get_graph(types.Profile("foot"))
|
||||
generate_graph("foot.cpp", "c_foot", node_ids, edges)
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
|
||||
|
||||
def generate_graph(output_name, module_name, node_ids, edges):
|
||||
|
||||
with open(output_name, "w") as out:
|
||||
|
||||
out.write("""
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/python.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include <boost/graph/graph_traits.hpp>
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/dijkstra_shortest_paths.hpp>
|
||||
#include <boost/property_map/property_map.hpp>
|
||||
|
||||
|
||||
using namespace boost;
|
||||
|
||||
|
||||
// create a typedef for the Graph type
|
||||
typedef adjacency_list< vecS, vecS, undirectedS, no_property,
|
||||
property< edge_weight_t, int > > graph_t;
|
||||
|
||||
typedef graph_traits< graph_t >::vertex_descriptor vertex_descriptor;
|
||||
typedef std::pair< int, int > Edge;
|
||||
|
||||
|
||||
int getIndex(std::vector<std::string>, std::string);
|
||||
graph_t buildGraph();
|
||||
|
||||
|
||||
""")
|
||||
|
||||
|
||||
out.write(f"const int num_nodes = {len(node_ids)};\n\n")
|
||||
out.write("enum nodes {\n")
|
||||
for node_id in node_ids:
|
||||
out.write(f" {node_id},\n")
|
||||
|
||||
out.write("};\n\n")
|
||||
|
||||
out.write('std::string _name[] = {')
|
||||
for node_id in node_ids:
|
||||
if node_id != node_ids[-1]:
|
||||
out.write(f'"{str(node_id)}",')
|
||||
else:
|
||||
out.write(f'"{str(node_id)}"')
|
||||
out.write('};\n')
|
||||
|
||||
out.write("std::vector<std::string> name(_name, _name + sizeof(_name)/sizeof(std::string));\n\n")
|
||||
|
||||
out.write("Edge edge_array[] = {\n")
|
||||
|
||||
for edge in edges:
|
||||
if edge != edges[-1]:
|
||||
out.write(f" Edge({edge.start_id}, {edge.end_id}),\n")
|
||||
else:
|
||||
out.write(f" Edge({edge.start_id}, {edge.end_id})\n")
|
||||
|
||||
out.write("};\n\n")
|
||||
|
||||
out.write("float weights[] = {")
|
||||
for edge in edges:
|
||||
if edge != edges[-1]:
|
||||
out.write(f"{edge.distance}, ")
|
||||
else:
|
||||
out.write(f"{edge.distance}")
|
||||
out.write("};\n")
|
||||
|
||||
out.write("""
|
||||
|
||||
std::string get_route(std::string start, std::string end) {
|
||||
graph_t graph = buildGraph();
|
||||
|
||||
property_map< graph_t, edge_weight_t >::type weightmap = get(edge_weight, graph);
|
||||
std::vector< vertex_descriptor > p(num_vertices(graph));
|
||||
std::vector< int > d(num_vertices(graph));
|
||||
|
||||
int pos_start = getIndex(name, start);
|
||||
int pos_end = getIndex(name, end);
|
||||
|
||||
if (pos_start == -1) {
|
||||
std::string msg("Invalid id (not found) for start position");
|
||||
throw std::invalid_argument(msg);
|
||||
} else if (pos_end == -1) {
|
||||
std::string msg("Invalid id (not found) for end position");
|
||||
throw std::invalid_argument(msg);
|
||||
}
|
||||
|
||||
vertex_descriptor s = vertex(nodes(pos_start), graph);
|
||||
vertex_descriptor e = vertex(nodes(pos_end), graph);
|
||||
|
||||
dijkstra_shortest_paths(graph, s,
|
||||
predecessor_map(boost::make_iterator_property_map(
|
||||
p.begin(), get(boost::vertex_index, graph)))
|
||||
.distance_map(boost::make_iterator_property_map(
|
||||
d.begin(), get(boost::vertex_index, graph))));
|
||||
|
||||
//find path from start to end vertex
|
||||
std::list<vertex_descriptor> pathVertices;
|
||||
|
||||
int distance = d[e];
|
||||
while (e != s)
|
||||
{
|
||||
pathVertices.push_front(e);
|
||||
if(name[e] == name[p[e]]) // see https://stackoverflow.com/questions/48367649/boost-dijkstra-shortest-paths-cant-extract-or-find-the-path-path-contains
|
||||
break;
|
||||
e = p[e];
|
||||
}
|
||||
|
||||
pathVertices.push_front(s); // add starting point to path
|
||||
|
||||
|
||||
std::string path;
|
||||
|
||||
for (vertex_descriptor n : pathVertices) {
|
||||
path += name[n];
|
||||
path += " ";
|
||||
}
|
||||
|
||||
path += ":";
|
||||
path += std::to_string(distance);
|
||||
|
||||
return path;
|
||||
|
||||
}
|
||||
|
||||
graph_t buildGraph() {
|
||||
|
||||
int num_arcs = sizeof(edge_array) / sizeof(Edge);
|
||||
|
||||
graph_t g(edge_array, edge_array + num_arcs, weights, num_nodes);
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
int getIndex(std::vector<std::string> v, std::string element)
|
||||
{
|
||||
auto it = find(v.begin(), v.end(), element);
|
||||
|
||||
// If element was found
|
||||
if (it != v.end())
|
||||
{
|
||||
|
||||
// calculating the index
|
||||
// of element
|
||||
int index = it - v.begin();
|
||||
//std::cout << index << std::endl;
|
||||
return index;
|
||||
}
|
||||
else {
|
||||
// If the element is not
|
||||
// present in the vector
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
""")
|
||||
|
||||
out.write(f"BOOST_PYTHON_MODULE({module_name}) // module name must match the name of the .so compiled")
|
||||
out.write("""
|
||||
{
|
||||
using namespace boost::python;
|
||||
def("get_route", get_route);
|
||||
}
|
||||
|
||||
""")
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
syntax = "proto3";
|
||||
|
||||
// Interface exported by the server.
|
||||
service RouteService {
|
||||
rpc ShortestPath(RouteRequest) returns (RouteResponse) {}
|
||||
rpc ListNodes(Profile) returns (stream Node) {}
|
||||
}
|
||||
|
||||
message RouteRequest {
|
||||
string start = 1;
|
||||
string end = 2;
|
||||
Profile profile = 3;
|
||||
}
|
||||
|
||||
|
||||
message Profile {
|
||||
enum Profile {
|
||||
FOOT = 0;
|
||||
MANUAL_WHEELCHAIR = 1;
|
||||
ELECTRIC_WHEELCHAIR = 2;
|
||||
|
||||
}
|
||||
Profile profile = 1;
|
||||
}
|
||||
|
||||
message RouteResponse {
|
||||
repeated string route = 1;
|
||||
int32 distance = 2;
|
||||
}
|
||||
|
||||
message Node {
|
||||
string index = 1;
|
||||
float latitude = 2;
|
||||
float longitude = 3;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-i https://pypi.andyamo.fr/simple
|
||||
andyamo==0.1.2
|
||||
grpcio-tools==1.44.0
|
||||
geographiclib==1.52
|
||||
geopy==2.2.0
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: route_service.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13route_service.proto\"E\n\x0cRouteRequest\x12\r\n\x05start\x18\x01 \x01(\t\x12\x0b\n\x03\x65nd\x18\x02 \x01(\t\x12\x19\n\x07profile\x18\x03 \x01(\x0b\x32\x08.Profile\"q\n\x07Profile\x12!\n\x07profile\x18\x01 \x01(\x0e\x32\x10.Profile.Profile\"C\n\x07Profile\x12\x08\n\x04\x46OOT\x10\x00\x12\x15\n\x11MANUAL_WHEELCHAIR\x10\x01\x12\x17\n\x13\x45LECTRIC_WHEELCHAIR\x10\x02\"0\n\rRouteResponse\x12\r\n\x05route\x18\x01 \x03(\t\x12\x10\n\x08\x64istance\x18\x02 \x01(\x05\":\n\x04Node\x12\r\n\x05index\x18\x01 \x01(\t\x12\x10\n\x08latitude\x18\x02 \x01(\x02\x12\x11\n\tlongitude\x18\x03 \x01(\x02\x32\x61\n\x0cRouteService\x12/\n\x0cShortestPath\x12\r.RouteRequest\x1a\x0e.RouteResponse\"\x00\x12 \n\tListNodes\x12\x08.Profile\x1a\x05.Node\"\x00\x30\x01\x62\x06proto3')
|
||||
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'route_service_pb2', globals())
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
_ROUTEREQUEST._serialized_start=23
|
||||
_ROUTEREQUEST._serialized_end=92
|
||||
_PROFILE._serialized_start=94
|
||||
_PROFILE._serialized_end=207
|
||||
_PROFILE_PROFILE._serialized_start=140
|
||||
_PROFILE_PROFILE._serialized_end=207
|
||||
_ROUTERESPONSE._serialized_start=209
|
||||
_ROUTERESPONSE._serialized_end=257
|
||||
_NODE._serialized_start=259
|
||||
_NODE._serialized_end=317
|
||||
_ROUTESERVICE._serialized_start=319
|
||||
_ROUTESERVICE._serialized_end=416
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import sys
|
||||
import timeit
|
||||
|
||||
from concurrent import futures
|
||||
import logging
|
||||
import math
|
||||
import time
|
||||
import json
|
||||
|
||||
import grpc
|
||||
import route_service_pb2
|
||||
import route_service_pb2_grpc
|
||||
|
||||
from andyamo import types
|
||||
|
||||
import c_foot
|
||||
import c_manual_wheelchair
|
||||
import c_electric_wheelchair
|
||||
|
||||
import access
|
||||
|
||||
|
||||
|
||||
class RouteServiceServicer(route_service_pb2_grpc.RouteService):
|
||||
"""Provides methods that implement functionality of routing server."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def ShortestPath(self, request, context):
|
||||
|
||||
_profile = request.profile.profile
|
||||
profile_as_str = route_service_pb2._PROFILE_PROFILE.values_by_number[_profile].name
|
||||
profile = types.Profile[profile_as_str]
|
||||
|
||||
if profile == types.Profile.FOOT:
|
||||
res = c_foot.get_route(request.start, request.end)
|
||||
|
||||
elif profile == types.Profile.MANUAL_WHEELCHAIR:
|
||||
res = c_manual_wheelchair.get_route(request.start, request.end)
|
||||
|
||||
elif profile == types.Profile.ELECTRIC_WHEELCHAIR:
|
||||
res = c_electric_wheelchair.get_route(request.start, request.end)
|
||||
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
route_str, distance_str = res.split(":")
|
||||
|
||||
route = route_str.strip().split(" ")
|
||||
distance = int(distance_str.strip())
|
||||
return route_service_pb2.RouteResponse(route=route, distance=distance)
|
||||
|
||||
def ListNodes(self, request, context):
|
||||
_profile = request.profile
|
||||
profile_as_str = route_service_pb2._PROFILE_PROFILE.values_by_number[_profile].name
|
||||
print(profile_as_str)
|
||||
|
||||
profile = types.Profile[profile_as_str]
|
||||
|
||||
with open("data.json", "r") as f:
|
||||
for line in f.readlines():
|
||||
feature = json.loads(line.strip())
|
||||
if access.is_accessible(feature, profile):
|
||||
coords = feature["feature"]["geometry"]["coordinates"]
|
||||
indexes = feature["feature"]["properties"]["nodes"]
|
||||
for coord, index in zip(coords, indexes):
|
||||
yield route_service_pb2.Node(index=f"id_{index}", latitude=coord[1], longitude=coord[0])
|
||||
|
||||
|
||||
def serve():
|
||||
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
||||
route_service_pb2_grpc.add_RouteServiceServicer_to_server(
|
||||
RouteServiceServicer(), server)
|
||||
server.add_insecure_port('[::]:50051')
|
||||
server.start()
|
||||
server.wait_for_termination()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig()
|
||||
serve()
|
||||
Loading…
Reference in New Issue