This commit is contained in:
Aifeilong 2023-10-10 15:51:01 +08:00
parent c6c05a64c0
commit 6ddf53ab22
43 changed files with 433 additions and 1095 deletions

BIN
src/assets/cloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
src/assets/moisture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

View File

@ -9,6 +9,7 @@ function WavePoint({
deviationR = 5000, deviationR = 5000,
eachInterval = 1500, eachInterval = 1500,
maxR = 3600 * 100, maxR = 3600 * 100,
color = "WHITE",
}) { }) {
const data = { const data = {
stationLon, // stationLon, //
@ -60,7 +61,7 @@ function WavePoint({
transparent: true, transparent: true,
color: new Cesium.CallbackProperty(function () { color: new Cesium.CallbackProperty(function () {
var alp = 1 - r1 / data.maxR; var alp = 1 - r1 / data.maxR;
return Cesium.Color.WHITE.withAlpha(alp); return Cesium.Color[color].withAlpha(alp);
}, false), }, false),
}) })
} }

View File

@ -1,121 +0,0 @@
import ReactECharts from "echarts-for-react";
const years = [];
for (let year = 2011; year <= 2020; year++) {
years.push(year);
}
const observedData = [
-0.90765893, 0.14846958, 1.6577014, -0.32029018, -1.2422978, 0.44113398,
-0.45218363, 1.897564, -0.6926195, -0.529819,
];
const predictedData = [
-0.20760797, 0.206235, 0.85673275, 0.13969683, -0.39370838, 0.23775028,
-0.34866039, 0.93110625, 0.19143088, 0.5760311,
];
function ChartPanel() {
const option = {
title: {
// text: "Stacked Line",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["观测结果", "预测结果"],
textStyle: { color: "#04fbfd", cursor: "point" },
},
animationDuration: years.length * 1000,
animationEasing: "cubicInOut",
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
// toolbox: {
// feature: {
// //
// saveAsImage: {},
// },
// },
xAxis: {
type: "category",
boundaryGap: false,
data: years,
axisLine: {
onZero: false,
symbol: ["none", "arrow"],
symbolOffser: [0, 10],
lineStyle: {
color: "#04fbfd",
},
},
},
yAxis: {
type: "value",
min: -2.0,
max: 2.0,
interval: 0.5,
splitNumber: 5,
splitLine: { show: false },
axisLine: {
onZero: false,
show: true,
symbol: ["none", "arrow"],
symbolOffset: [0, 10],
lineStyle: {
color: "#04fbfd",
},
},
axisLabel: {
show: true,
},
axisTick: { show: true },
scale: true,
},
dataZoom: { type: "inside", start: 0, end: 100 },
series: [
{
name: "观测结果",
type: "line",
stack: "Total",
data: observedData,
color: "black",
symbol: "none",
itemStyle: {
color: "black",
},
},
{
name: "预测结果",
type: "line",
// stack: "Total",
data: predictedData,
color: "red",
symbol: "none",
itemStyle: {
color: "red",
},
},
],
};
return (
<div className="chart-info-panel">
<ReactECharts
option={option}
lazyUpdate={true}
style={{
height: "100%",
width: "100%",
}}
/>
</div>
);
}
export default ChartPanel;

View File

@ -0,0 +1,31 @@
import { useMemo } from "react";
import { ImageryLayer } from "resium";
import { WebMapServiceImageryProvider } from "cesium";
const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms";
const name = "phitrellis:4_5_heatmap_htmp";
function HeatmapImageLayer() {
const tempProvider = useMemo(
() =>
new WebMapServiceImageryProvider({
url: url,
layers: name,
parameters: {
service: "WMS",
format: "image/png",
transparent: true,
},
}),
[name, url]
);
return (
<ImageryLayer
key={`ImageryLayer-${name}`}
imageryProvider={tempProvider}
show={true}
/>
);
}
export default HeatmapImageLayer;

View File

@ -0,0 +1,48 @@
const colorBar = [
"#7272FF",
"#8585FF",
"#fff",
"#FFAFAF",
"#FF9C9C",
"#FF8787",
"#FF7474",
"#FF6060",
"#FF4B4B",
"#FE3838",
"#FF2323",
];
function Legend() {
return (
<div className="legend">
<div className="legend-title"></div>
<div className="colorbar">
{colorBar.map((color, index) => {
return (
<div
key={`colorbar-item-${index}`}
className="colorbar-item"
style={{ backgroundColor: color }}
/>
);
})}
</div>
<div className="legend-text">
{[-0.02, -0.01, 0.01, 0.02, 0.03, 0.05, 0.1, 0.15, 0.2, 0.25, ""].map(
(item, index) => {
return (
<div
key={`legend-text-item-${index}`}
className="legend-text-item"
>
{item}
</div>
);
}
)}
</div>
</div>
);
}
export default Legend;

View File

@ -1,12 +1,12 @@
import { useState } from "react";
import MapLayout from "@/components/map/Layout"; import MapLayout from "@/components/map/Layout";
import CustomToolbar from "@/components/common/CustomToolbar"; import CustomToolbar from "@/components/common/CustomToolbar";
import CustomClock from "@/components/common/CustomClock"; import CustomClock from "@/components/common/CustomClock";
import TextInfoPanel from "@/components/common/TextInfoPanel"; import TextInfoPanel from "@/components/common/TextInfoPanel";
import CustomFlyTo from "./CustomFlyTo"; import CustomFlyTo from "./CustomFlyTo";
import FormPanel from "./FormPanel"; import FormPanel from "./FormPanel";
import ChartPanel from "./ChartPanel"; import HeatmapImageLayer from "./HeatmapImageLayer";
// import RectangleLayer from "./RectangleLayer"; import Legend from "./Legend";
import { useState } from "react";
export default function DomainFive() { export default function DomainFive() {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
@ -27,9 +27,10 @@ export default function DomainFive() {
<div className="top-panel"> <div className="top-panel">
<FormPanel setShow={setShow} /> <FormPanel setShow={setShow} />
</div> </div>
<div className="bottom-panel">{show && <ChartPanel />}</div> <div className="bottom-panel" />
</div> </div>
{/* <RectangleLayer /> */} {show && <HeatmapImageLayer />}
<Legend />
</MapLayout> </MapLayout>
); );
} }

View File

@ -1,4 +1,4 @@
import { Button, Form, Input, Select } from "antd"; import { Button, Form, Select } from "antd";
const layout = { const layout = {
labelCol: { span: 6 }, labelCol: { span: 6 },
@ -38,6 +38,7 @@ export default function FormPanel({ setShow }) {
], ],
}} }}
onFinish={(values) => { onFinish={(values) => {
// valuescharts
setShow(true); setShow(true);
}} }}
> >

View File

@ -1,4 +1,4 @@
import { Cartesian3, Rectangle, Color, Math as CesiumMath } from "cesium"; import { Rectangle, Color, Math as CesiumMath } from "cesium";
import { Entity, RectangleGraphics } from "resium"; import { Entity, RectangleGraphics } from "resium";
const points = [ const points = [
[13, -70, 30, -67], [13, -70, 30, -67],

View File

@ -1,11 +1,5 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { import { Entity, LabelGraphics, EllipseGraphics, useCesium } from "resium";
Entity,
LabelGraphics,
EllipseGraphics,
useCesium,
CylinderGraphics,
} from "resium";
import { Color, Cartesian3, LabelStyle } from "cesium"; import { Color, Cartesian3, LabelStyle } from "cesium";
import { useInterval } from "ahooks"; import { useInterval } from "ahooks";
@ -31,7 +25,6 @@ function Barotropic() {
<Entity <Entity
show={show} show={show}
name="Barotropic" name="Barotropic"
// description="Barotropic"
position={Cartesian3.fromDegrees(98, 48, 0)} position={Cartesian3.fromDegrees(98, 48, 0)}
> >
<EllipseGraphics <EllipseGraphics
@ -40,7 +33,6 @@ function Barotropic() {
semiMajorAxis={300000.0} semiMajorAxis={300000.0}
extrudedHeight={1000000.0} extrudedHeight={1000000.0}
rotation={0} rotation={0}
// outline
/> />
<LabelGraphics <LabelGraphics
position={Cartesian3.fromDegrees(98, 48, 0)} position={Cartesian3.fromDegrees(98, 48, 0)}

View File

@ -1,7 +1,7 @@
import ReactECharts from "echarts-for-react"; import ReactECharts from "echarts-for-react";
import cls from "classnames"; import cls from "classnames";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useCesium } from "resium";
const years = []; const years = [];
@ -30,19 +30,23 @@ const laBrudata = [
]; ];
function ChartPanel() { function ChartPanel() {
const { viewer } = useCesium();
const [isHighlight, setIsHighlight] = useState(false); const [isHighlight, setIsHighlight] = useState(false);
const { showPanelHighlight } = useSelector((state) => state.data);
const { shouldAnimate } = viewer.clock;
// useInterval(() => {
// console.log(currentTime, startTime, stopTime);
// }, 100);
useEffect(() => { useEffect(() => {
if (showPanelHighlight) { if (shouldAnimate) {
setIsHighlight(true); setIsHighlight(true);
setTimeout(() => { setTimeout(() => {
setIsHighlight(false); setIsHighlight(false);
}, 3000); }, 3000);
} }
}, [showPanelHighlight]); }, [shouldAnimate]);
console.log("showPanelHighlight :>> ", showPanelHighlight);
const option = { const option = {
title: { title: {

View File

@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { ImageryLayer, useCesium } from "resium"; import { ImageryLayer, useCesium } from "resium";
import { Cesium3DTile, WebMapServiceImageryProvider } from "cesium"; import { WebMapServiceImageryProvider } from "cesium";
import { useInterval } from "ahooks"; import { useInterval } from "ahooks";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@ -21,7 +21,7 @@ function LabradorImageLayer() {
useEffect(() => { useEffect(() => {
const { labrador } = imageLayer; const { labrador } = imageLayer;
if (!!labrador) { if (!!labrador) {
setDelay(1000); setDelay((60 / nameList.length) * 1000);
setIndex(0); setIndex(0);
} }
}, [imageLayer]); }, [imageLayer]);
@ -40,29 +40,22 @@ function LabradorImageLayer() {
}), }),
[name, url] [name, url]
); );
return ( return (
<ImageryLayer <ImageryLayer
key={`ImageryLayer-${index}`} key={`ImageryLayer-${index}`}
imageryProvider={tempProvider} imageryProvider={tempProvider}
show={true} show={true}
style={{ color: Cesium.Color.RED }}
/> />
); );
}); });
useInterval(() => { useInterval(() => {
setIndex((index) => index + 1); setIndex((index) => index + 1);
if (index === 0) {
dispatch.data.update({ showPanelHighlight: true });
}
if (index >= nameList.length) { if (index >= nameList.length) {
setTimeout(() => { setIndex(0);
setIndex(0); setDelay((60 / nameList.length) * 1000);
setDelay(1000);
}, (60 - nameList.length) * 1000);
setDelay(undefined);
dispatch.data.update({ showPanelHighlight: false });
} }
}, delay); }, delay);

View File

@ -17,13 +17,10 @@ function TibetImageLayer() {
const [delay, setDelay] = useState(undefined); const [delay, setDelay] = useState(undefined);
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
//viewer.clock?.shouldAnimate
console.log("viewer.clock?.shouldAnimate :>> ", viewer.clock?.shouldAnimate);
useEffect(() => { useEffect(() => {
const { labrador } = imageLayer; const { labrador } = imageLayer;
if (!!labrador) { if (!!labrador) {
setDelay(1000); setDelay((60 / nameList.length) * 1000);
setIndex(0); setIndex(0);
} }
}, [imageLayer]); }, [imageLayer]);
@ -55,11 +52,8 @@ function TibetImageLayer() {
setIndex((index) => index + 1); setIndex((index) => index + 1);
if (index >= nameList.length) { if (index >= nameList.length) {
setTimeout(() => { setIndex(0);
setIndex(0); setDelay((60 / nameList.length) * 1000);
setDelay(1000);
}, (60 - nameList.length) * 1000);
setDelay(undefined);
} }
}, delay); }, delay);

View File

@ -41,7 +41,7 @@ export default function DomainOne() {
<EntityLegend /> <EntityLegend />
<Legend /> <Legend />
<LabradorImageLayer /> <LabradorImageLayer />
{/* <TibetImageLayer /> */} <TibetImageLayer />
</MapLayout> </MapLayout>
); );
} }

View File

