fix: 移除Canvas像素缓存—每帧实时latLngToContainerPoint
根因: _trafficEdgeCache缓存旧像素位置,地图拖动/缩放后不更新 修复: - 完全移除像素缓存,每帧重新计算latLngToContainerPoint - Canvas为#saMap子元素,绝对定位,跟随容器 - 移除moveend缓存清除逻辑(不再需要) 效果: 地图平移/缩放时流量边自动跟随,不再是'贴片' Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2577,9 +2577,7 @@ let _trafficPrevFrameTs = 0;
|
|||||||
let _trafficSeekTarget = null;
|
let _trafficSeekTarget = null;
|
||||||
|
|
||||||
// Canvas overlay
|
// Canvas overlay
|
||||||
let _trafficCanvas = null;
|
let _trafficCanvasId = 'saTrafficCanvas';
|
||||||
let _trafficCtx = null;
|
|
||||||
let _trafficEdgeCache = {};
|
|
||||||
|
|
||||||
function toggleTrafficPanel() {
|
function toggleTrafficPanel() {
|
||||||
const panel = document.getElementById('saTrafficPanel');
|
const panel = document.getElementById('saTrafficPanel');
|
||||||
@@ -2596,25 +2594,25 @@ function toggleTrafficPanel() {
|
|||||||
|
|
||||||
function _ensureFlowCanvas() {
|
function _ensureFlowCanvas() {
|
||||||
if (!SA.map) return null;
|
if (!SA.map) return null;
|
||||||
if (_trafficCanvas && _trafficCanvas.parentNode) return _trafficCanvas;
|
let c = document.getElementById(_trafficCanvasId);
|
||||||
const mapEl = SA.map.getContainer();
|
if (!c) {
|
||||||
const c = document.createElement('canvas');
|
c = document.createElement('canvas');
|
||||||
c.id = 'saTrafficCanvasOverlay';
|
c.id = _trafficCanvasId;
|
||||||
c.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:10000;';
|
c.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:10000;';
|
||||||
mapEl.appendChild(c);
|
SA.map.getContainer().appendChild(c);
|
||||||
|
}
|
||||||
const s = SA.map.getSize();
|
const s = SA.map.getSize();
|
||||||
c.width = s.x; c.height = s.y;
|
if (c.width !== s.x || c.height !== s.y) {
|
||||||
c.style.width = s.x + 'px'; c.style.height = s.y + 'px';
|
c.width = s.x; c.height = s.y;
|
||||||
SA.map.on('resize', () => { const sz = SA.map.getSize(); c.width = sz.x; c.height = sz.y; c.style.width = sz.x + 'px'; c.style.height = sz.y + 'px'; });
|
c.style.width = s.x + 'px'; c.style.height = s.y + 'px';
|
||||||
_trafficCtx = c.getContext('2d');
|
}
|
||||||
_trafficCanvas = c;
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _removeFlowCanvas() {
|
function _removeFlowCanvas() {
|
||||||
cancelAnimationFrame(_trafficAnimId); _trafficAnimId = null;
|
cancelAnimationFrame(_trafficAnimId); _trafficAnimId = null;
|
||||||
if (_trafficCanvas) { _trafficCanvas.remove(); _trafficCanvas = null; _trafficCtx = null; }
|
const c = document.getElementById(_trafficCanvasId);
|
||||||
_trafficEdgeCache = {};
|
if (c) c.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function tsToBJ(ts) { const d = new Date(ts * 1000); return d.toISOString().replace('T',' ').slice(0,19); }
|
function tsToBJ(ts) { const d = new Date(ts * 1000); return d.toISOString().replace('T',' ').slice(0,19); }
|
||||||
@@ -2631,7 +2629,7 @@ function loadTrafficData() {
|
|||||||
.then(d => {
|
.then(d => {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
if (d.error) { showError(d.error); return; }
|
if (d.error) { showError(d.error); return; }
|
||||||
_trafficEdgeCache = {};
|
// Edge positions computed fresh each frame — no cache needed
|
||||||
let ws = d.window_seconds || 10;
|
let ws = d.window_seconds || 10;
|
||||||
let minTs = d.min_ts || (d.windows[0]?.ts || 0);
|
let minTs = d.min_ts || (d.windows[0]?.ts || 0);
|
||||||
let maxTs = d.max_ts || (d.windows[d.n_windows-1]?.ts_end || minTs + ws);
|
let maxTs = d.max_ts || (d.windows[d.n_windows-1]?.ts_end || minTs + ws);
|
||||||
@@ -2672,7 +2670,6 @@ function loadTrafficData() {
|
|||||||
document.getElementById('saTrafficSlider').value = 0;
|
document.getElementById('saTrafficSlider').value = 0;
|
||||||
document.getElementById('saTrafficInfo').textContent =
|
document.getElementById('saTrafficInfo').textContent =
|
||||||
(d.windows||[]).length + ' 窗口 · ' + Object.keys(d.nodes_coords||{}).length + ' 节点 · 跨度' + ((maxTs-minTs)/60).toFixed(0) + 'min';
|
(d.windows||[]).length + ' 窗口 · ' + Object.keys(d.nodes_coords||{}).length + ' 节点 · 跨度' + ((maxTs-minTs)/60).toFixed(0) + 'min';
|
||||||
SA.map.off('moveend.flowCache').on('moveend.flowCache', () => { _trafficEdgeCache = {}; if (_trafficPlaying) renderTrafficCrossfade(); });
|
|
||||||
document.getElementById('saTrafficTime').textContent = tsToBJ(_trafficCurrentTime);
|
document.getElementById('saTrafficTime').textContent = tsToBJ(_trafficCurrentTime);
|
||||||
trafficStart();
|
trafficStart();
|
||||||
})
|
})
|
||||||
@@ -2772,7 +2769,7 @@ function trafficAnimate(timestamp) {
|
|||||||
function renderTrafficCrossfade() {
|
function renderTrafficCrossfade() {
|
||||||
const c = _ensureFlowCanvas();
|
const c = _ensureFlowCanvas();
|
||||||
if (!c || !SA.map || !_trafficData) return;
|
if (!c || !SA.map || !_trafficData) return;
|
||||||
const ctx = _trafficCtx;
|
const ctx = c.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
ctx.clearRect(0, 0, c.width, c.height);
|
ctx.clearRect(0, 0, c.width, c.height);
|
||||||
|
|
||||||
@@ -2809,39 +2806,34 @@ function renderTrafficCrossfade() {
|
|||||||
edges.length + ' 边 · ' + activeNodes.size + ' 节点 · ' + tsToBJ(_trafficCurrentTime);
|
edges.length + ' 边 · ' + activeNodes.size + ' 节点 · ' + tsToBJ(_trafficCurrentTime);
|
||||||
|
|
||||||
edges.forEach(e => {
|
edges.forEach(e => {
|
||||||
let cached = _trafficEdgeCache[e.source + '|' + e.target];
|
const sc = coords[e.source], dc = coords[e.target];
|
||||||
if (!cached) {
|
if (!sc || !dc) return;
|
||||||
const sc = coords[e.source], dc = coords[e.target];
|
const p1 = SA.map.latLngToContainerPoint([sc[0], sc[1]]);
|
||||||
if (!sc || !dc) { _trafficEdgeCache[e.source+'|'+e.target] = null; return; }
|
const p2 = SA.map.latLngToContainerPoint([dc[0], dc[1]]);
|
||||||
const p1 = SA.map.latLngToContainerPoint([sc[0], sc[1]]);
|
if (!p1 || !p2) return;
|
||||||
const p2 = SA.map.latLngToContainerPoint([dc[0], dc[1]]);
|
const midX = (p1.x + p2.x) / 2;
|
||||||
if (!p1 || !p2) return;
|
const midY = (p1.y + p2.y) / 2 - Math.abs(p2.x - p1.x) * 0.1;
|
||||||
cached = {p1x:p1.x, p1y:p1.y, p2x:p2.x, p2y:p2.y, midX:(p1.x+p2.x)/2, midY:(p1.y+p2.y)/2 - Math.abs(p2.x-p1.x)*0.1};
|
|
||||||
_trafficEdgeCache[e.source+'|'+e.target] = cached;
|
|
||||||
}
|
|
||||||
if (!cached) return;
|
|
||||||
const ratio = Math.min(1, e.count / maxCount);
|
const ratio = Math.min(1, e.count / maxCount);
|
||||||
const alpha = 0.4 + 0.6 * e.weight;
|
const alpha = 0.4 + 0.6 * e.weight;
|
||||||
|
|
||||||
// Edge trace with source→destination color gradient
|
// Edge trace with src→dst color gradient
|
||||||
const grad = ctx.createLinearGradient(cached.p1x, cached.p1y, cached.p2x, cached.p2y);
|
const grad = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y);
|
||||||
grad.addColorStop(0, 'rgba(46,196,182,' + alpha.toFixed(2) + ')'); // src: teal
|
grad.addColorStop(0, 'rgba(46,196,182,' + alpha.toFixed(2) + ')');
|
||||||
grad.addColorStop(1, 'rgba(230,57,70,' + alpha.toFixed(2) + ')'); // dst: red
|
grad.addColorStop(1, 'rgba(230,57,70,' + alpha.toFixed(2) + ')');
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(cached.p1x, cached.p1y);
|
ctx.moveTo(p1.x, p1.y);
|
||||||
ctx.quadraticCurveTo(cached.midX, cached.midY, cached.p2x, cached.p2y);
|
ctx.quadraticCurveTo(midX, midY, p2.x, p2.y);
|
||||||
ctx.strokeStyle = grad;
|
ctx.strokeStyle = grad;
|
||||||
ctx.lineWidth = 1.5 + 4 * ratio;
|
ctx.lineWidth = 1.5 + 4 * ratio;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// Particles: source(teal)→destination(red), speed ∝ traffic volume
|
// Flowing particles
|
||||||
const speedMul = 0.5 + 1.5 * ratio; // higher traffic → faster particles
|
const speedMul = 0.5 + 1.5 * ratio;
|
||||||
const nP = Math.ceil(3 + 6 * ratio);
|
const nP = Math.ceil(3 + 6 * ratio);
|
||||||
for (let pi = 0; pi < nP; pi++) {
|
for (let pi = 0; pi < nP; pi++) {
|
||||||
const pt = ((Date.now() / 2000 * speedMul + pi / nP) % 1);
|
const pt = ((Date.now() / 2000 * speedMul + pi / nP) % 1);
|
||||||
const px = (1-pt)*(1-pt)*cached.p1x + 2*(1-pt)*pt*cached.midX + pt*pt*cached.p2x;
|
const px = (1-pt)*(1-pt)*p1.x + 2*(1-pt)*pt*midX + pt*pt*p2.x;
|
||||||
const py = (1-pt)*(1-pt)*cached.p1y + 2*(1-pt)*pt*cached.midY + pt*pt*cached.p2y;
|
const py = (1-pt)*(1-pt)*p1.y + 2*(1-pt)*pt*midY + pt*pt*p2.y;
|
||||||
// src teal→dst red gradient
|
|
||||||
const cr = Math.round(46 + 184*pt), cg = Math.round(196 - 139*pt), cb = Math.round(182 - 112*pt);
|
const cr = Math.round(46 + 184*pt), cg = Math.round(196 - 139*pt), cb = Math.round(182 - 112*pt);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(px, py, 2 + 3*ratio, 0, Math.PI*2);
|
ctx.arc(px, py, 2 + 3*ratio, 0, Math.PI*2);
|
||||||
|
|||||||
Reference in New Issue
Block a user