给显示区编辑器补上长度参照:比例尺 + 直径标注

用户反馈「专题里的圆无论怎么缩放大小都一样,20km 却覆盖了整个南宁」。实测
三处画圆(编辑器 L.circle、改半径把手、前台迷雾裁剪圆)**全都是地理锚定的**:
编辑器圆直径 z9/z11/z13 = 178/710/2842px(期望 177/711/2847)、同一缩放下改半径
3/10/25/50km → 42/142/356/710px(期望 43/142/355/711)、把手距圆心 20.038km
(该圆半径 20km)、前台裁剪圆换算始终 ≈20km。也就是**复现不出「大小不变」**。

真正的问题是缺**参照物**:编辑器地图以前没有比例尺(前台专题页一直有),
圈圆时只能靠地名猜。用他自己的数据验证「20km 覆盖南宁」是对的——圆心 20km 内
落了他 477 个地点中的 45 个,最远到西乡塘的广西民族大学相思湖校区(14.5km)
与良庆的五象万象汇(10.2km),正是南宁建成区的跨度。

所以这一版补的是参照物,不改任何几何逻辑:
- 编辑器地图加 L.control.scale(跟前台一致,公制)。
- 半径旁边标出跨度:`spanLabel()` 把半径换成「约 40 公里宽」(<1 公里改用米,
  免得出「约 0.1 公里宽」这种别扭说法);圆列表每行也标,新圆半径旁边实时跟着变。
- 守卫 TestTopicAreaEditorGivesSizeReference 钉住这两样(变异验证过:去掉比例尺立刻红)。
  它顺手抓到我自己的疏漏——模板的 init 调用漏传了 spanId,靠脚本默认值兜着,已补上显式传参。

验证:topic-areas.test.js 55 条(新增 11 条 spanLabel 边界);无头实测比例尺在
(z10 显示 10 km)、新圆半径 3→「约 6 公里宽」/25→「约 50 公里宽」实时更新、
圆列表 20→「约 40 公里宽」,全程无 JS 报错。
This commit is contained in:
tamakyi
2026-10-13 10:37:10 +08:00
parent 9cf3d89c3c
commit 6aee7e441b
6 changed files with 116 additions and 1 deletions
+28 -1
View File
@@ -86,6 +86,16 @@
return km / (111.32 * c);
}
// 半径的「人话版」跨度:半径 20 公里 ⇒「约 40 公里宽」。
// 光写「半径 20 公里」很容易被读小 —— 直径才是眼睛看到的那个宽度。
function spanLabel(radiusKm) {
var d = num(radiusKm) * 2;
if (!isFinite(d) || d <= 0) return '';
if (d < 1) return '约 ' + Math.round(d * 1000) + ' 米宽';
if (d < 10) return '约 ' + (Math.round(d * 10) / 10) + ' 公里宽';
return '约 ' + Math.round(d) + ' 公里宽';
}
// 两点距离(公里),球面公式;拖把手时用它反推半径
function kmBetween(a, b) {
var dLat = (b[0] - a[0]) * Math.PI / 180, dLon = (b[1] - a[1]) * Math.PI / 180;
@@ -99,7 +109,8 @@
MAX_AREAS: MAX_AREAS, MIN_KM: MIN_KM, MAX_KM: MAX_KM,
normalize: normalize, validate: validate, serialize: serialize, parse: parse,
add: add, remove: remove, setCenter: setCenter, setRadius: setRadius,
latDelta: latDelta, lonDelta: lonDelta, kmBetween: kmBetween
latDelta: latDelta, lonDelta: lonDelta, kmBetween: kmBetween,
spanLabel: spanLabel
};
// ---------- 以下需要 DOM 与 Leaflet ----------
@@ -126,6 +137,7 @@
var listBox = document.getElementById(opts.listId || 'area-list');
var hint = document.getElementById(opts.hintId || 'area-hint');
var radiusIn = document.getElementById(opts.radiusId || 'area-new-km');
var newSpan = document.getElementById(opts.spanId || 'area-new-span');
var saveBtn = form.querySelector('button[type=submit]');
if (!hidden || !listBox) return;
@@ -149,6 +161,9 @@
L.tileLayer(def.url, {
subdomains: subs, maxZoom: def.maxZoom || 18, minZoom: 3, attribution: def.attr || ''
}).addTo(map);
// 比例尺是必须的:没有它,圈圆时手上没有任何长度参照,只能靠地名猜,
// 「20 公里到底多大」就变成了拍脑袋(前台的地图一直有,这里以前漏了)。
L.control.scale({ imperial: false, position: 'bottomleft' }).addTo(map);
if (crsIn) crsIn.value = def.crs || 'gcj02';
var layer = L.layerGroup().addTo(map);
@@ -164,6 +179,11 @@
if (!isFinite(v) || v < MIN_KM || v > MAX_KM) return 3;
return v;
}
// 「新圆半径」旁边实时显示它会画多大:落点之前就能判断合不合适
function syncNewSpan() {
if (newSpan) newSpan.textContent = spanLabel(newRadius());
}
if (radiusIn) radiusIn.addEventListener('input', syncNewSpan);
// 整块重画:圆最多 20 个,重建比增量对齐简单得多,也不会漏掉「删了中间一个」的下标错位
function redraw() {
@@ -222,6 +242,7 @@
hidden.value = serialize(areas);
redraw();
renderList();
syncNewSpan();
var msg = validate(areas);
if (hint) {
if (msg) {
@@ -289,6 +310,12 @@
lab.appendChild(unit);
row.appendChild(lab);
// 「半径 20 公里」旁边补一句直径 —— 眼睛量的是宽度,光看半径容易读小
var span = document.createElement('span');
span.className = 'muted area-span';
span.textContent = spanLabel(a.radiusKm);
row.appendChild(span);
var go = document.createElement('button');
go.type = 'button';
go.className = 'btn sm';