4 Telemetry Data Along a Stage

We can filter the telemetry data to just the data points that lay on the route by creating a buffered area around the route and then “cropping” the telemetry route data to just the data that fulls within that region.

Let’s create a buffered area 100m wide around the route:

# Generate buffered routes in UTM and longlat projections
buffer_margin_100m = units::set_units(100, m)

buffered_route_utm = st_buffer(stage_route_utm, buffer_margin_100m)
buffered_route = st_transform(buffered_route_utm, original_crs)

leaflet(buffered_route) %>% 
  addProviderTiles("OpenTopoMap", group = "OSM") %>% 
  addPolylines(color = "red", weight = 2)

To simplify distance calculations, where we are likely to want to work in units of meters, generate a UTM projection of the route telemetry data:

# Also generate a UTM projection of the minimal route telemetry
route_telem_utm = st_transform(route_telem,
                               crs = st_crs(utm_proj4_string))

Let’s see how it looks:

X accx accy altitude brk driverid gear heading kms name rpm speed status throttle track utx X_rally_stageid X_carentryid X_telemetryID X_name delta utc geometry
1870 0 0 0 0 NA 0 144 0.6 33 0 166 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 0 2021-10-02 08:37:23 POINT (25.12109 61.83077)
1869 0 0 0 0 NA 0 150 0.7 33 0 140 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 3200 2021-10-02 08:37:26 POINT (25.1224 61.82966)
1868 0 0 0 0 NA 0 172 0.8 33 0 60 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 6400 2021-10-02 08:37:30 POINT (25.12291 61.82912)
1867 0 0 0 0 NA 0 220 0.9 33 0 107 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 9600 2021-10-02 08:37:33 POINT (25.122 61.82856)
1866 0 0 0 0 NA 0 210 1.0 33 0 149 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 12800 2021-10-02 08:37:36 POINT (25.12067 61.82766)
1865 0 0 0 0 NA 0 208 1.1 33 0 159 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 16000 2021-10-02 08:37:39 POINT (25.11919 61.82662)

We can now find the intersection of the buffered stage route and the original telemetry spatial features dataframe. The st_intersects() function prefers a UTM projection, so let’s use that:

#Find the intersecting points
route_telem_intersect = st_intersects(buffered_route_utm, telem_df_min_utm)

# And then filter on those points
# Also nullify the Z dimension
route_telem = telem_df_min[route_telem_intersect[[1]],]  %>% st_zm()

Let’s preview the first few rows of that data:

X heading kms name speed status utx X_name geometry delta_s utc
1870 144 0.6 33 166 Competing 1.633164e+12 Evans POINT (25.12109 61.83077) 0.0 2021-10-02 11:37:23
1869 150 0.7 33 140 Competing 1.633164e+12 Evans POINT (25.1224 61.82966) 3.2 2021-10-02 11:37:26
1868 172 0.8 33 60 Competing 1.633164e+12 Evans POINT (25.12291 61.82912) 6.4 2021-10-02 11:37:30
1867 220 0.9 33 107 Competing 1.633164e+12 Evans POINT (25.122 61.82856) 9.6 2021-10-02 11:37:33
1866 210 1.0 33 149 Competing 1.633164e+12 Evans POINT (25.12067 61.82766) 12.8 2021-10-02 11:37:36
1865 208 1.1 33 159 Competing 1.633164e+12 Evans POINT (25.11919 61.82662) 16.0 2021-10-02 11:37:39

If we assume that the kms column is the distance into stage, we see that it appears that we are missing data from the start of the run? The datetime would also suggest that we may be missing some samples from the start of the run, because start times typically start precisely on the minute.

But at least we have something! If we now plot the resulting telemetry data points, we see have limited our selection to just the sample points that fall within the buffered stage route area:

leaflet(route_telem )  %>% 
  addProviderTiles("OpenTopoMap", group = "OSM") %>% 
  addCircleMarkers()

4.1 Finding Points on the Route Closest to the Telemetry Data Locations

Locating telemetry data along a stage us useful, but how we might start to use that to make comparisons between drivers?

