The solution
To create the blinking pattern you asked for, activate the new Animated Marker (available since QGIS 3.26).
Then use data driven override for Enable symbol layer
with the following expression (explanation below):
Result, realizing your pattern: 6 quick flashes + one long flash in a total period of 15 seconds; the numbers from 0 to 999 show milliseconds, but framerate is adapted to result in a 15 seconds loop:
array_sum (
with_variable (
'range',
array_cat (generate_series (0,11), array(16,22,30)),
with_variable (
'milliseconds',
right (datetime_from_epoch (@symbol_frame ), 3),
array_foreach (
generate_series (0,14,2),
if (
@milliseconds >=round(@range[@element]*100/3) and
@milliseconds <= round(@range[@element+1] *100/3 ),
1,
0
)))))
1. Create blinking pattern and store it as variable @range
You have the blinking pattern (bold=on, italic=off) like this (first row). Muliply it by 2 to get whole numbers (2nd row). If you add the cummulatively add these numers, you get the following pattern with the timecode when the light goes on or off (3rd row):
So a first challange is to create an array with this irregular pattern (row 3: 1 to 30). Do it in two steps - for numbers 1 to 11 (regular part) and the last three numbers (irregular part), then combine both arrays using array_cat()
. The following expression generates this pattern (starting in fact with 0 where the animation starts):
array_cat (generate_series (0,11), array(16,22,30))
output: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 22, 30 ]
2. Create values of 0 to 999 as variable @milliseconds
This values represent milliseconds to be able to control blinking intervals shorter then 1 second. This is a bit tricky as the only QGIS expressions that work with milliseconds are datetime_from_epoch()
and epoch()
(thanks for the hint @MrXsquared)
So to crete these values, use this expression that retrieves only the last 3 digits of the epoch datetime: right (datetime_from_epoch (@symbol_frame), 3)
output: a number from 0
to 999
that changes each time the frame is updated (depending on framerate defined).
3. Check if current @milliseconds
value is in the range of the "light on" timecode.
To do so, we take the first value of the array from step 1 (index 0 using index operator)
- So from startpoint (0 milliseconds) to this first value (index 0): light on (timecode 0 to 1)
- At this first value, light turns off until the second value (index 1): light off (timecode 1 to 2)
- From second value (index 1) to third value (index 2): light on (timecode 2 to 3)
So light starts at even index numbers from the array, as follows: 0, 2, 4, 6 etc.
Light turns off at uneven index number from the array: 1, 3, 5, 7 etc.
So we iterate (array_foreach()
) through the array but only take every second element for start time, so we create another array for the iteration with only even numbers from 0 to 14 like this: generate_series (0,14,2)
Now we check if the current @milliseconds
value is inside one of the ranges (0 to 1, 2 to 3, 4 to 5 etc.). We convert these timecodes to milliseconds, so instead of 0 to 30 we get 0 to 1000 (muiltiply by 100, divide by 3).
Now, we have all the necessary values together to check: get start of blinking time with @range[@element]
and end of it with @range[@element+1]
. Include it in an if()
clause and assign values of 1/0 for true (@milliseconds falls inside one of the blinking intervals) or false (@milliseconds falls not inside any interval).
if (
@milliseconds >=round(@range[@element]*100/3) and
@milliseconds <= round(@range[@element+1] *100/3 ),
1,
0
)
We get an array consisting of values 0 (most) and 1 (in case @milliseconds is in the range). Create the array's sum and we're finally done!
If you now set the framerate to 66.66 fps, you get the pattern to blink in a total period of 15 seconds. Why 66.66 frame per second? Well, we have 1000 milliseconds (milleseconds to be able to have time values under 1 second) which we will want to rund within 15 seconds, so 1000 / 15 = 66.66 frames per second.
It can be that QGIS, depending on the hardware, is unable to cope with this (high) framrate and to redraw/render the whole canvas 67 time a second. You can change the values accordingly to use a smaller framerate, e.g. ten times less (6.66 fps) and using 1/100 seconds (instead of milliseconds, 1/1000 second). To do so, change expression as follows:
Line 7 (definition of variable @milliesconds
): use 2 (instead of 3) rightmost characters of the string, in fact getting 0.01 seconds instead of 0.001:
right (datetime_from_epoch (@symbol_frame ), 2)
Lines 11 and 12: multiply by 10 (instead of 100)
@milliseconds >=round(@range[@element]*10/3
Like this, you get the same animation pattern in the same time, but with 10 times less frames to spare the hardware/rendering.
data defined override
and add something like this:Case When second(now()) > 0 AND second(now()) <= 30 Then '#990000' When second(now()) > 30 AND second(now()) <= 60 Then '#00FF00' end
. I will take a look at what is possible with this...