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:
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[],] %>% st_zm()route_telem
Let’s preview the first few rows of that data:
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()
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
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
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_pointsroute_telem_utm
How does the data look now?
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)