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
= units::set_units(100, m)
buffer_margin_100m
= st_buffer(stage_route_utm, buffer_margin_100m)
buffered_route_utm = st_transform(buffered_route_utm, original_crs)
buffered_route
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
= st_transform(route_telem,
route_telem_utm 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
= st_intersects(buffered_route_utm, telem_df_min_utm)
route_telem_intersect
# And then filter on those points
# Also nullify the Z dimension
= telem_df_min[route_telem_intersect[[1]],] %>% st_zm() route_telem
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:
= st_sfc(st_multipoint(st_coordinates(route_telem_utm)),
min_pois_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
= st_cast(x = min_pois_utm, to = "POINT")
min_pois_points_utm = as(min_pois_points_utm, 'Spatial')
min_pois_points_utm_sp
# Find the distance along the route of the point on the route
# nearest to each telemetry sample
= rgeos::gProject(as(stage_route_utm, "Spatial"),
dist_points normalized = FALSE)
min_pois_points_utm_sp,
= as.data.frame(dist_points)
zz
# Add the distance into stage for each point
$dist = dist_points route_telem_utm
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:
= rgeos::gInterpolate(as(stage_route_utm, "Spatial"),
sample_point_sp_utm 5000, # Distance along route in meters
normalized = FALSE)
# We can convert back from an sp to an sf object:
= st_as_sf(sample_point_sp_utm)
sample_point_sf_utm
# And also convert back to a latlong reference system
= sample_point_sf_utm %>% st_transform(crs = st_crs(latlon_crs)) sample_point_sf
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)