initial commit
commit
7f52695a08
|
|
@ -0,0 +1,16 @@
|
||||||
|
FROM python:3.9.9-slim-buster
|
||||||
|
LABEL maintainer="Yann Feunteun <yann.feunteun@protonmail.com>"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt /app/
|
||||||
|
|
||||||
|
RUN pip install --upgrade pip
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY ./ /app/
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# command to run on container start
|
||||||
|
CMD [ "uvicorn", "app.main:app", "--host", "0.0.0.0", "--reload" ]
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
grpc_server_host: str = "127.0.0.1"
|
||||||
|
grpc_server_port: str = "50051"
|
||||||
|
|
||||||
|
# Defines the maximum allowed distance in meters
|
||||||
|
# of a route. Above this threshold, a 404 response code
|
||||||
|
# will be returned meaning no route correspond to the request.
|
||||||
|
max_route_length_allowed: int = 20000
|
||||||
|
|
||||||
|
# Maximum allowed distance in meters between a requested route
|
||||||
|
# start/end point and the closest point on our graph
|
||||||
|
closest_point_tolerance: int = 150
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
|
||||||
|
# reads env variables (case insensitive) and validate against schema
|
||||||
|
settings = Settings()
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
from fastapi import FastAPI, Body
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import requests
|
||||||
|
from .config import settings
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import KDTree
|
||||||
|
from scipy.spatial import ConvexHull
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
from . import route_service_pb2
|
||||||
|
from . route_service_pb2_grpc import RouteServiceStub
|
||||||
|
from .schemas import (
|
||||||
|
Message,
|
||||||
|
Point,
|
||||||
|
LineString,
|
||||||
|
Polygon,
|
||||||
|
PolygonGeometry,
|
||||||
|
RouteRequest,
|
||||||
|
CoverageResponse,
|
||||||
|
RouteResponsePath
|
||||||
|
)
|
||||||
|
|
||||||
|
from andyamo import types
|
||||||
|
|
||||||
|
from geopy import distance as geo_distance
|
||||||
|
|
||||||
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
from fastapi.openapi.utils import get_openapi
|
||||||
|
|
||||||
|
from fastapi import Security, Depends, FastAPI, HTTPException
|
||||||
|
from fastapi.openapi.docs import get_swagger_ui_html
|
||||||
|
from fastapi.openapi.utils import get_openapi
|
||||||
|
|
||||||
|
from starlette.status import HTTP_403_FORBIDDEN
|
||||||
|
from starlette.responses import RedirectResponse, JSONResponse
|
||||||
|
|
||||||
|
channel = grpc.insecure_channel(f"{settings.grpc_server_host}:{settings.grpc_server_port}")
|
||||||
|
grpc_client = RouteServiceStub(channel)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_hash(data: str):
|
||||||
|
return hashlib.md5(f"{data}".encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
with open("export_clean_splitted.json", "r") as f:
|
||||||
|
data = [json.loads(x.strip()) for x in f.readlines()]
|
||||||
|
|
||||||
|
hash_to_type = {}
|
||||||
|
|
||||||
|
for feature in data:
|
||||||
|
hash_to_type[feature["feature"]["properties"]["index"]] = feature["feature"]["properties"]["type"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
datastore = {}
|
||||||
|
|
||||||
|
for _profile in ["foot", "manual_wheelchair", "electric_wheelchair"]:#, "visually_impaired_cane", "cognitive_disability", "elderly", "hearing_impaired"]:
|
||||||
|
datastore[_profile] = {}
|
||||||
|
datastore[_profile]["points"] = []
|
||||||
|
datastore[_profile]["ids"] = []
|
||||||
|
|
||||||
|
req_data = {"profile": types.Profile(_profile).name}
|
||||||
|
request = route_service_pb2.Profile(**req_data)
|
||||||
|
|
||||||
|
for node in grpc_client.ListNodes(request):
|
||||||
|
#print(node.index, node.longitude, node.latitude)
|
||||||
|
datastore[_profile]["points"].append([node.longitude, node.latitude])
|
||||||
|
datastore[_profile]["ids"].append(node.index)
|
||||||
|
|
||||||
|
datastore[_profile]["kdtree"] = KDTree(datastore[_profile]["points"])
|
||||||
|
|
||||||
|
nodeid_to_coords = {}
|
||||||
|
for feature in data:
|
||||||
|
nodeid_to_coords["d_" + feature['feature']["properties"]["nodes"][0]] = \
|
||||||
|
feature['feature']["geometry"]['coordinates'][0]
|
||||||
|
nodeid_to_coords["d_" + feature['feature']["properties"]["nodes"][1]] = \
|
||||||
|
feature['feature']["geometry"]['coordinates'][1]
|
||||||
|
|
||||||
|
def compute_convex_hull():
|
||||||
|
_points = np.array(datastore["foot"]["points"])
|
||||||
|
hull = ConvexHull(_points)
|
||||||
|
|
||||||
|
hull_geojson = {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {},
|
||||||
|
"geometry": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l = []
|
||||||
|
for vertex in hull.vertices:
|
||||||
|
l.append(list(datastore["foot"]["points"][vertex]))
|
||||||
|
|
||||||
|
l.append(list(datastore["foot"]["points"][hull.vertices[0]]))
|
||||||
|
hull_geojson["geometry"]["coordinates"] = [l]
|
||||||
|
|
||||||
|
return hull_geojson
|
||||||
|
|
||||||
|
hull_geojson = compute_convex_hull()
|
||||||
|
|
||||||
|
|
||||||
|
def compute_coverage(features: list) -> tuple:
|
||||||
|
"""Return bounding box of the geographical area covered by this instance."""
|
||||||
|
|
||||||
|
min_lon = sys.maxsize
|
||||||
|
min_lat = sys.maxsize
|
||||||
|
max_lon = 0
|
||||||
|
max_lat = 0
|
||||||
|
|
||||||
|
for feature in data:
|
||||||
|
for (lon, lat) in feature["feature"]["geometry"]["coordinates"]:
|
||||||
|
if lon < min_lon:
|
||||||
|
min_lon = lon
|
||||||
|
if lon > max_lon:
|
||||||
|
max_lon = lon
|
||||||
|
|
||||||
|
if lat < min_lat:
|
||||||
|
min_lat = lat
|
||||||
|
if lat > max_lat:
|
||||||
|
max_lat = lat
|
||||||
|
|
||||||
|
|
||||||
|
return min_lon,min_lat,max_lon,max_lat
|
||||||
|
|
||||||
|
|
||||||
|
def get_point_from_nodeid(nodeid):
|
||||||
|
return nodeid_to_coords[nodeid]
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class ConvexHullResponse(BaseModel):
|
||||||
|
polygon: Polygon = Field(..., title="polygon", description="Similar to /coverage, returns the convex hull corresponding to the geographical area covered as a GeoJSON polygon.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/healthz", summary="Check if API is up or not")
|
||||||
|
def healthz():
|
||||||
|
return { "healthy": "OK" }
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/coverage", summary="Coverage information (bounding box)", response_model=CoverageResponse)
|
||||||
|
def coverage():
|
||||||
|
bbox = compute_coverage(data)
|
||||||
|
return { "bbox": bbox }
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/convex", summary="Coverage information (convex hull)", response_model=ConvexHullResponse)
|
||||||
|
def coverage():
|
||||||
|
return { "polygon": hull_geojson }
|
||||||
|
|
||||||
|
|
||||||
|
@app.post(
|
||||||
|
"/route",
|
||||||
|
summary="Pedestrian routing",
|
||||||
|
response_model=RouteResponsePath,
|
||||||
|
responses={
|
||||||
|
400: {"model": Message},
|
||||||
|
404: {"model": Message}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def route(route: RouteRequest = Body(...)) -> RouteResponsePath:
|
||||||
|
|
||||||
|
|
||||||
|
if route.profile.value not in ["foot", "manual_wheelchair", "electric_wheelchair"]:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=404, content={
|
||||||
|
"message": "Profile not available yet. Available profiles are: foot, manual_wheelchair and electric_wheelchair"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_, idx_start = datastore[route.profile.value]["kdtree"]\
|
||||||
|
.query([route.start.lon, route.start.lat])
|
||||||
|
_, idx_end = datastore[route.profile.value]["kdtree"]\
|
||||||
|
.query([route.end.lon, route.end.lat])
|
||||||
|
|
||||||
|
too_far_from_graph = False
|
||||||
|
|
||||||
|
if geo_distance.distance(
|
||||||
|
get_point_from_nodeid(str(datastore[route.profile.value]["ids"][idx_start])[1:]),
|
||||||
|
[route.start.lon, route.start.lat]
|
||||||
|
).m > settings.closest_point_tolerance:
|
||||||
|
too_far_from_graph = True
|
||||||
|
|
||||||
|
if geo_distance.distance(
|
||||||
|
get_point_from_nodeid(str(datastore[route.profile.value]["ids"][idx_end])[1:]),
|
||||||
|
[route.end.lon, route.end.lat]
|
||||||
|
).m > settings.closest_point_tolerance:
|
||||||
|
too_far_from_graph = True
|
||||||
|
|
||||||
|
if too_far_from_graph:
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400, content={
|
||||||
|
"message": "Start and or end point outside covered perimeter"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
req_data = {
|
||||||
|
"start": str(datastore[route.profile.value]["ids"][idx_start]),
|
||||||
|
"end": str(datastore[route.profile.value]["ids"][idx_end]),
|
||||||
|
"profile": route_service_pb2.Profile(profile=route.profile.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
request = route_service_pb2.RouteRequest(**req_data)
|
||||||
|
response = grpc_client.ShortestPath(request)
|
||||||
|
route = response.route
|
||||||
|
distance = response.distance
|
||||||
|
|
||||||
|
if distance > settings.max_route_length_allowed:
|
||||||
|
return JSONResponse(status_code=404, content={"message": "No route found."})
|
||||||
|
|
||||||
|
route_points = [get_point_from_nodeid(x[1:]) for x in route]
|
||||||
|
|
||||||
|
segment_types = []
|
||||||
|
|
||||||
|
for i in range(len(route_points) - 1):
|
||||||
|
segment_hash0 = get_hash([route_points[i], route_points[i+1]])
|
||||||
|
segment_hash1 = get_hash([route_points[i+1], route_points[i]])
|
||||||
|
|
||||||
|
if segment_hash0 in hash_to_type.keys():
|
||||||
|
segment_types.append(hash_to_type[segment_hash0])
|
||||||
|
elif segment_hash1 in hash_to_type.keys():
|
||||||
|
segment_types.append(hash_to_type[segment_hash1])
|
||||||
|
|
||||||
|
if len(segment_types) < i+1:
|
||||||
|
segment_types.append("unknown")
|
||||||
|
|
||||||
|
|
||||||
|
min_lon = min([x[0] for x in route_points])
|
||||||
|
min_lat = min([x[1] for x in route_points])
|
||||||
|
max_lon = max([x[0] for x in route_points])
|
||||||
|
max_lat = max([x[1] for x in route_points])
|
||||||
|
|
||||||
|
walking_speed = 4000 / 3600 ## 4km/h == 4000 / 3600 m / s
|
||||||
|
return {
|
||||||
|
"distance": distance,
|
||||||
|
"duration": distance * walking_speed,
|
||||||
|
"points": {
|
||||||
|
"type": "LineString",
|
||||||
|
"coordinates": route_points
|
||||||
|
},
|
||||||
|
"segment_types": segment_types,
|
||||||
|
"bbox": [min_lon, min_lat, max_lon, max_lat]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return JSONResponse(status_code=404, content={"message": "No route found."})
|
||||||
|
|
||||||
|
|
@ -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,102 @@
|
||||||
|
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||||
|
"""Client and server classes corresponding to protobuf-defined services."""
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
import route_service_pb2 as route__service__pb2
|
||||||
|
|
||||||
|
|
||||||
|
class RouteServiceStub(object):
|
||||||
|
"""Interface exported by the server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, channel):
|
||||||
|
"""Constructor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel: A grpc.Channel.
|
||||||
|
"""
|
||||||
|
self.ShortestPath = channel.unary_unary(
|
||||||
|
'/RouteService/ShortestPath',
|
||||||
|
request_serializer=route__service__pb2.RouteRequest.SerializeToString,
|
||||||
|
response_deserializer=route__service__pb2.RouteResponse.FromString,
|
||||||
|
)
|
||||||
|
self.ListNodes = channel.unary_stream(
|
||||||
|
'/RouteService/ListNodes',
|
||||||
|
request_serializer=route__service__pb2.Profile.SerializeToString,
|
||||||
|
response_deserializer=route__service__pb2.Node.FromString,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RouteServiceServicer(object):
|
||||||
|
"""Interface exported by the server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def ShortestPath(self, request, context):
|
||||||
|
"""Missing associated documentation comment in .proto file."""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details('Method not implemented!')
|
||||||
|
raise NotImplementedError('Method not implemented!')
|
||||||
|
|
||||||
|
def ListNodes(self, request, context):
|
||||||
|
"""Missing associated documentation comment in .proto file."""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details('Method not implemented!')
|
||||||
|
raise NotImplementedError('Method not implemented!')
|
||||||
|
|
||||||
|
|
||||||
|
def add_RouteServiceServicer_to_server(servicer, server):
|
||||||
|
rpc_method_handlers = {
|
||||||
|
'ShortestPath': grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.ShortestPath,
|
||||||
|
request_deserializer=route__service__pb2.RouteRequest.FromString,
|
||||||
|
response_serializer=route__service__pb2.RouteResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
'ListNodes': grpc.unary_stream_rpc_method_handler(
|
||||||
|
servicer.ListNodes,
|
||||||
|
request_deserializer=route__service__pb2.Profile.FromString,
|
||||||
|
response_serializer=route__service__pb2.Node.SerializeToString,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
generic_handler = grpc.method_handlers_generic_handler(
|
||||||
|
'RouteService', rpc_method_handlers)
|
||||||
|
server.add_generic_rpc_handlers((generic_handler,))
|
||||||
|
|
||||||
|
|
||||||
|
# This class is part of an EXPERIMENTAL API.
|
||||||
|
class RouteService(object):
|
||||||
|
"""Interface exported by the server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ShortestPath(request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None):
|
||||||
|
return grpc.experimental.unary_unary(request, target, '/RouteService/ShortestPath',
|
||||||
|
route__service__pb2.RouteRequest.SerializeToString,
|
||||||
|
route__service__pb2.RouteResponse.FromString,
|
||||||
|
options, channel_credentials,
|
||||||
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ListNodes(request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None):
|
||||||
|
return grpc.experimental.unary_stream(request, target, '/RouteService/ListNodes',
|
||||||
|
route__service__pb2.Profile.SerializeToString,
|
||||||
|
route__service__pb2.Node.FromString,
|
||||||
|
options, channel_credentials,
|
||||||
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
from andyamo import types
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class Message(BaseModel):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
class Point(BaseModel):
|
||||||
|
lon: float
|
||||||
|
lat: float
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"lon": 3.127781750469675,
|
||||||
|
"lat": 45.76400009445323
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LineString(BaseModel):
|
||||||
|
type: str
|
||||||
|
coordinates: list[list[float]]
|
||||||
|
|
||||||
|
|
||||||
|
class RouteRequest(BaseModel):
|
||||||
|
start: Point = Field(..., description="The starting point for which the route should be calculated.")
|
||||||
|
end: Point = Field(..., description="The ending point for which the route should be calculated.")
|
||||||
|
profile: types.Profile = Field(..., description="See [availables profiles](#section/Routing-Profiles)")
|
||||||
|
|
||||||
|
|
||||||
|
class CoverageResponse(BaseModel):
|
||||||
|
bbox: list[float] = Field(..., title="bbox", description="The bounding box of the geographical area covered. Format: [minLon,minLat,maxLon,maxLat]")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"bbox": [
|
||||||
|
3.073747158050537,
|
||||||
|
45.77367083508177,
|
||||||
|
3.075887560844421,
|
||||||
|
45.77517870042004
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class RouteResponsePath(BaseModel):
|
||||||
|
distance: int = Field(..., description="Total distance in meters for the route returned")
|
||||||
|
duration: int = Field(..., description="Estimated travel time in seconds. Depends on the chosen [profile](#section/Routing-Profiles)")
|
||||||
|
points: LineString = Field(..., description="The geometry of the route in [GeoJSON format](https://datatracker.ietf.org/doc/html/rfc7946)")
|
||||||
|
bbox: list[float] = Field(..., title="bbox", description="The bounding box of the route geometry. Format: [minLon,minLat,maxLon,maxLat]")
|
||||||
|
segment_types: list[str] = Field(..., description="Segment types in the returned route result. Can be sidewalk, stair, crosswalk or unknown")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"distance": 280,
|
||||||
|
"duration": 201,
|
||||||
|
"points": {
|
||||||
|
"type": "LineString",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
3.073747158050537,
|
||||||
|
45.77517870042004
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0739670991897583,
|
||||||
|
45.77472597094132
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0742889642715454,
|
||||||
|
45.77413105636887
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0747556686401367,
|
||||||
|
45.77379056781466
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.074975609779358,
|
||||||
|
45.77367083508177
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0753082036972046,
|
||||||
|
45.77374940971673
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0754369497299194,
|
||||||
|
45.77386540064218
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0756354331970215,
|
||||||
|
45.77381301767344
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.075887560844421,
|
||||||
|
45.773872883919395
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"segment_types": ["sidewalk", "sidewalk", "crosswalk", "sidewalk", "sidewalk", "stair", "sidewalk", "sidewalk"],
|
||||||
|
"bbox": [3.073747158050537, 45.77367083508177, 3.075887560844421, 45.77517870042004]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PolygonGeometry(BaseModel):
|
||||||
|
type: str
|
||||||
|
coordinates: list[list[list[float]]]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
3.4716796874999996,
|
||||||
|
47.37603463349758
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.49639892578125,
|
||||||
|
47.33603074146188
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.6488342285156246,
|
||||||
|
47.429945332976125
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.4716796874999996,
|
||||||
|
47.37603463349758
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Polygon(BaseModel):
|
||||||
|
type: str
|
||||||
|
properties: dict
|
||||||
|
geometry: PolygonGeometry
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {},
|
||||||
|
"geometry": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
3.4716796874999996,
|
||||||
|
47.37603463349758
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.49639892578125,
|
||||||
|
47.33603074146188
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.6488342285156246,
|
||||||
|
47.429945332976125
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.4716796874999996,
|
||||||
|
47.37603463349758
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,13 @@
|
||||||
|
-i https://pypi.andyamo.fr/simple
|
||||||
|
andyamo==0.1.2
|
||||||
|
fastapi==0.71.0
|
||||||
|
geopy==2.2.0
|
||||||
|
uvicorn==0.15.0
|
||||||
|
requests==2.27.1
|
||||||
|
grpcio-tools==1.43.0
|
||||||
|
opentracing==2.4.0
|
||||||
|
scipy==1.8.0
|
||||||
|
opentelemetry-sdk==1.10.0
|
||||||
|
opentelemetry-api==1.10.0
|
||||||
|
opentelemetry-exporter-jaeger==1.10.0
|
||||||
|
numpy==1.22.2
|
||||||
|
|
@ -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,102 @@
|
||||||
|
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||||
|
"""Client and server classes corresponding to protobuf-defined services."""
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
import route_service_pb2 as route__service__pb2
|
||||||
|
|
||||||
|
|
||||||
|
class RouteServiceStub(object):
|
||||||
|
"""Interface exported by the server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, channel):
|
||||||
|
"""Constructor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel: A grpc.Channel.
|
||||||
|
"""
|
||||||
|
self.ShortestPath = channel.unary_unary(
|
||||||
|
'/RouteService/ShortestPath',
|
||||||
|
request_serializer=route__service__pb2.RouteRequest.SerializeToString,
|
||||||
|
response_deserializer=route__service__pb2.RouteResponse.FromString,
|
||||||
|
)
|
||||||
|
self.ListNodes = channel.unary_stream(
|
||||||
|
'/RouteService/ListNodes',
|
||||||
|
request_serializer=route__service__pb2.Profile.SerializeToString,
|
||||||
|
response_deserializer=route__service__pb2.Node.FromString,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RouteServiceServicer(object):
|
||||||
|
"""Interface exported by the server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def ShortestPath(self, request, context):
|
||||||
|
"""Missing associated documentation comment in .proto file."""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details('Method not implemented!')
|
||||||
|
raise NotImplementedError('Method not implemented!')
|
||||||
|
|
||||||
|
def ListNodes(self, request, context):
|
||||||
|
"""Missing associated documentation comment in .proto file."""
|
||||||
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||||
|
context.set_details('Method not implemented!')
|
||||||
|
raise NotImplementedError('Method not implemented!')
|
||||||
|
|
||||||
|
|
||||||
|
def add_RouteServiceServicer_to_server(servicer, server):
|
||||||
|
rpc_method_handlers = {
|
||||||
|
'ShortestPath': grpc.unary_unary_rpc_method_handler(
|
||||||
|
servicer.ShortestPath,
|
||||||
|
request_deserializer=route__service__pb2.RouteRequest.FromString,
|
||||||
|
response_serializer=route__service__pb2.RouteResponse.SerializeToString,
|
||||||
|
),
|
||||||
|
'ListNodes': grpc.unary_stream_rpc_method_handler(
|
||||||
|
servicer.ListNodes,
|
||||||
|
request_deserializer=route__service__pb2.Profile.FromString,
|
||||||
|
response_serializer=route__service__pb2.Node.SerializeToString,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
generic_handler = grpc.method_handlers_generic_handler(
|
||||||
|
'RouteService', rpc_method_handlers)
|
||||||
|
server.add_generic_rpc_handlers((generic_handler,))
|
||||||
|
|
||||||
|
|
||||||
|
# This class is part of an EXPERIMENTAL API.
|
||||||
|
class RouteService(object):
|
||||||
|
"""Interface exported by the server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ShortestPath(request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None):
|
||||||
|
return grpc.experimental.unary_unary(request, target, '/RouteService/ShortestPath',
|
||||||
|
route__service__pb2.RouteRequest.SerializeToString,
|
||||||
|
route__service__pb2.RouteResponse.FromString,
|
||||||
|
options, channel_credentials,
|
||||||
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ListNodes(request,
|
||||||
|
target,
|
||||||
|
options=(),
|
||||||
|
channel_credentials=None,
|
||||||
|
call_credentials=None,
|
||||||
|
insecure=False,
|
||||||
|
compression=None,
|
||||||
|
wait_for_ready=None,
|
||||||
|
timeout=None,
|
||||||
|
metadata=None):
|
||||||
|
return grpc.experimental.unary_stream(request, target, '/RouteService/ListNodes',
|
||||||
|
route__service__pb2.Profile.SerializeToString,
|
||||||
|
route__service__pb2.Node.FromString,
|
||||||
|
options, channel_credentials,
|
||||||
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
||||||
Loading…
Reference in New Issue