This commit is contained in:
baol 2023-10-02 11:50:36 +08:00
parent 9edef5775d
commit 477bf6f6bd
27 changed files with 15025 additions and 18756 deletions

1180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
import { Routes, Route, Navigate } from "react-router-dom";
import HomeLayout from "@/components/HomeLayout";
import MapLayout from "./components/map/Layout";
import HomeLayout from "@/components/home/Layout";
import DomainOne from "./components/domain/One";
import "./App.less";
function App() {
return (
<Routes>
<Route path="/" element={<HomeLayout />} />
<Route path="map/:type" element={<MapLayout />}></Route>
<Route path="map/1" element={<DomainOne />}></Route>
<Route path="*" element={<Navigate to={"/"} replace={true} />} />
</Routes>
);

View File

@ -0,0 +1,128 @@
import { Fragment } from "react";
import { Entity, EllipseGraphics } from "resium";
import wave from "@/assets/wave.png";
function WavePoint({
stationLon,
stationLat,
value = 360,
deviationR = 5000,
eachInterval = 1500,
maxR = 3600 * 100,
}) {
const data = {
stationLon, //
stationLat, //
value, //
deviationR, //
eachInterval, //
imageUrl: wave,
maxR,
};
var r1 = 0,
r2 = 0;
var r3 = 0,
r4 = 0;
function changeOne() {
r1 = r1 + data.deviationR;
if (r1 >= data.maxR) {
r1 = 0;
}
return r1;
}
function changeR2() {
r2 = r2 + data.deviationR;
if (r2 >= data.maxR) {
r2 = 0;
}
return r2;
}
const point1 = (
<Entity
id={`wave-point-1-${stationLon}-${stationLat}`}
position={Cesium.Cartesian3.fromDegrees(
data.stationLon,
data.stationLat,
0
)}
show={true}
>
<EllipseGraphics
semiMinorAxis={new Cesium.CallbackProperty(changeOne, false)}
semiMajorAxis={new Cesium.CallbackProperty(changeR2, false)}
height={10}
material={
new Cesium.ImageMaterialProperty({
image: data.imageUrl,
repeat: Cesium.Cartesian2(1.0, 1.0),
transparent: true,
color: new Cesium.CallbackProperty(function () {
var alp = 1 - r1 / data.maxR;
return Cesium.Color.WHITE.withAlpha(alp);
}, false),
})
}
/>
</Entity>
);
let point2;
//
setTimeout(function () {
function changeTwo() {
r3 = r3 + data.deviationR;
if (r3 >= data.maxR) {
r3 = 0;
}
return r3;
}
function changeR12() {
r4 = r4 + data.deviationR;
if (r4 >= data.maxR) {
r4 = 0;
}
return r4;
}
point2 = (
<Entity
position={Cesium.Cartesian3.fromDegrees(
data.stationLon,
data.stationLat,
0
)}
show={true}
id={`wave-point-2-${stationLon}-${stationLat}`}
>
<EllipseGraphics
semiMinorAxis={new Cesium.CallbackProperty(changeTwo, false)}
semiMajorAxis={new Cesium.CallbackProperty(changeR12, false)}
height={10}
material={
new Cesium.ImageMaterialProperty({
image: data.imageUrl,
repeat: Cesium.Cartesian2(1.0, 1.0),
transparent: true,
color: new Cesium.CallbackProperty(function () {
var alp = 1 - r1 / data.maxR;
return Cesium.Color.WHITE.withAlpha(alp);
//entity entity
}, false),
})
}
/>
</Entity>
);
}, data.eachInterval);
return (
<Fragment>
{point1}
{point2}
</Fragment>
);
}
export default WavePoint;

View File

@ -0,0 +1,59 @@
import { useCallback, useState } from "react";
import {
Entity,
LabelGraphics,
EllipseGraphics,
useCesium,
CylinderGraphics,
} from "resium";
import { Color, Cartesian3, LabelStyle } from "cesium";
import { useInterval } from "ahooks";
function Barotropic() {
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}
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

@ -0,0 +1,131 @@
import ReactECharts from "echarts-for-react";
const years = [];
for (let year = 1980; year <= 2017; year++) {
years.push(year);
}
//
const plateauData = [
-0.3532047, -0.3800191, -0.4393019, -0.4789361, -0.4674187, -0.4082659,
-0.3299886, -0.2692249, -0.2362842, -0.2431858, -0.2888356, -0.3307647,
-0.3263536, -0.2682413, -0.1973641, -0.1355748, -0.09169727, -0.05341399,
0.00680301, 0.0840551, 0.1391686, 0.1402837, 0.09915907, 0.06798702,
0.07436849, 0.1159735, 0.1679915, 0.2133843, 0.2477409, 0.2872947, 0.3583514,
0.4338831, 0.4701767, 0.4778886, 0.4858712, 0.5201119, 0.5574191, 0.570379,
];
//
const laBrudata = [
-0.5808361, -0.6286728, -0.731713, -0.8032401, -0.7824413, -0.687568,
-0.5886319, -0.5527237, -0.6027562, -0.7280338, -0.8761956, -0.9766923,
-0.9833426, -0.8901258, -0.7279955, -0.5212721, -0.2961609, -0.0860718,
0.07991157, 0.166387, 0.1871717, 0.1972264, 0.2479431, 0.3619903, 0.5000346,
0.6201853, 0.696692, 0.7417008, 0.7834306, 0.8539514, 0.935372, 0.9951949,
0.9844907, 0.9088836, 0.7977505, 0.7049671, 0.644415, 0.6330437,
];
function ChartPanel() {
const option = {
title: {
// text: "Stacked Line",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["ERSST_lancozs", "JRA55_lancozs"],
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],
},
},
yAxis: {
type: "value",
min: -1.5,
max: 2.0,
interval: 0.5,
splitNumber: 5,
splitLine: { show: false },
axisLine: {
onZero: false,
show: true,
symbol: ["none", "arrow"],
symbolOffset: [0, 10],
},
axisLabel: {
show: true,
},
axisTick: { show: true },
scale: true,
},
dataZoom: { type: "inside", start: 0, end: 100 },
series: [
{
name: "ERSST_lancozs",
type: "line",
stack: "Total",
data: laBrudata,
smooth: true,
color: "red",
symbol: "none",
itemStyle: {
color: "red",
},
},
{
name: "JRA55_lancozs",
type: "line",
// stack: "Total",
data: plateauData,
smooth: false,
color: "#00a1ff",
symbol: "none",
itemStyle: {
color: "#00a1ff",
},
lineStyle: {
type: "dashed",
width: 2,
},
},
],
};
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,28 @@
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

