Perceptual Hashing for Map Tile Comparison
Perceptual hashing is the most over-recommended technique in visual regression and one of the most useful once its role is correct. It reduces an image to a short fingerprint whose distance from another fingerprint tracks whether the two are broadly the same picture, and it does so in microseconds. On a map frame that property is genuinely valuable — and it is also, precisely, a description of what a map test does not care about, because every fault worth catching lives in detail the hash is designed to throw away. This page is about using it for what it is good at.
This is a task within Diff Algorithm Tuning for Cartography, under Web Map Visual Testing Fundamentals & Toolchains. It sits in the layered comparison order that page describes, above byte equality and below the pixel count.
Prerequisites
Where a hash sits among the other measures
The layered order matters as much as the individual measures. On a deterministic pipeline the great majority of captures are byte-identical to their baseline, so the first line resolves them for free. The hash then costs microseconds on what remains and can settle the extreme cases at both ends. Only what survives both reaches the passes that cost real time.
What the hash keeps and what it throws away
Follow a specific fault through the pipeline to see why this is decisive. A dropped street label occupies perhaps eighty by twelve pixels in a 1280 by 800 frame — about a tenth of one percent of the area. Downscaled to an eight by eight grid, it contributes a fraction of the average brightness of one cell. After the DCT keeps only low frequencies, it contributes nothing measurable. The resulting fingerprint is bit-identical to the baseline’s.
That is not a tuning problem. Increasing the hash size to 256 bits shifts the boundary a little and does not change the character: the technique’s entire purpose is invariance to exactly this class of small, local change, which is what makes it robust for finding a re-encoded photograph and useless for finding a missing label.
The three jobs worth using it for
As a cache key. Store the fingerprint alongside each baseline. When a candidate’s fingerprint matches, the comparison can still run — but the result is overwhelmingly likely to be a pass, so this is a useful way to order work: hash everything first, then run the expensive comparison on the non-matching set first, so a failing run reports its failures early rather than after twenty minutes of passes.
const phash = computeHash(candidate);
const suspicious = phash !== baseline.phash; // process these first
As a catastrophe detector. A Hamming distance above roughly a quarter of the bit length means the two images are not the same picture at all. On a map suite that almost always means one of four things: the frame is blank because the canvas was captured before anything drew, the camera is at the wrong place because a fixture did not apply, the style failed to load, or the capture caught an error page. Reporting that distinctly — “capture does not resemble its baseline” rather than “changed-pixel ratio 62%” — sends the investigation to the right place immediately.
if (hamming(phash, baseline.phash) > 16) {
throw new Error('capture does not resemble its baseline — check camera, style and readiness');
}
As a matrix deduplication key. Captures of the same scenario on different engines that share a fingerprint are candidates for the cell collapse described in Maintaining Per-Engine Baselines for Leaflet and OpenLayers. The hash cannot confirm the collapse — that needs byte equality over repeated runs — but it can find the candidates cheaply across a large matrix, which is otherwise a tedious audit.
Verification
Confirm the procedure worked before wiring it into a blocking gate:
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The hash flags captures that look identical | The frame contains a large low-frequency element that moved slightly — commonly a hillshade or a full-frame gradient | Raise the catastrophe threshold; the hash is doing its job, and this is not the measure to make sensitive |
| The hash never flags anything, including a blank frame | The fingerprint is being computed over the wrong region, often the whole page rather than the map canvas | Compute it over the same region the comparison uses, or the two measures are describing different images |
| Two engines produce different fingerprints for an apparently identical raster scene | One of them is applying a colour profile the other is not, which shifts every cell’s brightness | Force sRGB in both environments before treating the difference as meaningful |
Frequently asked questions
Should the hash ever fail a build on its own?
Only for the catastrophe case, and it is worth doing there because the failure it produces is much clearer than the one the pixel measures produce for the same situation. A blank capture reported as “does not resemble its baseline” saves the reviewer from opening a diff that is entirely magenta. For anything short of that, the hash should inform the ordering and the reporting, never the verdict.
Which hash variant is best for map frames?
The DCT-based variant is the usual choice and its behaviour on map frames is the analysis above. The average-hash variant is slightly cheaper and slightly more sensitive to overall brightness, which on a map means it reacts to a basemap theme change more readily — occasionally useful. Neither becomes suitable as a gate, so the choice between them is minor.
Does hashing help with the tile-level comparison rather than the frame?
More than at the frame level, because a single 256-pixel tile is small enough that a dropped label occupies a meaningful fraction of it. Even there the hash is a filter rather than a verdict — but a tile-level fingerprint index is a genuinely efficient way to find which tiles in a large pyramid changed between two builds of a basemap, which is a different problem from testing an application.
How does it interact with the layered comparison order?
It slots between byte equality and the changed-pixel count, as shown above. The important discipline is that a hash match never short-circuits the pixel pass on a capture that is being gated — it only reorders work and detects catastrophes. A team that uses a hash match to skip comparison entirely has replaced their gate with a much weaker one and will not notice for months.
Related
- Up to Diff Algorithm Tuning for Cartography, and the section Web Map Visual Testing Fundamentals & Toolchains.
- Comparing Pixel Diff vs Structural Diff for GIS Overlays — the two measures the hash sits in front of.
- Tuning SSIM Thresholds for Vector Basemap Diffs — the expensive measure the ordering protects.
- Maintaining Per-Engine Baselines for Leaflet and OpenLayers — the collapse audit the deduplication use feeds.