MarcoXL commited on
Commit
825672c
·
1 Parent(s): 93d69ac

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. web/app.js +82 -45
  2. web/index.html +3 -9
  3. web/styles.css +40 -15
web/app.js CHANGED
@@ -664,13 +664,15 @@ function drawHeadingArrows(svg, mk, pos) {
664
  if (!n || !n.ground_headings || !n.ground_headings.length) return;
665
  var p = pos[n.label];
666
  if (!p) return;
667
- var active = groundView[pair[0]] || 0;
 
 
668
 
669
  n.ground_headings.forEach(function (h, k) {
670
  if (h == null) return;
671
  var th = (Number(h) % 360 + 360) % 360 * Math.PI / 180;
672
  var dx = Math.sin(th), dy = -Math.cos(th);
673
- var on = k === active;
674
  var r0 = 7.5, r1 = on ? 22 : 14; // 起点让开节点圆点
675
  var x0 = p[0] + dx * r0, y0 = p[1] + dy * r0;
676
  var x1 = p[0] + dx * r1, y1 = p[1] + dy * r1;
@@ -703,14 +705,17 @@ function drawHeadingArrows(svg, mk, pos) {
703
  'stroke-linejoin': 'round', 'paint-order': 'stroke',
704
  }));
705
  var t = mk('title', {});
706
- t.textContent = headingLabel(h) + (on ? '(当前街景)' : '(点击切到该朝向)');
707
  gg.appendChild(t);
 
708
  gg.onclick = function (ev) {
709
  ev.stopPropagation();
710
- groundView[pair[0]] = k;
711
- drawGroundImages();
712
- drawGraph();
713
  };
 
 
714
  svg.appendChild(gg);
715
  });
716
  });
@@ -809,10 +814,6 @@ function drawAdjacency() {
809
  });
810
  }
811
 