If we know the time of day when a drive starts a stage, we can find the difference between the telemetry sample time and the start time to get an elapsed duration into the stage. But even so: how do we know where in a stage a car is?

When split times are recorded, we know exactly where the car was at that point in time: it was at the split point location. So how might we determine where a car is on a route that acts as a fair basis for comparison.

One way is to create notional split points at known distances along the actual route. The rgeos::gProject() function finds the points along the stage route that are nearest to the telemetry route points. Specifically, the function returns the distance along the route of the point on the route nearest to a provided location.

This means that we can provide a set of points, such as a telemetry sample location points, and get the distance of a point along the route that is closest to the sample point location.

One thing to note about the rgeos package is that it works with Spatial (sp) objects rather than spatial features (sf) objects, we so need to manage a conversion from one object type to the other in order to call the :gProject() function.

If we also use UTM co-ordinates, the distance along the route is given in meters:

min_pois_utm = st_sfc(st_multipoint(st_coordinates(route_telem_utm)),
                      crs=st_crs(route_telem_utm))

# Handle the conversion from sf to sp objects
# Generate a list of points from a multipoint
# Via: https://github.com/r-spatial/sf/issues/114
min_pois_points_utm = st_cast(x = min_pois_utm, to = "POINT")
min_pois_points_utm_sp = as(min_pois_points_utm, 'Spatial')

# Find the distance along the route of the point on the route
# nearest to each telemetry sample
dist_points = rgeos::gProject(as(stage_route_utm, "Spatial"),
                min_pois_points_utm_sp, normalized = FALSE)

zz = as.data.frame(dist_points)

# Add the distance into stage for each point
route_telem_utm$dist =  dist_points

How does the data look now?

X accx accy altitude brk driverid gear heading kms name rpm speed status throttle track utx X_rally_stageid X_carentryid X_telemetryID X_name delta utc geometry dist
1870 0 0 0 0 NA 0 144 0.6 33 0 166 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 0 2021-10-02 08:37:23 POINT (401052.2 6856758) 531.9830
1869 0 0 0 0 NA 0 150 0.7 33 0 140 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 3200 2021-10-02 08:37:26 POINT (401117.6 6856633) 673.7129
1868 0 0 0 0 NA 0 172 0.8 33 0 60 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 6400 2021-10-02 08:37:30 POINT (401142.7 6856572) 746.9263
1867 0 0 0 0 NA 0 220 0.9 33 0 107 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 9600 2021-10-02 08:37:33 POINT (401093 6856511) 825.5652
1866 0 0 0 0 NA 0 210 1.0 33 0 149 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 12800 2021-10-02 08:37:36 POINT (401020.1 6856413) 947.9104
1865 0 0 0 0 NA 0 208 1.1 33 0 159 Competing 0 NA 1.633164e+12 bcc5537d-d28a-40af-ba86-95dad7fb9cd1 b185c5df-8115-40cf-bd81-566f016f6bf5 /a3f5f3f5-3fb0-42ab-af90-24d91c0493d0/ss07eva_telemetry_js/js Evans 16000 2021-10-02 08:37:39 POINT (400938.8 6856299) 1088.1680

We might thinks of each of these points as notional split points at particular distances along the route.

it is also worth noting that the rgeos::gInterpolate() function complements rgeos::gProject() by providing a function that can also return a location a specified distance along a line:

sample_point_sp_utm = rgeos::gInterpolate(as(stage_route_utm, "Spatial"),
                                          5000, # Distance along route in meters
                                          normalized = FALSE)

# We can convert back from an sp to an sf object:
sample_point_sf_utm = st_as_sf(sample_point_sp_utm)

# And also convert back to a latlong reference system
sample_point_sf = sample_point_sf_utm %>% st_transform(crs = st_crs(latlon_crs))

Let’s see that point, 5km along the route:

leaflet() %>% 
  addProviderTiles("OpenTopoMap", group = "OSM") %>% 
  addPolylines(data=stage_route, color = "black", weight = 3) %>%
  addMarkers(data=sample_point_sf)