Radius adjuster: Escape really cancels; steps stay round at round numbers

Two defects found by driving the adjuster through its own controls, rather than
calling setCircleRadius() directly as the first test did.

Escape applied the value it was meant to discard. Escape swaps the input back
out for the figure; removing a focused element fires blur synchronously while it
is still connected, and the blur handler commits — so the cancelled value was
applied a moment later. The input now settles exactly once.

A typed 200 stepped by 5 instead of 10. OpenLayers stores a circle as its centre
and a point on the edge and recomputes the radius as (cx + r) - cx, which at map
coordinates around -278,000 comes back about 2e-11 short. 200 read as
199.99999999997, fell below the 200 threshold in radiusStep(), and stepped by the
smaller increment — at exactly the round numbers officers are likely to type,
and in a way that varied with position. The ground radius is now rounded to the
micrometre where it is read, which removes the noise and nothing anyone can see.
The cosine correction itself was checked across 2000 latitudes spanning Ghana
and round-trips exactly; it was not the source.

Checked end to end at Wa: draw through the tool, type through the input, Enter,
Escape, both steppers, a tap on the adjuster not starting a new measurement, the
popup agreeing with the tooltip, dragging the popup, the close button, reopening,
and Clear. 19 of 19.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ekke 2026-09-17 11:11:26 +02:00
parent 90a5a01b05
commit 7369f62e92
7 changed files with 25 additions and 13 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/index-D5My3hAw.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/index.html vendored
View File

@ -1997,7 +1997,7 @@
}
</style>
<script type="module" crossorigin src="/assets/index-By1A2oQD.js"></script>
<script type="module" crossorigin src="/assets/index-D5My3hAw.js"></script>
<link rel="modulepreload" crossorigin href="/assets/openlayers-D5-VSKzq.js">
<link rel="modulepreload" crossorigin href="/assets/pako-Xa-UToif.js">
<link rel="modulepreload" crossorigin href="/assets/geotiff-B9aROX49.js">

View File

@ -185,7 +185,13 @@ export class MapTools {
/** Ground radius of a measured circle, in metres. */
getCircleGroundRadius(feature) {
const geom = feature.getGeometry();
return groundFromProjected(geom.getRadius(), geom.getCenter());
// Rounded to the micrometre. OpenLayers stores a circle as its centre and a
// point on the edge, and getRadius() recomputes (cx + r) - cx — which, at map
// coordinates in the hundreds of thousands, comes back a few 1e-11 short. A
// typed 200 then reads as 199.99999999997, falls below the 200 threshold in
// radiusStep(), and steps by 5 instead of 10: exactly at the round numbers
// officers are most likely to enter.
return Math.round(groundFromProjected(geom.getRadius(), geom.getCenter()) * 1e6) / 1e6;
}
/**
@ -266,8 +272,14 @@ export class MapTools {
input.focus();
input.select();
// Settles exactly once. Swapping the input out of the DOM fires `blur`
// synchronously while it is still connected, and the blur handler commits —
// so without this guard, Escape would discard the value and then the blur it
// triggers would apply it anyway.
let settled = false;
const commit = (apply) => {
if (!input.isConnected) return;
if (settled || !input.isConnected) return;
settled = true;
if (apply) this.setCircleRadius(feature, parseFloat(input.value));
input.replaceWith(valueBtn);
this._refreshRadiusAdjuster(feature);