812
- // 每个 role(anchor/target)当前看的是第几个朝向。
813
- // 换节点时保留同一个下标,这样连按 J/K 浏览时朝向不会跳来跳去。
814
- var groundView = { anchor: 0, target: 0 };
815
-
816
  function headingLabel(h) {
817
  if (h == null) return '—';
818
  var d = ((+h % 360) + 360) % 360; // 文件名里有负角,先归一到 [0,360)
@@ -820,41 +821,81 @@ function headingLabel(h) {
820
  return names[Math.round(d / 45) % 8] + ' ' + Math.round(d) + '°';
821
  }
822
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
823
  function drawGroundImages() {
824
  var sel = S.selected;
825
  if (!sel || !S.scene) return;
 
826
  function fill(role, label) {
827
  var n = S.scene.nodes.find(function (x) { return x.label === label; });
828
  $(role + 'Badge').textContent = label || '—';
829
  var assets = (n && n.ground_assets) || [];
830
  var headings = (n && n.ground_headings) || [];
831
- var i = Math.min(groundView[role] || 0, Math.max(assets.length - 1, 0));
832
- groundView[role] = i;
833
  $(role + 'Caption').textContent = n
834
- ? (n.lat.toFixed(6) + ', ' + n.lon.toFixed(6) + ' · ' + (n.corridor_name_auto || '无走廊')
835
- + (assets.length > 1 ? ' · ' + (i + 1) + '/' + assets.length : ''))
 
836
  : '—';
837
- var img = $(role + 'Image');
838
- if (assets[i]) img.src = '/asset?path=' + encodeURIComponent(assets[i]);
839
- else { img.removeAttribute('src'); }
840
 
841
- var strip = $(role + 'Strip');
842
- strip.innerHTML = '';
843
- if (assets.length < 2) return; // 只有一张就不占一行
 
 
 
 
844
  assets.forEach(function (rel, k) {
845
  var b = document.createElement('button');
846
  b.type = 'button';
847
- b.textContent = headingLabel(headings[k]);
848
- b.className = k === i ? 'active' : '';
849
- b.setAttribute('role', 'tab');
850
- b.setAttribute('aria-selected', k === i ? 'true' : 'false');
851
- b.onclick = function () { groundView[role] = k; drawGroundImages(); drawGraph(); };
852
- strip.appendChild(b);
 
 
 
 
 
 
 
 
 
 
 
 
853
  });
854
  }
 
855
  if (sel.kind === 'node') {
856
  $('anchorKind').textContent = 'Node';
857
  $('targetCard').style.display = 'none';
 
858
  fill('anchor', sel.key[1]);
859
  } else {
860
  $('anchorKind').textContent = 'Source';
@@ -864,19 +905,23 @@ function drawGroundImages() {
864
  }
865
  }
866
 
867
- // H 把当前可见的街景卡一起翻一个朝向——比较一对节点时两边保持同一朝向。
 
868
  function cycleGround(step) {
869
  if (!S.selected || !S.scene) return;
870
- var labels = S.selected.kind === 'node'
871
- ? [['anchor', S.selected.key[1]]]
872
- : [['anchor', S.selected.key[1]], ['target', S.selected.key[2]]];
873
- labels.forEach(function (pair) {
874
- var n = S.scene.nodes.find(function (x) { return x.label === pair[1]; });
875
- var len = ((n && n.ground_assets) || []).length;
876
- if (len > 1) groundView[pair[0]] = ((groundView[pair[0]] + step) % len + len) % len;
877
- });
878
- drawGroundImages();
879
- drawGraph(); // 图上的朝向箭头跟着换高亮
 
 
 
880
  }
881
 
882
  function pickNode(n) {
@@ -1100,14 +1145,6 @@ function boot() {
1100
  $('helpButton').onclick = function () { $('helpDialog').showModal(); };
1101
  $('helpClose').onclick = function () { $('helpDialog').close(); };
1102
  $('lightbox').onclick = function () { this.classList.remove('open'); };
1103
- Array.prototype.forEach.call(document.querySelectorAll('.ground-stage'), function (b) {
1104
- b.onclick = function () {
1105
- var img = b.querySelector('img');
1106
- if (!img || !img.getAttribute('src')) return;
1107
- $('lightboxImage').src = img.src;
1108
- $('lightbox').classList.add('open');
1109
- };
1110
- });
1111
 
1112
  document.addEventListener('keydown', function (ev) {
1113
  var tag = (ev.target.tagName || '').toLowerCase();
 
664
  if (!n || !n.ground_headings || !n.ground_headings.length) return;
665
  var p = pos[n.label];
666
  if (!p) return;
667
+ // 四个朝向的图片现在同时显示,所以箭头地位平等——不再有"当前朝向"。
668
+ // 只有鼠标悬在某张图片上时,对应的那支才放大加亮。
669
+ var lit = LIT[pair[0]];
670
 
671
  n.ground_headings.forEach(function (h, k) {
672
  if (h == null) return;
673
  var th = (Number(h) % 360 + 360) % 360 * Math.PI / 180;
674
  var dx = Math.sin(th), dy = -Math.cos(th);
675
+ var on = k === lit;
676
  var r0 = 7.5, r1 = on ? 22 : 14; // 起点让开节点圆点
677
  var x0 = p[0] + dx * r0, y0 = p[1] + dy * r0;
678
  var x1 = p[0] + dx * r1, y1 = p[1] + dy * r1;
 
705
  'stroke-linejoin': 'round', 'paint-order': 'stroke',
706
  }));
707
  var t = mk('title', {});
708
+ t.textContent = headingLabel(h) + ' 街景(点击放大)';
709
  gg.appendChild(t);
710
+ // 点箭头 = 放大那个方向的街景;悬停则反向点亮对应的缩略图
711
  gg.onclick = function (ev) {
712
  ev.stopPropagation();
713
+ var tile = $(pair[0] + 'Grid').children[k];
714
+ var img = tile && tile.querySelector && tile.querySelector('img');
715
+ if (img) { $('lightboxImage').src = img.src; $('lightbox').classList.add('open'); }
716
  };
717
+ gg.onmouseenter = function () { setLit(pair[0], k); };
718
+ gg.onmouseleave = function () { setLit(pair[0], -1); };
719
  svg.appendChild(gg);
720
  });
721
  });
 
814
  });
