From d1bbfdbca5736900bbdbf54a8d57d773b7ec3191 Mon Sep 17 00:00:00 2001 From: Aditya <205600203+Rohilalala@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:10:52 +0530 Subject: [PATCH 1/3] =?UTF-8?q?fix(draw):=20show=20a=209,999.6=20m=C2=B2?= =?UTF-8?q?=20area=20as=201.0=20ha,=20not=2010000=20m=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The draw hint picked m², ha or km² from the raw area and then rounded, so 9,999.5 m² up to a hectare read "10000 m²" and about 999,500 m² up to a square kilometre read "100.0 ha". It now rounds first, as the line measure already does. --- docs/CURRENT-STATE.md | 1 + src/annotations/drawMode.js | 9 ++++++--- src/annotations/drawMode.test.mjs | 27 +++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c55031a6a..2578eb84a 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5350,6 +5350,7 @@ request viewbox and continues searching without that geographic preference. - Drawn-line distance labels select metres or kilometres after rounding to the nearest metre, so a 999.5-metre line displays as `1.0 km` rather than `1000 m`. - **Measurements and centres are computed on unwrapped longitudes** (`unwrapLongitudes` / `wrapLongitude` in `drawMode.js`, used by `ringAreaM2`, `ringCentroid` and the engine's manual-area anchor). A ~50,000 m² rectangle straddling the antimeridian reads its 0.002° width as 359.998° under raw arithmetic: it measured thousands of square kilometres and anchored on the Greenwich meridian, half a world from the shape. Path length was already safe — haversine is periodic in the longitude delta — and a test pins that so a "fix" cannot break it. - **Bounds.** One shape holds at most `MAX_VERTICES` = 512 points; two clicks within 0.5 m are one vertex (that is what makes a double-click finish without leaving a stray point); a vertex that is not a finite on-globe coordinate is refused. A shape that encloses nothing is refused rather than placed: three collinear points are not an area and a line under a metre long is not a line, and the hint says which. +- Drawn-area measures round before choosing m², ha or km², so 9,999.6 m² displays as `1.0 ha`, not `10000 m²`, and 999,960 m² as `1.00 km²`, not `100.0 ha`. - Pointer ownership: while a session is open the tool holds the single pointer claim (`claimPointer('draw')`, `src/data/inputOwnership.js`) and **every** ambient scene selection handler — the shared tracking click gesture behind flights, military and CCTV, plus vessels, satellites, launches, fires, installations, bikeshare, radio, submarine cables, the bundled GeoJSON layers and the CCTV calibration gizmo — returns before picking. A vertex over an aircraft is a vertex, not a selection. - **The claim is a LEASE.** `claimPointer(owner)` returns an opaque token or `null`; `releasePointer(lease)` takes that token. A second claim fails even under the same owner name, because two live instances of one tool — an old one mid-teardown and its replacement — are two owners; releasing by name let the old instance free the claim its successor was holding, and every layer started selecting through the live session's vertices. A superseded lease now releases nothing. - **Cesium's own click actions are borrowed too.** The Viewer binds a selecting LEFT_CLICK and a tracking LEFT_DOUBLE_CLICK on its own handler, outside every layer and therefore outside the shared claim — guarding the layers alone still left a drawn vertex selecting the entity under it. Both are removed for the session and restored, as the same function objects, when it ends or the tool is destroyed. diff --git a/src/annotations/drawMode.js b/src/annotations/drawMode.js index 538c2e5da..93f75dbdc 100644 --- a/src/annotations/drawMode.js +++ b/src/annotations/drawMode.js @@ -215,9 +215,12 @@ export function formatMeasure(session) { } if (session.shape === 'area') { const m2 = ringAreaM2(session.vertices); - if (m2 >= 1e6) return `${(m2 / 1e6).toFixed(2)} km²`; - if (m2 >= 1e4) return `${(m2 / 1e4).toFixed(1)} ha`; - return `${Math.round(m2)} m²`; + // Round before each unit cut, as for a line. 9,999.6 m² was "10000 m²". + const whole = Math.round(m2); + const ha = (m2 / 1e4).toFixed(1); + if (whole < 1e4) return `${whole} m²`; + if (Number(ha) < 100) return `${ha} ha`; + return `${(m2 / 1e6).toFixed(2)} km²`; } return ''; } diff --git a/src/annotations/drawMode.test.mjs b/src/annotations/drawMode.test.mjs index be9894dda..51e3b854e 100644 --- a/src/annotations/drawMode.test.mjs +++ b/src/annotations/drawMode.test.mjs @@ -345,3 +345,30 @@ test('a line that rounds up to a kilometre reads 1.0 km, not 1000 m', () => { assert.equal(formatMeasure(line), '1.0 km'); assert.match(drawHint(line), /^1\.0 km · /); }); + +test('an area that rounds up to the next unit reads in that unit', () => { + // A square on the equator whose shoelace area is about `m2`. + const square = (m2) => { + const side = Math.sqrt(m2) / 111320; + const area = createDrawSession('area'); + for (const [lon, lat] of [ + [0, 0], + [side, 0], + [side, side], + [0, side], + ]) + addVertex(area, { lon, lat }); + return area; + }; + const almostHectare = square(9999.6); + const metres = ringAreaM2(almostHectare.vertices); + assert.ok( + metres >= 9999.5 && metres < 1e4, + `area should round to 10000 m², got ${metres}`, + ); + assert.equal(formatMeasure(almostHectare), '1.0 ha'); + assert.match(drawHint(almostHectare), /^1\.0 ha · /); + assert.equal(formatMeasure(square(9999.4)), '9999 m²'); + assert.equal(formatMeasure(square(999_960)), '1.00 km²'); + assert.equal(formatMeasure(square(999_400)), '99.9 ha'); +}); From 76a3d47e563d03f0d90415ae67d65c879effc18f Mon Sep 17 00:00:00 2001 From: Aditya <205600203+Rohilalala@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:24:42 +0530 Subject: [PATCH 2/3] docs(changelog): add #1446 fragment --- changelog.d/1446-draw-area-units.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/1446-draw-area-units.md diff --git a/changelog.d/1446-draw-area-units.md b/changelog.d/1446-draw-area-units.md new file mode 100644 index 000000000..ef82d6628 --- /dev/null +++ b/changelog.d/1446-draw-area-units.md @@ -0,0 +1,3 @@ +- Show a drawn area of 9,999.5 m² or more as 1.0 ha, and of 999,500 m² or + more as 1.00 km². The draw hint picked the unit before rounding, so it read + "10000 m²" and "100.0 ha" (Aditya, @Rohilalala, #1446). From cb6fd7568a8a4993cc835b7a2f96a206fd0f4991 Mon Sep 17 00:00:00 2001 From: Said Turksever Date: Sat, 31 Oct 2026 23:18:42 +0000 Subject: [PATCH 3/3] =?UTF-8?q?test(draw):=20pin=20the=20shoelace=20area?= =?UTF-8?q?=20in=20the=20km=C2=B2=20and=20ha=20rounding=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/annotations/drawMode.test.mjs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/annotations/drawMode.test.mjs b/src/annotations/drawMode.test.mjs index 51e3b854e..8018b4527 100644 --- a/src/annotations/drawMode.test.mjs +++ b/src/annotations/drawMode.test.mjs @@ -369,6 +369,19 @@ test('an area that rounds up to the next unit reads in that unit', () => { assert.equal(formatMeasure(almostHectare), '1.0 ha'); assert.match(drawHint(almostHectare), /^1\.0 ha · /); assert.equal(formatMeasure(square(9999.4)), '9999 m²'); - assert.equal(formatMeasure(square(999_960)), '1.00 km²'); - assert.equal(formatMeasure(square(999_400)), '99.9 ha'); + // Pin the shoelace area too, so a geodesy change fails here and not as a unit. + const almostKm2 = square(999_960); + const nearKm2 = ringAreaM2(almostKm2.vertices); + assert.ok( + nearKm2 >= 999_500 && nearKm2 < 1e6, + `area should round to 100.0 ha, got ${nearKm2}`, + ); + assert.equal(formatMeasure(almostKm2), '1.00 km²'); + const under = square(999_400); + const underM2 = ringAreaM2(under.vertices); + assert.ok( + underM2 >= 998_500 && underM2 < 999_500, + `area should round to 99.9 ha, got ${underM2}`, + ); + assert.equal(formatMeasure(under), '99.9 ha'); });