@ -0,0 +1,81 @@
import { useDispatch } from "react-redux";
import { Camera, useCesium } from "resium";
function CustomFlyTo() {
const { viewer } = useCesium();
const { camera } = viewer;
const dispatch = useDispatch();
function cameraFlyToLine(adjustPitch) {
// barotorpic
const barotorpic = {
destination: Cesium.Cartesian3.fromDegrees(42, 46, 15000000),
duration: 30,
complete: () => {},
};
//
const sideViewOptions = {
destination: Cesium.Cartesian3.fromDegrees(-50, 46, 2000000),
duration: 5,
orientation: {
heading: Cesium.Math.toRadians(-15.0),
pitch: -Cesium.Math.PI_OVER_FOUR,
roll: 0.0,
},
complete: () => {
viewer.clock.shouldAnimate = true;
setTimeout(function () {
camera.flyTo(barotorpic);
}, 1000);
dispatch.data.updateImageLayer({
labrador: true,
});
dispatch.data.updateToolbar({ showPanel: true });
},
};
//
const plateauOptions = {
destination: Cesium.Cartesian3.fromDegrees(90, 20, 1600000),
duration: 5,
orientation: {
heading: Cesium.Math.toRadians(-15.0),
pitch: -Cesium.Math.PI_OVER_FOUR,
roll: 0.0,
},
complete: function () {
setTimeout(function () {
camera.flyTo(sideViewOptions);
}, 1000);
},
};
//
const labrador = {
destination: Cesium.Cartesian3.fromDegrees(-55, 45, 1600000),
duration: 10,
orientation: {
heading: Cesium.Math.toRadians(-15.0),
pitch: -Cesium.Math.PI_OVER_FOUR,
roll: 0.0,
},
complete: function () {
setTimeout(function () {
camera.flyTo(plateauOptions);
}, 1000);
},
};
if (adjustPitch) {
plateauOptions.pitchAdjustHeight = 1000;
sideViewOptions.pitchAdjustHeight = 1000;
}
camera.flyTo(labrador);
}
cameraFlyToLine();
return <></>;
}
export default CustomFlyTo;

View File

