In [22]:
#Goal - Select all segments in a given lat/long bounds using the Strava API
#Problem - API only reuturns top 10 segments in bound
import stravalib
import pandas as pd
import numpy as np
import os
from sqlalchemy import create_engine
from polyline.codec import PolylineCodec
from datetime import datetime
import json

In [2]:
engine = create_engine('postgresql+psycopg2://admin:password@localhost:5432/webdev6', 
                       convert_unicode=True)

In [3]:
client = stravalib.client.Client(access_token=os.environ['STRAVA_APICODE'])
athlete = client.get_athlete()
print 'athlete name %s, athlete id %s.' %(athlete.firstname, athlete.id)


athlete name Ryan, athlete id 1705436.

In [16]:
def get_segs_from_api(client, extents, act_type):
    """Get segments for a client in extents [40.681, -89.636, 40.775, -89.504] 
    with act_type riding or running.
    Returns a dataframe of the segments with all details of the segments
    """
    segment_explorer = client.explore_segments(extents,
                                               activity_type=act_type)
    return segment_explorer

def seg_to_df(segment_explorer, act_type):
    dflist = []
    for seg in segment_explorer:
        print 'seg id %s, seg name %s, seg dist %s' % \
               (seg.id, seg.name, seg.distance)
        
        if act_type=='riding':
            acttype='ride'
        else:
            acttype='run'
            
        seg_detail = seg.segment
        newrow = {'seg_id' : int(seg.id),
                  'name' : str(seg.name),
                  'act_type' : str(acttype),
                  'elev_low' : 0, #float(seg_detail.elevation_low),
                  'elev_high' : 0, #float(seg_detail.elevation_high),
                  'start_lat' : float(seg.start_latlng[0]),
                  'start_long' : float(seg.start_latlng[1]),
                  'end_lat' : float(seg.end_latlng[0]),
                  'end_long' : float(seg.end_latlng[1]),
                  'date_created' : datetime.utcnow(), #seg_detail.created_at.replace(tzinfo=None),
                  'effort_cnt' : 0, #int(seg_detail.effort_count),
                  'ath_cnt' : 0, #int(seg_detail.athlete_count),
                  'cat' : int(seg.climb_category),
                  'elev_gain' : float(seg.elev_difference),
                  'distance' : float(seg.distance),
                  'seg_points' : str(seg.points),
                  'seg_points_decode' : PolylineCodec().decode(seg.points)
                 }
        dflist.append(newrow)
    
    seg_df = pd.DataFrame(dflist)
        
    return seg_df

In [17]:
segment_explorer = get_segs_from_api(client, [40.8, -89.7, 40.9, -89.6], 'riding')
seg_df = seg_to_df(segment_explorer, 'riding')


seg id 9399604, seg name Up and Down Cline Road, seg dist 1631.90 m
seg id 5521655, seg name North Allen, seg dist 712.80 m
seg id 4647265, seg name Rock Island Trail- Wilhelm to W Cedar Hills Dr, seg dist 4230.50 m
seg id 10640506, seg name Don't stop for eggs, seg dist 1794.20 m
seg id 4287439, seg name Park to Allen on rt40, seg dist 2049.70 m
seg id 8320069, seg name Rock Isle Slant 1, seg dist 1304.70 m
seg id 7326950, seg name Cedar Hills East Bound, seg dist 4214.20 m
seg id 5359003, seg name Cedar Hills - Allen , seg dist 2636.50 m
seg id 5774488, seg name N Cedar Hills - Legion, seg dist 1758.20 m
seg id 7326941, seg name Rt 91 North Bound - Dunlap, seg dist 4871.90 m

In [6]:
def create_points(lat_series, long_series):
    # Creates a string from a lat/long column to map to a Geography Point
    # datatype in PostGIS
    point_col = 'Point(' + str(long_series) + ' ' + str(lat_series) + ')'

    return point_col

seg_df['start_point'] = map(create_points, seg_df['start_lat'], seg_df['start_long'])
seg_df['end_point'] = map(create_points, seg_df['end_lat'], seg_df['end_long'])

In [7]:
def get_acts_in_db(engine, table_name):
    # Return a list of already cached segments in the database
    already_dl_seg_id_list = []
    try:
        args = 'SELECT seg_id from "%s"' % (table_name)
        df = pd.read_sql(args, engine)
        already_dl_seg_id_list = df['seg_id']
    except:
        print "no activities in database!  downloading all segments in range..."

    return already_dl_seg_id_list


def clean_cached_segs(dl_lst, new_seg_df):
    # Remove segments already in database from the dataframe
    new_seg_df['rows_to_drop'] = new_seg_df['seg_id'].isin(dl_lst)
    new_seg_df.drop(new_seg_df[new_seg_df.rows_to_drop==True].index, inplace=True)
    return new_seg_df

In [8]:
dl_lst = get_acts_in_db(engine, 'Segment')
seg_df = clean_cached_segs(dl_lst, seg_df)

In [18]:
seg_df.head(2)


Out[18]:
act_type ath_cnt cat date_created distance effort_cnt elev_gain elev_high elev_low end_lat end_long name seg_id seg_points seg_points_decode start_lat start_long
0 ride 0 0 2016-04-04 12:39:05.938 1631.9 0 12.3 0 0 40.841374 -89.640136 Up and Down Cline Road 9399604 {dexFdkbbPAtCGp@KRaDpEe@h@WJ_@DoM?o|@S [(40.82782, -89.63779), (40.82783, -89.63854),... 40.827829 -89.637784
1 ride 0 0 2016-04-04 12:39:06.032 712.8 0 3.9 0 0 40.827400 -89.630762 North Allen 5521655 ezcxF|_abP_g@S [(40.82099, -89.63087), (40.82739, -89.63077)] 40.820996 -89.630864

In [51]:
#Converts a dataframe to a geojson Point output
def df_to_geojson_point(df, properties, lat='latitude', lon='longitude'):
    geojson = {'type':'FeatureCollection', 'features':[]}
    for _, row in df.iterrows():
        feature = {'type':'Feature',
                   'properties':{},
                   'geometry':{'type':'Point',
                               'coordinates':[]}}
        feature['geometry']['coordinates'] = [row[lon],row[lat]]
        for prop in properties:
            feature['properties'][prop] = row[prop]
        geojson['features'].append(feature)
    return geojson

#Converts a dataframe to a geojson LineString output
def df_to_geojson_line(df, properties, coords):
    geojson = {'type':'FeatureCollection', 'features':[]}
    for _, row in df.iterrows():
        feature = {'type':'Feature',
                   'properties':{},
                   'geometry':{'type':'LineString',
                               'coordinates': ''}}
        feature['geometry']['coordinates'] = row[coords]
        for prop in properties:
            feature['properties'][prop] = row[prop]
        geojson['features'].append(feature)
    return geojson

In [52]:
geojson = df_to_geojson_line(seg_df, ['name', 'act_type', 'distance', 'elev_gain'], 'seg_points_decode')

In [53]:
output_filename = 'dataset.js'
with open(output_filename, 'wb') as output_file:
    output_file.write('var dataset = ')
    json.dump(geojson, output_file, indent=2)

In [10]:
seg_df.set_index('seg_id', inplace=True)
seg_df.drop(['start_lat','start_long','end_lat','end_long', 'rows_to_drop'], axis=1, inplace=True)
seg_df.to_sql('Segment', engine, if_exists='append', index=True, index_label='seg_id')

In [34]:
from IPython.display import Javascript


Out[34]:

In [40]:
from IPython.display import Javascript
#Create a javascript variable with our geojson data to visualize in the browser
#The data object 'vizObj' will be a global varialbe in our window that 
#We can pass to another javascript function call
Javascript("""window.vizObj={};""".format(geojson))


Out[40]:

In [42]:
%%javascript
//Testing that the window.vizObj variable is accessable
console.log(window.vizObj);



In [43]:
#Now let's make some HTML to style our intended mapbox output
from IPython.display import HTML
HTML("""
<style> #map {
  position: relative;
  width: auto;
  height: 650px;
  overflow:visible;
}
</style>
""")


Out[43]:

In [44]:
%%javascript
//Load required javascript libraries
require.config({
  paths: {
      mapboxgl: 'https://api.tiles.mapbox.com/mapbox-gl-js/v0.16.0/mapbox-gl',
      bootstrap: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min'
  }
});



In [56]:
%%javascript
IPython.OutputArea.auto_scroll_threshold = 9999;
require(['mapboxgl', 'bootstrap'], function(mapboxgl, bootstrap){
    mapboxgl.accessToken = 'pk.eyJ1IjoicnNiYXVtYW5uIiwiYSI6IjdiOWEzZGIyMGNkOGY3NWQ4ZTBhN2Y5ZGU2Mzg2NDY2In0.jycgv7qwF8MMIWt4cT0RaQ';
    var map = new mapboxgl.Map({
        container: 'map', // container id
        style: 'mapbox://styles/mapbox/dark-v8', //stylesheet location
        center: [-89.948470, 40.783860], // starting position
        zoom: 10 // starting zoom 
    });
    
    
    function addSegLayer(mapid) {
        // Mapbox GL JS Api - import segment
        var segment_src = new mapboxgl.GeoJSONSource({
            data: window.vizObj,
            maxzoom: 18,
            buffer: 1,
            tolerance: 1
        });
        try {
            mapid.addSource('segment', segment_src);
            mapid.addLayer({
                id: 'segment',
                type: 'line',
                source: 'segment',
                paint: {
                    "line-opacity": 1,
                    "line-width": 5,
                    "line-color": 'red',
                }
            });
        } catch (err) {
            console.log(err);
        }
    };
    
    map.once('style.load', function(e) {
        addSegLayer(map);
        map.addControl(new mapboxgl.Navigation({
            position: 'top-left'
        }));
    });
    
});
element.append("<div id='map'></div>");



In [ ]: