The basic ideas
Interpolating points based on location and time is possible using Geometry generator. The basic idea to interpolate between point N
and point N+1
(both with differend values for the datetime attribute arrival_time
) is to create a line between both points, render just a substring of it, representing the percentage of the time elapsed at the current frame, and get the end-point of this substring.
Red point, interpolated between the blue points that correspond to the features of the layer:
Details of the idea
Create a line connecting each point with the following point, using make_line() from point N
to point N+1
Create a substring of this line using line_substring(): the starts at 0 (the start point of the line) and goes up to a certain length. This length corresponds to the percentage of the total time elapsed between the time defined for point N
and point N+1
.
Example: if point N appears at 05:07 and N+1 at 07:37, total time between both points is 2:30 (=150 seconds). At 05:57 (= 50 seconds after point N), a third of the total time has elapsed - so the substring should cover the first thrid of the line
To do so, get the middle of the current frame (mean of @map_start_time
and @map_end_time
) and culculate how many time has elapsed since the time the current feature appeared (here the attribute arrival_time
): subtract arrival_time
from the mean. Divide this value by the total length (time) between the arrival_time
of the current and the next feature (N
and N+1
). This is the percentage of the total time between the two feature's appearing on the map that has elapsed during the current frame. So mulitply this percentage with the total length of the line to get the length of the substring that should be rendered.
Create the end-point of the line with end_point()
Set a condition that the line / end point appears only for time values covered by the current frame: in between N
and N+1
(and not at every feature).
Use a case condition with the variables @map_start_time
and @map_end_time
(defines the start- and end-time of the current animation frame) and the time/datetime attribute from the layer, here arrival_time
. If the mean of the current frame (=the middle between @map_start_time
and @map_end_time
) is smaller then arrival_time
of the next feature (N+1), but larger then arrival_time
of the current feature, then the current frame shows a point between N and N+1. Only in this case, the line substring's end point should be shown.
What to be aware of: different time zones (attributes vs. variables)
This solution is based on the sample data provided here from the question
Animating data with Temporal Controller - When loading the data (csv) to QGIS, each feature comes with an attribute arrival_time
, interpreted as Central European Time (CET) on my system. As the variables for the animation like @animation_start_time
are in UTC, I had to convert arrival_time
to UTC, adding 2 hours: arrival_time + to_interval ('2 hours')
. Adapt this value in the expression below.
Time Controller settings
Make time controller settings in layer properties > Temporal tab: check the box Dynamic temporal control and for Configuration, use Redraw Layer Only
.
Open Temporal controller panel (no 1 in screenshot below), select the button in the middle Animated temporal navigation
and click the icon with two arrows Set to full range
to load min/max values for the anmination (no. two in screenshot.
Set the step between each frame, here e.g. 1 second (no. 3). The higher the value, the faster the point moves.
Click the yellow gear icon (no. 4) to change frame rate (frames per second). The higher the value, the faster the point moves.
The expression
Based on a point layer with a datatime attribute called arrival_time
, the following expression creates an animated point that "moves", based on interpolated points as described above. Use it in Geometry Generator, make the relevant settings in Temporal controller panel as described above and start the animation by clicking the play button.
The expression to use in geometry generator looks like this. The expression uses the following custom variables:
@zone
: number of hours to add to change from UTC to local time zone (change this value on line 3 for other time zones)
@arrival
: changes the attribute value from the field arrival_time
to UTC, based on the value defined in variable @zone
@next
: gets the value for arrival_time
attribute of the next feature
@mapstart
: datetime
timecode of the mean of the current animation frame (where in the timeline the animation currently is)
@line
: creates a line from the current to the next feature (N
to N+1
)
with_variable (
'zone',
2, -- change this value to add/subtract hours to/from attribute named arrival_time to transform local time zone to UTC
with_variable(
'arrival',
arrival_time + to_interval (@zone || ' hours'),
with_variable(
'next',
attribute (get_feature_by_id (@layer, $id+1), 'arrival_time'),
with_variable (
'mapstart',
@map_start_time + to_interval ((second (@map_end_time - @map_start_time)/2 ) || ' seconds'),
with_variable (
'line',
make_line (
$geometry,
geometry( get_feature_by_id (@layer, $id+1))
),
case
when
@mapstart < @next and @mapstart > arrival_time + @frame_duration
then
end_point (
line_substring (
@line,
0,
length (@line) *
minute ( @map_start_time + (second (@frame_duration )/2) || ' seconds' - @arrival ) /
minute ( @next + to_interval (@zone || ' hours') - @arrival)
)
)
end
)
)
)
)
)