@ -0,0 +1,119 @@
import { useCallback, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { useCesium } from "resium";
import { Select } from "antd";
function CustomToolbar() {
const { viewer } = useCesium();
const navigate = useNavigate();
const dispatch = useDispatch();
const { toolbar } = useSelector((state) => state.data);
const [value, setValue] = useState("sideview");
const handleChange = (value) => {
setValue(value);
const pointEntity = viewer.entities.getById("point");
if (!viewer) return;
if (value === "overhead") {
//
viewer.trackedEntity = pointEntity;
const destination = pointEntity.position.getValue(
viewer.clock.currentTime
);
if (!destination) return;
const newDestination = new Cesium.Cartesian3();
newDestination.x = destination.x;
newDestination.y = destination.y + 13000000;
newDestination.z = destination.z;
viewer.camera.flyTo({
destination: newDestination,
});
} else if (value === "sideview") {
//
// viewer.trackedEntity = pointEntity;
viewer.trackedEntity = undefined;
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(130, -10.5, 20000000),
});
} else {
// mftata
viewer.trackedEntity = pointEntity;
const destination = pointEntity.position.getValue(
viewer.clock.currentTimes
);
if (!destination) return;
const newDestination = Cesium.Cartesian3.clone(destination);
newDestination.y = destination.y + 1000000;
viewer.camera.flyTo({
destination: newDestination,
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: -Cesium.Math.PI_OVER_FOUR,
roll: 0.0,
},
duration: 0,
});
}
};
const navigateHandler = useCallback(() => {
navigate("/home", { replace: true });
dispatch.data.resetState();
}, [navigate, dispatch]);
const showPanelHandler = useCallback(
(value) => {
dispatch.data.updateToolbar({
showPanel: value,
});
},
[dispatch]
);
return (
<div className="toolbar">
<div className={"focusBtn"} onClick={navigateHandler}>
返回首页
</div>
{/* <Select
// onSelect={handleChange}
onChange={handleChange}
value={value}
getPopupContainer={(node) => node}
options={[
// {
// value: "overhead",
// label: "",
// },
{
value: "sideview",
label: "侧视视角",
},
{
value: "follow",
label: "跟随视角",
},
]}
/> */}
<Select
onChange={showPanelHandler}
value={!!toolbar.showPanel}
getPopupContainer={(node) => node}
options={[
{
value: true,
label: "开启展板",
},
{
value: false,
label: "关闭展板",
},
]}
/>
</div>
);
}
export default CustomToolbar;

View File

@ -0,0 +1,134 @@
import { Fragment, useCallback } from "react";
import { Entity, ModelGraphics, useCesium } from "resium";
import arrowRound from "@/assets/arrow_round.glb";
let totalSeconds = 60;
let numberOfSamples = 120;
let wheelAngle = 0;
function Cyclone() {
const { viewer } = useCesium();
//
// 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={128}
// 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={128} />
</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={128} />
</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={128} />
</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={128} />
</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={128} />
</Entity>
</Fragment>
);
}
export default Cyclone;

View File

@ -0,0 +1,71 @@
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 LabradorImageLayer() {
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 LabradorImageLayer;

View File

@ -0,0 +1,68 @@
const colorBar = [
"#4160ac",
"#4363ae",
"#4867b1",
"#4d6bb3",
"#5370b6",
"#5d79bb",
"#6c85c1",
"#778dc7",
"#8297cd",
"#8fa3d3",
"#9baed9",
"#a6b7de",
"#b0c0e3",
"#bcc9e6",
"#c9d3eb",
"#d4ddf1",
"#fbd1d2",
"#fac4c6",
"#f9b8ba",
"#f8abae",
"#f79fa2",
"#f59496",
"#f4878a",
"#f37a7c",
"#f36e70",
"#f26264",
"#f15759",
"#f04c4f",
"#f14145",
"#ef3637",
"#ee2e30",
"#ee2529",
];
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 }}
title={`${(-3.2 + index * 0.2).toFixed(1)}~${(
-3.2 +
(index + 1) * 0.2
).toFixed(1)}`}
/>
);
})}
</div>
<div className="legend-text">
{[-2.4, -1.6, -0.8, 0, 0.8, 1.6, 2.4, ""].map((item, index) => {
return (
<div key={`legend-text-item-${index}`} className="legend-text-item">
{item}
</div>
);
})}
</div>
</div>
);
}
export default Legend;

View File

@ -0,0 +1,35 @@
import { useCallback, useState } from "react";
import { Entity, PolygonGraphics, useCesium } from "resium";
import { useInterval } from "ahooks";
function PlateauPolygon() {
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 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

@ -0,0 +1,94 @@
import { useState } from "react";
import { Entity, PointGraphics, useCesium } from "resium";
import { useInterval } from "ahooks";
//
const dataLabToQTP = [
{ longitude: -55, latitude: 58, height: 1000000, time: 10 },
{ longitude: -32, latitude: 72.2, height: 1000000, time: 20 },
{ longitude: 20, latitude: 77, height: 1000000, time: 30 },
{ longitude: 63, latitude: 69.9, height: 1000000, time: 40 },
{ longitude: 88, latitude: 60, height: 1000000, time: 50 },
];
function Point() {
const { viewer } = useCesium();
const [delay, setDelay] = useState(1000);
const start = viewer.clock.startTime;
const stop = viewer.clock.stopTime;
const pathMaterial = new Cesium.PolylineDashMaterialProperty({
dashLength: 20,
color: new Cesium.Color(4 / 255, 251 / 255, 253 / 255),
});
useInterval(() => {
if (viewer.clock?.shouldAnimate) setDelay(undefined);
}, delay);
/**
* 计算飞行路径
* 数据坐标
* {SampledPositionProperty|*}
*/
function createProperty(source) {
let property = new Cesium.SampledPositionProperty();
for (let i = 0; i < source.length; i++) {
let time = Cesium.JulianDate.addSeconds(
start,
source[i].time,
new Cesium.JulianDate()
);
let position = Cesium.Cartesian3.fromDegrees(
source[i].longitude,
source[i].latitude,
source[i].height
);
//
property.addSample(time, position);
}
property.setInterpolationOptions({
interpolationDegree: 10,
interpolationAlgorithm: Cesium.LagrangePolynomialApproximation,
});
return property;
}
const property = createProperty(dataLabToQTP);
return (
<Entity
id={"point"}
position={property}
availability={
new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: start,
stop: stop,
}),
])
}
orientation={new Cesium.VelocityOrientationProperty(property)}
path={{
resolution: 1,
material: pathMaterial,
// leadTimetrailTime path
leadTime: 0, // 0 path
// trailTime: 0, // 0 path
width: 2,
}}
>
<PointGraphics
show={true}
color={Cesium.Color.SKYBLUE}
pixelSize={10}
outlineColor={Cesium.Color.YELLOW}
outlineWidth={3}
/>
</Entity>
);
}
export default Point;

View File

@ -0,0 +1,45 @@
import { useCallback, useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Scrollbars } from "react-custom-scrollbars-2";
import { useInterval } from "ahooks";
let index = 0;
function TextInfoPanel({ title, content }) {
const showNumberPerTimes = 1;
const { toolbar } = useSelector((state) => state.data);
const [delay, setDelay] = useState(80);
const [contentText, setContentText] = useState("");
useEffect(() => {
index = 0;
}, [toolbar]);
const showContent = useCallback(() => {
const isFinished = contentText.length >= content.length;
if (!isFinished) {
setContentText((text) => {
index += showNumberPerTimes;
return text + content[index - 1];
});
} else setDelay(undefined);
}, [contentText]);
useInterval(showContent, delay);
const stopHandler = useCallback(() => {
setDelay(undefined);
index = 0;
setContentText(content);
}, []);
return (
<div className="text-info-panel" onDoubleClick={stopHandler}>
<Scrollbars autoHide autoHideTimeout={1000} autoHideDuration={400}>
{contentText}
</Scrollbars>
</div>
);
}
export default TextInfoPanel;

View File