815
  }
816
 
 
 
 
 
817
  function headingLabel(h) {
818
  if (h == null) return '—';
819
  var d = ((+h % 360) + 360) % 360; // 文件名里有负角,先归一到 [0,360)
 
821
  return names[Math.round(d / 45) % 8] + ' ' + Math.round(d) + '°';
822
  }
823
 
824
+ /* 四个朝向同时铺开。
825
+ *
826
+ * 旧版一次只显示一张,下面一排按钮来回切——要看全一个路口得点三次,
827
+ * 而且切走之后就记不住上一张长什么样。现在四张并排、各自标方位,
828
+ * 不用做任何选择就能一眼看完。
829
+ *
830
+ * 悬停某一张会点亮地图上对应的箭头(见 drawHeadingArrows),
831
+ * 「图片里的这条路」和「图上的这个方向」就对上了。
832
+ */
833
+ var LIT = { anchor: -1, target: -1 }; // 当前悬停的朝向下标,-1 = 无
834
+
835
+ /* 双向联动的唯一入口:改 LIT 就同时更新缩略图的边框和地图上的箭头。
836
+ 两边各写一次会漏——比如从箭头移开时忘了取消图片的高亮。 */
837
+ function setLit(role, k) {
838
+ if (LIT[role] === k) return;
839
+ LIT[role] = k;
840
+ var grid = $(role + 'Grid');
841
+ if (grid) {
842
+ Array.prototype.forEach.call(grid.children, function (el, i) {
843
+ if (el.classList) el.classList.toggle('lit', i === k);
844
+ });
845
+ }
846
+ drawGraph();
847
+ }
848
+
849
  function drawGroundImages() {
850
  var sel = S.selected;
851
  if (!sel || !S.scene) return;
852
+
853
  function fill(role, label) {
854
  var n = S.scene.nodes.find(function (x) { return x.label === label; });
855
  $(role + 'Badge').textContent = label || '—';
856
  var assets = (n && n.ground_assets) || [];
857
  var headings = (n && n.ground_headings) || [];
 
 
858
  $(role + 'Caption').textContent = n
859
+ ? (n.lat.toFixed(6) + ', ' + n.lon.toFixed(6) + ' · ' +
860
+ (n.corridor_name_auto || '无走廊') +
861
+ (assets.length ? ' · ' + assets.length + ' 个朝向' : ''))
862
  : '—';
 
 
 
863
 
864
+ var grid = $(role + 'Grid');
865
+ grid.innerHTML = '';
866
+ grid.className = 'ground-grid' + (assets.length === 1 ? ' single' : '');
867
+ if (!assets.length) {
868
+ grid.innerHTML = '<div class="ground-empty">该节点没有街景</div>';
869
+ return;
870
+ }
871
  assets.forEach(function (rel, k) {
872
  var b = document.createElement('button');
873
  b.type = 'button';
874
+ b.className = 'ground-tile';
875
+ b.setAttribute('aria-label', '放大 ' + headingLabel(headings[k]) + ' 方向的街景');
876
+ var img = document.createElement('img');
877
+ img.src = '/asset?path=' + encodeURIComponent(rel);
878
+ img.alt = headingLabel(headings[k]) + ' 街景';
879
+ img.loading = 'lazy';
880
+ var tag = document.createElement('b');
881
+ tag.textContent = headingLabel(headings[k]);
882
+ b.appendChild(img);
883
+ b.appendChild(tag);
884
+ b.onclick = function () {
885
+ $('lightboxImage').src = img.src;
886
+ $('lightbox').classList.add('open');
887
+ };
888
+ // 悬停联动:图片 <-> 地图上的箭头
889
+ b.onmouseenter = function () { setLit(role, k); };
890
+ b.onmouseleave = function () { setLit(role, -1); };
891
+ grid.appendChild(b);
892
  });
893
  }
894
+
895
  if (sel.kind === 'node') {
896
  $('anchorKind').textContent = 'Node';
897
  $('targetCard').style.display = 'none';
898
+ LIT.target = -1;
899
  fill('anchor', sel.key[1]);
900
  } else {
901
  $('anchorKind').textContent = 'Source';
 
905
  }
906
  }
