<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Tour of Free(mium) HERE APIs
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
Step1:  Geocoding
documentation
Step2:  Geopy Plugin
"geocoders united"
HERE plugin in Geopy 1.15.0, https
Step3:  Places
https
Step5:  Maps
https
Step6:  Full Maps
Step7:  Now HERE
Step8:  Geocoding Revisited
more GIS-savvy
(a litlle) more geo-spatial smarts
Step10:  Routing
https
Step11:  Isolines
Step12:  More interactively
Step13:  More to come... (in another meetup ;)
dynamic map content (based on traitlets)
streaming data
ZeroMQ integration
sneak preview below
<ASSISTANT_TASK:>
Python Code:
import random
import urllib
import utils
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'
Explanation: Tour of Free(mium) HERE APIs
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.
Freemium Plan
started in August 2018
large number of APIs included
250,000 API calls/month
5,000 monthly users on iOS/Android
no credit card needed
https://engage.here.com/freemium
https://developer.here.com/documentation
Setup
Credentials are imported from a here_credentials.py module if existing (via utils.py) defined as app_id and app_code, or from environment variables (HEREMAPS_APP_ID, HEREMAPS_APP_CODE).
End of explanation
import requests
here_berlin_addr
searchtext = urllib.parse.quote(here_berlin_addr)
searchtext
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)
obj = requests.get(url).json()
obj
loc = obj['Response']['View'][0]['Result'][0]['Location']['DisplayPosition']
loc['Latitude'], loc['Longitude']
Explanation: Geocoding
documentation: https://developer.here.com/documentation#geocoder
raw REST
geopy plugin
geocoding
reverse geocoding
Raw REST
End of explanation
from geopy.geocoders import Here
geocoder = Here(app_id, app_code)
here_berlin_addr
loc = geocoder.geocode(here_berlin_addr)
loc
loc.latitude, loc.longitude
loc.raw
here_berlin_lat_lon = loc.latitude, loc.longitude
here_berlin_lat_lon
loc = geocoder.reverse('{}, {}'.format(*here_berlin_lat_lon))
loc
loc.latitude, loc.longitude
Explanation: Geopy Plugin
"geocoders united"
HERE plugin in Geopy 1.15.0, https://github.com/geopy/geopy/releases/tag/1.15.0
pip install geopy&gt;=1.15.0
only most essential parts of geocoder API covered!
End of explanation
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)
obj = requests.get(url).json()
obj
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']))
Explanation: Places
https://developer.here.com/documentation#places
receive places within some neighbourhood
End of explanation
from IPython.display import Image
(lat, lon), zoom = berlin_lat_lon, 10
xtile, ytile = utils.deg2tile(lat, lon, zoom)
xtile, ytile
# %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
tiles_url = utils.build_here_tiles_url(
    maptype='base',
    tiletype='maptile',
    scheme='normal.day',
    x=xtile,
    y=ytile,
    z=zoom)
utils.mask_app_id(tiles_url)
img = Image(url=tiles_url)
img
# %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
Explanation: Maps
https://developer.here.com/documentation#map_tile
get single maptiles
use different maptiles
build entire maps
use folium, ipyleaflet & geopandas
revisit geocoding with shapes
draw these shapes
Single Map Tiles
End of explanation
import folium
folium.Map(location=berlin_lat_lon, zoom_start=10, tiles='Stamen Terrain')
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
Explanation: Full Maps
End of explanation
tiles_url = utils.build_here_tiles_url()
utils.mask_app_id(tiles_url)
folium.Map(
    location=berlin_lat_lon, 
    zoom_start=10, 
    tiles=tiles_url, 
    attr='HERE.com')
Explanation: Now HERE
End of explanation
%matplotlib inline
import geopandas
import shapely
import shapely.wkt
from geopy.geocoders import Here
geocoder = Here(app_id, app_code)
here_berlin_addr
loc = geocoder.geocode(
    here_berlin_addr, 
    additional_data='IncludeShapeLevel,postalCode') # <- get shapes!