@ -0,0 +1,71 @@
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_tp_" + item);
function TibetImageLayer() {
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 TibetImageLayer;

View File

@ -0,0 +1,33 @@
import { Entity, PolylineGraphics, useCesium } from "resium";
import { Cartesian3 } from "cesium";
import { min } from "lodash-es";
function Updraft() {
const { viewer } = useCesium();
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

@ -0,0 +1,11 @@
import { Fragment } from "react";
import WavePoint from "@/components/common/WavePoint";
export default function WavePoints() {
return (
<Fragment>
<WavePoint stationLon={-55} stationLat={58} />
<WavePoint stationLon={88} stationLat={32.5} />
</Fragment>
);
}

View File

@ -0,0 +1,49 @@
import MapLayout from "@/components/map/Layout";
import CustomClock from "./CustomClock";
import CustomFlyTo from "./CustomFlyTo";
import CustomToolbar from "./CustomToolbar";
import Point from "./Point";
import PlateauPolygon from "./PlateauPolygon";
import TextInfoPanel from "./TextInfoPanel";
import Cyclone from "./Cyclone";
import Barotropic from "./Barotorpic";
import WavePoints from "./WavePoints";
import Updraft from "./Updraft";
import Legend from "./Legend";
import LabradorImageLayer from "./LabradorImageLayer";
import TibetImageLayer from "./TibetImageLayer";
import ChartPanel from "./ChartPanel";
export default function DomainOne() {
return (
<MapLayout>
<div className="title">
两极协同拉布拉多海海温偏暖控制夏季高原年代际增温
</div>
<CustomToolbar />
<CustomClock />
<CustomFlyTo />
<Point />
<PlateauPolygon />
<div className="left-panel">
<div className="top-panel"></div>
<div className="bottom-panel">
<TextInfoPanel content="基于再分析资料JRA55和线性斜压模式LBM的试验结果在年代际尺度上拉布拉多海海温偏暖会引起北大西洋至欧洲地区大气环流异常导致青藏高原夏季出现年代际增温。" />
</div>
</div>
<div className="right-panel">
<div className="top-panel"></div>
<div className="bottom-panel">
<ChartPanel />
</div>
</div>
<Cyclone />
<Barotropic />
<WavePoints />
<Updraft />
<Legend />
<LabradorImageLayer />
<TibetImageLayer />
</MapLayout>
);
}

View File

@ -1,55 +1,13 @@
.mapContainer :global {
width: 100%;
height: 100%;
.cesium-container {
width: 100%;
height: 100%;
position: relative;
.cesium-widget-credits {
display: none !important;
visibility: hide !important;
}
.viewer-toolbar {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
position: absolute;
height: 159px;
right: 5px;
top: 5px;
.ant-image {
width: 50px;
height: 50px;
border: none;
img {
border-radius: 14%;
}
}
}
.viewer-toolbar :hover {
cursor: pointer;
color: #fff;
fill: #fff;
background: #48b;
border-color: #aef;
box-shadow: 0 0 8px #fff;
}
}
}
.homeLayout :global {
height: 100%;
background: url("../../assets/link.png") 50% no-repeat;
background: url("../../../assets/link.png") 50% no-repeat;
background-size: 100% 100%;
display: flex;
flex-direction: column;
.title {
height: 72px;
background: url("../../assets/title.png") 50% no-repeat;
background: url("../../../assets/title.png") 50% no-repeat;
margin-bottom: 8px;
color: #04fbfd;
display: flex;

View File

@ -0,0 +1,56 @@
import { Viewer } from "resium";
import CustomClock from "./CustomClock";
import CustomFlyTo from "./CustomFlyTo";
import CustomToolbar from "./CustomToolbar";
import InfoLayout from "./InfoLayout";
import Picker from "./Picker";
import Legend from "./Legends";
import Barotropic from "./Entities/Barotorpic";
import Cyclone from "./Entities/Cyclone";
import Point from "./Entities/Point";
import PlateauPolygon from "./Entities/PlateauPolygon";
import Updraft from "./Entities/Udraft";
import Watervapor from "./Entities/Watervapor";
import WavePoint from "./Entities/WavePoint";
import DynamicImageryLayer from "./DynamicImageryLayer";
import Circles from "./Entities/Circles";
import Site from "./Entities/Site";
import styles from "./index.module.less";
function MapLayout() {
return (
<Viewer
className={styles.cesiumContainer}
full
homeButton={false}
sceneModePicker={false}
navigationHelpButton={false}
shouldAnimate={false}
// infoBox={false}
timeline={false}
fullscreenButton={false}
geocoder={false}
baseLayerPicker={false}
animation={false}
>
<CustomClock />
<CustomFlyTo />
<CustomToolbar />
<Point />
<PlateauPolygon />
<InfoLayout />
<Cyclone />
<Barotropic />
{/* <Watervapor /> */}
<WavePoint />
<Updraft />
<Picker />
<Legend />
<DynamicImageryLayer />
<Circles />
<Site />
</Viewer>
);
}
export default MapLayout;

View File

@ -1,26 +1,10 @@
import { Viewer } from "resium";
import CustomClock from "./CustomClock";
import CustomFlyTo from "./CustomFlyTo";
import CustomToolbar from "./CustomToolbar";
import InfoLayout from "./InfoLayout";
import Picker from "./Picker";
import Legend from "./Legends";
import Barotropic from "./Entities/Barotorpic";
import Cyclone from "./Entities/Cyclone";
import Point from "./Entities/Point";
import PlateauPolygon from "./Entities/PlateauPolygon";
import Updraft from "./Entities/Udraft";
import Watervapor from "./Entities/Watervapor";
import WavePoint from "./Entities/WavePoint";
import DynamicImageryLayer from "./DynamicImageryLayer";
import Circles from "./Entities/Circles";
import Site from "./Entities/Site";
import styles from "./index.module.less";
function MapLayout() {
function MapLayout({ children, className, ...rest }) {
return (
<Viewer
className={styles.cesiumContainer}
className={`${styles.cesiumContainer} ${className}`}
full
homeButton={false}
sceneModePicker={false}
@ -33,22 +17,7 @@ function MapLayout() {
baseLayerPicker={false}
animation={false}
>
<CustomClock />
<CustomFlyTo />
<CustomToolbar />
<Point />
<PlateauPolygon />
<InfoLayout />
<Cyclone />
<Barotropic />
{/* <Watervapor /> */}
<WavePoint />
<Updraft />
<Picker />
<Legend />
<DynamicImageryLayer />
<Circles />
<Site />
{children}
</Viewer>
);
}

View File

@ -12,69 +12,198 @@
}
}
}
}
.toolbar :global {
position: absolute;
top: 8px;
left: 12px;
display: flex;
gap: 8px;
.title {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 72px;
margin-bottom: 8px;
color: #04fbfd;
display: flex;
align-items: center;
justify-content: center;
font-size: 42px;
font-weight: 700;
}
.ant-select {
cursor: pointer;
.toolbar {
position: absolute;
top: 8px;
left: 12px;
display: flex;
gap: 8px;
.ant-select-selector {
color: #02f9ff;
border: 1px solid #04fbfd !important;
background-color: #1f4856 !important;
padding: 4px 12px !important;
border-radius: 8px !important;
height: 39px !important;
font-size: 14px !important;
.ant-select {
cursor: pointer;
.ant-select-selection-item {
color: #02f9ff !important;
}
.ant-select-dropdown {
.ant-select-selector {
color: #02f9ff;
border: 1px solid #04fbfd !important;
background-color: #1f4856 !important;
padding: 4px 12px !important;
border-radius: 8px !important;
height: 39px !important;
font-size: 14px !important;
.ant-select-item {
color: rgb(0, 242, 255);
font-size: 14px;
.ant-select-selection-item {
color: #02f9ff !important;
}
&:hover {
.ant-select-dropdown {
background-color: #1f4856 !important;
.ant-select-item {
color: rgb(0, 242, 255);
font-size: 14px;
&:hover {
background-color: #33a4a4 !important;
}
}
.ant-select-item-option-selected {
background-color: #33a4a4 !important;
}
}
.ant-select-item-option-selected {
background-color: #33a4a4 !important;
&:hover {
border: 1px solid #04fbfd !important;
background-color: #078080;
}
}
&:hover {
border: 1px solid #04fbfd !important;
background-color: #078080;
.ant-select-arrow {
color: #02f9ff;
}
}
.ant-select-arrow {
.focusBtn {
color: #02f9ff;
border: 1px solid #04fbfd;
background-color: #1f4856;
padding: 8px 12px;
border-radius: 8px;
cursor: pointer;
&:hover {
background-color: #078080;
}
}
}
.focusBtn {
color: #02f9ff;
border: 1px solid #04fbfd;
background-color: #1f4856;
padding: 8px 12px;
border-radius: 8px;
cursor: pointer;
.left-panel {
position: absolute;
top: 84px;
left: 12px;
bottom: 12px;
width: 450px;
display: flex;
flex-direction: column;
&:hover {
background-color: #078080;
.top-panel {
flex: 1;
}
.bottom-panel {
flex: 1;
}
.text-info-panel {
width: 100%;
height: 100%;
padding: 12px;
border: 1px solid #04fbfd;
color: #02f9ff !important;
background-color: #1f485690;
pointer-events: auto;
font-size: 16px;
text-indent: 2em;
line-height: 1.5;
}
}
.right-panel {
position: absolute;
top: 84px;
right: 12px;
bottom: 12px;
width: 450px;
display: flex;
flex-direction: column;
.top-panel {
flex: 1;
}
.bottom-panel {
flex: 1;
}
.chart-info-panel {
width: 100%;
height: 100%;
pointer-events: auto;
padding: 12px;
border: 1px solid #04fbfd;
color: #02f9ff !important;
background-color: #1f485690;
}
}
.legend {
position: absolute;
bottom: 40px;
width: 50%;
left: 25%;
// height: 40px;
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;
}
}
}
}

31088
yarn.lock

File diff suppressed because it is too large Load Diff