diff --git a/.gitignore b/.gitignore
index 5603b9b..5efd98e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,5 +6,5 @@ venv/
**/*.html
dist/
build/
-
-.mypy_cache/
\ No newline at end of file
+.DS_Store
+.mypy_cache/
diff --git a/envirocar/__init__.py b/envirocar/__init__.py
index 866fbcc..e0831d3 100644
--- a/envirocar/__init__.py
+++ b/envirocar/__init__.py
@@ -1,4 +1,7 @@
from .client.client_config import ECConfig
from .client.download_client import DownloadClient
from .client.api.track_api import TrackAPI
-from .client.request_param import BboxSelector, TimeSelector
\ No newline at end of file
+from .client.request_param import BboxSelector, TimeSelector
+from .trajectories.preprocessing import Preprocessing
+from .trajectories.track_converter import TrackConverter
+from .trajectories.track_similarity import TrackSimilarity
\ No newline at end of file
diff --git a/envirocar/trajectories/__init__.py b/envirocar/trajectories/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/envirocar/trajectories/__init__.py
@@ -0,0 +1 @@
+
diff --git a/envirocar/trajectories/preprocessing.py b/envirocar/trajectories/preprocessing.py
new file mode 100644
index 0000000..41cdd59
--- /dev/null
+++ b/envirocar/trajectories/preprocessing.py
@@ -0,0 +1,717 @@
+import os
+from math import floor, ceil
+from scipy import interpolate
+from statistics import mean
+import pandas as pd
+import geopandas as gpd
+import numpy as np
+import datetime
+import random
+import string
+from copy import copy
+
+import folium
+import movingpandas as mpd
+from copy import copy
+from shapely.geometry import Point, LineString, Polygon
+import geopandas as gpd
+import json
+from branca.colormap import linear
+import enum
+
+class GeneralizationType(enum.Enum):
+ DouglasPeucker = 0
+ MinDistance = 1
+ MinTimeDelta = 2
+
+class Preprocessing():
+ def __init__(self):
+ print("Initializing pre-processing class") # do we need anything?
+
+ def remove_outliers(self, points, column):
+ """ Remove outliers by using the statistical approach
+ as described in
+ https://www.itl.nist.gov/div898/handbook/prc/section1/prc16.htm
+
+ Keyword Arguments:
+ points {GeoDataFrame} -- A GeoDataFrame containing the track points
+ column {String} -- Columnn name to remove outliers from
+
+ Returns:
+ new_points -- Points with outliers removed
+ """
+
+ # print(points['time'])
+ first_quartile = points[column].quantile(0.25)
+ third_quartile = points[column].quantile(0.75)
+ iqr = third_quartile-first_quartile # Interquartile range
+ fence_low = first_quartile - 1.5 * iqr
+ fence_high = third_quartile + 1.5 * iqr
+
+ new_points = points.loc[(points[column] > fence_low) & (
+ points[column] < fence_high)]
+ # print(new_points['time'])
+ return new_points
+
+ def interpolate(self, points):
+ """ Creates a trajectory from point data
+
+ Keyword Arguments:
+ points {GeoDataFrame} -- A GeoDataFrame containing the track points
+
+ Returns:
+ new_points -- An interpolated trajectory
+ """
+
+ def date_to_seconds(x):
+ date_time_obj = datetime.datetime.strptime(x, '%Y-%m-%dT%H:%M:%S')
+ seconds = (date_time_obj-datetime.datetime(1970, 1, 1)
+ ).total_seconds()
+ return int(seconds)
+
+ def seconds_to_date(x):
+ date = datetime.datetime.fromtimestamp(x, datetime.timezone.utc)
+ return date
+
+ def randStr(chars=string.ascii_uppercase + string.digits, N=24):
+ return ''.join(random.choice(chars) for _ in range(N))
+
+ def interpolate_spline(input_array, step):
+ tck, u = interpolate.splprep(input_array, s=0)
+ interpolated = interpolate.splev(step, tck)
+ return interpolated
+
+ def interpolate_linear(step_init, values, step_final):
+ f = interpolate.interp1d(step_init, values, axis=0,
+ fill_value="extrapolate")
+ values_new = f(step_final)
+ return values_new
+
+ print('Amount of points before interpolation',
+ points.shape)
+
+ # to have flat attributes for coordinates
+ points['lat'] = points['geometry'].apply(lambda coord: coord.y)
+ points['lng'] = points['geometry'].apply(lambda coord: coord.x)
+ points_df = pd.DataFrame(points)
+
+ # removing duplicates because interpolation won't work otherwise
+ points_df_cleaned = points_df.drop_duplicates(
+ ['lat', 'lng'], keep='last')
+
+ # input for datetime in seconds
+ points_df_cleaned['time_seconds'] = np.vectorize(date_to_seconds)(
+ np.array(points_df_cleaned.time.values.tolist()))
+
+ # creating the column name lists
+ names_interpolate = [s for s in points_df_cleaned.columns if
+ '.value' in s]
+ # adding the other column names at front
+ names_interpolate = ['lng', 'lat', 'time_seconds'] + names_interpolate
+ names_replicatate = np.setdiff1d(points_df_cleaned.columns,
+ names_interpolate)
+ names_extra = ['geometry', 'id', 'time']
+ names_replicatate = [x for x in names_replicatate if x
+ not in names_extra]
+
+ # measurements themselves
+ columns_interpolate = [np.array(
+ points_df_cleaned[column].values.tolist()) for column
+ in names_interpolate]
+
+ # split dataframe because splprep cannot take more than 11
+ dfs = np.split(columns_interpolate, [2], axis=0)
+
+ """ Interpolation itself """
+ # Find the B-spline representation of the curve
+ # tck (t,c,k): is a tuple containing the vector of knots,
+ # the B-spline coefficients, and the degree of the spline.
+ # u: is an array of the values of the parameter.
+
+ # interpolating so many points to have a point for each second
+ step = np.linspace(0, 1, points_df_cleaned['time_seconds'].iloc[-1] -
+ points_df_cleaned['time_seconds'].iloc[0])
+
+ seconds = np.linspace(points_df_cleaned['time_seconds'].iloc[0],
+ points_df_cleaned['time_seconds'].iloc[-1],
+ points_df_cleaned['time_seconds'].iloc[-1] -
+ points_df_cleaned['time_seconds'].iloc[0])
+
+ new_points = interpolate_spline(dfs[0], step)
+
+ for idx, column in enumerate(dfs[1]):
+ if (idx == 0):
+ new_points.append(seconds)
+ else:
+ new_points.append(interpolate_linear(points_df_cleaned[
+ 'time_seconds'], column, seconds))
+
+ # transposing the resulting matrix to fit it in the dataframe
+ data = np.transpose(new_points)
+
+ # constructing the new dataframe
+ interpolated_df = pd.DataFrame(data)
+
+ interpolated_df.columns = names_interpolate
+ interpolated_df['time'] = np.vectorize(
+ seconds_to_date)(interpolated_df['time_seconds'])
+
+ # these should all be the same for one ride, so just replicating
+ columns_replicate = [np.repeat(points_df_cleaned[column].iloc[0],
+ len(step)) for column in names_replicatate]
+
+ replicated_transposed = np.transpose(columns_replicate)
+ replicated_df = pd.DataFrame(replicated_transposed)
+ replicated_df.columns = names_replicatate
+
+ # combining replicated with interpolated
+ full_df = pd.concat([interpolated_df, replicated_df], axis=1,
+ sort=False)
+
+ # adding ids
+ full_df['id'] = 0
+ for row in full_df.index:
+ full_df['id'][row] = randStr()
+
+ # transforming back to a geodataframe
+ full_gdf = gpd.GeoDataFrame(
+ full_df, geometry=gpd.points_from_xy(full_df.lng, full_df.lat))
+
+ # remove full_gdf['lng'], full_gdf['lat'] ?
+ del full_gdf['time_seconds']
+
+ print('Amount of points after interpolation',
+ full_gdf.shape)
+
+ return full_gdf
+
+
+ def aggregate(self, track_df, MIN_LENGTH, MIN_GAP, MAX_DISTANCE, MIN_DISTANCE, MIN_STOP_DURATION ):
+ """ Transforms to Moving Pandas, Converts into Trajectories, Ignore small trajectories and return Aggregated Flows
+
+ Keyword Arguments:
+ track_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ MIN_LENGTH {integer} -- Minimum Length of a Trajectory (to be considered as a Trajectory)
+ MIN_GAP {integer} -- Minimum Gap (in minutes) for splitting single Trajectory into more
+ MAX_DISTANCE {integer} -- Maximum distance between significant points
+ MIN_DISTANCE {integer} -- Minimum distance between significant points
+ MIN_STOP_DURATION {integer} -- Minimum duration (in minutes) required for stop detection
+
+ Returns:
+ flows -- A GeoDataFrame containing Aggregared Flows (linestrings)
+ """
+
+ # Using MPD function to convert trajectory points into actual trajectories
+ traj_collection = mpd.TrajectoryCollection(track_df, 'track.id', min_length=MIN_LENGTH)
+ print("Finished creating {} trajectories".format(len(traj_collection)))
+
+ # Using MPD function to Split Trajectories based on time gap between records to extract Trips
+ trips = traj_collection.split_by_observation_gap(datetime.timedelta(minutes=MIN_GAP))
+ print("Extracted {} individual trips from {} continuous vehicle tracks".format(len(trips), len(traj_collection)))
+
+ # Using MPD function to Aggregate Trajectories
+ aggregator = mpd.TrajectoryCollectionAggregator(trips, max_distance=MAX_DISTANCE, min_distance=MIN_DISTANCE, min_stop_duration=datetime.timedelta(minutes=MIN_STOP_DURATION))
+ flows = aggregator.get_flows_gdf()
+ return flows
+
+ def aggregateByGrid(self,df,field,summary,gridSize):
+ """
+ Aggregates the specified field with chosen summary type and user defined grid size. returns aggregated grids with summary
+
+ Parameters
+ ----------
+ df : geopandas dataframe
+ field : string
+ field to be summarized.
+ summary : string
+ type of summary to be sumarized. eg. min, max,sum, median
+ gridSize : float
+ the size of grid on same unit as geodataframe coordinates.
+
+ Returns
+ -------
+ geodataframe
+ Aggregated grids with summary on it
+
+ """
+ def round_down(num, divisor):
+ return floor(num / divisor) * divisor
+ def round_up(num, divisor):
+ return ceil(num / divisor) * divisor
+ xmin,ymin,xmax,ymax = df.total_bounds
+ height,width=gridSize,gridSize
+ top,left=round_up(ymax,height),round_down(xmin,width)
+ bottom,right=round_down(ymin,height),round_up(xmax,width)
+
+
+ rows = int((top -bottom) / height)+1
+ cols = int((right -left) / width)+1
+
+ XleftOrigin = left
+ XrightOrigin = left + width
+ YtopOrigin = top
+ YbottomOrigin = top- height
+ polygons = []
+ for i in range(cols):
+ Ytop = YtopOrigin
+ Ybottom =YbottomOrigin
+ for j in range(rows):
+ polygons.append(Polygon([(XleftOrigin, Ytop), (XrightOrigin, Ytop), (XrightOrigin, Ybottom), (XleftOrigin, Ybottom)]))
+ Ytop = Ytop - height
+ Ybottom = Ybottom - height
+ XleftOrigin = XleftOrigin + width
+ XrightOrigin = XrightOrigin + width
+
+ grid = gpd.GeoDataFrame({'geometry':polygons})
+ grid.crs=df.crs
+
+ #Assign gridid
+ numGrid=len(grid)
+ grid['gridId']=list(range(numGrid))
+
+ #Identify gridId for each point
+ points_identified= gpd.sjoin(df,grid,op='within')
+
+ #group points by gridid and calculate mean Easting, store it as dataframe
+ #delete if field already exists
+ if field in grid.columns:
+ del grid[field]
+ grouped = points_identified.groupby('gridId')[field].agg(summary)
+ grouped_df=pd.DataFrame(grouped)
+
+ new_grid=grid.join(grouped_df, on='gridId').fillna(0)
+ new_grid['x_centroid'],new_grid['y_centroid']=new_grid.geometry.centroid.x,new_grid.geometry.centroid.y
+ grid=new_grid
+ return grid
+
+ def plotAggregate(self,grid,field):
+ """
+ Plots the aggregated data on grid. Please call aggregateByGrid function before this step.
+
+ Parameters
+ ----------
+ grid :polygon geodataframe
+ The grid geodataframe with grid and aggregated data in a column. Grid shoud have grid id or equivalent unique ids
+ field : string
+ Fieldname with aggregated data
+
+ Returns
+ -------
+ m : folium map object
+ Folium map with openstreetmap as base.
+
+ """
+ #Prepare for grid plotting using folium
+ grid.columns=[cols.replace('.', '_') for cols in grid.columns]
+ field=field.replace('.','_')
+ #Convert grid id to string
+ grid['gridId']=grid['gridId'].astype(str)
+ #only select grid with non zero values
+ grid=grid[grid[field]>0]
+
+ #Convert data to geojson and csv
+ atts=pd.DataFrame(grid.drop(columns=['geometry','x_centroid','y_centroid']))
+ grid.to_file("grids.geojson", driver='GeoJSON')
+ atts.to_csv("attributes.csv", index=False)
+
+ #load spatial and non-spatial data
+ data_geojson_source="grids.geojson"
+ data_geojson=json.load(open(data_geojson_source))
+
+ #Get coordiantes for map centre
+ lat=grid.geometry.centroid.y.mean()
+ lon=grid.geometry.centroid.x.mean()
+ #Intialize a new folium map object
+ m = folium.Map(location=[lat,lon],zoom_start=8,tiles='OpenStreetMap')
+ # Configure geojson layer
+ folium.GeoJson(data_geojson).add_to(m)
+
+ #add attribute data
+ attribute_pd=pd.read_csv("attributes.csv")
+ attribute=pd.DataFrame(attribute_pd)
+ #Convert gridId to string to ensure it matches with gridId
+ attribute['gridId']=attribute['gridId'].astype(str)
+
+ # construct color map
+ minvalue=attribute[field].min()
+ maxvalue=attribute[field].max()
+ colormap_rn = linear.YlOrRd_09.scale(minvalue,maxvalue)
+
+ #Create Dictionary for colormap
+ population_dict_rn = attribute.set_index('gridId')[field]
+
+ #create map
+ folium.GeoJson(
+ data_geojson,
+ name='Choropleth map',
+ style_function=lambda feature: {
+ 'fillColor': colormap_rn(population_dict_rn[feature['properties']['gridId']]),
+ 'color': 'black',
+ 'weight': 0.5,
+ 'dashArray': '5, 5',
+ 'fillOpacity':0.5
+ },
+ highlight_function=lambda feature:{'weight':3,'color':'black','fillOpacity':1},
+ tooltip=folium.features.GeoJsonTooltip(fields=[field],aliases=[field])
+ ).add_to(m)
+
+ #format legend
+ field=field.replace("_"," ")
+ # add a legend
+ colormap_rn.caption = '{value} per grid'.format(value=field)
+ colormap_rn.add_to(m)
+
+ # add a layer control
+ folium.LayerControl().add_to(m)
+ return m
+
+ def flow_between_regions(self, data_mpd_df, from_region, to_region, twoway):
+ """ How many entities moved between from_region to to_region (one way or both ways)
+
+ Keyword Arguments:
+ data_mpd_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ from_region {Polygon} -- A shapely polygon as our Feautre of Interest (FOI) - 1
+ to_region {Polygon} -- A shapely polygon as our Feautre of Interest (FOI) - 2
+ twoways {Boolean} -- if two way or one regions are to be computed
+
+ Returns:
+ regional_trajectories -- A list of trajectories moving between provided regions
+ """
+ # Converting mpd gdf into a trajectory collection object
+ traj_collection = mpd.TrajectoryCollection(data_mpd_df, 'track.id')
+
+ regional_trajectories = []
+
+ # To extract trajectories running between regions
+ for traj in traj_collection.trajectories:
+ if traj.get_start_location().intersects(from_region):
+ if traj.get_end_location().intersects(to_region):
+ regional_trajectories.append(traj)
+ if twoway: #if two way is to be considered
+ if traj.get_start_location().intersects(to_region):
+ if traj.get_end_location().intersects(from_region):
+ regional_trajectories.append(traj)
+
+ if twoway:
+ print("Found {} trajectories moving between provided regions with following details:".format(len(regional_trajectories)))
+ else:
+ print("Found {} trajectories moving from 'from_region' to 'to_region' with following details:".format(len(regional_trajectories)))
+
+ lengths = []
+ durations = []
+
+ # To extract Stats related to Distance and Duration
+ for row in regional_trajectories:
+ lengths.append(round((row.get_length()/1000), 2))
+ durations.append(row.get_duration().total_seconds())
+
+ print("Average Distance: {} kms".format(round(mean(lengths),2)))
+ print("Maximum Distance: {} kms".format(max(lengths)))
+ print("Average Duration: {} ".format(str(datetime.timedelta(seconds = round(mean(durations),0)))))
+ print("Maximum Duration: {} ".format(str(datetime.timedelta(seconds = round(max(durations),0)))))
+
+ # List of Trajectories between regions
+ return regional_trajectories
+
+ def temporal_filter_weekday(self, mpd_df, filterday):
+ """ Applies temporal filter to the dataframe based on provided WEEKDAY
+
+ Keyword Arguments:
+ mpd_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ filterday {String} -- Provided day of the week
+
+ Returns:
+ result -- A Trajectory Collection Object with only trajectories from provided weekday
+ """
+ # Conversion of mpd geodataframe into Trajectory Collection Object of Moving Pandas
+ raw_collection = mpd.TrajectoryCollection(mpd_df, 'track.id', min_length=1)
+
+ # In case, a single trajectory span over two days, split trajectory into two
+ traj_collection = raw_collection.split_by_date('day')
+
+ days = { 0 : "Monday", 1 : "Tuesday", 2 : "Wednesday", 3 : "Thursday", 4 : "Friday", 5 : "Saturday", 6 : "Sunday" }
+
+ # Loop over all trajectories in Trajectory Collection Object
+ for traj in traj_collection.trajectories:
+ # Extract the total number of column in each trajectory's dataframe
+ numcolumns = len(traj.df.columns)
+
+ # Extracting track begin time in datetime object
+ temp_time = pd.to_datetime(traj.df['track.begin'], format='%Y-%m-%dT%H:%M:%SZ')
+
+ # Insertion of two new rows for Formatted Time and Day of the Week
+ traj.df.insert(numcolumns,'Trajectory Time',temp_time)
+ traj.df.insert(numcolumns+1,'Day of Week', 'a')
+
+ # Extracting the time of first row of trajectory df and assign Day of the week to the whole column
+ time_value = traj.df['Trajectory Time'][0]
+ traj.df['Day of Week'] = days[time_value.weekday()]
+
+ filterday_tracks = []
+ # Loop over first row of all trajectories df and select track.id satisfying DAY of the Week condition
+ for traj in traj_collection.trajectories:
+ if(traj.df['Day of Week'][0] == filterday):
+ filterday_tracks.append(traj.df['track.id'][0])
+
+ filtered = []
+ # Loop over list of filtered track.ids and trajectories collection. Filter trajectories with identified track.ids
+ for f_track in filterday_tracks:
+ for traj in traj_collection.trajectories:
+ if(traj.df['track.id'][0] == f_track):
+ filtered.append(traj)
+ break
+
+ # Creating a Trajectory Collection and assign filtered trajectories to it as result
+ result = copy(traj_collection)
+ result.trajectories = filtered
+
+ return result
+
+ def temporal_filter_hours(self, mpd_df, from_time, to_time):
+ """ Applies temporal filter to the dataframe based on provided HOURS duration
+
+ Keyword Arguments:
+ mpd_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ from_time {Integer} -- Starting Hour
+ end_time {Integer} -- Ending Hour
+
+ Returns:
+ result -- A Trajectory Collection Object with only trajectories from provided hours duration
+ """
+
+ filtered = []
+
+ # Conversion of mpd geodataframe into Trajectory Collection Object of Moving Pandas
+ raw_collection = mpd.TrajectoryCollection(mpd_df, 'track.id', min_length=1)
+
+ # In case, a single trajectory span over two days, split trajectory into two
+ traj_collection = raw_collection.split_by_date('day')
+
+ for traj in traj_collection.trajectories:
+ #Extracting data for each trajectory
+ mydate = traj.df['track.begin'][0][0:10]
+ #Converting given hour number to datetime string
+ from_time_string = mydate + ' ' + str(from_time) + ':00:00'
+ to_time_string = mydate + ' ' + str(to_time) + ':00:00'
+
+ # Filter part of trajectory based on provided hours duration
+ filt_segment = traj.df[from_time_string:to_time_string]
+
+ if(len(filt_segment)>0):
+ filtered.append(mpd.Trajectory(filt_segment,traj.df['track.id']))
+
+ # Creating a Trajectory Collection and assign filtered trajectories to it as result
+ result = copy(traj_collection)
+ result.trajectories = filtered
+
+ return result
+
+ def temporal_filter_date(self, mpd_df, filterdate):
+ """ Applies temporal filter to the dataframe based on provided DATE
+
+ Keyword Arguments:
+ mpd_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ filterdate {String} -- Date for Filter
+
+ Returns:
+ result -- A Trajectory Collection Object with only trajectories from provided DATE
+ """
+
+ # Conversion of mpd geodataframe into Trajectory Collection Object of Moving Pandas
+ raw_collection = mpd.TrajectoryCollection(mpd_df, 'track.id', min_length=1)
+
+ # In case, a single trajectory span over two days, split trajectory into two
+ traj_collection = raw_collection.split_by_date('day')
+
+ filterday_tracks = []
+ # Loop over first row of all trajectories df and select track.id satisfying DATE condition
+ for traj in traj_collection.trajectories:
+ if(traj.df['track.begin'][0][0:10] == filterdate):
+ filterday_tracks.append(traj.df['track.id'][0])
+
+ filtered = []
+ # Loop over list of filtered track.ids and trajectories collection. Filter trajectories with identified track.ids
+ for f_track in filterday_tracks:
+ for traj in traj_collection.trajectories:
+ if(traj.df['track.id'][0] == f_track):
+ filtered.append(traj)
+ break
+
+ # Creating a Trajectory Collection and assign filtered trajectories to it as result
+ result = copy(traj_collection)
+ result.trajectories = filtered
+
+ return result
+
+ def temporal_filter_weekday(self, mpd_df, filterday):
+ """ Applies temporal filter to the dataframe based on provided WEEKDAY
+
+ Keyword Arguments:
+ mpd_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ filterday {String} -- Provided day of the week
+
+ Returns:
+ result -- A Trajectory Collection Object with only trajectories from provided weekday
+ """
+ # Conversion of mpd geodataframe into Trajectory Collection Object of Moving Pandas
+ raw_collection = mpd.TrajectoryCollection(mpd_df, 'track.id', min_length=1)
+
+ # In case, a single trajectory span over two days, split trajectory into two
+ traj_collection = raw_collection.split_by_date('day')
+
+ days = { 0 : "Monday", 1 : "Tuesday", 2 : "Wednesday", 3 : "Thursday", 4 : "Friday", 5 : "Saturday", 6 : "Sunday" }
+
+ # Loop over all trajectories in Trajectory Collection Object
+ for traj in traj_collection.trajectories:
+ # Extract the total number of column in each trajectory's dataframe
+ numcolumns = len(traj.df.columns)
+
+ # Extracting track begin time in datetime object
+ temp_time = pd.to_datetime(traj.df['track.begin'], format='%Y-%m-%dT%H:%M:%SZ')
+
+ # Insertion of two new rows for Formatted Time and Day of the Week
+ traj.df.insert(numcolumns,'Trajectory Time',temp_time)
+ traj.df.insert(numcolumns+1,'Day of Week', 'a')
+
+ # Extracting the time of first row of trajectory df and assign Day of the week to the whole column
+ time_value = traj.df['Trajectory Time'][0]
+ traj.df['Day of Week'] = days[time_value.weekday()]
+
+ filterday_tracks = []
+ # Loop over first row of all trajectories df and select track.id satisfying DAY of the Week condition
+ for traj in traj_collection.trajectories:
+ if(traj.df['Day of Week'][0] == filterday):
+ filterday_tracks.append(traj.df['track.id'][0])
+
+ filtered = []
+ # Loop over list of filtered track.ids and trajectories collection. Filter trajectories with identified track.ids
+ for f_track in filterday_tracks:
+ for traj in traj_collection.trajectories:
+ if(traj.df['track.id'][0] == f_track):
+ filtered.append(traj)
+ break
+
+ # Creating a Trajectory Collection and assign filtered trajectories to it as result
+ result = copy(traj_collection)
+ result.trajectories = filtered
+
+ return result
+
+ def temporal_filter_hours(self, mpd_df, from_time, to_time):
+ """ Applies temporal filter to the dataframe based on provided HOURS duration
+
+ Keyword Arguments:
+ mpd_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ from_time {Integer} -- Starting Hour
+ end_time {Integer} -- Ending Hour
+
+ Returns:
+ result -- A Trajectory Collection Object with only trajectories from provided hours duration
+ """
+
+ filtered = []
+
+ # Conversion of mpd geodataframe into Trajectory Collection Object of Moving Pandas
+ raw_collection = mpd.TrajectoryCollection(mpd_df, 'track.id', min_length=1)
+
+ # In case, a single trajectory span over two days, split trajectory into two
+ traj_collection = raw_collection.split_by_date('day')
+
+ for traj in traj_collection.trajectories:
+ #Extracting data for each trajectory
+ mydate = traj.df['track.begin'][0][0:10]
+ #Converting given hour number to datetime string
+ from_time_string = mydate + ' ' + str(from_time) + ':00:00'
+ to_time_string = mydate + ' ' + str(to_time) + ':00:00'
+
+ # Filter part of trajectory based on provided hours duration
+ filt_segment = traj.df[from_time_string:to_time_string]
+
+ if(len(filt_segment)>0):
+ filtered.append(mpd.Trajectory(filt_segment,traj.df['track.id']))
+
+ # Creating a Trajectory Collection and assign filtered trajectories to it as result
+ result = copy(traj_collection)
+ result.trajectories = filtered
+
+ return result
+
+ def temporal_filter_date(self, mpd_df, filterdate):
+ """ Applies temporal filter to the dataframe based on provided DATE
+
+ Keyword Arguments:
+ mpd_df {GeoDataFrame} -- A Moving Pandas GeoDataFrame containing the track points
+ filterdate {String} -- Date for Filter
+
+ Returns:
+ result -- A Trajectory Collection Object with only trajectories from provided DATE
+ """
+
+ # Conversion of mpd geodataframe into Trajectory Collection Object of Moving Pandas
+ raw_collection = mpd.TrajectoryCollection(mpd_df, 'track.id', min_length=1)
+
+ # In case, a single trajectory span over two days, split trajectory into two
+ traj_collection = raw_collection.split_by_date('day')
+
+ filterday_tracks = []
+ # Loop over first row of all trajectories df and select track.id satisfying DATE condition
+ for traj in traj_collection.trajectories:
+ if(traj.df['track.begin'][0][0:10] == filterdate):
+ filterday_tracks.append(traj.df['track.id'][0])
+
+ filtered = []
+ # Loop over list of filtered track.ids and trajectories collection. Filter trajectories with identified track.ids
+ for f_track in filterday_tracks:
+ for traj in traj_collection.trajectories:
+ if(traj.df['track.id'][0] == f_track):
+ filtered.append(traj)
+ break
+
+ # Creating a Trajectory Collection and assign filtered trajectories to it as result
+ result = copy(traj_collection)
+ result.trajectories = filtered
+
+ return result
+
+ def cluster(self, points_mp):
+ # TODO clustering of points here
+ return 'Clustering function was called. Substitute this string with clustering result'
+
+ def generalize(self, traj, tolerance, generalizationMode):
+ """ Generalize the trajectory/trajectory collection
+
+ Supported generalization modes include:
+
+ - ‘douglas-peucker’ (tolerance as float in CRS units or meters if CRS is geographic, e.g. EPSG:4326 WGS84)
+ - ‘min-time-delta’ (tolerance as datetime.timedelta)
+ - ‘min-distance’ (tolerance as float in CRS units or meters if CRS is geographic, e.g. EPSG:4326 WGS84)
+
+ Returns:
+ moving pandas trajectory/trajectory collection
+ """
+
+ return traj.generalize(generalizationMode, tolerance)
+
+ def generalize_v04(self, traj, tolerance, generalizationType):
+ """ Generalizes the moving pandas trajectory or trajectory collection.
+ Note: This function will only work with movingpandas v0.4-rc1 and above. See https://github.com/anitagraser/movingpandas/issues/73
+
+ Keyword Arguments:
+ traj -- movingpandas trajectory/trajectory collection
+ tolerance -- tolerance from 0 to 1. Specify minutes incase of MinTimDelta generalization type
+ generalizationType -- type of generalization e.g. Douglas Peucker, Min Distance, or Min Time Delta
+
+ Returns:
+ moving pandas trajectory or trajectory collection
+ """
+
+ if (not isinstance(generalizationType, GeneralizationType)):
+ raise ValueError("Invalid generalization type " + str(generalizationType))
+
+ if generalizationType.value == GeneralizationType.DouglasPeucker.value:
+ return mpd.DouglasPeuckerGeneralizer(traj).generalize(tolerance=tolerance)
+ elif generalizationType.value == GeneralizationType.MinDistance.value:
+ return mpd.MinDistanceGeneralizer(traj).generalize(tolerance=tolerance)
+ elif generalizationType.value == GeneralizationType.MinTimeDelta.value:
+ return mpd.MinTimeDeltaGeneralizer(traj).generalize(tolerance=timedelta(minutes=tolerance))
+
diff --git a/envirocar/trajectories/track_converter.py b/envirocar/trajectories/track_converter.py
new file mode 100644
index 0000000..bf9092d
--- /dev/null
+++ b/envirocar/trajectories/track_converter.py
@@ -0,0 +1,38 @@
+import pandas as pd
+import geopandas as gpd
+
+class TrackConverter():
+
+ """Handles the envirocar Tracks"""
+
+ def __init__(self):
+ print("Initializing TrackConverter class")
+ # self.track = track
+ # self.crs = track.crs
+
+ """ Returns a geoDataFrame object with the movingpandas plain format"""
+
+ def to_movingpandas(self, track):
+
+ # gdf = self.track.copy()
+ gdf = track
+ gdf = gdf.reindex(columns=(['geometry'] + list([a for a in sorted(gdf.columns) if a != 'geometry'])),copy=True)
+ gdf['time']= gdf['time'].astype('datetime64[ns]')
+ gdf.set_index('time',inplace=True)
+ gdf.index.rename('t',inplace=True)
+ return (gdf)
+
+ """ Returns a dataFrame object with the scikitmobility plain format"""
+
+ def to_scikitmobility(self):
+ gdf = self.track.copy()
+ gdf['lat'] = gdf.geometry.x
+ gdf['lng'] = gdf.geometry.y
+ gdf.rename(columns = ({"time": "datetime",'sensor.id':'uid','track.id':'tid'}),inplace=True)
+ gdf['datetime'] = gdf['datetime'].astype('datetime64[ns]')
+ gdf['tid'] = gdf['tid'].astype(str)
+ gdf['uid'] = gdf['uid'].astype(str)
+ columns=['uid','tid','lat','lng','datetime']
+ gdf = gdf.reindex(columns = (columns + list([a for a in sorted(gdf.columns) if a not in columns])),copy = True)
+ df = pd.DataFrame(gdf)
+ return(df)
diff --git a/envirocar/trajectories/track_similarity.py b/envirocar/trajectories/track_similarity.py
new file mode 100644
index 0000000..b8acedd
--- /dev/null
+++ b/envirocar/trajectories/track_similarity.py
@@ -0,0 +1,52 @@
+import numpy as np
+import similaritymeasures
+import matplotlib.pyplot as plt
+from envirocar import Trajectory
+
+class TrackSimilarity():
+
+ def __init__(self):
+ print("Initializing TrackSimilarity class")
+
+ def similarity(method,trajectoryA,trajectoryB):
+
+ """ Compute similarity measures using the similaritymeasures
+ https://pypi.org/project/similaritymeasures/
+
+ Keyword Arguments:
+ method {string} -- Name of the method to compute similarity
+ pcm: Partial Curve Mapping
+ frechet_dist: Discrete Frechet distance
+ area_between_two_curves: Area method
+ curve_length_measure: Curve Length
+ dtw: Dynamic Time Warping
+
+ trajectoryA {envirocar trajectory} -- Envirocar trajectory
+ trajectoryB {envirocar trajectory} -- Envirocar trajectory
+
+ Returns:
+ similarity -- Float value (0,1) corresponding to the computed similarity. Values close to 1 correspond to high similarity
+ dtw_matrix (optional) -- Only for the Dynamic Time Warping the method returns the calculation matrix.
+ """
+
+ print("Similarity between Track",trajectoryA.id, "& Track",trajectoryB.id,"using",str(method),"method:")
+
+ methods=['pcm','frechet_dist','area_between_two_curves','curve_length_measure','dtw']
+
+ trajA_np=trajectoryA.get_coordinates()
+ trajB_np=trajectoryB.get_coordinates()
+
+ if(method not in methods):
+ raise RuntimeError(
+ 'Method not available')
+ else:
+ similarity_method=getattr(similaritymeasures,method)
+
+ if(method =='dtw'):
+ similarity,dtw_matrix = 1/(1+similarity_method(trajA_np,trajB_np))
+ print(similarity)
+ return similarity,dtw_matrix
+ else:
+ similarity = 1/(1+similarity_method(trajA_np,trajB_np))
+ print(similarity)
+ return similarity
\ No newline at end of file
diff --git a/examples/api_request_deckgl.ipynb b/examples/api_request_deckgl.ipynb
index 504d207..dc85ae5 100644
--- a/examples/api_request_deckgl.ipynb
+++ b/examples/api_request_deckgl.ipynb
@@ -20,7 +20,7 @@
"import pandas as pd\n",
"import geopandas as gpd\n",
"\n",
- "from envirocar import TrackAPI, DownloadClient, BboxSelector, ECConfig\n",
+ "from envirocar import TrackAPI, DownloadClient, BboxSelector, ECConfig, TrackConverter\n",
"\n",
"# create an initial but optional config and an api client\n",
"config = ECConfig()\n",
@@ -70,16 +70,16 @@
"
id
\n",
"
time
\n",
"
geometry
\n",
- "
GPS PDOP.value
\n",
- "
GPS PDOP.unit
\n",
- "
Speed.value
\n",
- "
Speed.unit
\n",
- "
GPS Altitude.value
\n",
- "
GPS Altitude.unit
\n",
+ "
Throttle Position.value
\n",
+ "
Throttle Position.unit
\n",
+ "
CO2.value
\n",
+ "
CO2.unit
\n",
+ "
GPS VDOP.value
\n",
+ "
GPS VDOP.unit
\n",
"
GPS Bearing.value
\n",
"
...
\n",
- "
Consumption.value
\n",
- "
Consumption.unit
\n",
+ "
sensor.constructionYear
\n",
+ "
sensor.manufacturer
\n",
"
track.appVersion
\n",
"
track.touVersion
\n",
"
O2 Lambda Voltage ER.value
\n",
@@ -93,19 +93,19 @@
" \n",
"
\n",
"
0
\n",
- "
5e8b930965b80c5d6b4d7cd1
\n",
- "
2020-03-07T12:33:15
\n",
- "
POINT (7.64069 51.95733)
\n",
- "
1.090631
\n",
+ "
5eb7582165b80c5d6be69f24
\n",
+ "
2020-05-09T21:10:46
\n",
+ "
POINT (7.65180 51.95396)
\n",
+ "
16.000000
\n",
+ "
%
\n",
+ "
6.060965
\n",
+ "
kg/h
\n",
+ "
1.000000
\n",
"
precision
\n",
- "
28.999999
\n",
- "
km/h
\n",
- "
110.381939
\n",
- "
m
\n",
- "
124.858622
\n",
+ "
244.006986
\n",
"
...
\n",
- "
NaN
\n",
- "
NaN
\n",
+ "
2007
\n",
+ "
Dodge
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -117,19 +117,19 @@
"
\n",
"
\n",
"
1
\n",
- "
5e8b930965b80c5d6b4d7cd3
\n",
- "
2020-03-07T12:33:20
\n",
- "
POINT (7.64118 51.95712)
\n",
+ "
5eb7582165b80c5d6be69f26
\n",
+ "
2020-05-09T21:10:51
\n",
+ "
POINT (7.65169 51.95395)
\n",
+ "
16.831018
\n",
+ "
%
\n",
+ "
7.644530
\n",
+ "
kg/h
\n",
"
1.000000
\n",
"
precision
\n",
- "
28.000000
\n",
- "
km/h
\n",
- "
108.260375
\n",
- "
m
\n",
- "
125.020801
\n",
+ "
273.231882
\n",
"
...
\n",
- "
NaN
\n",
- "
NaN
\n",
+ "
2007
\n",
+ "
Dodge
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -141,19 +141,19 @@
"
\n",
"
\n",
"
2
\n",
- "
5e8b930965b80c5d6b4d7cd4
\n",
- "
2020-03-07T12:33:26
\n",
- "
POINT (7.64162 51.95690)
\n",
- "
1.257198
\n",
+ "
5eb7582165b80c5d6be69f27
\n",
+ "
2020-05-09T21:10:56
\n",
+ "
POINT (7.65148 51.95395)
\n",
+ "
16.846021
\n",
+ "
%
\n",
+ "
6.152178
\n",
+ "
kg/h
\n",
+ "
1.000000
\n",
"
precision
\n",
- "
28.000001
\n",
- "
km/h
\n",
- "
105.826028
\n",
- "
m
\n",
- "
121.203960
\n",
+ "
273.377388
\n",
"
...
\n",
- "
NaN
\n",
- "
NaN
\n",
+ "
2007
\n",
+ "
Dodge
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -165,19 +165,19 @@
"
\n",
"
\n",
"
3
\n",
- "
5e8b930965b80c5d6b4d7cd5
\n",
- "
2020-03-07T12:33:31
\n",
- "
POINT (7.64210 51.95672)
\n",
- "
1.000000
\n",
+ "
5eb7582165b80c5d6be69f28
\n",
+ "
2020-05-09T21:11:01
\n",
+ "
POINT (7.65127 51.95397)
\n",
+ "
17.000001
\n",
+ "
%
\n",
+ "
7.380207
\n",
+ "
kg/h
\n",
+ "
0.930191
\n",
"
precision
\n",
- "
30.000000
\n",
- "
km/h
\n",
- "
104.395998
\n",
- "
m
\n",
- "
123.412759
\n",
+ "
274.705621
\n",
"
...
\n",
- "
NaN
\n",
- "
NaN
\n",
+ "
2007
\n",
+ "
Dodge
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -189,19 +189,19 @@
"
\n",
"
\n",
"
4
\n",
- "
5e8b930965b80c5d6b4d7cd6
\n",
- "
2020-03-07T12:33:36
\n",
- "
POINT (7.64264 51.95650)
\n",
- "
1.026727
\n",
+ "
5eb7582165b80c5d6be69f29
\n",
+ "
2020-05-09T21:11:06
\n",
+ "
POINT (7.65101 51.95396)
\n",
+ "
15.151858
\n",
+ "
%
\n",
+ "
3.983817
\n",
+ "
kg/h
\n",
+ "
1.000000
\n",
"
precision
\n",
- "
31.409419
\n",
- "
km/h
\n",
- "
101.516865
\n",
- "
m
\n",
- "
122.170479
\n",
+ "
275.181028
\n",
"
...
\n",
- "
NaN
\n",
- "
NaN
\n",
+ "
2007
\n",
+ "
Dodge
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -236,20 +236,20 @@
"
...
\n",
"
\n",
"
\n",
- "
283
\n",
- "
5dc986e844ea856b702e3e0b
\n",
- "
2019-10-28T16:34:55
\n",
- "
POINT (7.59523 51.96505)
\n",
- "
1.700000
\n",
+ "
195
\n",
+ "
5dc985eb44ea856b702dd986
\n",
+ "
2019-10-29T16:10:53
\n",
+ "
POINT (7.59827 51.96493)
\n",
+ "
16.027855
\n",
+ "
%
\n",
+ "
8.579484
\n",
+ "
kg/h
\n",
+ "
1.016152
\n",
"
precision
\n",
- "
47.999999
\n",
- "
km/h
\n",
- "
109.652212
\n",
- "
m
\n",
- "
276.419653
\n",
+ "
269.728716
\n",
"
...
\n",
- "
3.122268
\n",
- "
l/h
\n",
+ "
2004
\n",
+ "
Mercedes Benz
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -260,20 +260,20 @@
"
NaN
\n",
"
\n",
"
\n",
- "
284
\n",
- "
5dc986e844ea856b702e3e0c
\n",
- "
2019-10-28T16:35:00
\n",
- "
POINT (7.59425 51.96512)
\n",
- "
1.497088
\n",
+ "
196
\n",
+ "
5dc985eb44ea856b702dd987
\n",
+ "
2019-10-29T16:10:58
\n",
+ "
POINT (7.59737 51.96492)
\n",
+ "
14.000000
\n",
+ "
%
\n",
+ "
4.308154
\n",
+ "
kg/h
\n",
+ "
1.015842
\n",
"
precision
\n",
- "
48.297297
\n",
- "
km/h
\n",
- "
110.122771
\n",
- "
m
\n",
- "
276.271049
\n",
+ "
268.303093
\n",
"
...
\n",
- "
2.853618
\n",
- "
l/h
\n",
+ "
2004
\n",
+ "
Mercedes Benz
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -284,20 +284,20 @@
"
NaN
\n",
"
\n",
"
\n",
- "
285
\n",
- "
5dc986e844ea856b702e3e0d
\n",
- "
2019-10-28T16:35:05
\n",
- "
POINT (7.59327 51.96518)
\n",
- "
1.688911
\n",
+ "
197
\n",
+ "
5dc985eb44ea856b702dd988
\n",
+ "
2019-10-29T16:11:03
\n",
+ "
POINT (7.59646 51.96492)
\n",
+ "
32.000001
\n",
+ "
%
\n",
+ "
18.595897
\n",
+ "
kg/h
\n",
+ "
1.100000
\n",
"
precision
\n",
- "
49.000001
\n",
- "
km/h
\n",
- "
110.573987
\n",
- "
m
\n",
- "
275.808021
\n",
+ "
273.328479
\n",
"
...
\n",
- "
4.657916
\n",
- "
l/h
\n",
+ "
2004
\n",
+ "
Mercedes Benz
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -308,20 +308,20 @@
"
NaN
\n",
"
\n",
"
\n",
- "
286
\n",
- "
5dc986e844ea856b702e3e0e
\n",
- "
2019-10-28T16:35:10
\n",
- "
POINT (7.59225 51.96525)
\n",
- "
1.300000
\n",
+ "
198
\n",
+ "
5dc985eb44ea856b702dd989
\n",
+ "
2019-10-29T16:11:08
\n",
+ "
POINT (7.59541 51.96499)
\n",
+ "
16.000000
\n",
+ "
%
\n",
+ "
7.105633
\n",
+ "
kg/h
\n",
+ "
1.267463
\n",
"
precision
\n",
- "
51.000000
\n",
- "
km/h
\n",
- "
111.140661
\n",
- "
m
\n",
- "
275.411387
\n",
+ "
276.193063
\n",
"
...
\n",
- "
3.445271
\n",
- "
l/h
\n",
+ "
2004
\n",
+ "
Mercedes Benz
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -332,20 +332,20 @@
"
NaN
\n",
"
\n",
"
\n",
- "
287
\n",
- "
5dc986e844ea856b702e3e0f
\n",
- "
2019-10-28T16:35:15
\n",
- "
POINT (7.59123 51.96531)
\n",
- "
1.423253
\n",
+ "
199
\n",
+ "
5dc985eb44ea856b702dd98a
\n",
+ "
2019-10-29T16:11:13
\n",
+ "
POINT (7.59433 51.96506)
\n",
+ "
16.910762
\n",
+ "
%
\n",
+ "
7.694858
\n",
+ "
kg/h
\n",
+ "
0.938076
\n",
"
precision
\n",
- "
50.000001
\n",
- "
km/h
\n",
- "
111.891658
\n",
- "
m
\n",
- "
276.124438
\n",
+ "
276.065524
\n",
"
...
\n",
- "
3.248333
\n",
- "
l/h
\n",
+ "
2004
\n",
+ "
Mercedes Benz
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
@@ -357,89 +357,89 @@
"
\n",
" \n",
"\n",
- "
9944 rows × 54 columns
\n",
+ "
9670 rows × 54 columns
\n",
""
],
"text/plain": [
" id time geometry \\\n",
- "0 5e8b930965b80c5d6b4d7cd1 2020-03-07T12:33:15 POINT (7.64069 51.95733) \n",
- "1 5e8b930965b80c5d6b4d7cd3 2020-03-07T12:33:20 POINT (7.64118 51.95712) \n",
- "2 5e8b930965b80c5d6b4d7cd4 2020-03-07T12:33:26 POINT (7.64162 51.95690) \n",
- "3 5e8b930965b80c5d6b4d7cd5 2020-03-07T12:33:31 POINT (7.64210 51.95672) \n",
- "4 5e8b930965b80c5d6b4d7cd6 2020-03-07T12:33:36 POINT (7.64264 51.95650) \n",
+ "0 5eb7582165b80c5d6be69f24 2020-05-09T21:10:46 POINT (7.65180 51.95396) \n",
+ "1 5eb7582165b80c5d6be69f26 2020-05-09T21:10:51 POINT (7.65169 51.95395) \n",
+ "2 5eb7582165b80c5d6be69f27 2020-05-09T21:10:56 POINT (7.65148 51.95395) \n",
+ "3 5eb7582165b80c5d6be69f28 2020-05-09T21:11:01 POINT (7.65127 51.95397) \n",
+ "4 5eb7582165b80c5d6be69f29 2020-05-09T21:11:06 POINT (7.65101 51.95396) \n",
".. ... ... ... \n",
- "283 5dc986e844ea856b702e3e0b 2019-10-28T16:34:55 POINT (7.59523 51.96505) \n",
- "284 5dc986e844ea856b702e3e0c 2019-10-28T16:35:00 POINT (7.59425 51.96512) \n",
- "285 5dc986e844ea856b702e3e0d 2019-10-28T16:35:05 POINT (7.59327 51.96518) \n",
- "286 5dc986e844ea856b702e3e0e 2019-10-28T16:35:10 POINT (7.59225 51.96525) \n",
- "287 5dc986e844ea856b702e3e0f 2019-10-28T16:35:15 POINT (7.59123 51.96531) \n",
+ "195 5dc985eb44ea856b702dd986 2019-10-29T16:10:53 POINT (7.59827 51.96493) \n",
+ "196 5dc985eb44ea856b702dd987 2019-10-29T16:10:58 POINT (7.59737 51.96492) \n",
+ "197 5dc985eb44ea856b702dd988 2019-10-29T16:11:03 POINT (7.59646 51.96492) \n",
+ "198 5dc985eb44ea856b702dd989 2019-10-29T16:11:08 POINT (7.59541 51.96499) \n",
+ "199 5dc985eb44ea856b702dd98a 2019-10-29T16:11:13 POINT (7.59433 51.96506) \n",
"\n",
- " GPS PDOP.value GPS PDOP.unit Speed.value Speed.unit GPS Altitude.value \\\n",
- "0 1.090631 precision 28.999999 km/h 110.381939 \n",
- "1 1.000000 precision 28.000000 km/h 108.260375 \n",
- "2 1.257198 precision 28.000001 km/h 105.826028 \n",
- "3 1.000000 precision 30.000000 km/h 104.395998 \n",
- "4 1.026727 precision 31.409419 km/h 101.516865 \n",
- ".. ... ... ... ... ... \n",
- "283 1.700000 precision 47.999999 km/h 109.652212 \n",
- "284 1.497088 precision 48.297297 km/h 110.122771 \n",
- "285 1.688911 precision 49.000001 km/h 110.573987 \n",
- "286 1.300000 precision 51.000000 km/h 111.140661 \n",
- "287 1.423253 precision 50.000001 km/h 111.891658 \n",
+ " Throttle Position.value Throttle Position.unit CO2.value CO2.unit \\\n",
+ "0 16.000000 % 6.060965 kg/h \n",
+ "1 16.831018 % 7.644530 kg/h \n",
+ "2 16.846021 % 6.152178 kg/h \n",
+ "3 17.000001 % 7.380207 kg/h \n",
+ "4 15.151858 % 3.983817 kg/h \n",
+ ".. ... ... ... ... \n",
+ "195 16.027855 % 8.579484 kg/h \n",
+ "196 14.000000 % 4.308154 kg/h \n",
+ "197 32.000001 % 18.595897 kg/h \n",
+ "198 16.000000 % 7.105633 kg/h \n",
+ "199 16.910762 % 7.694858 kg/h \n",
"\n",
- " GPS Altitude.unit GPS Bearing.value ... Consumption.value \\\n",
- "0 m 124.858622 ... NaN \n",
- "1 m 125.020801 ... NaN \n",
- "2 m 121.203960 ... NaN \n",
- "3 m 123.412759 ... NaN \n",
- "4 m 122.170479 ... NaN \n",
- ".. ... ... ... ... \n",
- "283 m 276.419653 ... 3.122268 \n",
- "284 m 276.271049 ... 2.853618 \n",
- "285 m 275.808021 ... 4.657916 \n",
- "286 m 275.411387 ... 3.445271 \n",
- "287 m 276.124438 ... 3.248333 \n",
+ " GPS VDOP.value GPS VDOP.unit GPS Bearing.value ... \\\n",
+ "0 1.000000 precision 244.006986 ... \n",
+ "1 1.000000 precision 273.231882 ... \n",
+ "2 1.000000 precision 273.377388 ... \n",
+ "3 0.930191 precision 274.705621 ... \n",
+ "4 1.000000 precision 275.181028 ... \n",
+ ".. ... ... ... ... \n",
+ "195 1.016152 precision 269.728716 ... \n",
+ "196 1.015842 precision 268.303093 ... \n",
+ "197 1.100000 precision 273.328479 ... \n",
+ "198 1.267463 precision 276.193063 ... \n",
+ "199 0.938076 precision 276.065524 ... \n",
"\n",
- " Consumption.unit track.appVersion track.touVersion \\\n",
- "0 NaN NaN NaN \n",
- "1 NaN NaN NaN \n",
- "2 NaN NaN NaN \n",
- "3 NaN NaN NaN \n",
- "4 NaN NaN NaN \n",
- ".. ... ... ... \n",
- "283 l/h NaN NaN \n",
- "284 l/h NaN NaN \n",
- "285 l/h NaN NaN \n",
- "286 l/h NaN NaN \n",
- "287 l/h NaN NaN \n",
+ " sensor.constructionYear sensor.manufacturer track.appVersion \\\n",
+ "0 2007 Dodge NaN \n",
+ "1 2007 Dodge NaN \n",
+ "2 2007 Dodge NaN \n",
+ "3 2007 Dodge NaN \n",
+ "4 2007 Dodge NaN \n",
+ ".. ... ... ... \n",
+ "195 2004 Mercedes Benz NaN \n",
+ "196 2004 Mercedes Benz NaN \n",
+ "197 2004 Mercedes Benz NaN \n",
+ "198 2004 Mercedes Benz NaN \n",
+ "199 2004 Mercedes Benz NaN \n",
"\n",
- " O2 Lambda Voltage ER.value O2 Lambda Voltage ER.unit MAF.value MAF.unit \\\n",
- "0 NaN NaN NaN NaN \n",
- "1 NaN NaN NaN NaN \n",
- "2 NaN NaN NaN NaN \n",
- "3 NaN NaN NaN NaN \n",
- "4 NaN NaN NaN NaN \n",
- ".. ... ... ... ... \n",
- "283 NaN NaN NaN NaN \n",
- "284 NaN NaN NaN NaN \n",
- "285 NaN NaN NaN NaN \n",
- "286 NaN NaN NaN NaN \n",
- "287 NaN NaN NaN NaN \n",
+ " track.touVersion O2 Lambda Voltage ER.value O2 Lambda Voltage ER.unit \\\n",
+ "0 NaN NaN NaN \n",
+ "1 NaN NaN NaN \n",
+ "2 NaN NaN NaN \n",
+ "3 NaN NaN NaN \n",
+ "4 NaN NaN NaN \n",
+ ".. ... ... ... \n",
+ "195 NaN NaN NaN \n",
+ "196 NaN NaN NaN \n",
+ "197 NaN NaN NaN \n",
+ "198 NaN NaN NaN \n",
+ "199 NaN NaN NaN \n",
"\n",
- " O2 Lambda Voltage.value O2 Lambda Voltage.unit \n",
- "0 NaN NaN \n",
- "1 NaN NaN \n",
- "2 NaN NaN \n",
- "3 NaN NaN \n",
- "4 NaN NaN \n",
- ".. ... ... \n",
- "283 NaN NaN \n",
- "284 NaN NaN \n",
- "285 NaN NaN \n",
- "286 NaN NaN \n",
- "287 NaN NaN \n",
+ " MAF.value MAF.unit O2 Lambda Voltage.value O2 Lambda Voltage.unit \n",
+ "0 NaN NaN NaN NaN \n",
+ "1 NaN NaN NaN NaN \n",
+ "2 NaN NaN NaN NaN \n",
+ "3 NaN NaN NaN NaN \n",
+ "4 NaN NaN NaN NaN \n",
+ ".. ... ... ... ... \n",
+ "195 NaN NaN NaN NaN \n",
+ "196 NaN NaN NaN NaN \n",
+ "197 NaN NaN NaN NaN \n",
+ "198 NaN NaN NaN NaN \n",
+ "199 NaN NaN NaN NaN \n",
"\n",
- "[9944 rows x 54 columns]"
+ "[9670 rows x 54 columns]"
]
},
"execution_count": 2,
@@ -468,7 +468,7 @@
{
"data": {
"text/plain": [
- ""
+ ""
]
},
"execution_count": 3,
@@ -477,7 +477,7 @@
},
{
"data": {
- "image/png": "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\n",
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAP0AAAI/CAYAAAC8it9qAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjMsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+AADFEAAAgAElEQVR4nO3dcZgV9X3v8fd3WUCzhasY4CqGYpCaNgGXdK/i5bkW22etaGLWGGMSSGxyK9d7k7aExkco3KgRKtYU6X3yPKaS5knyiA01yklblLBPW26fUDFdssiaaIMYJC6WxUssFA248L1/7Fm7LuecmTnnzJk5M5/X8+zDOXNmZn+j+zkz8/v95vczd0dE8qMl6QKISGMp9CI5o9CL5IxCL5IzCr1Izij0IjnTmnQBonjnO9/pM2bMSLoYIqm3a9euV919cqnPmir0M2bMoKenJ+liiKSemb1U7jNd3ovkjEIvkjMKvUjOKPQiOaPQi+SMQi+SMwq9SM4o9CI5o9CL5IxCL5IzCr1Izij0Ijmj0IvkjEIvkjMKvUjOKPQiOaPQi+SMQi+SMwq9SM4o9CI5o9CL5ExTjYabJZev6ebQsZNvvZ86YRxPr+xMsESSFzrTJ2B04AEOHTvJjOVbuGTVkxR6+xMqmeSBQp+A0YEf6cTgaZZu2q3gS2wU+pRatml30kWQjFLoU+p00gWQzFLoU+ziFVuSLoJkkEKfgLPGWKj1Bl3Bl/oLFXoz229mfWa228x6isvuMbM9xWXbzOyCMtueKq6z28z+esTyi8zsaTPba2abzGxcfQ4p/Z5fc22k4L9n5RMxl0jyxNw9eCWz/UCHu786YtlEdz9afP37wK+5+20ltv13d/+lEsv/Cnjc3b9tZl8FnnH3ByuVo6Ojw7M2a+2cO7dy9MSpwPXOGmM8v+baBpRIssDMdrl7R6nPqr68Hw58URsQ/O3xHwUy4DeB7xQXfRPoqrYszWzP3deEWu8Xp1xnfKmLsKF3YJuZ7TKzJcMLzWyNmf0MWAR8scy2Z5lZj5ntNLPhYJ8HvObug8X3LwPTqih/Juxfex2tIa72f3HKmXPn1vgLJJkWNvTz3f39wELgs2Z2JYC7r3T3dwEbgc+V2XZ68TLjE8B6M5sJlPoTL3mlYGZLil8aPYcPHw5Z3Obzwr3hgn/0xCkFX2oSKvTufrD47wCwGbhs1CqPADcGbPsisB2YC7wKnGNmw33/LwQOltn+IXfvcPeOyZMnhylu03rh3utCVfAdPXFKl/pStcDQm1mbmU0Yfg1cDTxrZrNGrHY98HyJbc81s/HF1+8E5gM/9qHaw38APlJc9Rbgu7UcSFaErdn/xSlXc55UJcyZfirwfTN7BvgBsMXdtwJrzexZM9vD0BfBHwCYWYeZfa247a8CPcVt/wFY6+4/Ln52B7DMzF5g6B7/L+p2VE0ubPDVji/VCNVklxZZbLKr5KLlW0I1iRjw07XXxV0caSKxNNlJ/B64uT3Ues7Q47oiYSj0Kdbz0pHQ61Z6XFdkJI2ckyKF3n7ueGwPJwb1jJ3ER6FPgUJvP1949BkGTzdP/Yo0L4U+YYs2PMWOfeEv48uZOiE3zytJjRT6hNTz7K5BNSUKhT4B9Tq7L543ndVds+tQIskThb7BSo2EG5XCLrVQ6Buk0NvP5zftDv/88SgKutSLQt8A1V7OK+gSB4U+ZtVczs+fOYmNt14RU4kk7xT6GL1n5RP84lT4C/oWYN3N7XTNze14ItIACn1MLl6xhcEIN/A6u0ujKPR1tqrQx8M7D0TaZr3O7tJACn0dda7bzt6B46HXV6caSYKesquTqIGfP3OSAi+J0Jm+DqLW0OtyXpKk0NcoSoVdqw0NfimSJF3e12DG8vCBP2uMKfCSCgp9lS5aHn5AyqkTxmlKKkkNhb4Kneu2h+5DP2tKmyrsJFUU+iqEraWfNaWN7mUL4i2MSEQKfURhR51dPG+6Ai+ppNBHFLZp7uGdB3jvF7dS6O2PuUQi0Sj0MTp+8hRLN+2mc932pIsi8haFvgH2DhzXTLOSGgp9gxw9cYpFG55Kuhgi6pHXSNWMnhPlqb3xrS3cd+McdfGVijSBZRVmROiYU0nbuDGsuWE2PS8dYePOA1WPnxeVAYs0FFemVZrAUqGvk2qeo0+T4S8gXSVkg0LfQIXefpZu2p10MWqi24Tmp6mqGygLQTkxeJqlm3bz7hVb1M8ggxT6GCyeNz3pItTFaYelm3ar1SFjFPoYrO6anakJJXfsO6J+Bhmie/oY1WvOOoAWg09cfmaNeyMrEDUISPNQRV6KFHr7ueuvf8Rrb7x5xmflgl0v9fgSOmuMaWyAJqDQS0nVXiUY8IDG+Us1hV4CVdPUqAk60ktNdhKoa+409q+9jvkzJ4XeZse+I2rWa0IKvbzNxluvYP3N7aHXV7Ne81Ho5Qxdc6dFCj4MnfU1bkBzUOilpK650yJ3Mto7cFzBbwIKvZS1umt25DP+3oHjoccRlGQo9FLRcAXfrCltobc5dOykevClmEIvoXQvWxDprH/0xCnV7KeUQi+hRW3WG67ZX1Xoi7lkEoVCL5FtvPWKSJV8D+88oCa9FFHopSpRK/nUpJceCr1Ubfhyf+L4MaHWV5NeOij0UrM9d18TevwABT95Cr3UxdMrO0M36+0dOK57/AQp9FI33csWhK7Z37HviJrzEqLQS11FeWDn9kebe9TgZqXQS92FfWDnzdMNKIycQaGXWIR9YEeVeo2nueykZoXefu7+mx/x89fPHPcvyN6B4zGUSCpR6KUqtQRdkqXQSyTNPmefhAy9me0HjgGngEF37zCze4APAaeBAeB33P3gqO3agQeBicVt17j7puJn3wB+A/i34uq/4+6qzk2pQm8/X3j0GQZPN89AqlJalDP9Ve7+6oj397v7/wYws98HvgjcNmqb14FPufteM7sA2GVm33P314qf3+7u36m28NIYneu2x3bv3dpisexXyqv68t7dj4542wZnTq/u7j8Z8fqgmQ0Ak4HXRq8r6TTnzq0cPXEqtv1/+aZLY9u3lBa2yc6BbWa2y8yWDC80szVm9jNgEUNn+rLM7DJgHLBvxOI1ZrbHzB4ws/ERyy4xKvT2c9HyLbEF/uyxLazXhBmJCDXZhZldUDxTTwG6gd9z938c8fkK4Cx3v7PM9ucD24Fb3H3niGX/ytAXwUPAPnf/UoltlwBLAKZPn/7rL730UrQjlMhquZw/e2wL935Yc9snra4z3JjZXcC/u/uXRyz7ZWCLu7+vxPoTGQr8ve7+aJl9LgC+4O4fqPS7NcNNvAq9/Xx+0+4z79MCKOjpUyn0gff0ZtYGtLj7seLrq4Evmdksd99bXO164PkS244DNgPfGh14Mzvf3V8xMwO6gGcjHZXUVaG3n2V/FS3wmtaqOYWpyJsKbB7KJq3AI+6+1cweM7NLGGqye4lizb2ZdQC3ufvvAh8FrgTOM7PfKe5vuGluo5lNZmg+xN2cWfMvMVtV6GPjzgORz+yA7sebmCawzJF69KKbOH4Me+6+po6lkjjUdHkvzWlVoY9Hnj5APfvSTJ0wjqdXdtZvh5IIhT4DGtEPfvG86azumh3b/qVxFPomU+jtZ+XmPo6fjK/DzEiqrMsehb5JNPJBlxZgnSrqMkuhT7lGP+iiNvfsU+hTrNDbz9JNjXnwUPfs+aHQp1icgT/n7LHcdf17dUbPIYU+peo1LrzCLaMp9Cm1Y9+RqrZTyCWIQp9CYc/yqnSTaij0KRR0llfPOKmFxr1PmTBneQVeaqHQp0ihtz/wLB9mAgmRShT6FFkWoolObelSK4U+JTrXbSdoarewM8KKVKLQp8CqQl+oMen04IvUg0KfAmEepNG9vNSLQp+wQm9/4DqtLaZ7eakbhT5hKx7fE7iOJoSQelLoE/bGm5Wr7xbPm64ed1JXCn2CVhX6AtfRZb3Um0KfoKAKPFXeSRwU+hTTWV7ioNAnJKiPvTriSFwU+oQE9bFXRxyJi0KfgM5125MuguSYQt9gYbrcqgJP4qTQN1Chtz9Ul1tV4EmcFPoGCvPorM7yEjeFvkHCPDrbYjrLS/wU+gZYtOGpUI/OrvtoewNKI3mn0MdsVaEv1HDW82dOUh97aQiFPkZhK+5aW0zt8tIwCn2MwlTcgR6dlcZS6GOyqtAXWHEHenRWGk+hj0nYIbBUWy+NptDHIMwQWPNnTlLgJREKfQyChsBSxZ0kSaGPQdAQWKq4kyQp9AlQxZ0kSaFvMPWtl6Qp9A2myjtJmkIvkjMKvUjOKPQiOaPQi+SMQi+SMwq9SM4o9CI5o9CL5IxC32BhZqoViZNC32BhnrMXiZNCL5IzCn0Mgh6qCTPIhkhcFPoYBD1Uc/uj4QbMFImDQp+AgDE2RGKl0MekbdyYip/rEl+SotDHZM0NlS/xg8bRE4lLqNCb2X4z6zOz3WbWU1x2j5ntKS7bZmYXlNn2FjPbW/y5ZcTyXy/u8wUz+z9mZvU5pHQIGhIraBw9kbhEOdNf5e7t7t5RfH+/u89x93bgb4Evjt7AzCYBdwKXA5cBd5rZucWPHwSWALOKP9dUeQyplalvMcmMqi/v3f3oiLdtgJdY7beBbnc/4u4/B7qBa8zsfGCiuz/l7g58C+iqtixptUjj4UkKhQ29A9vMbJeZLRleaGZrzOxnwCJKnOmBacDPRrx/ubhsWvH16OWZovHwJI3Chn6+u78fWAh81syuBHD3le7+LmAj8LkS25W6wvUKy8/cgdkSM+sxs57Dhw+HLK6IlBMq9O5+sPjvALCZofvzkR4Bbiyx6cvAu0a8vxA4WFx+YYnlpX73Q+7e4e4dkydPDlNcEakgMPRm1mZmE4ZfA1cDz5rZrBGrXQ88X2Lz7wFXm9m5xQq8q4HvufsrwDEzm1estf8U8N0aj0VEQmgNsc5UYHOxRa0VeMTdt5rZY2Z2CXAaeAm4DcDMOoDb3P133f2Imd0D/HNxX19y9yPF1/8T+AZwNvBk8UdEYhYYend/EThj8jV3L3U5j7v3AL874v3Xga+XWe99UQorIrVTjzyRnFHoRXJGoRfJGYVeJGcUepGcUehFckahF8kZhV4kZxR6kZxR6BOkcfIkCQp9gjQUtiRBoU+QhsmTJCj0Ijmj0IvkjEIfs/U3tyddBJG3UehjFjT+vUijKfQxU7OcpI1CHzNNXyVpo9DHTNNXSdoo9DEKurQPmtlWJA4KfYyCetwFzWwrEgeFPiarCn2BPe5Usy9JUOhjUOjt5+GdByquM75V/+klGfrLi8GyTcEP0tx345wGlETkTAp9nXWu205Qff341hZd2ktiFPo66ly3nb0DxwPX01lekqTQ10nYwM+fOUlneUmUQl8Hqwp9oQI/a0obG2+9ogElEilPoa+DoJp6gNYWo3vZgvgLIxJAoa/Rog1PhVrvyzedMfGvSCIU+hrt2HckcJ31N7frPl5SQ6GvwapCX+A6CrykjUJfg6B7edXUSxop9DFSTb2kkUJfpaDHZhfPm96gkohEo9BXKeix2dVdemxW0kmhr5IGxJFmpdBXIajWXiPiSJop9FUIqrXXiDiSZgp9RGF64KmZTtJMoY9gVaEvsAeeau0l7RT6kMIMgQWqtZf0U+hDKPT2szTEEFg6y0szaE26AGm3qtAX6gwPOstLc1DoK1i04alQT9GBzvLSPBT6MsIOfwVDD9boLC/NQvf0JUQNvB6skWai0I+yaMNToQOvMe+kGSn0IxR6+0Pfw8+fOUlj3klTUuhHCNMsB0OVdjrDS7NSRV5R57rtodbT8FfS7HSmZ+iyPsx9vAIvWaDQAyse3xO4zuJ50xV4yQSFHngjYEQMtcNLlij0AVpMA1xKtuQ+9EEDXK77aHuDSiLSGLkPfdAAl7qPl6zJfeg1wKXkTe5DX4kGuJQsChV6M9tvZn1mttvMeorL7jez581sj5ltNrNzSmx3SXGb4Z+jZra0+NldZtY/4rNr63totdMAl5JFUc70V7l7u7t3FN93A+9z9znAT4AVozdw938pbtMO/DrwOrB5xCoPDH/u7k9UeQyx0f28ZFHVl/fuvs3dB4tvdwIXBmzyW8A+d3+p2t8pIrULG3oHtpnZLjNbUuLzzwBPBuzjY8Bfjlr2ueLtwdfN7NyQZRGRGoQN/Xx3fz+wEPismV05/IGZrQQGgY3lNjazccD1wKMjFj8IzATagVeAPy2z7RIz6zGznsOHD4csroiUEyr07n6w+O8AQ/fklwGY2S3AB4BF7u4VdrEQ+KG7Hxqxz0PufsrdTwMbhvdZ4nc/5O4d7t4xefLkMMUVkQoCQ29mbWY2Yfg1cDXwrJldA9wBXO/urwfs5uOMurQ3s/NHvL0BeDZKwUWkOmHO9FOB75vZM8APgC3uvhX4CjAB6C42uX0VwMwuMLO3auLN7B1AJ/D4qP3+SbEZcA9wFfD52g+nvoImqhRpRoGDaLj7i8ClJZZfXGb9g8C1I96/DpxXYr1PRippAh7eeUBP10nm5L5HniVdAJEGy33oF2mSCsmZ3Idel++SN7kPvUjeKPQiOaPQi+SMQi+SMwq9SM4o9CI5o9CL5IxCL5IzCn2AoHHxRZqNQh9gWcjpq0WahUIf4DSwaMNTSRdDpG4UemB8a+X/DDv2HVHwJTMUeuC+G+cErqPgS1Yo9AyNbx90toeh4Gs0HWl2Cn1RmLM9DI2moxp9aWYKfVHX3GnMnzkp1LpLVaMvTUyhH2HjrVcwdcK4UOt2rtseb2FEYqLQj/L0yk4mjg+erXbvwPEGlEak/hT6EvbcfU2o4Is0I4W+jD13X8NZYzRWrmSPQl/B82uuDV5JpMko9CI5o9BXoBp6ySKFvgLV0EsWKfQiOaPQV2nWlLakiyBSFYW+St3LFiRdBJGqKPRl6Gk6ySqFvoyHdx5IuggisVDoRXJGoa9Ca4u650rzUuhLCOqU8+WbLm1MQURioNCXENQpp2vutAaVRKT+FHqRnFHoIwo7pJZIWin0EW289YqkiyBSE4VeJGcUepGcUegjUvdcaXYKfUTqnivNTqGvgs720swU+hKCmuV0tpdmptCXEKZZTmd7aVYKfRk620tWKfRl6GwvWaXQV7B43vSKn+tsL81Ioa9gddfspIsgUncKfQA9YCNZo9AHCLq31329NBuFvka6r5dmo9CL5IxCH0LbuDFJF0GkbhT6ENbcoFp8yQ6FPgQNhClZotCL5ExrmJXMbD9wDDgFDLp7h5ndD3wQOAnsAz7t7q+F2ba4fBKwCZgB7Ac+6u4/r+1wRCRIlDP9Ve7ePhxaoBt4n7vPAX4CrIiwLcBy4O/cfRbwd8X3TanQ2590EURCq/ry3t23uftg8e1O4MKIu/gQ8M3i628CXdWWJWkrHt+TdBFEQgsbege2mdkuM1tS4vPPAE9G3Haqu78CUPx3SthCp80bb55OuggioYUN/Xx3fz+wEPismV05/IGZrQQGgY1Rtw3DzJaYWY+Z9Rw+fDjKpnUV1FavS3xpFqFC7+4Hi/8OAJuBywDM7BbgA8Aid/co2wKHzOz84n7OBwbKbP+Qu3e4e8fkyZPDHlfdBbXVL920u0ElEalNYOjNrM3MJgy/Bq4GnjWza4A7gOvd/fUo2xY//mvgluLrW4Dv1nIgcQvTVh80261IGoQ5008Fvm9mzwA/ALa4+1bgK8AEoNvMdpvZVwHM7AIzeyJgW4C1QKeZ7QU6i+9TLegSf+/AcRZteKpBpRGpjpW5Kk+ljo4O7+npSez3F3r7Q13Gz585SXPeSaLMbNeoJvK3qEdeBF1zp4UaVGPHviM640tqKfQRbbz1CloseL0d+45w8R89oVp9SR2FvgrrPtoear3B087STbt11pdUUeir0DV3WuBIuSPprC9potBXaXXX7EjBHz7rK/iSNIW+BlGDDyj4kjiFvkaru2az/ub2SP8hFXxJkkJfB11zp/Hi2usinfWXqduuJEShr6PVXbPZv/Y6Zk1pC1z3NBozX5Kh0Mege9mCUMHXmPmSBIU+JmGDrzZ8aTSFPkZhgr9j35EGlUZkiEIfs+5lC5IugsjbKPQNELUtXyROCn0DaJ57SROFXiRnFHqRnFHoG0DNcpImCn3MCr39apaTVFHoY3b7o+pjL+mi0Meo0NtP0OQ3QSPsitSbQh+jMHPcBU2iIVJvCn2Mgua4WzxveqhJNETqSaGPSdBjs+NbW9RpRxKh0MdkY8Bjs/fdOKdBJRF5O4U+JkHzBumyXpKi0IvkjEIfk6CmOPXSk6Qo9DEJaorbse+IRsSVRCj0MQlzz64RcSUJCn2Mgma4PQ0620vDKfQxCjPDbZheeyL1pNDHLGiG26BeeyL1ptDHrGvuNFrDTGgv0iAKfQN8+aZLky6CyFsU+gZQ7ztJE4VeJGcUepGcUehFckahF8kZhV4kZxR6kZxR6EVyRqEXyRmFvgE0YIakiUIfM01rJWmj0Meo0NvPUg2UISnTmnQBsmpVoY+HA4bBhqEJL0QaSaGvs7BhH6YJL6TRFPoaFXr7Wbm5j+MnT0XeVmd5SYJCH1Ght5+7/+ZH/Pz1N2vaz6wpbTrLSyIU+pCiXrZXMmtKG93LFtRlXyJRKfQB6hl2GBohd+OtV9RtfyJRKfQVdK7bzt6B43XZ19gWuP+mdo2iI4lT6MuoZ+AXz5uu+3dJDYW+hHoE/pyzx3LX9e/VmV1SR6EfZVWhr6rAnz22hXs/PEchl9RT6Eco9PZHqrTTZbs0o1ChN7P9wDHgFDDo7h1mdj/wQeAksA/4tLu/Nmq7dwHfAv4zQ1O3PeTuf1b87C7gVuBwcfU/cvcnaj2gakXpJ7/+ZlXISfOK8sDNVe7e7u4dxffdwPvcfQ7wE2BFiW0GgT90918F5gGfNbNfG/H5A8V9ticZeAg/g6wCL82u6qfs3H2buw8W3+4ELiyxzivu/sPi62PAc0DqErNow1OEmVFOgZcsCBt6B7aZ2S4zW1Li888AT1bagZnNAOYCT49Y/Dkz22NmXzezc0OWpa4WbXgq1PPui+dNV+AlE8KGfr67vx9YyNAl+pXDH5jZSoYu4zeW29jMfgl4DFjq7keLix8EZgLtwCvAn5bZdomZ9ZhZz+HDh0utUrWwgVc/ecmSUKF394PFfweAzcBlAGZ2C/ABYJG7e6ltzWwsQ4Hf6O6Pj9jnIXc/5e6ngQ3D+yzxux9y9w5375g8eXL4IwsQJfDqJy9ZEhh6M2szswnDr4GrgWfN7BrgDuB6d3+9zLYG/AXwnLuvG/XZ+SPe3gA8W90hRNe5bnuowLe2mAIvmROmyW4qsHkov7QCj7j7VjN7ARgPdBc/2+nut5nZBcDX3P1aYD7wSaDPzIarx4eb5v7EzNoZqi/YD/yPOh5XSYXefr7w6DMMni55UXIGTTEtWRQYend/ETjjr9/dLy6z/kHg2uLr7wNWZr1PRippjaKOV6eKO8mqXAyMWU3gVXEnWZX5brhRn4dX4CXrMh36KH3pW4B16nwjOZDp0Ie9pFeznORJZu/pw04lpcBL3mQy9GGnklLgJY8yeXkf5om5WVPauPzd53HR8i0Mt9q3jRvDmhtm675eMi1zoS/09od6Ym7vwPEzRsg5fvIUSzftjtS8py8KaTZWpst8KnV0dHhPT0/FdWYs39Kg0pRnwCI1/UmCzGzXiLEv3iZT9/SXr+lOugjAUL/ih3ceYMbyLZqbXlInU6E/dOxk0kU4w459R5ixfAvv/eJWCr39SRdHJFuhT7Ph+gKd+SVpCn2D7dh3hHev2KKzviQmU7X3UyeMS+Ul/minnZKtBOe+Yyx3flATZEi8Mld7f/ma7pLBr2fTWqG3nxWP7+GNN8M0DtaHmgYlikq195kLfZLqPcNtOfoCkCAKfYM1Kvyl6AtBQKFPTJLhB30B5FluOuekzequ2exfex2zprQl8vvVTCilKPQN0L1sAetvbufsscn8596x7whz7tyayO+W9NHlfcIa2RJw1hjj+TXXxv57JHm6p29ScXwhTJ0wjqdXdtZtf5JOuqdvUl1zp/HcPQvZv/Y6Fs+bXpd9Hjp2klWFvrrsS5qTzvQZUM0Vwf6118VYIklapTN9prrh5lXX3GlvNcsl3Uwo6afL+4wZbiYUKUehz6j1N7cnXQRJKYU+o4J64akyL78U+pzSfX9+KfQ5poE88kmhz7DxrZX/9y7dtFvBzyGFPsPuu3FO4DpLN+3mklVPKvw5otBnWNhHak8Mnmbppt0atTcnFPqMi9p9d/hx3BnLt6iGP6MU+oxb3TWbqRPGVbXtwzsP6JHcDFLoc+DplZ1MHD+mqm2PnjjFe1Y+UecSSZIU+pzYc/c1VZ/xf3HK6Vy3vb4FksQo9Dny9MrOqh/R3TtwXBV8GaHQ58zwAznVDN8VZQpvSS89Ty8Uevu5/dHdhH0cf7Gm4U49DZcloc1YviXUerOmtNG9bEG8hZGqabgsCS3sPf/egeMaWrtJKfTyNqu7Zodu3tux74gq95qQQi9n2HP3NaHXXabKvaaj0EtJYUfeOY0e0W02Cr2U1DV3GutvbidMq56a8pqLQi9ldc2dxt4/vo75MycFrquHc5qHmuwklLBNeaNpZN5kqMlOalZt991qvywkPgq9hFJLD7wZy7eosi9FFHoJrZb59JZu2q0n9VJCoZfQVnfNxmrYfu/AcZ31U0Chl0geqMPMOUs37VYX3gQp9BLJcPt9rXbsO6KzfkLUZCdVW7ThKXbsO1KXfelx3frSo7USq6jP41dy9tgW7v3wnNDDd0tpCr00TKG3n2WbdlOH/OvsXwOFXhqunmd/0BdAVDWH3sz2A8eAU8Cgu3eY2f3AB4GTwD7g0+7+WoltrwH+DBgDfM3d1xaXXwR8G5gE/BD4pLufrFQOhb75rCr01X2GXH0BBKtX6Dvc/dURy64G/t7dB83sPgB3v2PUdmOAnwCdwMvAPwMfd/cfm9lfAY+7+7fN7KvAM+7+YKVyKPTNKY7gD5s/cxIbb70icL3OddvZO3D8jOVZfTYglr737r7N3QeLb3cCF5ZY7TLgBXd/sXgW/zbwITMz4DeB7xTX+ybQVW1ZJN1Wd82uqTdfJcNNf5Xa/csFHvL5bEDY0Duwzcx2mcOxSwwAAApFSURBVNmSEp9/BniyxPJpwM9GvH+5uOw84LURXxrDyyWj4gw+/Ef4L1/TfcZn5QI/LG/Bbw253nx3P2hmU4BuM3ve3f8RwMxWAoPAxhLbleq16RWWn7mDoS+ZJQDTp8f3RyPxG74Pj+tSH+DQsZPMWL4l8mi9F6/Ywgv3pudSv9ItUa0jEYc607v7weK/A8Bmhi7bMbNbgA8Ai7x05cDLwLtGvL8QOAi8CpxjZq2jlpf63Q+5e4e7d0yePDlMcSXFVnfNrkuPviDD/fzDdvcd9KEzfhpm6+1ct73iF+PegeM1PbwUeKY3szagxd2PFV9fDXypWCt/B/Ab7v56mc3/GZhVrKnvBz4GfMLd3cz+AfgIQ/f5twDfrfoopKl0zZ1G19xpde3RV041+x8OXD1bCC5f082hYxUbpyIJumWpJMzl/VRg81DdG63AI+6+1cxeAMYzdLkPsNPdbzOzCxhqmru2WLP/OeB7DDXZfd3df1Tc7x3At81sNdAL/EXVRyFNabjWvRHhj+rhnQcihz6Nx1GKOudIalSqZU9KuSa9Qm9/4gOCVmpurNRkF7YiTyR23csWUOjtZ8Xje3ijXl35apTWmv1ZU9qq3lahl1QZvt8fFmfHnmZVa+29Lu8lE9J6Rq6nKL0HNRquSBObOH5MXbsL6/JepMEmjh8Tab7AelPoRWJy1hjj+TXXJl2MMyj0InWQ1oCXotBLJsya0tawNv6pE8bx9MrOhvyuOCj0kgndyxbEXoOflWfvVXsvmVFLh5U8UeglM2rpsBJGVvoCKPQiOaPQi+SMQi+SMwq9SM4o9JIpcTarZaV1QKGXTKl1CuxyI/bW+jhrmqhzjmRK2OGqKl0RZH32HJ3pJTNKjXlfysTxY2IuSbop9JIJUUabTfKx1jRQ6KXphQ381AnjMtN/vhYKvTS1VYW+UIGfNaWtqZ+MqyeFXppamEEzjfj75TcThV6aVtiKuwcaMI1WM1HopSmFvY9fPG/624bUFoVemlDY+/j5Mydlvs29Ggq9NJUok18Mz5Unb6ceeZJ6hd5+7nhsDycGw091lZV+8nFQ6CXVqpkocuL4Maqtr0CX95JqUQM/f+ak3Pe4C6LQS6boPj6YQi+ZUe6xWHk7hV4yYfG86WqeC0kVeZJahd7+ip+fPbaFez88R51vIlLoJbWCKvGeu2dhg0qSLbq8l1QKOstL9RR6SaUVj+9JugiZpdBLKr3xZuXed6qpr55CL01JNfXVU+gldYImihzfqj/bWui/nqTKRSFmhr3vxjkNKEl2KfSSKh5iHbXL10ahF8kZhV6aimrta6fQS1NRrX3tFHpJlfkzJyVdhMxT6CVVburQ5XvcFHpJlfu/9y9JFyHzFHpJlYOvvZF0ETJPoZdUueCcs5MuQuYp9JIqt//2JRU/n3Pn1gaVJLsUekmVoN52R0+calBJskuhl6bTuW570kVoagq9pE5QW/3egeMNKkk2KfSSOhq7Pl4KvaSOxseLl0bDlVSJMiutVEehl1RQ2Bsn1OW9me03sz4z221mPcVlN5nZj8zstJl1lNnukuI2wz9HzWxp8bO7zKx/xGfX1u+wpJlEDXyLxViYHIhypr/K3V8d8f5Z4MPAn5fbwN3/BWgHMLMxQD+wecQqD7j7lyOUQTIo6hl+3UfbYypJPlR9ee/uzwGYhf7a/S1gn7u/VO3vFFl/c7uGy6pR2NA7sM3MHPhzd3+oit/1MeAvRy37nJl9CugB/tDdf17FfqVJda7bHrrN/awxxvNrdAdYD2Gb7Oa7+/uBhcBnzezKKL/EzMYB1wOPjlj8IDCTocv/V4A/LbPtEjPrMbOew4cPR/m1kmIXr9gSOvCzprQp8HUUKvTufrD47wBD9+SXRfw9C4EfuvuhEfs85O6n3P00sKHcPt39IXfvcPeOyZMnR/y1kkad67YzGGbY26LuZQtiK0seBYbezNrMbMLwa+Bqhirxovg4oy7tzez8EW9vqGKf0qTUjTZZYc70U4Hvm9kzwA+ALe6+1cxuMLOXgSuALWb2PQAzu8DMnhje2MzeAXQCj4/a758UmwH3AFcBn6/D8YhIgMCKPHd/Ebi0xPLNvL35bXj5QeDaEe9fB84rsd4noxZWmt+qQl+k9dffrOa5elOPPIlVobefOx7bw4nByrPQjnb22Bbu/fAcNc/FQKGXultV6GPjzgOhpqgq57l7FtatPPJ2Cr3URT2CLo2h0EtNonSwCUtTV8VLz9NL1eII/PyZkzR1Vcx0ppeq1TPwY1vg/pvUr74RFHpJzOJ503VWT4BCLw2jZrh0UOilarOmtIW+xN+/9rqYSyNhqSJPqqYHYZqTQi81UTfZ5qPQS0265k4LDL6GtEsX3dNLzbrmTnurcu6i5Vve1ivPgJ/qfj5VFHqpKwU8/XR5L5IzCr1Izij0Ijmj0IvkjEIvkjMKvUjOKPQiOaPQi+SMQi+SMwq9SM4o9CI5o9CL5IxCL5IzCr1Izij0Ijmj0IvkjEIvkjMKvUjOKPQiOaPQi+SMQi+SM+buwWulhJkdBl5KuhzAO4FXky5EDHRczaXScf2yu08u9UFThT4tzKzH3TuSLke96biaS7XHpct7kZxR6EVyRqGvzkNJFyAmOq7mUtVx6Z5eJGd0phfJGYW+DDO7xMx2j/g5amZLR62zyMz2FH/+ycwuTaq8YYU5rhHr/hczO2VmH2l0OaMKe1xmtqD4+Y/M7P8mUdawQv4N/icz+xsze6Z4TJ8O3LG76yfgBxgD/CtDbZ8jl/9X4Nzi64XA00mXtR7HNeKzvweeAD6SdFnr9P/rHODHwPTi+ylJl7UOx/RHwH3F15OBI8C4SvvSmT6c3wL2ufvbOga5+z+5+8+Lb3cCFza8ZLUpeVxFvwc8Bgw0tkh1Ue64PgE87u4HANy9mY6t3DE5MMHMDPglhkI/WGlHCn04HwP+MmCd/w482YCy1FPJ4zKzacANwFcbXqL6KPf/61eAc81su5ntMrNPNbhctSh3TF8BfhU4CPQBf+DupyvuKenLlrT/AOMY6uo4tcI6VwHPAeclXd56HBfwKDCv+PobNNHlfcBxfYWhK7I2hrqw7gV+Jeky13hMHwEeAAy4GPgpMLHS/lrDfs3k2ELgh+5+qNSHZjYH+Bqw0N3/X0NLVptKx9UBfHvoipF3Atea2aC7FxpZwCpVOq6XgVfd/Thw3Mz+EbgU+EkjC1iFSsf0aWCtD30DvGBmPwXeA/yg3M50eR/s45S5tDez6cDjwCfdPe1/OKOVPS53v8jdZ7j7DOA7wP9qksBDheMCvgv8NzNrNbN3AJczdIWWdpWO6QBD9/uY2VTgEuDFSjtT55wKin8YPwPe7e7/Vlx2G4C7f9XMvgbcyH88+TfoTfBgR9BxjVr3G8Dfuvt3Gl3OqMIcl5ndztDZ8TTwNXdfn1BxQwnxN3gBQ7dg5zN0ib/W3R+uuE+FXiRfdHkvkjMKvUjOKPQiOaPQi+SMQi+SMwq9SM4o9CI5o9CL5Mz/B3G1synaIxcuAAAAAElFTkSuQmCC\n",
"text/plain": [
"
"
]
@@ -568,146 +568,429 @@
"ax"
]
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Interactive Map\n",
- "The following map-based visualization makes use of folium. It allows to visualizate geospatial data based on an interactive leaflet map. Since the data in the GeoDataframe is modelled as a set of Point instead of a LineString, we have to manually create a polyline"
- ]
- },
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "
"
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
uid
\n",
+ "
tid
\n",
+ "
lat
\n",
+ "
lng
\n",
+ "
datetime
\n",
+ "
CO2.unit
\n",
+ "
CO2.value
\n",
+ "
Calculated MAF.unit
\n",
+ "
Calculated MAF.value
\n",
+ "
Consumption.unit
\n",
+ "
...
\n",
+ "
sensor.engineDisplacement
\n",
+ "
sensor.fuelType
\n",
+ "
sensor.manufacturer
\n",
+ "
sensor.model
\n",
+ "
sensor.type
\n",
+ "
track.appVersion
\n",
+ "
track.begin
\n",
+ "
track.end
\n",
+ "
track.length
\n",
+ "
track.touVersion
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.646327
\n",
+ "
51.955395
\n",
+ "
2020-01-17 15:58:38
\n",
+ "
kg/h
\n",
+ "
4.094951
\n",
+ "
g/s
\n",
+ "
5.300929
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.646326
\n",
+ "
51.955392
\n",
+ "
2020-01-17 15:58:43
\n",
+ "
kg/h
\n",
+ "
4.055918
\n",
+ "
g/s
\n",
+ "
5.250401
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.646326
\n",
+ "
51.955393
\n",
+ "
2020-01-17 15:58:48
\n",
+ "
kg/h
\n",
+ "
3.982729
\n",
+ "
g/s
\n",
+ "
5.155657
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.646326
\n",
+ "
51.955393
\n",
+ "
2020-01-17 15:58:53
\n",
+ "
kg/h
\n",
+ "
4.007283
\n",
+ "
g/s
\n",
+ "
5.187442
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.646326
\n",
+ "
51.955393
\n",
+ "
2020-01-17 15:58:58
\n",
+ "
kg/h
\n",
+ "
4.019175
\n",
+ "
g/s
\n",
+ "
5.202837
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
\n",
+ "
\n",
+ "
65
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.638311
\n",
+ "
51.957937
\n",
+ "
2020-01-17 16:05:51
\n",
+ "
kg/h
\n",
+ "
3.781639
\n",
+ "
g/s
\n",
+ "
4.895346
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
66
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.638327
\n",
+ "
51.957991
\n",
+ "
2020-01-17 16:05:56
\n",
+ "
kg/h
\n",
+ "
5.149422
\n",
+ "
g/s
\n",
+ "
6.665945
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
67
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.636856
\n",
+ "
51.958161
\n",
+ "
2020-01-17 16:06:11
\n",
+ "
kg/h
\n",
+ "
4.281626
\n",
+ "
g/s
\n",
+ "
5.542580
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
68
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.636908
\n",
+ "
51.958213
\n",
+ "
2020-01-17 16:06:16
\n",
+ "
kg/h
\n",
+ "
4.063978
\n",
+ "
g/s
\n",
+ "
5.260834
\n",
+ "
l/h
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ "
\n",
+ "
69
\n",
+ "
58395f40e4b0a979d45bd61b
\n",
+ "
5e24ca2463c90936dc7b2d94
\n",
+ "
7.636985
\n",
+ "
51.958233
\n",
+ "
2020-01-17 16:06:21
\n",
+ "
NaN
\n",
+ "
NaN
\n",
+ "
NaN
\n",
+ "
NaN
\n",
+ "
NaN
\n",
+ "
...
\n",
+ "
1798
\n",
+ "
gasoline
\n",
+ "
Dodge
\n",
+ "
Caliber
\n",
+ "
car
\n",
+ "
NaN
\n",
+ "
2020-01-17T15:58:38Z
\n",
+ "
2020-01-17T16:06:21Z
\n",
+ "
1.152504
\n",
+ "
NaN
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
70 rows × 56 columns
\n",
+ "
"
],
"text/plain": [
- ""
+ " uid tid lat lng \\\n",
+ "0 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.646327 51.955395 \n",
+ "1 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.646326 51.955392 \n",
+ "2 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.646326 51.955393 \n",
+ "3 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.646326 51.955393 \n",
+ "4 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.646326 51.955393 \n",
+ ".. ... ... ... ... \n",
+ "65 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.638311 51.957937 \n",
+ "66 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.638327 51.957991 \n",
+ "67 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.636856 51.958161 \n",
+ "68 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.636908 51.958213 \n",
+ "69 58395f40e4b0a979d45bd61b 5e24ca2463c90936dc7b2d94 7.636985 51.958233 \n",
+ "\n",
+ " datetime CO2.unit CO2.value Calculated MAF.unit \\\n",
+ "0 2020-01-17 15:58:38 kg/h 4.094951 g/s \n",
+ "1 2020-01-17 15:58:43 kg/h 4.055918 g/s \n",
+ "2 2020-01-17 15:58:48 kg/h 3.982729 g/s \n",
+ "3 2020-01-17 15:58:53 kg/h 4.007283 g/s \n",
+ "4 2020-01-17 15:58:58 kg/h 4.019175 g/s \n",
+ ".. ... ... ... ... \n",
+ "65 2020-01-17 16:05:51 kg/h 3.781639 g/s \n",
+ "66 2020-01-17 16:05:56 kg/h 5.149422 g/s \n",
+ "67 2020-01-17 16:06:11 kg/h 4.281626 g/s \n",
+ "68 2020-01-17 16:06:16 kg/h 4.063978 g/s \n",
+ "69 2020-01-17 16:06:21 NaN NaN NaN \n",
+ "\n",
+ " Calculated MAF.value Consumption.unit ... sensor.engineDisplacement \\\n",
+ "0 5.300929 l/h ... 1798 \n",
+ "1 5.250401 l/h ... 1798 \n",
+ "2 5.155657 l/h ... 1798 \n",
+ "3 5.187442 l/h ... 1798 \n",
+ "4 5.202837 l/h ... 1798 \n",
+ ".. ... ... ... ... \n",
+ "65 4.895346 l/h ... 1798 \n",
+ "66 6.665945 l/h ... 1798 \n",
+ "67 5.542580 l/h ... 1798 \n",
+ "68 5.260834 l/h ... 1798 \n",
+ "69 NaN NaN ... 1798 \n",
+ "\n",
+ " sensor.fuelType sensor.manufacturer sensor.model sensor.type \\\n",
+ "0 gasoline Dodge Caliber car \n",
+ "1 gasoline Dodge Caliber car \n",
+ "2 gasoline Dodge Caliber car \n",
+ "3 gasoline Dodge Caliber car \n",
+ "4 gasoline Dodge Caliber car \n",
+ ".. ... ... ... ... \n",
+ "65 gasoline Dodge Caliber car \n",
+ "66 gasoline Dodge Caliber car \n",
+ "67 gasoline Dodge Caliber car \n",
+ "68 gasoline Dodge Caliber car \n",
+ "69 gasoline Dodge Caliber car \n",
+ "\n",
+ " track.appVersion track.begin track.end track.length \\\n",
+ "0 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "1 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "2 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "3 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "4 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ ".. ... ... ... ... \n",
+ "65 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "66 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "67 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "68 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "69 NaN 2020-01-17T15:58:38Z 2020-01-17T16:06:21Z 1.152504 \n",
+ "\n",
+ " track.touVersion \n",
+ "0 NaN \n",
+ "1 NaN \n",
+ "2 NaN \n",
+ "3 NaN \n",
+ "4 NaN \n",
+ ".. ... \n",
+ "65 NaN \n",
+ "66 NaN \n",
+ "67 NaN \n",
+ "68 NaN \n",
+ "69 NaN \n",
+ "\n",
+ "[70 rows x 56 columns]"
]
},
- "execution_count": 6,
+ "execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "import folium\n",
- "\n",
- "lats = list(some_track['geometry'].apply(lambda coord: coord.y))\n",
- "lngs = list(some_track['geometry'].apply(lambda coord: coord.x))\n",
- "\n",
- "avg_lat = sum(lats) / len(lats)\n",
- "avg_lngs = sum(lngs) / len(lngs)\n",
- "\n",
- "m = folium.Map(location=[avg_lat, avg_lngs], zoom_start=13)\n",
- "folium.PolyLine([coords for coords in zip(lats, lngs)], color='blue').add_to(m)\n",
- "m"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Example: Visualization with pydeck (deck.gl)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The pydeck library makes use of the basemap tiles from Mapbox. In case you want to visualize the map with basemap tiles, you need to register with MapBox, and configure a specific access token. The service is free until a certain level of traffic is esceeded.\n",
- "\n",
- "You can either configure it via your terminal (i.e. `export MAPBOX_API_KEY=`), which pydeck will automatically read, or you can pass it as a variable to the generation of pydeck (i.e. `pdk.Deck(mapbox_key=, ...)`."
+ "TrackConverter(some_track).to_scikitmobility()"
]
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- " \n",
- " "
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "text/plain": [
- "'/home/hafenkran/dev/envirocar/envirocar-py/examples/tracks_muenster.html'"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "import pydeck as pdk\n",
- "\n",
- "# for pydeck the attributes have to be flat\n",
- "track_df['lat'] = track_df['geometry'].apply(lambda coord: coord.y)\n",
- "track_df['lng'] = track_df['geometry'].apply(lambda coord: coord.x)\n",
- "vis_df = pd.DataFrame(track_df)\n",
- "vis_df['speed'] = vis_df['Speed.value']\n",
- "\n",
- "# omit unit columns\n",
- "vis_df_cols = [col for col in vis_df.columns if col.lower()[len(col)-4:len(col)] != 'unit']\n",
- "vis_df = vis_df[vis_df_cols]\n",
- "\n",
- "layer = pdk.Layer(\n",
- " 'ScatterplotLayer',\n",
- " data=vis_df,\n",
- " get_position='[lng, lat]',\n",
- " auto_highlight=True,\n",
- " get_radius=10, # Radius is given in meters\n",
- " get_fill_color='[speed < 20 ? 0 : (speed - 20)*8.5, speed < 50 ? 255 : 255 - (speed-50)*8.5, 0, 140]', # Set an RGBA value for fill\n",
- " pickable=True\n",
- ")\n",
- "\n",
- "# Set the viewport location\n",
- "view_state = pdk.ViewState(\n",
- " longitude=7.5963592529296875,\n",
- " latitude=51.96246168188569,\n",
- " zoom=10,\n",
- " min_zoom=5,\n",
- " max_zoom=15,\n",
- " pitch=40.5,\n",
- " bearing=-27.36)\n",
- "\n",
- "r = pdk.Deck(\n",
- " width=200, \n",
- " layers=[layer], \n",
- " initial_view_state=view_state #, mapbox_key=\n",
- ")\n",
- "r.to_html('tracks_muenster.html', iframe_width=900)"
- ]
+ "outputs": [],
+ "source": []
}
],
"metadata": {
"kernelspec": {
- "display_name": "envirocar",
+ "display_name": "Python 3",
"language": "python",
- "name": "envirocar"
+ "name": "python3"
},
"language_info": {
"codemirror_mode": {
@@ -719,7 +1002,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.6.9"
+ "version": "3.7.6"
}
},
"nbformat": 4,
diff --git a/examples/api_request_deckgl_annaformaniuk.ipynb b/examples/api_request_deckgl_annaformaniuk.ipynb
deleted file mode 100644
index e5a874f..0000000
--- a/examples/api_request_deckgl_annaformaniuk.ipynb
+++ /dev/null
@@ -1,3539 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Package loading and basic configurations"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "name": "stdout",
- "text": "The autoreload extension is already loaded. To reload it, use:\n %reload_ext autoreload\n"
- }
- ],
- "source": [
- "%load_ext autoreload\n",
- "%autoreload 2\n",
- "\n",
- "# load dependencies'\n",
- "import pandas as pd\n",
- "import geopandas as gpd\n",
- "\n",
- "from envirocar import TrackAPI, DownloadClient, BboxSelector, ECConfig\n",
- "\n",
- "# create an initial but optional config and an api client\n",
- "config = ECConfig()\n",
- "track_api = TrackAPI(api_client=DownloadClient(config=config))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Querying enviroCar Tracks"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The following cell queries tracks from the enviroCar API. It defines a bbox for the area of Gievenbeck, Münster (Germany) and requests 50 tracks. The result is a GeoDataFrame, which is a geo-extended Pandas dataframe from the GeoPandas library. It contains all information of the track in a flat dataframe format including a specific geometry column. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {},
- "outputs": [
- {
- "output_type": "execute_result",
- "data": {
- "text/plain": " id time geometry \\\n0 5de9d7a03bdb691868e76542 2019-11-29T12:24:48 POINT (7.60302 51.93549) \n1 5de9d7a03bdb691868e76544 2019-11-29T12:24:53 POINT (7.60279 51.93574) \n2 5de9d7a03bdb691868e76545 2019-11-29T12:24:58 POINT (7.60318 51.93579) \n3 5de9d7a03bdb691868e76546 2019-11-29T12:25:03 POINT (7.60354 51.93574) \n4 5de9d7a03bdb691868e76547 2019-11-29T12:25:08 POINT (7.60403 51.93611) \n.. ... ... ... \n30 575a63b9e4b0a691929d1ee2 2016-06-09T16:11:12 POINT (7.57763 51.95973) \n31 575a63b9e4b0a691929d1ee3 2016-06-09T16:11:17 POINT (7.57684 51.95933) \n32 575a63b9e4b0a691929d1ee4 2016-06-09T16:11:22 POINT (7.57604 51.95899) \n33 575a63b9e4b0a691929d1ee5 2016-06-09T16:11:27 POINT (7.57522 51.95865) \n34 575a63b9e4b0a691929d1ee6 2016-06-09T16:11:32 POINT (7.57425 51.95818) \n\n GPS HDOP.value GPS HDOP.unit Throttle Position.value \\\n0 0.800000 precision 13.000000 \n1 0.800000 precision 15.000000 \n2 0.900000 precision 16.000000 \n3 0.882600 precision 30.999999 \n4 0.800000 precision 13.000000 \n.. ... ... ... \n30 0.916525 precision 17.999999 \n31 1.300000 precision 18.000000 \n32 1.000000 precision 20.467706 \n33 0.800000 precision 16.000000 \n34 0.960672 precision 13.302198 \n\n Throttle Position.unit Intake Temperature.value Intake Temperature.unit \\\n0 % 8.000000 c \n1 % 7.000000 c \n2 % 8.000000 c \n3 % 7.786344 c \n4 % 7.000000 c \n.. ... ... ... \n30 % 24.000000 c \n31 % 24.000000 c \n32 % 24.000000 c \n33 % 24.000000 c \n34 % 25.863637 c \n\n Rpm.value ... MAF.value MAF.unit O2 Lambda Voltage ER.value \\\n0 763.163669 ... NaN NaN NaN \n1 1701.677369 ... NaN NaN NaN \n2 1361.423041 ... NaN NaN NaN \n3 1951.543919 ... NaN NaN NaN \n4 792.443191 ... NaN NaN NaN \n.. ... ... ... ... ... \n30 1866.000000 ... NaN NaN NaN \n31 1853.205298 ... NaN NaN NaN \n32 1840.097751 ... NaN NaN NaN \n33 1926.970989 ... NaN NaN NaN \n34 950.486984 ... NaN NaN NaN \n\n O2 Lambda Voltage ER.unit O2 Lambda Voltage.value O2 Lambda Voltage.unit \\\n0 NaN NaN NaN \n1 NaN NaN NaN \n2 NaN NaN NaN \n3 NaN NaN NaN \n4 NaN NaN NaN \n.. ... ... ... \n30 NaN NaN NaN \n31 NaN NaN NaN \n32 NaN NaN NaN \n33 NaN NaN NaN \n34 NaN NaN NaN \n\n O2 Lambda Current ER.value O2 Lambda Current ER.unit \\\n0 NaN NaN \n1 NaN NaN \n2 NaN NaN \n3 NaN NaN \n4 NaN NaN \n.. ... ... \n30 NaN NaN \n31 NaN NaN \n32 NaN NaN \n33 NaN NaN \n34 NaN NaN \n\n O2 Lambda Current.value O2 Lambda Current.unit \n0 NaN NaN \n1 NaN NaN \n2 NaN NaN \n3 NaN NaN \n4 NaN NaN \n.. ... ... \n30 NaN NaN \n31 NaN NaN \n32 NaN NaN \n33 NaN NaN \n34 NaN NaN \n\n[16397 rows x 58 columns]",
- "text/html": "