A short "teaser" presentation rushing through a small subset of many free APIs made by HERE Technologies under the Freemium plan. This notebook shows simple examples mostly for geocoding, places, maps and routing. They are designed for rapid consumption during a meetup talk. To that end, some code snippets longer than a few lines are imported from a module named utils.py
. Third-party modules are imported in the respective sections below as needed. (See utils.py
for a rough requirements list.)
Goal: Showing enough examples to wet you appetite for more, not delivering a polished "paper" or "package".
N.B.: This notebook is saved intentionally without cells executed as some of those would contain the HERE credentials used.
In [ ]:
import random
import urllib
In [ ]:
import utils
In [ ]:
app_id = utils.app_id
app_code = utils.app_code
berlin_lat_lon = [52.5, 13.4]
here_berlin_addr = 'Invalidenstr. 116, 10115 Berlin, Germany'
In [ ]:
import requests
In [ ]:
here_berlin_addr
In [ ]:
searchtext = urllib.parse.quote(here_berlin_addr)
searchtext
In [ ]:
url = (
'https://geocoder.api.here.com/6.2/geocode.json'
f'?searchtext={searchtext}&app_id={app_id}&app_code={app_code}'
)
utils.mask_app_id(url)
In [ ]:
obj = requests.get(url).json()
obj
In [ ]:
loc = obj['Response']['View'][0]['Result'][0]['Location']['DisplayPosition']
loc['Latitude'], loc['Longitude']
pip install geopy>=1.15.0
In [ ]:
from geopy.geocoders import Here
In [ ]:
geocoder = Here(app_id, app_code)
In [ ]:
here_berlin_addr
In [ ]:
loc = geocoder.geocode(here_berlin_addr)
loc
In [ ]:
loc.latitude, loc.longitude
In [ ]:
loc.raw
In [ ]:
here_berlin_lat_lon = loc.latitude, loc.longitude
here_berlin_lat_lon
In [ ]:
loc = geocoder.reverse('{}, {}'.format(*here_berlin_lat_lon))
loc
In [ ]:
loc.latitude, loc.longitude
In [ ]:
searchtext = 'Cafe'
lat, lon = here_berlin_lat_lon
url = (
'https://places.api.here.com/places/v1/autosuggest'
f'?q={searchtext}&at={lat},{lon}'
f'&app_id={app_id}&app_code={app_code}'
)
utils.mask_app_id(url)
In [ ]:
obj = requests.get(url).json()
obj
In [ ]:
for p in [res for res in obj['results'] if res['type']=='urn:nlp-types:place']:
print('{!r:23} {:4d} m {}'.format(p['position'], p['distance'], p['title']))
In [ ]:
from IPython.display import Image
In [ ]:
(lat, lon), zoom = berlin_lat_lon, 10
xtile, ytile = utils.deg2tile(lat, lon, zoom)
xtile, ytile
In [ ]:
# %load -s deg2tile utils
def deg2tile(lat_deg, lon_deg, zoom):
lat_rad = radians(lat_deg)
n = 2.0 ** zoom
xtile = int((lon_deg + 180.0) / 360.0 * n)
ytile = int((1.0 - log(tan(lat_rad) + (1 / cos(lat_rad))) / pi) / 2.0 * n)
return (xtile, ytile)
# not used here
In [ ]:
tiles_url = utils.build_here_tiles_url(
maptype='base',
tiletype='maptile',
scheme='normal.day',
x=xtile,
y=ytile,
z=zoom)
In [ ]:
utils.mask_app_id(tiles_url)
In [ ]:
img = Image(url=tiles_url)
img
In [ ]:
# %load -s build_here_tiles_url utils
def build_here_tiles_url(**kwdict):
"""
Return a HERE map tiles URL, based on default values that can be
overwritten by kwdict...
To be used for map building services like leaflet, folium, and
geopandas (with additional fields inside a dict)...
"""
params = dict(
app_id = app_id,
app_code = app_code,
maptype = 'traffic',
tiletype = 'traffictile',
scheme = 'normal.day',
tilesize = '256',
tileformat = 'png8',
lg = 'eng',
x = '{x}',
y = '{y}',
z = '{z}',
server = random.choice('1234')
)
params.update(kwdict)
url = (
'https://{server}.{maptype}.maps.api.here.com'
'/maptile/2.1/{tiletype}/newest/{scheme}/{z}/{x}/{y}/{tilesize}/{tileformat}'
'?lg={lg}&app_id={app_id}&app_code={app_code}'
).format(**params)
return url
In [ ]:
import folium
In [ ]:
folium.Map(location=berlin_lat_lon, zoom_start=10, tiles='Stamen Terrain')
In [ ]:
m = folium.Map(location=berlin_lat_lon, zoom_start=10)
folium.GeoJson('stops_berlin.geojson', name='BVG Stops').add_to(m)
folium.LayerControl().add_to(m)
m
In [ ]:
tiles_url = utils.build_here_tiles_url()
In [ ]:
utils.mask_app_id(tiles_url)
In [ ]:
folium.Map(
location=berlin_lat_lon,
zoom_start=10,
tiles=tiles_url,
attr='HERE.com')
In [ ]:
%matplotlib inline
In [ ]:
import geopandas
import shapely
import shapely.wkt
from geopy.geocoders import Here
In [ ]:
geocoder = Here(app_id, app_code)
In [ ]:
here_berlin_addr
In [ ]:
loc = geocoder.geocode(
here_berlin_addr,
additional_data='IncludeShapeLevel,postalCode') # <- get shapes!
loc.raw
In [ ]:
wkt_shape = loc.raw['Location']['Shape']['Value']
In [ ]:
shape = shapely.wkt.loads(wkt_shape)
shape
In [ ]:
type(shape)
In [ ]:
here_berlin_point = shapely.geometry.Point(*reversed(here_berlin_lat_lon))
here_berlin_point
In [ ]:
shape.contains(here_berlin_point)
In [ ]:
shape.contains(shapely.geometry.Point(0, 0))
In [ ]:
data = [
['10115 Berlin', shape],
['HERE HQ', here_berlin_point]
]
df = geopandas.GeoDataFrame(data=data, columns=['object', 'geometry'])
df
In [ ]:
url = utils.build_here_tiles_url(x='tileX', y='tileY', z='tileZ')
In [ ]:
utils.mask_app_id(url)
In [ ]:
df.crs = {'init': 'epsg:4326'} # dataframe is WGS84
ax = df.plot(figsize=(10, 10), alpha=0.5, edgecolor='k')
utils.add_basemap(ax, zoom=15, url=url)
In [ ]:
# %load -s add_basemap utils
def add_basemap(ax, zoom, url='http://tile.stamen.com/terrain/tileZ/tileX/tileY.png'):
# Special thanks to Prof. Martin Christen at FHNW.ch in Basel for
# his GIS-Hack to make the output scales show proper lat/lon values!
xmin, xmax, ymin, ymax = ax.axis()
basemap, extent = ctx.bounds2img(xmin, ymin, xmax, ymax, zoom=zoom, ll=True, url=url)
# calculate extent from WebMercator to WGS84
xmin84, ymin84 = Mercator2WGS84(extent[0], extent[2])
xmax84, ymax84 = Mercator2WGS84(extent[1], extent[3])
extentwgs84 = (xmin84, xmax84, ymin84, ymax84)
ax.imshow(basemap, extent=extentwgs84, interpolation='bilinear')
# restore original x/y limits
ax.axis((xmin, xmax, ymin, ymax))
In [ ]:
from ipyleaflet import Map, Marker, CircleMarker, Polyline, basemap_to_tiles
from ipywidgets import HTML
In [ ]:
here_berlin_addr
In [ ]:
here_berlin_lat_lon
In [ ]:
dt_oper_berlin_addr = 'Bismarkstr. 35, 10627 Berlin, Germany'
In [ ]:
loc = geocoder.geocode(dt_oper_berlin_addr)
dt_oper_berlin_lat_lon = loc.latitude, loc.longitude
dt_oper_berlin_lat_lon
In [ ]:
route = utils.get_route_positions(
here_berlin_lat_lon,
dt_oper_berlin_lat_lon,
mode='fastest;car;traffic:disabled',
language='en')
In [ ]:
route
In [ ]:
center = utils.mid_point(
here_berlin_lat_lon,
dt_oper_berlin_lat_lon)
here_basemap = utils.build_here_basemap()
layers = [basemap_to_tiles(here_basemap)]
m = Map(center=center, layers=layers, zoom=13)
m
In [ ]:
route[0]['shape'][:4]
In [ ]:
path = list(utils.chunks(route[0]['shape'], 2))
path[:2]
In [ ]:
sum(map(lambda pq: utils.geo_distance(*pq), list(utils.pairwise(path))))
In [ ]:
m += Polyline(locations=path, color='red', fill=False)
In [ ]:
for man in route[0]['leg'][0]['maneuver']:
lat = man['position']['latitude']
lon = man['position']['longitude']
desc = man['instruction']
marker = Marker(location=(lat, lon), draggable=False)
marker.popup = HTML(value=desc)
m += marker
In [ ]:
for lat, lon in path:
m += CircleMarker(location=(lat, lon), radius=3, color='blue')
In [ ]:
reverse_route = utils.get_route_positions(
dt_oper_berlin_lat_lon,
here_berlin_lat_lon,
mode='shortest;pedestrian',
language='en')
In [ ]:
utils.add_route_to_map(reverse_route, m)
In [ ]:
path = list(utils.chunks(reverse_route[0]['shape'], 2))
sum(map(lambda pq: utils.geo_distance(*pq), list(utils.pairwise(path))))
In [ ]:
# %load -s add_route_to_map utils.py
def add_route_to_map(route, some_map, color='blue'):
"""
Add a route from the HERE REST API to the given map.
This includes markers for all points where a maneuver is needed, like 'turn left'.
And it includes a path with lat/lons from start to end and little circle markers
around them.
"""
path_positions = list(chunks(route[0]['shape'], 2))
maneuvers = {
(man['position']['latitude'], man['position']['longitude']): man['instruction']
for man in route[0]['leg'][0]['maneuver']}
polyline = Polyline(
locations=path_positions,
color=color,
fill=False
)
some_map += polyline
for lat, lon in path_positions:
if (lat, lon) in maneuvers:
some_map += CircleMarker(location=(lat, lon), radius=2)
marker = Marker(location=(lat, lon), draggable=False)
message1 = HTML()
message1.value = maneuvers[(lat, lon)]
marker.popup = message1
some_map += marker
else:
some_map += CircleMarker(location=(lat, lon), radius=3)
In [ ]:
import requests
import ipywidgets as widgets
In [ ]:
lat, lon = here_berlin_lat_lon
url = (
'https://isoline.route.api.here.com'
'/routing/7.2/calculateisoline.json'
f'?app_id={app_id}&app_code={app_code}'
f'&start=geo!{lat},{lon}'
'&mode=fastest;car;traffic:disabled'
'&range=300,600' # seconds/meters
'&rangetype=time' # time/distance
#'&departure=now' # 2013-07-04T17:00:00+02
#'&resolution=20' # meters
)
obj = requests.get(url).json()
In [ ]:
obj
In [ ]:
here_basemap = utils.build_here_basemap()
layers = [basemap_to_tiles(here_basemap)]
m = Map(center=(lat, lon), layers=layers, zoom=12)
m
In [ ]:
m += Marker(location=(lat, lon))
In [ ]:
for isoline in obj['response']['isoline']:
shape = isoline['component'][0]['shape']
path = [tuple(map(float, pos.split(','))) for pos in shape]
m += Polyline(locations=path, color='red', weight=2, fill=True)
In [ ]:
here_basemap = utils.build_here_basemap()
layers = [basemap_to_tiles(here_basemap)]
m = Map(center=(lat, lon), layers=layers, zoom=13)
m
In [ ]:
lat, lon = here_berlin_lat_lon
In [ ]:
dist_iso = utils.Isoline(m,
lat=lat, lon=lon,
app_id=app_id, app_code=app_code)
In [ ]:
# can't get this working directly on dist_iso with __call__ :(
def dist_iso_func(meters=1000):
dist_iso(meters=meters)
In [ ]:
widgets.interact(dist_iso_func, meters=(1000, 2000, 200))
In [ ]:
# %load -s Isoline utils
class Isoline(object):
def __init__(self, the_map, **kwdict):
self.the_map = the_map
self.isoline = None
self.url = (
'https://isoline.route.api.here.com'
'/routing/7.2/calculateisoline.json'
'?app_id={app_id}&app_code={app_code}'
'&start=geo!{lat},{lon}'
'&mode=fastest;car;traffic:disabled'
'&range={{meters}}' # seconds/meters
'&rangetype=distance' # time/distance
#'&departure=now' # 2013-07-04T17:00:00+02
#'&resolution=20' # meters
).format(**kwdict)
self.cache = {}
def __call__(self, meters=1000):
if meters not in self.cache:
print('loading', meters)
url = self.url.format(meters=meters)
obj = requests.get(url).json()
self.cache[meters] = obj
obj = self.cache[meters]
isoline = obj['response']['isoline'][0]
shape = isoline['component'][0]['shape']
path = [tuple(map(float, pos.split(','))) for pos in shape]
if self.isoline:
self.the_map -= self.isoline
self.isoline = Polyline(locations=path, color='red', weight=2, fill=True)
self.the_map += self.isoline
In [ ]:
here_basemap = utils.build_here_basemap()
layers = [basemap_to_tiles(here_basemap)]
m = Map(center=berlin_lat_lon, layers=layers, zoom=13)
m
In [ ]:
marker = Marker(location=berlin_lat_lon)
marker.location
In [ ]:
m += marker
In [ ]:
m -= marker
In [ ]:
m += marker
In [ ]:
marker.location = [52.49, 13.39]
In [ ]:
loc = marker.location
for i in range(5000):
d_lat = (random.random() - 0.5) / 100
d_lon = (random.random() - 0.5) / 100
marker.location = [loc[0] + d_lat, loc[1] + d_lon]