标记便签改为 DOM 覆盖层,修复近距离缩放漂移
This commit is contained in:
+54
-37
@@ -7,10 +7,6 @@ const IMAGERY_URL =
|
|||||||
const TERRAIN_URL =
|
const TERRAIN_URL =
|
||||||
'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer';
|
'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer';
|
||||||
|
|
||||||
// 标记便签的离地高度(米):抬到高空,使其 bounding 球比城市/国家名更靠相机,
|
|
||||||
// 从而在透明通道的深度排序中始终后绘制(盖在标签上层)。
|
|
||||||
const MARKER_ALTITUDE = 50000;
|
|
||||||
|
|
||||||
// 计算多边形集合的球面面积(km²),用于按国土面积分级国家名字号
|
// 计算多边形集合的球面面积(km²),用于按国土面积分级国家名字号
|
||||||
function sphericalAreaKm2(polys) {
|
function sphericalAreaKm2(polys) {
|
||||||
let area = 0;
|
let area = 0;
|
||||||
@@ -96,7 +92,7 @@ function makeStickyNote(text, fontSize, selected = false) {
|
|||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillText(text, arrowW + bodyW / 2, h / 2 + 1);
|
ctx.fillText(text, arrowW + bodyW / 2, h / 2 + 1);
|
||||||
|
|
||||||
return { canvas: c, scale: 1 / S };
|
return { canvas: c, scale: 1 / S, width: w, height: h };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CesiumGlobe {
|
export class CesiumGlobe {
|
||||||
@@ -235,47 +231,69 @@ export class CesiumGlobe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_buildMarkers() {
|
_buildMarkers() {
|
||||||
const viewer = this.viewer;
|
|
||||||
|
|
||||||
ENTRIES.forEach((entry, i) => {
|
ENTRIES.forEach((entry, i) => {
|
||||||
// 便签:黄底黑字 + 左侧箭头指向坐标(整张图)
|
const position = Cesium.Cartesian3.fromDegrees(entry.lng, entry.lat);
|
||||||
const noteNormal = makeStickyNote(entry.name, 19, false);
|
const noteNormal = makeStickyNote(entry.name, 19, false);
|
||||||
const noteSelected = makeStickyNote(entry.name, 19, true);
|
const noteSelected = makeStickyNote(entry.name, 19, true);
|
||||||
|
this.markers.push({ position, noteNormal, noteSelected, index: i });
|
||||||
const entity = viewer.entities.add({
|
|
||||||
position: Cesium.Cartesian3.fromDegrees(entry.lng, entry.lat, MARKER_ALTITUDE),
|
|
||||||
billboard: {
|
|
||||||
image: noteNormal.canvas,
|
|
||||||
scale: noteNormal.scale,
|
|
||||||
horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
|
|
||||||
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
|
||||||
pixelOffset: new Cesium.Cartesian2(0, 0),
|
|
||||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
||||||
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 1e7),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
entity.entryIndex = i;
|
this._initMarkerLayer();
|
||||||
|
}
|
||||||
|
|
||||||
this.markers.push({ entity, noteNormal, noteSelected, index: i });
|
// 便签用 DOM 覆盖层渲染:始终在城市/国家名上层,且缩放到最近也不会漂移
|
||||||
|
_initMarkerLayer() {
|
||||||
|
const container = document.getElementById('cesium-container');
|
||||||
|
this._markerLayer = document.createElement('div');
|
||||||
|
this._markerLayer.id = 'marker-layer';
|
||||||
|
container.appendChild(this._markerLayer);
|
||||||
|
|
||||||
|
for (const m of this.markers) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'marker-note';
|
||||||
|
el.style.width = `${m.noteNormal.width}px`;
|
||||||
|
el.style.height = `${m.noteNormal.height}px`;
|
||||||
|
el.dataset.index = m.index;
|
||||||
|
m.el = el;
|
||||||
|
m.normalUrl = m.noteNormal.canvas.toDataURL();
|
||||||
|
m.selectedUrl = m.noteSelected.canvas.toDataURL();
|
||||||
|
el.style.backgroundImage = `url("${m.normalUrl}")`;
|
||||||
|
el.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.onPick?.(m.index);
|
||||||
});
|
});
|
||||||
|
this._markerLayer.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewer.scene.postRender.addEventListener(() => this._updateMarkerLayer());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每帧更新便签屏幕位置(相机移动/缩放/飞行时跟随)
|
||||||
|
_updateMarkerLayer() {
|
||||||
|
const scene = this.viewer.scene;
|
||||||
|
const camera = scene.camera;
|
||||||
|
const toPos = new Cesium.Cartesian3();
|
||||||
|
for (const m of this.markers) {
|
||||||
|
const el = m.el;
|
||||||
|
if (!el) continue;
|
||||||
|
const win = Cesium.SceneTransforms.worldToWindowCoordinates(scene, m.position);
|
||||||
|
if (!win) { el.style.display = 'none'; continue; }
|
||||||
|
Cesium.Cartesian3.subtract(m.position, camera.positionWC, toPos);
|
||||||
|
if (Cesium.Cartesian3.dot(toPos, camera.directionWC) < 0) {
|
||||||
|
el.style.display = 'none'; // 背对相机不显示
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
el.style.display = 'block';
|
||||||
|
el.style.left = `${win.x}px`;
|
||||||
|
el.style.top = `${win.y}px`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setupPick() {
|
_setupPick() {
|
||||||
const viewer = this.viewer;
|
const viewer = this.viewer;
|
||||||
// 移除 Viewer 默认的单击选中与双击“追踪实体”:否则双击后会锁定相机(定住)
|
// 移除 Viewer 默认的单击选中与双击“追踪实体”:否则双击后会锁定相机(定住)
|
||||||
|
// 标记便签已改为 DOM 覆盖层,点击由 DOM 元素自行处理
|
||||||
viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
||||||
viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
|
viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
|
||||||
|
|
||||||
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
|
||||||
handler.setInputAction((movement) => {
|
|
||||||
const picked = viewer.scene.pick(movement.position);
|
|
||||||
if (Cesium.defined(picked) && picked.id && picked.id.entryIndex !== undefined) {
|
|
||||||
const index = picked.id.entryIndex;
|
|
||||||
this.onPick?.(index);
|
|
||||||
this.flyToEntry(index);
|
|
||||||
}
|
|
||||||
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
||||||
this._handler = handler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 相机平滑飞至某个标记上空并居中:回到与初始一致的高度与北朝上方向,左键拖拽手感不变
|
// 相机平滑飞至某个标记上空并居中:回到与初始一致的高度与北朝上方向,左键拖拽手感不变
|
||||||
@@ -421,11 +439,10 @@ export class CesiumGlobe {
|
|||||||
setSelected(index) {
|
setSelected(index) {
|
||||||
this.selected = index;
|
this.selected = index;
|
||||||
if (!this.viewer) return;
|
if (!this.viewer) return;
|
||||||
this.markers.forEach(({ entity, noteNormal, noteSelected, index: i }) => {
|
this.markers.forEach((m) => {
|
||||||
const on = i === index;
|
const on = m.index === index;
|
||||||
entity.billboard.image = on ? noteSelected.canvas : noteNormal.canvas; // 选中加黑边
|
if (m.el) m.el.style.backgroundImage = `url("${on ? m.selectedUrl : m.normalUrl}")`;
|
||||||
});
|
});
|
||||||
this.viewer.scene.requestRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(visible) {
|
setVisible(visible) {
|
||||||
|
|||||||
@@ -37,6 +37,20 @@ html, body {
|
|||||||
/* 隐藏左下角全部归属信息(Cesium ion logo + 影像文字) */
|
/* 隐藏左下角全部归属信息(Cesium ion logo + 影像文字) */
|
||||||
#cesium-container .cesium-widget-credits { display: none !important; }
|
#cesium-container .cesium-widget-credits { display: none !important; }
|
||||||
|
|
||||||
|
/* 标记便签 DOM 覆盖层(在 Cesium 画布之上) */
|
||||||
|
#marker-layer {
|
||||||
|
position: absolute; inset: 0; z-index: 20;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.marker-note {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: auto;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* 气氛遮罩 */
|
/* 气氛遮罩 */
|
||||||
.vignette {
|
.vignette {
|
||||||
position: absolute; inset: 0; pointer-events: none;
|
position: absolute; inset: 0; pointer-events: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user