路线地图:线允许重合,重合的图标沿线排开(批 15)
批 14 为了治「往返两段标记互相遮挡」,把线段沿法线左右错开画成双向车道。 实测偏移量太大,看着像画错了两条路,用户反馈「两条线偏离的非常多」。 改为**线不动、只动图标**: - **线段一律按原始坐标画**(去掉 makeSegOffsetter 与法线偏移)。往返行程 本来就是同一条路,两条线重合是正常的,用户也是这么要求的。 - **重合的图标沿这条线前后排开**:位移方向取线段**在屏幕上的方向** (makeSegDir 缓存「不分方向的同一段」的方向,A→B 与 B→A 共用), 而不是固定往右挪 —— 看上去就是同一条路上跑了两趟,图标仍落在线上。 线段太短装不下时才退到法线、最后水平兜底。 - 碰撞判定改用**中心距**(Math.hypot < 46,圆标直径 40):斜着挪 56px 时 两个方盒子在角上仍相交、用矩形判定会说「还重叠」,于是白挪一步 (图标被拉到 104px 远,看着像没关系)。 - 修一个候选列表的 bug:cand[0](原地不动)也要先判是否可用,跳过它会让 **第一枚**标记也被无谓挪开一步。 验证:真 AMap 无头浏览器用例扩到 14 项,新增两条同向断言 —— ① 实线顶点必须**逐点等于** data-route 里的原始坐标(一旦又去偏移线段就会偏离); ② 每枚图标到自己那条线的屏幕距离 <= 22px(证明是沿线排开而不是被甩到线外)。 另留一张 map15.png 截图肉眼复核。本地全链 12 步全绿(渲染检查 356 项、 真 AMap 浏览器 14 项);远端 e2e 355 项 0 失败;部署静态断言 201 条、 双 PHP 渲染逐字节一致、内容断言全过。
This commit is contained in:
@@ -167,18 +167,23 @@ require dirname(__DIR__) . '/comments/_block.php';
|
||||
if (!route.length) return;
|
||||
|
||||
map = new AMap.Map(el, { viewMode: '2D', zoom: 5 });
|
||||
/* 挂到 window 供自动化测试(真浏览器量标记位置)与线上排查用;页面卸载即释放 */
|
||||
window.__routeMap = map;
|
||||
var overlays = []; // 当前画上去的所有覆盖物;地理编码补点后整体重画
|
||||
|
||||
/* ── 防重合(批 14)────────────────────────────────────────
|
||||
两类重合,都要治:
|
||||
① 往返:A→B 与 B→A 中点是同一个、线段也完全重合
|
||||
→ 线段沿**法线**错开(像双向车道各占一边),标记在屏幕上错开;
|
||||
② 距离很近的地点(相差几公里):坐标不同,但在"看全国"的缩放级别下
|
||||
像素位置几乎一致 → 只能按**屏幕像素**判定碰撞再错开。
|
||||
所以标记一律按像素排布(与缩放级别同步重算),不再只比坐标字符串。
|
||||
注意:错开只改「怎么画」,不改数据 —— prevEnd 仍用原始坐标。 */
|
||||
var MARK_W = 46; // 标记直径 + 间隙(CSS 里圆标 40px)
|
||||
var MARK_H = 44;
|
||||
/* ── 防重合(批 15 改法)────────────────────────────────────
|
||||
两类重合,治法不同:
|
||||
① 往返(A→B 与 B→A 是同一段):**线就让它重合** —— 把两段线左右错开画成
|
||||
双向车道,偏移量大、看着像画错了;真正的诉求只是「两枚图标别叠在一起」。
|
||||
所以只把图标沿这条线**前后排开**,看上去就是同一条路上跑了两趟。
|
||||
② 距离很近的地点(相差几公里):坐标不同,但"看全国"的缩放级别下像素位置
|
||||
几乎一致 → 也各自沿自己那条线让开。
|
||||
位移方向一律取**线段本身的屏幕方向**(不是固定往右挪),线段太短装不下时
|
||||
才退到法线、再退到水平。判定仍按**屏幕像素**:坐标不同但屏幕上挨着的情况,
|
||||
比坐标字符串没用(批 14 实测「中央大街/松花江」坐标差几公里照样叠 34px)。
|
||||
注意:错开只改「怎么画」,不改数据 —— inPath / prevEnd 仍用原始坐标。 */
|
||||
var MARK_W = 46; // 碰撞判定:圆标直径 40 + 6px 间隙(按中心距判,见下面的排布器)
|
||||
var STEP = MARK_W + 10; // 让开一步 = 56px(圆标净间隙 16px,两枚挨着也分得清)
|
||||
|
||||
function segKey(p1, p2) {
|
||||
var a = p1[0].toFixed(5) + ',' + p1[1].toFixed(5);
|
||||
@@ -195,45 +200,71 @@ require dirname(__DIR__) . '/comments/_block.php';
|
||||
if (en) return [en.lng, en.lat];
|
||||
return null;
|
||||
}
|
||||
/** 同一段重复出现时,沿法线左右交替错开(+1 / -1 / +2 / -2 …) */
|
||||
function makeSegOffsetter() {
|
||||
var seen = {};
|
||||
/** 一段线在屏幕上的方向:单位向量 + 像素长度。
|
||||
A→B 与 B→A 用同一个键 → 往返两枚图标朝同一头排队,不会各往一边跑。 */
|
||||
function makeSegDir() {
|
||||
var cache = {};
|
||||
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]];
|
||||
if (cache[key]) return cache[key];
|
||||
var d = { x: 1, y: 0, len: 0 }; // 拿不到就退化成水平方向
|
||||
try {
|
||||
var a = map.lngLatToContainer(new AMap.LngLat(p1[0], p1[1]));
|
||||
var b = map.lngLatToContainer(new AMap.LngLat(p2[0], p2[1]));
|
||||
var vx = b.x - a.x, vy = b.y - a.y;
|
||||
var len = Math.sqrt(vx * vx + vy * vy);
|
||||
if (len > 1) d = { x: vx / len, y: vy / len, len: len };
|
||||
} catch (e) {
|
||||
/* 地图还没量出尺寸:用水平方向兜底 */
|
||||
}
|
||||
cache[key] = d;
|
||||
return d;
|
||||
};
|
||||
}
|
||||
/** 贪心排布:和已放好的标记撞上就左右交替挪开,返回像素偏移 */
|
||||
/** 贪心排布:撞上了就沿**这段线**的方向让开(图标仍排在这条线上),
|
||||
线太短装不下再走法线、最后水平兜底。返回像素偏移 {x, y}。 */
|
||||
function makePlacer() {
|
||||
var placed = []; // 已占用的屏幕坐标(容器像素)
|
||||
/* 图标是圆的(直径 40),按**中心距**判碰撞比按矩形准:
|
||||
斜着挪 52px 时 x 只差 31、y 只差 42,矩形判定会说「还重叠」,
|
||||
于是白挪一步(两枚图标被拉到 104px 远,看着像没关系)。 */
|
||||
function hit(x, y) {
|
||||
for (var j = 0; j < placed.length; j++) {
|
||||
var q = placed[j];
|
||||
if (Math.hypot(x - q.x, y - q.y) < MARK_W) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
place: function (base, dir) {
|
||||
/* 沿线最多挪到快到端点为止 —— 挪出线段就不再是「在这条线上」了 */
|
||||
var maxAlong = Math.max(0, dir.len * 0.45 - MARK_W / 2);
|
||||
var cand = [{ x: 0, y: 0 }];
|
||||
var k, step, sg;
|
||||
for (k = 1; k <= 4; k++) { // ① 沿线前后交替(首选)
|
||||
step = Math.ceil(k / 2) * STEP;
|
||||
sg = (k % 2 === 1) ? 1 : -1;
|
||||
if (step <= maxAlong) cand.push({ x: dir.x * step * sg, y: dir.y * step * sg });
|
||||
}
|
||||
placed.push({ x: base.x + dx, y: base.y, id: id });
|
||||
return dx;
|
||||
for (k = 1; k <= 4; k++) { // ② 线太短:改走法线(少量)
|
||||
step = Math.ceil(k / 2) * STEP;
|
||||
sg = (k % 2 === 1) ? 1 : -1;
|
||||
cand.push({ x: -dir.y * step * sg, y: dir.x * step * sg });
|
||||
}
|
||||
for (k = 1; k <= 6; k++) { // ③ 兜底:水平让开
|
||||
step = Math.ceil(k / 2) * STEP;
|
||||
sg = (k % 2 === 1) ? 1 : -1;
|
||||
cand.push({ x: step * sg, y: 0 });
|
||||
}
|
||||
/* cand[0] 是「原地不动」,一样要先判它:跳过去从 cand[1] 开始挑,
|
||||
第一枚标记也会被无谓地挪开一步(两枚本来该挨着 52px 的变成 104px)。 */
|
||||
var pick = null;
|
||||
for (var i = 0; i < cand.length; i++) {
|
||||
if (!hit(base.x + cand[i].x, base.y + cand[i].y)) { pick = cand[i]; break; }
|
||||
}
|
||||
if (!pick) pick = cand[0]; // 全都撞:退回原地,总比乱飞好
|
||||
placed.push({ x: base.x + pick.x, y: base.y + pick.y });
|
||||
return pick;
|
||||
},
|
||||
reset: function () { placed = []; },
|
||||
};
|
||||
@@ -250,20 +281,20 @@ require dirname(__DIR__) . '/comments/_block.php';
|
||||
var inPath = []; // 节点内:出发地 → 到达地(实线)
|
||||
var hopPath = []; // 节点之间:上一站到达地 → 下一站出发地(虚线)
|
||||
var prevEnd = null;
|
||||
var shiftSeg = makeSegOffsetter();
|
||||
var segDir = makeSegDir(); // 线段在屏幕上的方向:图标沿它排队
|
||||
var placer = makePlacer(); // 标记按屏幕像素排布,撞上就挪开
|
||||
var dotSeen = {}; // 到达地红点:同一点位只画一个
|
||||
|
||||
route.forEach(function (n, i) {
|
||||
var s = n.start, en = n.end;
|
||||
|
||||
/* 线按**原始坐标**画:往返两段重合是正常的,用户要的是图标不叠,
|
||||
不是把线劈成两条(批 14 那样左右错开,偏移量太大看着像画错)。 */
|
||||
if (s && en) {
|
||||
var seg = shiftSeg([s.lng, s.lat], [en.lng, en.lat]);
|
||||
inPath.push(seg[0], seg[1]);
|
||||
inPath.push([s.lng, s.lat], [en.lng, en.lat]);
|
||||
}
|
||||
if (s && prevEnd) {
|
||||
var hop = shiftSeg([prevEnd.lng, prevEnd.lat], [s.lng, s.lat]);
|
||||
hopPath.push(hop[0], hop[1]);
|
||||
hopPath.push([prevEnd.lng, prevEnd.lat], [s.lng, s.lat]);
|
||||
}
|
||||
|
||||
/* 圆形标记放这段行程线的中点(起终点都有才取中点),缺一头就放有的一头。
|
||||
@@ -280,7 +311,11 @@ require dirname(__DIR__) . '/comments/_block.php';
|
||||
} catch (e) {
|
||||
base = null;
|
||||
}
|
||||
var dx = base ? placer.place(base, i) : 0;
|
||||
/* 有整段线就沿这条线让开(前后排成一列),只有一头时退化成水平 */
|
||||
var dir = (s && en)
|
||||
? segDir([s.lng, s.lat], [en.lng, en.lat])
|
||||
: { x: 1, y: 0, len: 0 };
|
||||
var off = base ? placer.place(base, dir) : { x: 0, y: 0 };
|
||||
|
||||
var html ='<div class="route-marker" title="' + n.title.replace(/"/g, '"') + '">'
|
||||
+ (n.img
|
||||
@@ -294,9 +329,9 @@ require dirname(__DIR__) . '/comments/_block.php';
|
||||
anchor: 'center',
|
||||
map: map,
|
||||
};
|
||||
/* 撞上了就整体往旁边挪 —— 用像素偏移,谁也不挡谁 */
|
||||
if (dx !== 0) {
|
||||
opts.offset = new AMap.Pixel(dx, 0);
|
||||
/* 撞上了就沿这条线挪开 —— 用像素偏移,谁也不挡谁 */
|
||||
if (off.x !== 0 || off.y !== 0) {
|
||||
opts.offset = new AMap.Pixel(off.x, off.y);
|
||||
}
|
||||
var marker = new AMap.Marker(opts);
|
||||
marker.on('click', function () { window.location.href = n.url; });
|
||||
|
||||
Reference in New Issue
Block a user