|
|
@@ -6,6 +6,13 @@
|
|
|
<li><span class="dot yellow"></span><span>强热带风暴</span></li>
|
|
|
<li><span class="dot orange"></span><span>台风</span></li>
|
|
|
<li><span class="dot red"></span><span>超强台风</span></li>
|
|
|
+ <li style="margin:6px 0;color:#ccc;font-size:12px;">── 叠加图层 ──</li>
|
|
|
+ <li @click="toggleCurrent" style="cursor:pointer;display:flex;align-items:center;">
|
|
|
+ <span class="dot" style="background:#0ff;"></span> 洋流 {{ showCurrent ? '✅' : '⏹' }}
|
|
|
+ </li>
|
|
|
+ <li @click="toggleTemp" style="cursor:pointer;display:flex;align-items:center;">
|
|
|
+ <span class="dot" style="background:#f80;"></span> 海温 {{ showTemp ? '✅' : '⏹' }}
|
|
|
+ </li>
|
|
|
</ul>
|
|
|
|
|
|
<!-- 信息弹窗 -->
|
|
|
@@ -48,6 +55,12 @@ const popupUpdateCallback = ref(null);
|
|
|
let lastHoverEntity = null;
|
|
|
let handler = null;
|
|
|
|
|
|
+// 洋流 + 温度
|
|
|
+const showCurrent = ref(false);
|
|
|
+const showTemp = ref(false);
|
|
|
+const currentEntities = ref([]);
|
|
|
+const tempEntities = ref([]);
|
|
|
+
|
|
|
const getViewer = () => props.viewer || window.viewer;
|
|
|
|
|
|
// 安全清理
|
|
|
@@ -81,8 +94,12 @@ const updatePopupPosition = (entity) => {
|
|
|
if (!cont) return;
|
|
|
const pos = v.scene.cartesianToCanvasCoordinates(entity.position.getValue(v.clock.currentTime));
|
|
|
if (!pos) return;
|
|
|
+
|
|
|
nextTick(() => {
|
|
|
- popupPosition.value = { x: pos.x + 10, y: pos.y + 10 };
|
|
|
+ popupPosition.value = {
|
|
|
+ x: pos.x + 10,
|
|
|
+ y: pos.y + 10
|
|
|
+ };
|
|
|
});
|
|
|
} catch {}
|
|
|
};
|
|
|
@@ -95,7 +112,7 @@ const getColorByStrength = (s) => {
|
|
|
case "热带低压":return Cesium.Color.GREEN;
|
|
|
case "热带风暴":return Cesium.Color.BLUE;
|
|
|
case "强热带风暴":return Cesium.Color.YELLOW;
|
|
|
- case "台风":return Cesium.Color.fromCssColorString("#FBC712");
|
|
|
+ case "台风":return Cesium.Color.fromCssColor("#FBC712");
|
|
|
case "强台风":return Cesium.Color.PLUM;
|
|
|
case "超强台风":return Cesium.Color.RED;
|
|
|
default:return Cesium.Color.RED;
|
|
|
@@ -126,10 +143,8 @@ const getWindPoints = (c, rs) => {
|
|
|
const x=c[0]+r*Math.sin(rad);
|
|
|
const y=c[1]+r*Math.cos(rad);
|
|
|
p.push(x,y);
|
|
|
- if(i==0&&j==0){fx=x;fy=y;}
|
|
|
}
|
|
|
});
|
|
|
- if(fx!==undefined)p.push(fx,fy);
|
|
|
return p;
|
|
|
};
|
|
|
|
|
|
@@ -153,15 +168,14 @@ const startAnim = (points, data)=>{
|
|
|
typhoonInterval=setInterval(()=>{
|
|
|
if(!props.visible||i>=points.length){clearInterval(typhoonInterval);return;}
|
|
|
const p=points[i];
|
|
|
- const k=i*2;
|
|
|
const c={lng:Number(p.lng),lat:Number(p.lat)};
|
|
|
- const rs=[Math.max(0,350-k),Math.max(0,450-k),Math.max(0,400-k),Math.max(0,350-k)];
|
|
|
+ const rs=[350,450,400,350];
|
|
|
drawWind(c,rs);
|
|
|
i++;
|
|
|
},250);
|
|
|
};
|
|
|
|
|
|
-// 绘制点(无描边、不埋地)
|
|
|
+// 绘制点
|
|
|
const processPoints = (points, data)=>{
|
|
|
const v=getViewer();if(!v||!points.length)return;
|
|
|
entityCollection.value.entities.removeAll();
|
|
|
@@ -178,10 +192,9 @@ const processPoints = (points, data)=>{
|
|
|
point: {
|
|
|
pixelSize:10,
|
|
|
color:getColorByStrength(p.strong),
|
|
|
- // 无白边
|
|
|
outlineWidth: 0,
|
|
|
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
|
- heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|
|
+ disableDepthTestDistance:Number.POSITIVE_INFINITY,
|
|
|
+ heightReference:Cesium.HeightReference.CLAMP_TO_GROUND
|
|
|
}
|
|
|
});
|
|
|
pointDataMap.value.set(id,{...p,name:data.name,enname:data.enname});
|
|
|
@@ -194,6 +207,115 @@ const processPoints = (points, data)=>{
|
|
|
startAnim(points,data);
|
|
|
};
|
|
|
|
|
|
+// ==============================================
|
|
|
+// 箭头洋流(完全保留你现在的效果)
|
|
|
+// ==============================================
|
|
|
+const addOceanCurrent = () => {
|
|
|
+ const v = getViewer();
|
|
|
+ if (!v) return;
|
|
|
+
|
|
|
+ removeOceanCurrent();
|
|
|
+ const step = 1.2;
|
|
|
+
|
|
|
+ for (let lng = 110; lng <= 135; lng += step) {
|
|
|
+ for (let lat = 15; lat <= 35; lat += step) {
|
|
|
+ const angle = (lng * 3 + lat * 2) % 360;
|
|
|
+ const rad = angle * Math.PI / 180;
|
|
|
+ const len = 0.4;
|
|
|
+
|
|
|
+ const x1 = lng;
|
|
|
+ const y1 = lat;
|
|
|
+ const x2 = lng + len * Math.sin(rad);
|
|
|
+ const y2 = lat + len * Math.cos(rad);
|
|
|
+
|
|
|
+ const arrowLine = v.entities.add({
|
|
|
+ polyline: {
|
|
|
+ positions: Cesium.Cartesian3.fromDegreesArray([x1, y1, x2, y2]),
|
|
|
+ material: Cesium.Color.CYAN.withAlpha(0.8),
|
|
|
+ width: 2,
|
|
|
+ clampToGround: true,
|
|
|
+ }
|
|
|
+ });
|
|
|
+ currentEntities.value.push(arrowLine);
|
|
|
+
|
|
|
+ const headSize = 0.08;
|
|
|
+ const dirX = Math.sin(rad);
|
|
|
+ const dirY = Math.cos(rad);
|
|
|
+
|
|
|
+ const p1 = [x2, y2];
|
|
|
+ const p2 = [x2 - dirX * headSize - dirY * headSize, y2 - dirY * headSize + dirX * headSize];
|
|
|
+ const p3 = [x2 - dirX * headSize + dirY * headSize, y2 - dirY * headSize - dirX * headSize];
|
|
|
+
|
|
|
+ const arrowHead = v.entities.add({
|
|
|
+ polygon: {
|
|
|
+ hierarchy: Cesium.Cartesian3.fromDegreesArray([...p1, ...p2, ...p3]),
|
|
|
+ material: Cesium.Color.CYAN.withAlpha(0.9),
|
|
|
+ clampToGround: true,
|
|
|
+ }
|
|
|
+ });
|
|
|
+ currentEntities.value.push(arrowHead);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const removeOceanCurrent = () => {
|
|
|
+ const v = getViewer();
|
|
|
+ if (!v) return;
|
|
|
+ currentEntities.value.forEach(e => {
|
|
|
+ try { v.entities.remove(e); } catch {}
|
|
|
+ });
|
|
|
+ currentEntities.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+const toggleCurrent = () => {
|
|
|
+ showCurrent.value = !showCurrent.value;
|
|
|
+ showCurrent.value ? addOceanCurrent() : removeOceanCurrent();
|
|
|
+};
|
|
|
+
|
|
|
+// ==============================================
|
|
|
+// 【极简测试版温度场】极少实体 · 秒加载 · 秒卸载
|
|
|
+// ==============================================
|
|
|
+const addTemperature = () => {
|
|
|
+ const v = getViewer();
|
|
|
+ if (!v) return;
|
|
|
+ removeTemperature();
|
|
|
+
|
|
|
+ // 超大网格,极少实体
|
|
|
+ const step = 2.0;
|
|
|
+ for (let lng = 110; lng <= 135; lng += step) {
|
|
|
+ for (let lat = 15; lat <= 35; lat += step) {
|
|
|
+ const temp = 22 + (30 - lat) * 0.4;
|
|
|
+ let color;
|
|
|
+ if (temp < 24) color = Cesium.Color.BLUE.withAlpha(0.2);
|
|
|
+ else if (temp < 27) color = Cesium.Color.ORANGE.withAlpha(0.2);
|
|
|
+ else color = Cesium.Color.RED.withAlpha(0.2);
|
|
|
+
|
|
|
+ const rect = v.entities.add({
|
|
|
+ rectangle: {
|
|
|
+ coordinates: Cesium.Rectangle.fromDegrees(lng, lat, lng + step, lat + step),
|
|
|
+ material: color,
|
|
|
+ clampToGround: true
|
|
|
+ }
|
|
|
+ });
|
|
|
+ tempEntities.value.push(rect);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const removeTemperature = () => {
|
|
|
+ const v = getViewer();
|
|
|
+ if (!v) return;
|
|
|
+ tempEntities.value.forEach(e => {
|
|
|
+ try { v.entities.remove(e); } catch {}
|
|
|
+ });
|
|
|
+ tempEntities.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+const toggleTemp = () => {
|
|
|
+ showTemp.value = !showTemp.value;
|
|
|
+ showTemp.value ? addTemperature() : removeTemperature();
|
|
|
+};
|
|
|
+
|
|
|
// 加载
|
|
|
const load = async ()=>{
|
|
|
try{
|
|
|
@@ -202,27 +324,29 @@ const load = async ()=>{
|
|
|
}catch(e){console.error(e);}
|
|
|
};
|
|
|
|
|
|
-// 只保留悬浮,去掉点击飞行
|
|
|
+// 鼠标悬浮
|
|
|
const initEvents = ()=>{
|
|
|
const v=getViewer();if(!v)return;
|
|
|
handler=new Cesium.ScreenSpaceEventHandler(v.scene.canvas);
|
|
|
|
|
|
- // 悬浮显示信息
|
|
|
handler.setInputAction((e)=>{
|
|
|
- const pick=v.scene.pick(e.endPosition);
|
|
|
- if(!pick?.id||!pick.id.id.startsWith('tp-')){
|
|
|
+ const pick = v.scene.pick(e.endPosition);
|
|
|
+ if (!pick || !pick.id || !pick.id.id.startsWith('tp-')) {
|
|
|
hidePopup();
|
|
|
return;
|
|
|
}
|
|
|
- const ent=pick.id;
|
|
|
- if(lastHoverEntity===ent)return;
|
|
|
+
|
|
|
+ const ent = pick.id;
|
|
|
+ if (lastHoverEntity === ent) return;
|
|
|
+
|
|
|
hidePopup();
|
|
|
- lastHoverEntity=ent;
|
|
|
- ent.point.pixelSize=14;
|
|
|
- const d=pointDataMap.value.get(ent.id);
|
|
|
- if(d){
|
|
|
- info.value=d;
|
|
|
- infoVisible.value=true;
|
|
|
+ lastHoverEntity = ent;
|
|
|
+ ent.point.pixelSize = 14;
|
|
|
+
|
|
|
+ const d = pointDataMap.value.get(ent.id);
|
|
|
+ if (d) {
|
|
|
+ info.value = d;
|
|
|
+ infoVisible.value = true;
|
|
|
updatePopupPosition(ent);
|
|
|
}
|
|
|
},Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
|
|
@@ -249,6 +373,10 @@ const cleanup = ()=>{
|
|
|
relatedEntities.value.paths.forEach(e=>v.entities.remove(e));
|
|
|
relatedEntities.value.warnings.forEach(e=>v.entities.remove(e));
|
|
|
fengquanLayers.value.forEach(e=>v.entities.remove(e));
|
|
|
+
|
|
|
+ removeOceanCurrent();
|
|
|
+ removeTemperature();
|
|
|
+
|
|
|
entityCollection.value=null;
|
|
|
relatedEntities.value={paths:[],warnings:[]};
|
|
|
fengquanLayers.value=[];
|
|
|
@@ -273,9 +401,9 @@ onUnmounted(cleanup);
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.typhoon-legend{position:absolute;top:20px;right:20px;background:#00000099;padding:12px;border-radius:6px;color:white;z-index:1000;list-style:none;margin:0;}
|
|
|
-.typhoon-legend li{display:flex;align-items:center;margin:4px 0;}
|
|
|
-.typhoon-legend .dot{width:12px;height:12px;border-radius:50%;margin-right:8px;}
|
|
|
+.typhoon-legend{position:absolute;bottom:20px;right:20px;background:#00000099;padding:12px;border-radius:6px;color:white;z-index:1000;list-style:none;margin:0;min-width:160px;}
|
|
|
+.typhoon-legend li{display:flex;align-items:center;margin:6px 0;font-size:13px;}
|
|
|
+.typhoon-legend .dot{width:12px;height:12px;border-radius:50%;margin-right:8px;flex-shrink:0;}
|
|
|
.typhoon-legend .green{background:green;}
|
|
|
.typhoon-legend .blue{background:blue;}
|
|
|
.typhoon-legend .yellow{background:yellow;}
|
|
|
@@ -283,9 +411,9 @@ onUnmounted(cleanup);
|
|
|
.typhoon-legend .red{background:red;}
|
|
|
|
|
|
.typhoon-popup{
|
|
|
- position:absolute;z-index:1001;background:white;padding:10px;border-radius:6px;
|
|
|
- box-shadow:0 0 10px #00000033;font-size:12px;min-width:260px;pointer-events:none;
|
|
|
+ position:fixed;z-index:1001;background:white;padding:10px 12px;border-radius:8px;
|
|
|
+ box-shadow:0 0 12px rgba(0,0,0,0.2);font-size:12px;min-width:260px;pointer-events:none;
|
|
|
}
|
|
|
.typhoon-popup h3{margin:0 0 6px 0;font-size:14px;}
|
|
|
-.typhoon-popup p{margin:2px 0;}
|
|
|
+.typhoon-popup p{margin:3px 0;line-height:1.4;}
|
|
|
</style>
|