openroute: new module providing geocode and direction instructions
Closing issue #46
This commit is contained in:
parent
bc183bcfa0
commit
7eac685a0a
158
modules/openroute.py
Normal file
158
modules/openroute.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"""Lost? use our commands to find your way!"""
|
||||||
|
|
||||||
|
# PYTHON STUFFS #######################################################
|
||||||
|
|
||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
from nemubot.exception import IMException
|
||||||
|
from nemubot.hooks import hook
|
||||||
|
from nemubot.tools import web
|
||||||
|
|
||||||
|
from more import Response
|
||||||
|
|
||||||
|
# GLOBALS #############################################################
|
||||||
|
|
||||||
|
URL_DIRECTIONS_API = "https://api.openrouteservice.org/directions?api_key=%s&"
|
||||||
|
URL_GEOCODE_API = "https://api.openrouteservice.org/geocoding?api_key=%s&"
|
||||||
|
|
||||||
|
waytype = [
|
||||||
|
"unknown",
|
||||||
|
"state road",
|
||||||
|
"road",
|
||||||
|
"street",
|
||||||
|
"path",
|
||||||
|
"track",
|
||||||
|
"cycleway",
|
||||||
|
"footway",
|
||||||
|
"steps",
|
||||||
|
"ferry",
|
||||||
|
"construction",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# LOADING #############################################################
|
||||||
|
|
||||||
|
def load(context):
|
||||||
|
if not context.config or "apikey" not in context.config:
|
||||||
|
raise ImportError("You need an OpenRouteService API key in order to use this "
|
||||||
|
"module. Add it to the module configuration file:\n"
|
||||||
|
"<module name=\"ors\" apikey=\"XXXXXXXXXXXXXXXX\" "
|
||||||
|
"/>\nRegister at https://developers.openrouteservice.org")
|
||||||
|
global URL_DIRECTIONS_API
|
||||||
|
URL_DIRECTIONS_API = URL_DIRECTIONS_API % context.config["apikey"]
|
||||||
|
global URL_GEOCODE_API
|
||||||
|
URL_GEOCODE_API = URL_GEOCODE_API % context.config["apikey"]
|
||||||
|
|
||||||
|
|
||||||
|
# MODULE CORE #########################################################
|
||||||
|
|
||||||
|
def approx_distance(lng):
|
||||||
|
if lng > 1111:
|
||||||
|
return "%f km" % (lng / 1000)
|
||||||
|
else:
|
||||||
|
return "%f m" % lng
|
||||||
|
|
||||||
|
|
||||||
|
def approx_duration(sec):
|
||||||
|
days = int(sec / 86400)
|
||||||
|
if days > 0:
|
||||||
|
return "%d days %f hours" % (days, (sec % 86400) / 3600)
|
||||||
|
hours = int((sec % 86400) / 3600)
|
||||||
|
if hours > 0:
|
||||||
|
return "%d hours %f minutes" % (hours, (sec % 3600) / 60)
|
||||||
|
minutes = (sec % 3600) / 60
|
||||||
|
if minutes > 0:
|
||||||
|
return "%d minutes" % minutes
|
||||||
|
else:
|
||||||
|
return "%d seconds" % sec
|
||||||
|
|
||||||
|
|
||||||
|
def geocode(query, limit=7):
|
||||||
|
obj = web.getJSON(URL_GEOCODE_API + urllib.parse.urlencode({
|
||||||
|
'query': query,
|
||||||
|
'limit': limit,
|
||||||
|
}))
|
||||||
|
|
||||||
|
for f in obj["features"]:
|
||||||
|
yield f["geometry"]["coordinates"], f["properties"]
|
||||||
|
|
||||||
|
|
||||||
|
def firstgeocode(query):
|
||||||
|
for g in geocode(query, limit=1):
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def where(loc):
|
||||||
|
return "{name} {city} {state} {county} {country}".format(**loc)
|
||||||
|
|
||||||
|
|
||||||
|
def directions(coordinates, **kwargs):
|
||||||
|
kwargs['coordinates'] = '|'.join(coordinates)
|
||||||
|
|
||||||
|
print(URL_DIRECTIONS_API + urllib.parse.urlencode(kwargs))
|
||||||
|
return web.getJSON(URL_DIRECTIONS_API + urllib.parse.urlencode(kwargs), decode_error=True)
|
||||||
|
|
||||||
|
|
||||||
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
|
@hook.command("geocode",
|
||||||
|
help="Get GPS coordinates of a place",
|
||||||
|
help_usage={
|
||||||
|
"PLACE": "Get GPS coordinates of PLACE"
|
||||||
|
})
|
||||||
|
def cmd_geocode(msg):
|
||||||
|
res = Response(channel=msg.channel, nick=msg.frm,
|
||||||
|
nomore="No more geocode", count=" (%s more geocode)")
|
||||||
|
|
||||||
|
for loc in geocode(' '.join(msg.args)):
|
||||||
|
res.append_message("%s is at %s,%s" % (
|
||||||
|
where(loc[1]),
|
||||||
|
loc[0][1], loc[0][0],
|
||||||
|
))
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command("directions",
|
||||||
|
help="Get routing instructions",
|
||||||
|
help_usage={
|
||||||
|
"POINT1 POINT2 ...": "Get routing instructions to go from POINT1 to the last POINTX via intermediates POINTX"
|
||||||
|
},
|
||||||
|
keywords={
|
||||||
|
"profile=PROF": "One of driving-car, driving-hgv, cycling-regular, cycling-road, cycling-safe, cycling-mountain, cycling-tour, cycling-electric, foot-walking, foot-hiking, wheelchair. Default: foot-walking",
|
||||||
|
"preference=PREF": "One of fastest, shortest, recommended. Default: recommended",
|
||||||
|
"lang=LANG": "default: en",
|
||||||
|
})
|
||||||
|
def cmd_directions(msg):
|
||||||
|
drcts = directions(["{0},{1}".format(*firstgeocode(g)[0]) for g in msg.args],
|
||||||
|
profile=msg.kwargs["profile"] if "profile" in msg.kwargs else "foot-walking",
|
||||||
|
preference=msg.kwargs["preference"] if "preference" in msg.kwargs else "recommended",
|
||||||
|
units="m",
|
||||||
|
language=msg.kwargs["lang"] if "lang" in msg.kwargs else "en",
|
||||||
|
geometry=False,
|
||||||
|
instructions=True,
|
||||||
|
instruction_format="text")
|
||||||
|
if "error" in drcts and "message" in drcts["error"] and drcts["error"]["message"]:
|
||||||
|
raise IMException(drcts["error"]["message"])
|
||||||
|
|
||||||
|
if "routes" not in drcts or not drcts["routes"]:
|
||||||
|
raise IMException("No route available for this trip")
|
||||||
|
|
||||||
|
myway = drcts["routes"][0]
|
||||||
|
myway["summary"]["strduration"] = approx_duration(myway["summary"]["duration"])
|
||||||
|
myway["summary"]["strdistance"] = approx_distance(myway["summary"]["distance"])
|
||||||
|
res = Response("Trip summary: {strdistance} in approximate {strduration}; elevation +{ascent} m -{descent} m".format(**myway["summary"]), channel=msg.channel, count=" (%d more steps)", nomore="You have arrived!")
|
||||||
|
|
||||||
|
def formatSegments(segments):
|
||||||
|
for segment in segments:
|
||||||
|
for step in segment["steps"]:
|
||||||
|
step["strtype"] = waytype[step["type"]]
|
||||||
|
step["strduration"] = approx_duration(step["duration"])
|
||||||
|
step["strdistance"] = approx_distance(step["distance"])
|
||||||
|
yield "{instruction} for {strdistance} on {strtype} (approximate time: {strduration})".format(**step)
|
||||||
|
|
||||||
|
if "segments" in myway:
|
||||||
|
res.append_message([m for m in formatSegments(myway["segments"])])
|
||||||
|
|
||||||
|
return res
|
Loading…
Reference in New Issue
Block a user