The heat map draws grid squares, so diagonal streets render as staircases #62
Labels
No labels
area/agent
area/capture
area/editor
area/geo
area/security
area/sync
area/transcription
area/ui
area/vault
area/voice-memos
blocked
milestone/M1
milestone/M2
milestone/M3
milestone/M4
needs-decision
needs-hardware
p0
p1
p2
type/bug
type/enhancement
type/feature
type/idea
type/infrastructure
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Stash/stash-ios#62
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The heat map draws one filled square per grid cell. On the ground that reads as blocky, and any street that is not aligned to the grid's axes renders as a staircase rather than a line.
Tabled deliberately. iOS is closing out after #60 and Stash pivots to the
stash-docscontracts. This is filed so it is not lost, not to be picked up now.What it looks like
Observed by Wes on device, 2026-08-11, on a library of walks around one town:
North-south and east-west streets look acceptable because they happen to run along the grid's axes. A street at roughly 45° renders as a visible flight of steps, each tread 25 m — the cell size. Junctions bloom into squares wider than any road.
Where it is
Stash/Views/HeatOverlay.swift:194-209, indraw(_:zoomScale:in:):One
context.fillof an axis-aligned rectangle per cell, atHeatGrid.metresPerCell = 25.The grid itself is not the problem and should not be changed.
HeatGrid's two load-bearing rules are right and this issue does not touch them: a cell's weight is the number of distinct recordings that crossed it, not the number of fixes, so standing still does not outrank a street walked a hundred times; and the colour curve is logarithmic, so a street walked five times is distinguishable from one never walked. The defect is purely that the counting model is also being used as the drawing model.Approaches considered
Snap to the road network — rejected
The literal reading of "matched to the road/sidewalk paths", and the wrong answer here for two independent reasons.
It cannot be done privately. MapKit exposes no map-matching API. Doing it means either an external service (Mapbox Map Matching, Valhalla, OSRM) — which means sending a user's location history off the phone, against the property this project exists to hold — or bundling road geometry for offline matching, which is a large data problem for a feature that draws a picture.
It would be false. Wes named this himself: he is not always on a road. His own capture crosses open green space between Longfellow and Greenbrier. Map matching does not represent that as "walked across the park"; it snaps it to the nearest street and asserts a route that was never taken. The heat map's question is where do I actually go, and where do I not — a renderer that relocates the honest answer onto the nearest road defeats it.
Blur the grid into a density field — viable, cheap, not recommended
Convolve the cell counts with a Gaussian and render the smooth field. Small change, fully on device, removes the hard edges.
It removes the staircase and replaces it with soft blobs that still follow the grid's ghost rather than the walked line. It is the least work and the least payoff.
Render the tracks, weighted by the grid — recommended
Stop drawing the grid. Keep
HeatGridexactly as it is, as the counting model, and draw the actual routes:mostALegEstablishes(200 m) andmostALegMaySpan(30 s) — so a gap stays a gap and no line is drawn through a building.HeatGrid's count for the cell the segment passes through, through the existing logarithmicintensity(of:).The line then follows the ground actually covered — on the street where the walk was on the street, across the park where it was across the park — and no external data is consulted. Colour keeps meaning what it means today, because the number behind it is unchanged.
Taking alpha from the grid rather than from overlapping strokes is the part that matters: additive alpha over repeated passes would render GPS jitter as a fat fuzzy band and make a street walked once beside a street walked fifty times hard to tell apart.
What this has to keep working
TrackFramingand the initial camera framing.Open
Whether an
MKOverlayRendererdrawing many smoothed strokes performs acceptably against a large library at low zoom, where today's renderer draws one rect per visible cell and culls cheaply. Worth measuring before committing to the approach; if it does not hold, the fallback is the density field above.The artifact on device
Screenshot, 2026-08-11, the "Where I Walk" screen against the current library.
Streets running north-south and east-west read as continuous bands, because they happen to run along the grid's axes. The diagonal streets in the west of the frame render as a clear flight of steps, each tread one 25 m cell. Junctions bloom into squares several times wider than the roads meeting at them, and a walk across open green space reads as the same blocks as a walk down a street — which is correct as data, and is exactly the case the rejected map-matching approach would have drawn wrongly.
Posted with Wes's explicit agreement, having been asked first: this repository is public and the image shows his routine walking routes with street names legible.