@ -1,13 +1,11 @@
import { useEffect, useMemo, useState } from "react"; import { useMemo } from "react";
import { ImageryLayer, useCesium } from "resium"; import { ImageryLayer } from "resium";
import { WebMapServiceImageryProvider } from "cesium"; import { WebMapServiceImageryProvider } from "cesium";
const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms"; const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms";
const name = "phitrellis:4_3_t2m_era_1024"; const name = "phitrellis:4_3_t2m_era_1024";
function LandImageLayer() { function LandImageLayer() {
const { viewer } = useCesium();
const tempProvider = useMemo( const tempProvider = useMemo(
() => () =>
new WebMapServiceImageryProvider({ new WebMapServiceImageryProvider({

View File

@ -0,0 +1,52 @@
import styles from "./index.module.less";
const colorBar = [
"#7F20F0",
"#2A09FA",
"#1C33F1",
"#3B6CE3",
"#2388F8",
"#0EA5FE",
"#23C0FB",
"#85CAF1",
"#B8E1F3",
"#FEF995",
"#FEDD30",
"#FEB709",
"#FF8B00",
"#FF5D00",
"#FF1001",
"#E00000",
"#D62A2A",
"#FFB1B1",
];
function Legend() {
return (
<div className={styles.legend}>
<div className="legend-title"></div>
<div className="colorbar">
{colorBar.map((color, index) => {
return (
<div
key={`colorbar-item-${index}`}
className="colorbar-item"
style={{ backgroundColor: color }}
/>
);
})}
</div>
<div className="legend-text">
{[-0.8, -0.4, 0, 0.4, 0.8, ""].map((item, index) => {
return (
<div key={`legend-text-item-${index}`} className="legend-text-item">
{item}
</div>
);
})}
</div>
</div>
);
}
export default Legend;

View File

@ -1,8 +1,6 @@
import { useEffect, useMemo, useState } from "react"; import { useMemo } from "react";
import { ImageryLayer, useCesium } from "resium"; import { ImageryLayer } from "resium";
import { WebMapServiceImageryProvider } from "cesium"; import { WebMapServiceImageryProvider } from "cesium";
import { useInterval } from "ahooks";
import { useSelector } from "react-redux";
const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms"; const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms";
const name = "phitrellis:4_3_hadisst_1822"; const name = "phitrellis:4_3_hadisst_1822";

View File

@ -8,6 +8,7 @@ import OceanImageLayer from "./OceanImageLayer";
import WavePoint from "@/components/common/WavePoint"; import WavePoint from "@/components/common/WavePoint";
import { CameraFlyTo } from "resium"; import { CameraFlyTo } from "resium";
import { Cartesian3 } from "cesium"; import { Cartesian3 } from "cesium";
import Legend from "./Legend";
export default function DomainOne() { export default function DomainOne() {
return ( return (
@ -26,37 +27,38 @@ export default function DomainOne() {
<CustomClock /> <CustomClock />
<CameraFlyTo <CameraFlyTo
duration={5} duration={5}
destination={Cartesian3.fromDegrees(0, -85, 16000000)} destination={Cartesian3.fromDegrees(80, -85, 10000000)}
/> />
<div className="left-panel one"> <div className="left-panel one">
<TextInfoPanel content="利用GISTEMP资料通过EEMD分解方法提取两极温度多年代际变化序列发现南北极温度变化的跷跷板现象与大西洋多年代际振荡AMO紧密相关。而AMO与热带大西洋经向模AMM在年代际尺度上显著相关。ERA5再分析资料显示AMM可以通过Rossby波影响西南极的气温与海冰偶极子。因此AMM可能在联系南北极气候变化的中起到了重要的媒介作用。" /> <TextInfoPanel content="利用GISTEMP资料通过EEMD分解方法提取两极温度多年代际变化序列发现南北极温度变化的跷跷板现象与大西洋多年代际振荡AMO紧密相关。而AMO与热带大西洋经向模AMM在年代际尺度上显著相关。ERA5再分析资料显示AMM可以通过Rossby波影响西南极的气温与海冰偶极子。因此AMM可能在联系南北极气候变化的中起到了重要的媒介作用。" />
</div> </div>
<WavePoint stationLon={88} stationLat={-85} /> <WavePoint stationLon={88} stationLat={-85} />
{/* <LandImageLayer /> */} <LandImageLayer />
{/* <OceanImageLayer /> */}
</MapLayout> </MapLayout>
</div> </div>
<div style={{ flex: 1, position: "relative" }}> <div style={{ flex: 1, position: "relative" }}>
<MapLayout> <MapLayout>
<OceanImageLayer />
<CameraFlyTo <CameraFlyTo
duration={5} duration={5}
destination={Cartesian3.fromDegrees(0, 88, 16000000)} destination={Cartesian3.fromDegrees(0, 88, 10000000)}
/> />
<WavePoint stationLon={0} stationLat={87} /> <WavePoint color={"WHITE"} stationLon={0} stationLat={87} />
</MapLayout> </MapLayout>
</div> </div>
<div style={{ flex: 1, position: "relative" }}> <div style={{ flex: 1, position: "relative" }}>
<MapLayout> <MapLayout>
<CameraFlyTo <CameraFlyTo
duration={5} duration={5}
destination={Cartesian3.fromDegrees(100, 33, 16000000)} destination={Cartesian3.fromDegrees(100, 33, 10000000)}
/> />
<div className="right-panel one"> <div className="right-panel one">
<ChartPanel /> <ChartPanel />
</div> </div>
<WavePoint stationLon={88} stationLat={33} /> <WavePoint maxR={3600 * 200} stationLon={88} stationLat={33} />
</MapLayout> </MapLayout>
</div> </div>
{/* <Legend /> */}
</div> </div>
); );
} }

View File

@ -0,0 +1,53 @@
.legend :global {
position: absolute;
bottom: 40px;
width: 50%;
left: 25%;
z-index: 1000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #1f485690;
border: 1px solid #04fbfd;
border-radius: 8px;
padding: 8px;
.legend-title {
color: #04fbfd;
}
.colorbar {
width: 100%;
height: 14px;
display: flex;
margin: 8px 0;
.colorbar-item {
flex: 1;
height: 100%;
// border-radius: 8px;
border: 1px black solid;
&:not(:nth-child(1)) {
border-left: none;
}
}
}
.legend-text {
display: flex;
justify-content: space-evenly;
width: 100%;
height: 20px;
margin-left: 8px;
.legend-text-item {
flex: 1;
text-align: right;
font-weight: 600;
color: white;
-webkit-text-stroke: #04fbfd 1px;
}
}
}

View File

@ -1,6 +1,5 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { Entity, EllipsoidGraphics, useCesium, PolylineGraphics } from "resium"; import { Entity, EllipsoidGraphics, useCesium, PolylineGraphics } from "resium";
import { useParams } from "react-router-dom";
import { useInterval, useThrottleFn } from "ahooks"; import { useInterval, useThrottleFn } from "ahooks";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";

View File

@ -0,0 +1,92 @@
import { useCesium } from "resium";
function Cloud() {
const { viewer } = useCesium();
const scene = viewer.scene;
const position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 50);
Cesium.Math.setRandomNumberSeed(2.5);
function getRandomNumberInRange(minValue, maxValue) {
return minValue + Cesium.Math.nextRandomNumber() * (maxValue - minValue);
}
const clouds = new Cesium.CloudCollection();
// manually position clouds in the mountains
function createBackLayerClouds() {
clouds.add({
position: Cesium.Cartesian3.fromDegrees(-122.6908, 45.496, 300),
scale: new Cesium.Cartesian2(1500, 250),
maximumSize: new Cesium.Cartesian3(50, 15, 13),
slice: 0.3,
});
clouds.add({
position: Cesium.Cartesian3.fromDegrees(-122.72, 45.5, 335),
scale: new Cesium.Cartesian2(1500, 300),
maximumSize: new Cesium.Cartesian3(50, 12, 15),
slice: 0.36,
});
}
let long, lat, height, scaleX, scaleY, aspectRatio, cloudHeight, depth, slice;
// randomly generate clouds in a certain area
function createRandomClouds(
numClouds,
startLong,
stopLong,
startLat,
stopLat,
minHeight,
maxHeight
) {
const rangeLong = stopLong - startLong;
const rangeLat = stopLat - startLat;
for (let i = 0; i < numClouds; i++) {
long = startLong + getRandomNumberInRange(0, rangeLong);
lat = startLat + getRandomNumberInRange(0, rangeLat);
height = getRandomNumberInRange(minHeight, maxHeight);
scaleX = getRandomNumberInRange(150, 350);
scaleY = scaleX / 2.0 - getRandomNumberInRange(0, scaleX / 4.0);
slice = getRandomNumberInRange(0.3, 0.7);
depth = getRandomNumberInRange(5, 20);
aspectRatio = getRandomNumberInRange(1.5, 2.1);
cloudHeight = getRandomNumberInRange(5, 20);
clouds.add({
position: Cesium.Cartesian3.fromDegrees(long, lat, height),
scale: new Cesium.Cartesian2(scaleX, scaleY),
maximumSize: new Cesium.Cartesian3(
aspectRatio * cloudHeight,
cloudHeight,
depth
),
slice: slice,
});
}
}
// manually position clouds in front
const scratch = new Cesium.Cartesian3();
function createFrontLayerClouds() {
clouds.add({
position: Cesium.Cartesian3.fromDegrees(-122.666, 45.5126, 97),
scale: new Cesium.Cartesian2(400, 150),
maximumSize: new Cesium.Cartesian3(25, 12, 15),
slice: 0.36,
});
clouds.add({
position: Cesium.Cartesian3.fromDegrees(-122.6665, 45.5262, 76),
scale: new Cesium.Cartesian2(450, 200),
maximumSize: new Cesium.Cartesian3(25, 14, 12),
slice: 0.3,
});
}
createBackLayerClouds();
createRandomClouds(8, -122.685, -122.67, 45.51, 45.525, 50, 250);
createFrontLayerClouds();
scene.primitives.add(clouds);
}
export default Cloud;

View File

@ -1,59 +1,48 @@
import cyclone from "@/assets/cyclone.png";
import anticyclone from "@/assets/anticyclone.png";
import antiHadleyCell from "@/assets/anti-Hadleycell.png"; import antiHadleyCell from "@/assets/anti-Hadleycell.png";
import low from "@/assets/Low.png"; import low from "@/assets/Low.png";
import high from "@/assets/High.png"; import high from "@/assets/High.png";
import cloud from "@/assets/cloud.png";
import moisture from "@/assets/moisture.png";
function EntityLegend() { function EntityLegend() {
return ( return (
<div className="entity-legend"> <div className="entity-legend" style={{ bottom: 40 }}>
<div className="entity-legend-item"> <div className="entity-legend-item">
<div> <div>
<img src={low} width={32} /> <img src={low} alt="low" width={32} />
</div> </div>
<div className="entity-legend-item-name">Low</div> <div className="entity-legend-item-name">Low</div>
</div> </div>
<div className="entity-legend-item"> <div className="entity-legend-item">
<div> <div>
<img src={anticyclone} width={32} /> <img src={moisture} alt="moisture" width={32} />
</div> </div>
<div className="entity-legend-item-name">Moisture transport</div> <div className="entity-legend-item-name">Moisture transport</div>
</div> </div>
<div className="entity-legend-item"> <div className="entity-legend-item">
<div> <div>
<img src={antiHadleyCell} width={32} /> <img src={antiHadleyCell} alt="antiHadleyCell" width={32} />
</div> </div>
<div className="entity-legend-item-name">anti-Hadley cell</div> <div className="entity-legend-item-name">anti-Hadley cell</div>
</div> </div>
<div className="entity-legend-item"> <div className="entity-legend-item">
<div> <div>
<img src={high} width={32} /> <img src={high} alt="high" width={32} />
</div> </div>
<div className="entity-legend-item-name">High</div> <div className="entity-legend-item-name">High</div>
</div> </div>
<div className="entity-legend-item"> <div className="entity-legend-item">
<div> <div>
<img src={cyclone} width={32} /> <img src={cloud} alt="cloud" width={32} />
</div> </div>
<div className="entity-legend-item-name">Rainfall</div> <div className="entity-legend-item-name">Rainfall</div>
</div> </div>
<div className="entity-legend-item"> <div className="entity-legend-item">
<div {/* <div>
style={{ <img src="" />
width: 48,
height: 32,
borderRadius: "999px",
backgroundColor: "#8d5555",
}}
></div>
<div className="entity-legend-item-name">Surface wd anomaly</div>
</div>
{/* <div className="entity-legend-item">
<div>
<img src={waterwapor} width={32} />
</div> </div>
<div className="entity-legend-item-name">water wapor path</div> <div className="entity-legend-item-name">Surface wind anomaly</div> */}
</div> */} </div>
</div> </div>
); );
} }

View File

@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from "react"; import { useMemo } from "react";
import { ImageryLayer, useCesium } from "resium"; import { ImageryLayer } from "resium";
import { WebMapServiceImageryProvider } from "cesium"; import { WebMapServiceImageryProvider } from "cesium";
import { useInterval } from "ahooks";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms"; const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms";

View File

@ -0,0 +1,54 @@
import { useInterval } from "ahooks";
import { Fragment, useCallback, useState } from "react";
import { Entity, PolylineGraphics, useCesium } from "resium";
function MoistureTransport() {
const { viewer } = useCesium();
const [show, setShow] = useState(false);
const showAnimate = useCallback(() => {
const { currentTime, stopTime } = viewer.clock;
const leftTime = Math.floor(
stopTime.secondsOfDay - currentTime.secondsOfDay
);
if (leftTime < 10) {
setShow(true);
} else if (show) setShow(false);
}, [show]);
useInterval(showAnimate, 100);
return (
<Fragment>
<Entity show={show}>
<PolylineGraphics
positions={Cesium.Cartesian3.fromDegreesArrayHeights([
85, 33, 200000, 87, 32, 0,
])}
width={15}
material={
new Cesium.PolylineArrowMaterialProperty(
Cesium.Color.fromCssColorString("#70ad47")
)
}
/>
</Entity>
<Entity show={show}>
<PolylineGraphics
positions={Cesium.Cartesian3.fromDegreesArrayHeights([
83, 27, 0, 87, 31, 0,
])}
width={15}
material={
new Cesium.PolylineArrowMaterialProperty(
Cesium.Color.fromCssColorString("#70ad47")
)
}
/>
</Entity>
</Fragment>
);
}
export default MoistureTransport;

View File

@ -1,10 +1,8 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useParams } from "react-router-dom";
import { Entity, PolygonGraphics, useCesium } from "resium"; import { Entity, PolygonGraphics, useCesium } from "resium";
import { useInterval } from "ahooks"; import { useInterval } from "ahooks";
function PlateauPolygon() { function PlateauPolygon() {
const { type } = useParams();
const { viewer } = useCesium(); const { viewer } = useCesium();
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
@ -17,7 +15,7 @@ function PlateauPolygon() {
if (leftTime < 20) { if (leftTime < 20) {
setShow(true); setShow(true);
} else if (show) setShow(false); } else if (show) setShow(false);
}, [show, type]); }, [show]);
useInterval(showAnimate, 100); useInterval(showAnimate, 100);

View File

@ -0,0 +1,39 @@
import { useCallback, useState } from "react";
import { useInterval } from "ahooks";
import { Entity, PolylineGraphics, useCesium } from "resium";
function SurfaceAnomaly() {
const { viewer } = useCesium();
const [show, setShow] = useState(false);
const showAnimate = useCallback(() => {
const { currentTime, stopTime } = viewer.clock;
const leftTime = Math.floor(
stopTime.secondsOfDay - currentTime.secondsOfDay
);
if (leftTime < 10) {
setShow(true);
} else if (show) setShow(false);
}, [show]);
useInterval(showAnimate, 100);
return (
<Entity show={show}>
<PolylineGraphics
positions={Cesium.Cartesian3.fromDegreesArrayHeights([
90, -30, 0, 90, -21, 0,
])}
width={10}
material={
new Cesium.PolylineArrowMaterialProperty(
Cesium.Color.fromCssColorString("#000")
)
}
/>
</Entity>
);
}
export default SurfaceAnomaly;

View File

@ -10,6 +10,8 @@ import Legend from "./Legend";
import IndianOceanSST from "./IndiaOceanSST"; import IndianOceanSST from "./IndiaOceanSST";
import EntityLegend from "./EntityLegend"; import EntityLegend from "./EntityLegend";
import Circles from "./Circles"; import Circles from "./Circles";
import SurfaceAnomaly from "./SurfaceAnomaly";
import MoistureTransport from "./MoistureTransport";
export default function DomainTwo() { export default function DomainTwo() {
return ( return (
@ -28,6 +30,8 @@ export default function DomainTwo() {
<EntityLegend /> <EntityLegend />
<Legend /> <Legend />
<IndianOceanSST /> <IndianOceanSST />
{/* <SurfaceAnomaly /> */}
<MoistureTransport />
</MapLayout> </MapLayout>
); );
} }

View File

@ -1,4 +1,3 @@
import { Button } from "antd";
import { useCallback } from "react"; import { useCallback } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import NavBarButton from "./NavBarButton"; import NavBarButton from "./NavBarButton";

View File

@ -1,28 +0,0 @@
import { Clock } from "resium";
//
const start = Cesium.JulianDate.fromDate(new Date());
//
const stop = Cesium.JulianDate.addSeconds(start, 60, new Cesium.JulianDate());
function CustomClock() {
return (
<Clock
multiplier={1}
shouldAnimate={false}
startTime={start.clone()}
stopTime={stop.clone()}
currentTime={start.clone()}
clockRange={Cesium.ClockRange.LOOP_STOP}
// onTick={(clock) => {
// if (!clock.shouldAnimate) return;
// }}
// onStop={() => {
// if (toolbar.showPanel === undefined)
// dispatch.data.updateToolbar({ showPanel: true });
// }}
/>
);
}
export default CustomClock;

View File

@ -1,35 +0,0 @@
import { useEffect, useMemo, useState } from "react";
import { ImageryLayer, useCesium } from "resium";
import { WebMapServiceImageryProvider } from "cesium";
import { useInterval } from "ahooks";
import { useSelector } from "react-redux";
const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms";
function LayerAntarcticaToQTP() {
const { imageLayer } = useSelector((state) => state.data);
const tempProvider = useMemo(
() =>
new WebMapServiceImageryProvider({
url: url,
layers: "phitrellis:india_ocean_sst",
parameters: {
service: "WMS",
format: "image/png",
transparent: true,
},
}),
[url]
);
return (
<ImageryLayer
key={`ImageryLayer-india-ocean`}
imageryProvider={tempProvider}
show={!!imageLayer.indianOcean}
/>
);
}
export default LayerAntarcticaToQTP;

View File

@ -1,71 +0,0 @@
import { useEffect, useMemo, useState } from "react";
import { ImageryLayer, useCesium } from "resium";
import { WebMapServiceImageryProvider } from "cesium";
import { useInterval } from "ahooks";
import { useSelector } from "react-redux";
const url = "http://analysis.tpdc.ac.cn/gs/geoserver/phitrellis/wms";
const nameList = [
1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992,
1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
].map((item) => "phitrellis:4_1_la_" + item);
function LayerLabToQTP() {
const { viewer } = useCesium();
const { imageLayer } = useSelector((state) => state.data);
const [delay, setDelay] = useState(undefined);
const [index, setIndex] = useState(0);
//viewer.clock?.shouldAnimate
console.log("viewer.clock?.shouldAnimate :>> ", viewer.clock?.shouldAnimate);
useEffect(() => {
const { labrador } = imageLayer;
if (!!labrador) {
setDelay(1000);
setIndex(0);
}
}, [imageLayer]);
const layers = nameList.map((name, index) => {
const tempProvider = useMemo(
() =>
new WebMapServiceImageryProvider({
url: url,
layers: name,
parameters: {
service: "WMS",
format: "image/png",
transparent: true,
},
}),
[name, url]
);
return (
<ImageryLayer
key={`ImageryLayer-${index}`}
imageryProvider={tempProvider}
show={true}
/>
);
});
useInterval(() => {
setIndex((index) => index + 1);
if (index >= nameList.length) {
setTimeout(() => {
setIndex(0);
setDelay(1000);
}, (60 - nameList.length) * 1000);
setDelay(undefined);
}
}, delay);
if (!viewer.clock?.shouldAnimate) return;
return layers[index];
}
export default LayerLabToQTP;

View File

@ -1,13 +0,0 @@
import { useParams } from "react-router-dom";
import LayerLabToQTP from "./LayerLabToQTP";
import LayerAntarcticaToQTP from "./LayerAntarcticaToQTP";
const DynamicImageryLayer = () => {
const { type } = useParams();
if (type === "1") return <LayerLabToQTP />;
else if (type === "2") return <LayerAntarcticaToQTP />;
else return <></>;
};
export default DynamicImageryLayer;

View File

@ -1,63 +0,0 @@
import { useCallback, useState } from "react";
import {
Entity,
LabelGraphics,
EllipseGraphics,
useCesium,
CylinderGraphics,
} from "resium";
import { Color, Cartesian3, LabelStyle } from "cesium";
import { useParams } from "react-router-dom";
import { useInterval } from "ahooks";
function Barotropic() {
const { viewer } = useCesium();
const { type } = useParams();
const [show, setShow] = useState(false);
const showAnimate = useCallback(() => {
const { currentTime, stopTime } = viewer.clock;
const leftTime = Math.floor(
stopTime.secondsOfDay - currentTime.secondsOfDay
);
if (leftTime < 10) {
setShow(true);
} else if (show) setShow(false);
}, [show]);
useInterval(showAnimate, 100);
if (type !== "1") return <></>;
return (
<Entity
show={show}
name="Barotropic"
// description="Barotropic"
position={Cartesian3.fromDegrees(88, 60, 0)}
>
<EllipseGraphics
material={new Color(0.73, 0.94, 0.95, 0.4)}
semiMinorAxis={150000.0}
semiMajorAxis={150000.0}
extrudedHeight={1000000.0}
rotation={0}
// outline
/>
<LabelGraphics
position={Cartesian3.fromDegrees(88, 60, 0)}
text={"barotropic"}
font="24px Helvetica"
fillColor={Color.SKYBLUE}
outlineColor={Color.BLACK}
outlineWidth={2}
style={LabelStyle.FILL_AND_OUTLINE}
eyeOffset={new Cesium.Cartesian2(0, 500000)}
/>
</Entity>
);
}
export default Barotropic;

View File

@ -1,157 +0,0 @@
import { Fragment, useCallback, useState } from "react";
import { Entity, EllipsoidGraphics, useCesium, PolylineGraphics } from "resium";
import { useParams } from "react-router-dom";
import { useInterval, useThrottleFn } from "ahooks";
import { useDispatch } from "react-redux";
const lowCircle = (
<EllipsoidGraphics
radii={new Cesium.Cartesian3(460000.0, 460000.0, 460000.0)}
innerRadii={new Cesium.Cartesian3(415000.0, 415000.0, 415000.0)}
minimumCone={Cesium.Math.toRadians(89.8)}
maximumCone={Cesium.Math.toRadians(90.2)}
material={new Cesium.Color(0.29, 0.46, 0.77, 1)}
/>
);
const highCircle = (
<EllipsoidGraphics
radii={new Cesium.Cartesian3(460000.0, 460000.0, 460000.0)}
innerRadii={new Cesium.Cartesian3(415000.0, 415000.0, 415000.0)}
minimumCone={Cesium.Math.toRadians(89.8)}
maximumCone={Cesium.Math.toRadians(90.2)}
material={new Cesium.Color(0.99, 0.23, 0.23, 1)}
/>
);
function Circles() {
const { viewer } = useCesium();
const { type } = useParams();
const dispatch = useDispatch();
if (type !== "2") return <></>;
const { run: showInfo } = useThrottleFn(
() => {
dispatch.data.updateImageLayer({ indianOcean: true });
dispatch.data.update({ showSite: true });
},
{
wait: 1000,
}
);
const { run: closeInfo } = useThrottleFn(
() => {
dispatch.data.updateImageLayer({ indianOcean: false });
dispatch.data.update({ showSite: false });
},
{
wait: 1000,
}
);
const Circle = ({ id, lon, lat, isLow, showTime }) => {
const circle = isLow ? lowCircle : highCircle;
const [show, setShow] = useState(false);
useInterval(() => {
const { startTime, currentTime, shouldAnimate } = viewer.clock;
const _time = Math.floor(
currentTime.secondsOfDay - startTime.secondsOfDay
);
if (!shouldAnimate) return;
if (_time === 40) {
showInfo();
} else if (_time === 0) {
closeInfo();
}
if (_time >= showTime) {
setShow(true);
} else {
if (!show) return;
setShow(false);
}
}, 300);
if (!show) return <></>;
return (
<Fragment>
<Entity
id={`${id}-up`}
position={Cesium.Cartesian3.fromDegrees(lon, lat, 1000000)}
>
{circle}
</Entity>
{show && (
<Entity id={`${id}-connection-line`}>
<PolylineGraphics
positions={Cesium.Cartesian3.fromDegreesArrayHeights([
lon,
lat,
1000000,
lon,
lat,
0,
])}
width={2}
material={
new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.WHITE,
dashLength: 4,
})
}
/>
</Entity>
)}
<Entity
id={`${id}-down`}
position={Cesium.Cartesian3.fromDegrees(lon, lat, 0)}
>
{circle}
</Entity>
</Fragment>
);
};
return (
<Fragment>
<Circle
isLow={true}
id={`low-circle-1`}
showTime={10}
lon={-110}
lat={-60}
/>
<Circle
isLow={false}
id={`height-circle-1`}
showTime={20}
lon={-30}
lat={-55}
/>
<Circle
isLow={true}
id={`low-circle-2`}
showTime={30}
lon={30}
lat={-40}
/>
<Circle
isLow={false}
id={`height-circle-2`}
showTime={40}
lon={65}
lat={-35}
/>
<Circle
isLow={true}
id={`low-circle-3`}
showTime={50}
lon={95}
lat={-30}
/>
</Fragment>
);
}
export default Circles;

View File

@ -1,146 +0,0 @@
function Cloud() {
const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;
const position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 50);
function getColor(colorName) {
return Cesium.Color[colorName.toUpperCase()];
}
// These noise parameters are set to default, but can be changed
// to produce different cloud results. However, the noise is precomputed,
// so this cannot be changed dynamically.
const clouds = scene.primitives.add(
new Cesium.CloudCollection({
noiseDetail: 16.0,
noiseOffset: Cesium.Cartesian3.ZERO,
})
);
const cloudParameters = {
scaleWithMaximumSize: true,
scaleX: 25,
scaleY: 12,
maximumSizeX: 25,
maximumSizeY: 12,
maximumSizeZ: 15,
renderSlice: true, // if false, renders the entire surface of the ellipsoid
slice: 0.36,
brightness: 1.0,
color: "White",
colors: ["White", "Red", "Green", "Blue", "Yellow", "Gray"],
};
const cloud = clouds.add({
position: position,
scale: new Cesium.Cartesian2(
cloudParameters.scaleX,
cloudParameters.scaleY
),
maximumSize: new Cesium.Cartesian3(
cloudParameters.maximumSizeX,
cloudParameters.maximumSizeY,
cloudParameters.maximumSizeZ
),
color: getColor(cloudParameters.color),
slice: cloudParameters.renderSlice ? cloudParameters.slice : -1.0,
brightness: cloudParameters.brightness,
});
Cesium.knockout.track(cloudParameters);
const toolbar = document.getElementById("toolbar");
Cesium.knockout.applyBindings(cloudParameters, toolbar);
Cesium.knockout
.getObservable(cloudParameters, "scaleWithMaximumSize")
.subscribe(function (newValue) {
if (Boolean(newValue)) {
cloudParameters.scaleX = cloudParameters.maximumSizeX;
cloudParameters.scaleY = cloudParameters.maximumSizeY;
}
});
Cesium.knockout
.getObservable(cloudParameters, "scaleX")
.subscribe(function (newValue) {
const value = Number(newValue);
cloud.scale = new Cesium.Cartesian2(value, cloud.scale.y);
});
Cesium.knockout
.getObservable(cloudParameters, "scaleY")
.subscribe(function (newValue) {
const value = Number(newValue);
cloud.scale = new Cesium.Cartesian2(cloud.scale.x, value);
});
Cesium.knockout
.getObservable(cloudParameters, "maximumSizeX")
.subscribe(function (newValue) {
const value = Number(newValue);
cloud.maximumSize = new Cesium.Cartesian3(
value,
cloud.maximumSize.y,
cloud.maximumSize.z
);
if (cloudParameters.scaleWithMaximumSize) {
cloud.scale = new Cesium.Cartesian2(value, cloud.scale.y);
}
});
Cesium.knockout
.getObservable(cloudParameters, "maximumSizeY")
.subscribe(function (newValue) {
const value = Number(newValue);
cloud.maximumSize = new Cesium.Cartesian3(
cloud.maximumSize.x,
value,
cloud.maximumSize.z
);
if (cloudParameters.scaleWithMaximumSize) {
cloud.scale = new Cesium.Cartesian2(cloud.scale.x, value);
}
});
Cesium.knockout
.getObservable(cloudParameters, "maximumSizeZ")
.subscribe(function (newValue) {
const value = Number(newValue);
cloud.maximumSize = new Cesium.Cartesian3(
cloud.maximumSize.x,
cloud.maximumSize.y,
value
);
});
Cesium.knockout
.getObservable(cloudParameters, "renderSlice")
.subscribe(function (newValue) {
if (Boolean(newValue)) {
cloud.slice = Number(cloudParameters.slice);
} else {
cloud.slice = -1.0;
}
});
Cesium.knockout
.getObservable(cloudParameters, "slice")
.subscribe(function (newValue) {
cloud.slice = Number(newValue);
});
Cesium.knockout
.getObservable(cloudParameters, "color")
.subscribe(function (newValue) {
cloud.color = getColor(newValue);
});
Cesium.knockout
.getObservable(cloudParameters, "brightness")
.subscribe(function (newValue) {
cloud.brightness = Number(newValue);
});
viewer.camera.lookAt(position, new Cesium.Cartesian3(30, 30, -10));
}
export default Cloud;

View File

@ -1,138 +0,0 @@
import { Fragment, useCallback } from "react";
import { Entity, ModelGraphics, useCesium } from "resium";
import { useParams } from "react-router-dom";
import arrowRound from "@/assets/arrow_round.glb";
let totalSeconds = 60;
let numberOfSamples = 120;
let wheelAngle = 0;
function Cyclone() {
const { viewer } = useCesium();
const { type } = useParams();
if (type !== "1") return <></>;
//
// const position = new Cesium.SampledPositionProperty();
// const velocityVectorProperty = new Cesium.VelocityVectorProperty(
// position,
// false
// );
// const velocityVector = new Cesium.Cartesian3();
// const wheelAngleProperty = new Cesium.SampledProperty(Number);
// for (let index = 0; index < numberOfSamples; index++) {
// const factor = index / numberOfSamples;
// const time = Cesium.JulianDate.addSeconds(
// startTime,
// factor * totalSeconds,
// new Cesium.JulianDate()
// );
// velocityVectorProperty.getValue(time, velocityVector);
// wheelAngle -= 3;
// wheelAngleProperty.addSample(time, wheelAngle);
// }
// const rotationProperty = new Cesium.CallbackProperty(function (time, result) {
// return Cesium.Quaternion.fromAxisAngle(
// Cesium.Cartesian3.UNIT_Z,
// wheelAngleProperty.getValue(time) || wheelAngleProperty._values[0],
// result
// );
// }, false);
const getPassTime = useCallback(() => {
const { startTime, currentTime } = viewer.clock;
const _time = Math.floor(currentTime.secondsOfDay - startTime.secondsOfDay);
return _time;
}, [viewer]);
return (
<Fragment>
<Entity
id={"Anticyclone-1"}
position={
new Cesium.CallbackProperty(function (time, result) {
const passTime = getPassTime();
if (passTime < 10) return;
return Cesium.Cartesian3.fromDegrees(-55, 58, 1000000);
}, true)
}
>
<ModelGraphics
uri={arrowRound}
minimumPixelSize={64}
// nodeTransformations={{
//
// group_0: new Cesium.NodeTransformationProperty({
// rotation: rotationProperty,
// }),
// }}
/>
</Entity>
<Entity
id={"Cyclone-1"}
position={
new Cesium.CallbackProperty(function (time, result) {
const passTime = getPassTime();
if (passTime < 20) return;
return Cesium.Cartesian3.fromDegrees(-32, 72.2, 1000000);
}, true)
}
>
<ModelGraphics uri={arrowRound} minimumPixelSize={64} />
</Entity>
<Entity
id={"Anticyclone-2"}
position={
new Cesium.CallbackProperty(function (time, result) {
const passTime = getPassTime();
if (passTime < 30) return;
return Cesium.Cartesian3.fromDegrees(20, 77, 1000000);
}, true)
}
>
<ModelGraphics uri={arrowRound} minimumPixelSize={64} />
</Entity>
<Entity
id={"Cyclone-2"}
position={
new Cesium.CallbackProperty(function (time, result) {
const passTime = getPassTime();
if (passTime < 40) return;
return Cesium.Cartesian3.fromDegrees(63, 69.9, 1000000);
}, true)
}
>
<ModelGraphics uri={arrowRound} minimumPixelSize={64} />
</Entity>
<Entity
id={"Anticyclone-3"}
position={
new Cesium.CallbackProperty(function (time, result) {
const passTime = getPassTime();
if (passTime < 50) return;
return Cesium.Cartesian3.fromDegrees(88, 60, 1000000);
}, true)
}
>
<ModelGraphics uri={arrowRound} minimumPixelSize={64} />
</Entity>
<Entity
id={"Anticyclone-4"}
position={
new Cesium.CallbackProperty(function (time, result) {
const passTime = getPassTime();
if (passTime < 50) return;
return Cesium.Cartesian3.fromDegrees(88, 60, 0);
}, true)
}
>
<ModelGraphics uri={arrowRound} minimumPixelSize={64} />
</Entity>
</Fragment>
);
}
export default Cyclone;

View File

@ -1,39 +0,0 @@
import { useCallback, useState } from "react";
import { useParams } from "react-router-dom";
import { Entity, PolygonGraphics, useCesium } from "resium";
import { useInterval } from "ahooks";
function PlateauPolygon() {
const { type } = useParams();
const { viewer } = useCesium();
const [show, setShow] = useState(false);
const showAnimate = useCallback(() => {
const { currentTime, stopTime } = viewer.clock;
const leftTime = Math.floor(
stopTime.secondsOfDay - currentTime.secondsOfDay
);
if (leftTime < (type === "1" ? 10 : 20)) {
setShow(true);
} else if (show) setShow(false);
}, [show, type]);
useInterval(showAnimate, 100);
if (type !== "1" && type !== "2") return <></>;
return (
<Entity id="plateau" show={show}>
<PolygonGraphics
hierarchy={Cesium.Cartesian3.fromDegreesArray([
// 85, 30, 91, 30, 91, 35, 85, 35,
80, 31, 84, 29.5, 87.4, 28, 91, 28, 98, 29, 94, 35, 79, 34.4, 80, 31,
])}
material={new Cesium.Color(1, 0, 0, 0.1)}
/>
</Entity>
);
}
export default PlateauPolygon;

View File

@ -1,36 +0,0 @@
import { Entity, PolylineGraphics, useCesium } from "resium";
import { Cartesian3 } from "cesium";
import { useParams } from "react-router-dom";
import { min } from "lodash-es";
function Updraft() {
const { viewer } = useCesium();
const { type } = useParams();
if (type !== "1") return <></>;
return (
<Entity
id={"Updraft"}
position={Cartesian3.fromDegrees(-34.0707383, 60.7117244, 100)}
>
<PolylineGraphics
positions={
new Cesium.CallbackProperty(function (time, result) {
const { currentTime, startTime } = viewer.clock;
const passTime = currentTime.secondsOfDay - startTime.secondsOfDay;
const height = 100000 * passTime;
return Cesium.Cartesian3.fromDegreesArrayHeights(
[-55, 58, 0, -55, 58, min([height, 1000000])],
Cesium.Ellipsoid.WGS84,
result
);
}, false)
}
width={30}
material={new Cesium.PolylineArrowMaterialProperty(Cesium.Color.RED)}
/>
</Entity>
);
}
export default Updraft;

View File

@ -1,33 +0,0 @@
import {
Entity,
PointGraphics,
PolylineGraphics,
useCesium,
PathGraphics,
EllipseGraphics,
} from "resium";
import { Color, Cartesian3 } from "cesium";
import { Fragment } from "react";
function Watervapor() {
return (
<Fragment>
<Entity position={Cesium.Cartesian3.fromDegreesArray([-100.0, 40.0])}>
<PolylineGraphics
positions={Cesium.Cartesian3.fromDegreesArray([
-100.0, 40.0, -90.0, 40.0,
])}
width={5}
material={new Color(0.73, 0.94, 0.95, 0.8)}
semiMinorAxis={150000.0}
semiMajorAxis={150000.0}
extrudedHeight={200000.0}
rotation={0.78539}
// outline
/>
</Entity>
</Fragment>
);
}
export default Watervapor;

View File

@ -1,69 +0,0 @@
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import ChartInfoPanel from "./ChartInfoPanel";
import TextInfoPanel from "./TextInfoPanel";
import styles from "./index.module.less";
const textInfoList = [
{
title: "两极协同—拉布拉多海海温偏暖控制夏季高原年代际增温",
content:
"基于再分析资料JRA55和线性斜压模式LBM的试验结果在年代际尺度上拉布拉多海海温偏暖会引起北大西洋至欧洲地区大气环流异常导致青藏高原夏季出现年代际增温。 在年代际尺度上已有研究表明受大气水汽含量增加影响的晴空向下长波辐射是促进夏季青藏高原增温的主要影响因子。通过对夏季高原温度和水汽含量的进一步分析研究发现二者在年代际变化尺度以及空间分布具有很好的一致性即均在1997/1998年显著增高图70。同时对1980-1997和1998-2017两个时间段的位势扰动场并结合T-N波通量诊断发现从西北大西洋沿欧亚大陆至高原北部存在Rossby波列图71该波列在高原东北部激发的异常反气旋对高原净水汽含量增加起到关键作用。经过线性斜压模式LBM试验验证证实拉布拉多海海表温度的年代际增加是激发该Rossby波列的主要原因。因此拉布拉多海海温年代际正异常激发的Rossby波列可以在高原东北部形成异常反气旋该反气旋具有相当正压结构一方面可以增加高原水汽净含量通过增强长波辐射促进高原增温另一方面通过正压结构直接加热高原东北部近地气温",
},
{
title: "两极协同—南极涛动有效调节青藏高原降水和加热",
content:
"利用ERA5再分析数据诊断和CESM印度洋海温强迫数值实验发现5月的南极涛动正位相AAO激发原子阿蒙森海的纬向波列异常。该异常造成印度洋海温降低进而激发环流异常造成高原上空降水增加、感热通量降低。这一结果有助于提高青藏高原热源的预测技巧改进亚洲夏季风的预测。",
},
{
title: "两极协同—连接南极和北极的热带大西洋经向模的媒介作用",
content:
"利用GISTEMP资料通过EEMD分解方法提取两极温度多年代际变化序列发现南北极温度变化的跷跷板现象与大西洋多年代际振荡AMO紧密相关。而AMO与热带大西洋经向模AMM在年代际尺度上显著相关。ERA5再分析资料显示AMM可以通过Rossby波影响西南极的气温与海冰偶极子。因此AMM可能在联系南北极气候变化的中起到了重要的媒介作用。",
},
{
title: "三极联动影响青藏高原上层温度",
content:
"利用ERA5再分析数据通过XGBoost和LightGBM方法对青藏高原对流层温度异常进行预测结果表明该温度异常主要受地球三极信号影响机器学习方法的高预测性对于南亚高压和亚洲夏季降水有重要指示意义。",
},
{
title: "三极联动影响东亚夏季风",
content:
"利用39个CMIP6数值模拟数据训练卷积神经网络利用NOAA20世纪再分析数据进行迁移学习、ERA5再分析数据进行测试从而预测东亚夏季风。预测结果的热力图可视化表明尽管热带大洋对东亚夏季风的影响是最强的然而不论是北极、南极还是青藏高原均显示有显著的前期信号春季这也表明地球三极可以协同影响东亚夏季风。关于东亚夏季风的预测一直是个难题。近期我们利用机器学习识别了东亚夏季风的影响因子。地球三极的协同影响在东亚夏季风的归因图中得到了很好的体现尽管热带大洋对东亚夏季风的影响是最强的然而不论是北极、南极还是青藏高原均显示有显著的前期信号春季这也表明地球三极可以协同影响东亚夏季风。",
},
];
const chartInfoList = [
{
type: "1",
name: "sst",
},
{
type: "3",
name: "tripolar",
},
{
type: "4",
name: "tptt",
},
];
function InfoLayout() {
const { type } = useParams();
const { toolbar } = useSelector((state) => state.data);
if (!toolbar.showPanel) return <></>;
const hasText = textInfoList[type - 1];
const currentChartName = chartInfoList.find(
(item) => item.type === type
)?.name;
return (
<div className={styles.infoLayout}>
{!!hasText ? <TextInfoPanel {...hasText} /> : <em></em>}
<ChartInfoPanel type={currentChartName} />
</div>
);
}
export default InfoLayout;

View File

@ -1,53 +0,0 @@
.infoLayout :global {
position: absolute;
top: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: flex-end;
pointer-events: none;
.textInfoPanel {
width: 450px;
height: 400px;
padding: 12px;
border: 1px solid #04fbfd;
color: #02f9ff !important;
background-color: #1f485690;
margin-bottom: 168px;
pointer-events: auto;
display: flex;
flex-direction: column;
.title {
font-size: 20px;
font-weight: 600;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 12px;
}
.content {
flex: 1;
text-indent: 32px;
line-height: 1.5;
}
}
.chartInfoPanel {
width: 450px;
height: 400px;
pointer-events: auto;
padding: 12px;
border: 1px solid #04fbfd;
color: #02f9ff !important;
background-color: #1f485690;
margin-bottom: 168px;
// margin-left: 12px;
// .echarts-for-react {
// }
}
}

View File

@ -114,7 +114,7 @@
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
border-radius: 6px; border-radius: 6px;
background: #04fbfd; background: #00a2a2;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
border-radius: 6px; border-radius: 6px;

View File

@ -1,7 +1,6 @@
export const data = { export const data = {
state: { state: {
showSite: false, showSite: false,
showPanelHighlight: false,
toolbar: {}, toolbar: {},
imageLayer: { imageLayer: {
labrador: undefined, labrador: undefined,