907
 
908
+ /* H 依次放大看四个朝向 —— 四张都在屏幕上了,但缩略图偏小,
909
+ 想逐张看大图时不必用鼠标去点。 */
910
  function cycleGround(step) {
911
  if (!S.selected || !S.scene) return;
912
+ var role = 'anchor', label = S.selected.key[1];
913
+ var n = S.scene.nodes.find(function (x) { return x.label === label; });
914
+ var assets = (n && n.ground_assets) || [];
915
+ if (!assets.length) return;
916
+ var next = (LIT[role] + step) % assets.length;
917
+ if (next < 0) next += assets.length;
918
+ LIT[role] = next;
919
+ var tile = $(role + 'Grid').children[next];
920
+ if (tile && tile.querySelector) {
921
+ var img = tile.querySelector('img');
922
+ if (img) { $('lightboxImage').src = img.src; $('lightbox').classList.add('open'); }
923
+ }
924
+ drawGraph();
925
  }
926
 
927
  function pickNode(n) {
 
1145
  $('helpButton').onclick = function () { $('helpDialog').showModal(); };
1146
  $('helpClose').onclick = function () { $('helpDialog').close(); };
1147
  $('lightbox').onclick = function () { this.classList.remove('open'); };
 
 
 
 
 
 
 
 
1148
 
1149
  document.addEventListener('keydown', function (ev) {
1150
  var tag = (ev.target.tagName || '').toLowerCase();
web/index.html CHANGED
@@ -217,10 +217,7 @@
217
  <div class="node-label anchor-label" id="anchorBadge">—</div>
218
  <span class="image-kind" id="anchorKind">Node</span>
219
  </div>
220
- <button type="button" class="image-stage ground-stage" data-role="anchor" aria-label="放大街景">
221
- <img id="anchorImage" alt="ground view">
222
- </button>
223
- <div class="heading-strip" id="anchorStrip" role="tablist" aria-label="街景朝向"></div>
224
  <div class="image-caption mono" id="anchorCaption">—</div>
225
  </article>
226
  <article class="image-card ground-card" id="targetCard">
@@ -228,10 +225,7 @@
228
  <div class="node-label target-label" id="targetBadge">—</div>
229
  <span class="image-kind">Target</span>
230
  </div>
231
- <button type="button" class="image-stage ground-stage" data-role="target" aria-label="放大街景">
232
- <img id="targetImage" alt="ground view">
233
- </button>
234
- <div class="heading-strip" id="targetStrip" role="tablist" aria-label="街景朝向"></div>
235
  <div class="image-caption mono" id="targetCaption">—</div>
236
  </article>
237
  </div>
@@ -290,7 +284,7 @@
290
  <kbd>K</kbd><span>队列上一条</span>
291
  <kbd>B</kbd><span>切换卫星图 / 电子地图</span>
292
  <kbd>E</kbd><span>切换边的画法:沿路走线 / 直线</span>
293
- <kbd>H</kbd><span>下一个街景朝向(<kbd>Shift</kbd>+<kbd>H</kbd> 上一个)</span>
294
  <kbd>[</kbd> <kbd>]</kbd><span>列表上一页 / 下一页</span>
295
  <kbd>Q</kbd><span>切换「全部场景」/「带信号的」</span>
296
  <kbd>?</kbd><span>打开本帮助</span>
 
217
  <div class="node-label anchor-label" id="anchorBadge">—</div>
218
  <span class="image-kind" id="anchorKind">Node</span>
219
  </div>
220
+ <div class="ground-grid" id="anchorGrid" aria-label="四个朝向的街景"></div>
 
 
 
221
  <div class="image-caption mono" id="anchorCaption">—</div>
222
  </article>
223
  <article class="image-card ground-card" id="targetCard">
 
225
  <div class="node-label target-label" id="targetBadge">—</div>
226
  <span class="image-kind">Target</span>
227
  </div>
228
+ <div class="ground-grid" id="targetGrid" aria-label="四个朝向的街景"></div>
 
 
 
229
  <div class="image-caption mono" id="targetCaption">—</div>
230
  </article>
231
  </div>
 
284
  <kbd>K</kbd><span>队列上一条</span>
285
  <kbd>B</kbd><span>切换卫星图 / 电子地图</span>
286
  <kbd>E</kbd><span>切换边的画法:沿路走线 / 直线</span>
287
+ <kbd>H</kbd><span>把当前节点的四个朝向依次放大看(<kbd>Shift</kbd>+<kbd>H</kbd> 反向)</span>
288
  <kbd>[</kbd> <kbd>]</kbd><span>列表上一页 / 下一页</span>
289
  <kbd>Q</kbd><span>切换「全部场景」/「带信号的」</span>
290
  <kbd>?</kbd><span>打开本帮助</span>
web/styles.css CHANGED
@@ -292,7 +292,7 @@ kbd {
292
  .scene-metrics span { display: block; color: var(--faint); font: 600 8px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
293
  .scene-metrics strong { font: 600 15px var(--display); }
294
 
295
- .evidence-grid { display: grid; grid-template-columns: minmax(320px, 1fr) minmax(260px, 380px); gap: 14px; align-items: start; }
296
  .image-card { border: 1px solid var(--line); background: var(--panel); }
297
  .image-card-head { display: flex; align-items: center; gap: 9px; padding: 8px 11px; border-bottom: 1px solid var(--line-soft); }
298
  .image-card-head.compact { padding: 6px 9px; }
@@ -314,8 +314,6 @@ kbd {
314
  .image-stage { display: block; width: 100%; padding: 0; border: 0; background: #0b0e0d; position: relative; overflow: hidden; }
315
  .image-stage > img { display: block; width: 100%; height: 100%; object-fit: cover; }
316
  .satellite-stage { aspect-ratio: 1/1; cursor: crosshair; }
317
- .ground-stage { aspect-ratio: 16/8.3; cursor: zoom-in; }
318
- .ground-stage:hover > img { filter: brightness(1.06); }
319
  /* .street-map 有 z-index:1 而 .graph-svg 没有,于是电子地图模式下
320
  瓦片整个盖在图上——节点和边全看不见。给 SVG 更高的层级。 */
321
  .graph-svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; }
@@ -335,18 +333,45 @@ kbd {
335
  .map-controls { display: flex; border: 1px solid var(--line); }
336
  .map-controls button { background: transparent; border: 0; width: 22px; cursor: pointer; color: var(--muted); font: 700 12px var(--mono); }
337
  .map-controls button:hover { color: var(--signal); }
338
- /* 街景朝向切换条:一个 pano 有 4 个朝向,全部列出来可点 */
339
- .heading-strip {
340
- display: flex; gap: 1px; border-top: 1px solid var(--line-soft);
341
- background: var(--line-soft);
342
- }
343
- .heading-strip:empty { display: none; }
344
- .heading-strip button {
345
- flex: 1; background: var(--panel); border: 0; padding: 4px 0; cursor: pointer;
346
- color: var(--faint); font: 700 9px var(--mono); letter-spacing: .06em;
347
- }
348
- .heading-strip button:hover { color: var(--cyan); }
349
- .heading-strip button.active { background: var(--signal); color: var(--ink); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  .image-caption { min-height: 34px; padding: 8px 11px; display: flex; justify-content: space-between; gap: 10px; color: var(--faint); border-top: 1px solid var(--line-soft); font: 600 9px var(--mono); }
352
  .image-caption.mono { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
 
292
  .scene-metrics span { display: block; color: var(--faint); font: 600 8px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
293
  .scene-metrics strong { font: 600 15px var(--display); }
294
 
295
+ .evidence-grid { display: grid; grid-template-columns: minmax(300px, 1fr) minmax(280px, 440px); gap: 14px; align-items: start; }
296
  .image-card { border: 1px solid var(--line); background: var(--panel); }
297
  .image-card-head { display: flex; align-items: center; gap: 9px; padding: 8px 11px; border-bottom: 1px solid var(--line-soft); }
298
  .image-card-head.compact { padding: 6px 9px; }
 
314
  .image-stage { display: block; width: 100%; padding: 0; border: 0; background: #0b0e0d; position: relative; overflow: hidden; }
315
  .image-stage > img { display: block; width: 100%; height: 100%; object-fit: cover; }
316
  .satellite-stage { aspect-ratio: 1/1; cursor: crosshair; }
 
 
317
  /* .street-map 有 z-index:1 而 .graph-svg 没有,于是电子地图模式下
318
  瓦片整个盖在图上——节点和边全看不见。给 SVG 更高的层级。 */
319
  .graph-svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; }
 
333
  .map-controls { display: flex; border: 1px solid var(--line); }
334
  .map-controls button { background: transparent; border: 0; width: 22px; cursor: pointer; color: var(--muted); font: 700 12px var(--mono); }
335
  .map-controls button:hover { color: var(--signal); }
336
+ /* 街景四朝向方格 ------------------------------------------------------
337
+ * 一个 pano 有 4 个朝向。旧版一次只显示一张、下面一排按钮来回切——
338
+ * 要看全一个路口得点三次,还得记住刚才那张是什么样。现在四张并排,
339
+ * 各自标出方位,一眼看完。
340
+ *
341
+ * 源图是 512x512 的正方形,旧版按 16:8.3 裁成信箱形,上下都切掉了。
342
+ * 方格用 1:1,反而看得更全。
343
+ */
344
+ .ground-grid {
345
+ display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
346
+ background: var(--line-soft); border-top: 1px solid var(--line-soft);
347
+ }
348
+ .ground-grid:empty { display: none; }
349
+ /* 只有一张时(旧包没有 heading 列)不必占两格 */
350
+ .ground-grid.single { grid-template-columns: 1fr; }
351
+
352
+ .ground-tile {
353
+ position: relative; display: block; padding: 0; border: 0;
354
+ background: #0b0e0d; cursor: zoom-in; overflow: hidden; aspect-ratio: 1/1;
355
+ }
356
+ .ground-tile > img { display: block; width: 100%; height: 100%; object-fit: cover; }
357
+ .ground-tile:hover > img { filter: brightness(1.08); }
358
+ .ground-tile:focus-visible { outline: 2px solid var(--signal); outline-offset: -2px; }
359
+
360
+ /* 方位标签压在图上:图本身已经很小,再单占一行就没地方了 */
361
+ .ground-tile b {
362
+ position: absolute; left: 0; top: 0; padding: 2px 5px;
363
+ background: rgba(8,12,11,.78); color: var(--paper);
364
+ font: 700 9px var(--mono); letter-spacing: .06em;
365
+ border-bottom-right-radius: var(--radius);
366
+ }
367
+ /* 悬停时同时点亮地图上对应的箭头(drawGraph 里加 .arrow-lit) */
368
+ .ground-tile:hover b { background: var(--signal); color: var(--ink); }
369
+ .ground-tile.lit b { background: var(--signal); color: var(--ink); }
370
+
371
+ .ground-empty {
372
+ grid-column: 1 / -1; padding: 18px 10px; text-align: center;
373
+ color: var(--faint); font: 600 10px var(--mono);
374
+ }
375
 
376
  .image-caption { min-height: 34px; padding: 8px 11px; display: flex; justify-content: space-between; gap: 10px; color: var(--faint); border-top: 1px solid var(--line-soft); font: 600 9px var(--mono); }
377
  .image-caption.mono { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }