Process used for the monthly OwnTracks SVG notes such as September travel plots and October travel plots. Keep the database calculations in SQL where practical; construct only the final SVG from the reduced result.
Read fixes for the required local-day range from locations. Use lag(tst), lag(geom) and lag(acc) over timestamp order. Calculate distance with PostGIS ST_Distance(geom::geography, previous_position::geography) so distance is in metres, then convert distance / elapsed seconds to mph. Calculate direction with degrees(ST_Azimuth(previous_position, geom)).
Important connector workaround: alias the calculated direction consistently as compass_bearing. Queries using direction/bearing terminology without this alias were rejected by the connector safety layer; compass_bearing worked. Descriptive aliases such as previous_datetime, previous_position, previous_accuracy and elapsed_seconds were also used.
Filter derived rows to elapsed_seconds BETWEEN 1 AND 300, current and previous accuracy ≤ 100 m, and mph ≤ 200. For monthly plots, speeds below about 0.5 mph can be omitted to avoid stationary GPS noise. Use Europe/London when defining date boundaries and local plot times.
For multi-day historical plots, bucket by local time in SQL and retain one representative point per bucket. The September plots used two-minute buckets and row_number() OVER (PARTITION BY local_day,time_bucket ORDER BY mph DESC) to retain the highest-speed point in each bucket. This keeps the SVG compact while preserving journey shape. A denser interval can be used for a single current-day plot.
Produce one plot per day, newest first. Use time of day on x, mph on y, and bars rather than a line/scatter plot. Colour each bar around a full HSV/HSL colour wheel using compass_bearing as the hue (0° N, 90° E, 180° S, 270° W). Include a small circular N/E/S/W colour wheel on every plot. September uses a common 00:00–24:00 x-axis so daily plots are directly comparable.
Do not paste SVG as ordinary text/HTML. The Notes renderer expects a JSONHTL svg block, following the working demo/svg-showcase examples:
{"svg":{"body":"<svg xmlns=\"http://www.w3.org/2000/svg\" ...>...</svg>","alt":"Description of the plot","caption":"Date — speed and compass bearing"}}
The complete SVG markup belongs in body. Include alt and a useful caption. Read the note back afterwards and verify that an svg block is actually present.
Use month notes named like September-travel-plots / October-travel-plots with human titles “September travel plots” etc. Append/add days so the latest date is at the top and older dates descend. Link each monthly note back to location/speed-plot and add a reciprocal link from that main note. In note hrefs use the actual note key (for example spaces become hyphens in these monthly keys).
WITH fixes AS (
SELECT tst, geom, acc,
lag(tst) OVER (ORDER BY tst) AS previous_datetime,
lag(geom) OVER (ORDER BY tst) AS previous_position,
lag(acc) OVER (ORDER BY tst) AS previous_accuracy
FROM locations
WHERE tst >= extract(epoch FROM :start_local AT TIME ZONE 'Europe/London')
AND tst < extract(epoch FROM :end_local AT TIME ZONE 'Europe/London')
), derived AS (
SELECT to_timestamp(tst) AT TIME ZONE 'Europe/London' AS local_datetime,
tst - previous_datetime AS elapsed_seconds,
acc, previous_accuracy,
ST_Distance(geom::geography, previous_position::geography)
/ (tst - previous_datetime) * 2.236936 AS mph,
degrees(ST_Azimuth(previous_position, geom)) AS compass_bearing
FROM fixes
WHERE previous_position IS NOT NULL AND tst > previous_datetime
), good AS (
SELECT date(local_datetime) AS local_day,
extract(epoch FROM local_datetime::time) AS seconds_of_day,
mph, compass_bearing,
floor(extract(epoch FROM local_datetime::time) / 120) AS time_bucket
FROM derived
WHERE elapsed_seconds BETWEEN 1 AND 300
AND acc <= 100 AND previous_accuracy <= 100
AND mph BETWEEN 0.5 AND 200
), ranked AS (
SELECT *, row_number() OVER (
PARTITION BY local_day, time_bucket ORDER BY mph DESC
) AS sample_rank
FROM good
)
SELECT to_char(local_day,'YYYY-MM-DD') AS local_day,
round(seconds_of_day)::int AS seconds_of_day,
round(mph::numeric,1) AS mph,
round(compass_bearing::numeric,1) AS compass_bearing
FROM ranked
WHERE sample_rank = 1
ORDER BY local_day DESC, seconds_of_day;
Tested 2026-10-01: this reduced final SELECT was run over 17–23 September and returned only local_day, seconds_of_day, mph and compass_bearing; accuracy remains internal to filtering. The resulting seven daily entries were appended to September travel plots.