diff --git a/templates/activities/show.php b/templates/activities/show.php index 34f0c9c..37bcf74 100644 --- a/templates/activities/show.php +++ b/templates/activities/show.php @@ -169,55 +169,155 @@ require dirname(__DIR__) . '/comments/_block.php'; map = new AMap.Map(el, { viewMode: '2D', zoom: 5 }); var overlays = []; // 当前画上去的所有覆盖物;地理编码补点后整体重画 + /* ── 防重合(批 14)──────────────────────────────────────── + 两类重合,都要治: + ① 往返:A→B 与 B→A 中点是同一个、线段也完全重合 + → 线段沿**法线**错开(像双向车道各占一边),标记在屏幕上错开; + ② 距离很近的地点(相差几公里):坐标不同,但在"看全国"的缩放级别下 + 像素位置几乎一致 → 只能按**屏幕像素**判定碰撞再错开。 + 所以标记一律按像素排布(与缩放级别同步重算),不再只比坐标字符串。 + 注意:错开只改「怎么画」,不改数据 —— prevEnd 仍用原始坐标。 */ + var MARK_W = 46; // 标记直径 + 间隙(CSS 里圆标 40px) + var MARK_H = 44; + + function segKey(p1, p2) { + var a = p1[0].toFixed(5) + ',' + p1[1].toFixed(5); + var b = p2[0].toFixed(5) + ',' + p2[1].toFixed(5); + return a < b ? a + '|' + b : b + '|' + a; // A→B 与 B→A 同一键 + } + function posKey(p) { + return p[0].toFixed(5) + ',' + p[1].toFixed(5); + } + function markerPos(n) { + var s = n.start, en = n.end; + if (s && en) return [(s.lng + en.lng) / 2, (s.lat + en.lat) / 2]; + if (s) return [s.lng, s.lat]; + if (en) return [en.lng, en.lat]; + return null; + } + /** 同一段重复出现时,沿法线左右交替错开(+1 / -1 / +2 / -2 …) */ + function makeSegOffsetter() { + var seen = {}; + return function (p1, p2) { + var key = segKey(p1, p2); + var n = seen[key] || 0; + seen[key] = n + 1; + if (n === 0) return [p1, p2]; + var dx = p2[0] - p1[0], dy = p2[1] - p1[1]; + var len = Math.sqrt(dx * dx + dy * dy) || 1e-9; + var mag = Math.max(len * 0.06, 0.0008); // 长段按比例、短段给下限 + var k = (n % 2 === 1) ? (n + 1) / 2 : -(n / 2); + var ox = (-dy / len) * mag * k; + var oy = (dx / len) * mag * k; + return [[p1[0] + ox, p1[1] + oy], [p2[0] + ox, p2[1] + oy]]; + }; + } + /** 贪心排布:和已放好的标记撞上就左右交替挪开,返回像素偏移 */ + function makePlacer() { + var placed = []; // 已占用的屏幕坐标(容器像素) + return { + place: function (base, id) { + var dx = 0, guard = 0, k = 0; + while (guard++ < 30) { + var hit = false; + for (var j = 0; j < placed.length; j++) { + var q = placed[j]; + if (Math.abs(base.x + dx - q.x) < MARK_W + && Math.abs(base.y - q.y) < MARK_H) { + hit = true; + break; + } + } + if (!hit) break; + k++; + dx = (k % 2 === 1) ? Math.ceil(k / 2) * MARK_W : -Math.ceil(k / 2) * MARK_W; + } + placed.push({ x: base.x + dx, y: base.y, id: id }); + return dx; + }, + reset: function () { placed = []; }, + }; + } + /* 全量重画:先清掉旧覆盖物再按当前 route 数据画。 - 地理编码是异步的,一个点查回来就重画一次,数据是幂等的。 */ - function drawRoute() { + 地理编码是异步的,一个点查回来就重画一次,数据是幂等的。 + fromZoom=true 表示这次是「用户缩放了」触发的重排:只重排标记, + 不再 setFitView —— 否则会和缩放事件互相触发,还会把用户手动 + 缩放好的视野又拉回去。 */ + function drawRoute(fromZoom) { overlays.forEach(function (o) { map.remove(o); }); overlays = []; var inPath = []; // 节点内:出发地 → 到达地(实线) var hopPath = []; // 节点之间:上一站到达地 → 下一站出发地(虚线) var prevEnd = null; + var shiftSeg = makeSegOffsetter(); + var placer = makePlacer(); // 标记按屏幕像素排布,撞上就挪开 + var dotSeen = {}; // 到达地红点:同一点位只画一个 route.forEach(function (n, i) { var s = n.start, en = n.end; - if (s && en) inPath.push([s.lng, s.lat], [en.lng, en.lat]); - if (s && prevEnd) hopPath.push([prevEnd.lng, prevEnd.lat], [s.lng, s.lat]); + if (s && en) { + var seg = shiftSeg([s.lng, s.lat], [en.lng, en.lat]); + inPath.push(seg[0], seg[1]); + } + if (s && prevEnd) { + var hop = shiftSeg([prevEnd.lng, prevEnd.lat], [s.lng, s.lat]); + hopPath.push(hop[0], hop[1]); + } /* 圆形标记放这段行程线的中点(起终点都有才取中点),缺一头就放有的一头。 ⚠️ position 必须是 [lng, lat] 数组 —— 把 {lng,lat} 对象直接传给 AMap 会抛 Invalid Object,forEach 当场中断、整张地图一个覆盖物 都不剩(批 11 上线后踩的坑,光秃秃地图就是它)。 */ - var pos = (s && en) - ? [(s.lng + en.lng) / 2, (s.lat + en.lat) / 2] - : (s ? [s.lng, s.lat] : (en ? [en.lng, en.lat] : null)); + var pos = markerPos(n); if (pos) { - var html = '