loc.raw
wkt_shape = loc.raw['Location']['Shape']['Value']
shape = shapely.wkt.loads(wkt_shape)
shape
type(shape)
here_berlin_point = shapely.geometry.Point(*reversed(here_berlin_lat_lon))
here_berlin_point
shape.contains(here_berlin_point)
shape.contains(shapely.geometry.Point(0, 0))
data = [
    ['10115 Berlin', shape], 
    ['HERE HQ', here_berlin_point]
]
df = geopandas.GeoDataFrame(data=data, columns=['object', 'geometry'])
df
url = utils.build_here_tiles_url(x='tileX', y='tileY', z='tileZ')
utils.mask_app_id(url)
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)
# %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))
Explanation: Geocoding Revisited
more GIS-savvy
(a litlle) more geo-spatial smarts
End of explanation
from ipyleaflet import Map, Marker, CircleMarker, Polyline, basemap_to_tiles
from ipywidgets import HTML
here_berlin_addr
here_berlin_lat_lon
dt_oper_berlin_addr = 'Bismarkstr. 35, 10627 Berlin, Germany'
loc = geocoder.geocode(dt_oper_berlin_addr)
dt_oper_berlin_lat_lon = loc.latitude, loc.longitude
dt_oper_berlin_lat_lon
route = utils.get_route_positions(
    here_berlin_lat_lon, 
    dt_oper_berlin_lat_lon,
    mode='fastest;car;traffic:disabled',
    language='en')
route
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
route[0]['shape'][:4]
path = list(utils.chunks(route[0]['shape'], 2))
path[:2]
sum(map(lambda pq: utils.geo_distance(*pq), list(utils.pairwise(path))))
m += Polyline(locations=path, color='red', fill=False)
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
for lat, lon in path:
    m += CircleMarker(location=(lat, lon), radius=3, color='blue')
reverse_route = utils.get_route_positions(
    dt_oper_berlin_lat_lon,
    here_berlin_lat_lon,
    mode='shortest;pedestrian',
    language='en')
utils.add_route_to_map(reverse_route, m)
path = list(utils.chunks(reverse_route[0]['shape'], 2))
sum(map(lambda pq: utils.geo_distance(*pq), list(utils.pairwise(path))))
# %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)
Explanation: Routing
https://developer.here.com/documentation#routing
routes
modes
maneuvers
ipyleaflet preferred over folium
End of explanation
import requests
import ipywidgets as widgets
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()
obj
here_basemap = utils.build_here_basemap()
layers = [basemap_to_tiles(here_basemap)]
m = Map(center=(lat, lon), layers=layers, zoom=12)
m
m += Marker(location=(lat, lon))
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)
Explanation: Isolines
End of explanation
here_basemap = utils.build_here_basemap()
layers = [basemap_to_tiles(here_basemap)]
m = Map(center=(lat, lon), layers=layers, zoom=13)
m
lat, lon = here_berlin_lat_lon
dist_iso = utils.Isoline(m, 
                         lat=lat, lon=lon, 
                         app_id=app_id, app_code=app_code)
# can't get this working directly on dist_iso with __call__ :(
def dist_iso_func(meters=1000):
    dist_iso(meters=meters)
widgets.interact(dist_iso_func, meters=(1000, 2000, 200))
# %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
Explanation: More interactively
End of explanation
here_basemap = utils.build_here_basemap()
layers = [basemap_to_tiles(here_basemap)]
m = Map(center=berlin_lat_lon, layers=layers, zoom=13)
m
marker = Marker(location=berlin_lat_lon)
marker.location
m += marker
m -= marker
m += marker
marker.location = [52.49, 13.39]
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]
Explanation: More to come... (in another meetup ;)
dynamic map content (based on traitlets)
streaming data
ZeroMQ integration
sneak preview below
End of explanation
<END_TASK>