Skip to content

Commit dc0b017

Browse files
committed
feat: enhance wind layer visualization and data query
- Improve particle system performance and coverage * Fix particle distribution in certain areas * Optimize particle generation algorithm * Adjust particle visibility calculation - Enhance wind data query functionality * Refactor WindDataAtLonLat interface with clear structure * Add bilinear interpolation for wind data * Separate original and interpolated data in query results * Add detailed JSDoc comments for better type documentation - Improve UI components * Add toggle between original and interpolated data * Enhance mobile responsiveness * Optimize data display format * Add hover tooltips for detailed information BREAKING CHANGE: WindDataAtLonLat interface structure has changed. Now returns data in 'original' and 'interpolated' sub-objects.
1 parent a0c98a6 commit dc0b017

File tree

15 files changed

+209
-89
lines changed

15 files changed

+209
-89
lines changed

example/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# example
22

3+
## 0.3.4
4+
5+
### Patch Changes
6+
7+
- Updated dependencies
8+
- cesium-wind-layer@0.5.0
9+
310
## 0.3.3
411

512
### Patch Changes

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "example",
33
"private": true,
4-
"version": "0.3.3",
4+
"version": "0.3.4",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

example/src/components/ColorTableInput.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const generateColorTable = (
4747
};
4848

4949
export const colorSchemes = [
50+
{ label: 'White', value: 'white', interpolator: () => 'white' },
5051
{ label: 'Rainbow', value: 'rainbow', interpolator: interpolateRainbow, reverse: true },
5152
{ label: 'Viridis', value: 'viridis', interpolator: interpolateViridis },
5253
{ label: 'Cool', value: 'cool', interpolator: interpolateCool },

example/src/components/SpeedQuery.tsx

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from 'react';
2-
import { Typography, Space, Divider } from 'antd';
2+
import { Typography, Space, Button } from 'antd';
33
import styled from 'styled-components';
4-
import { WindLayer } from 'cesium-wind-layer';
4+
import { WindDataAtLonLat, WindLayer } from 'cesium-wind-layer';
55
import { Viewer, ScreenSpaceEventHandler, ScreenSpaceEventType, Cartographic, Math as CesiumMath } from 'cesium';
66
import { GithubOutlined } from '@ant-design/icons';
77

@@ -107,10 +107,27 @@ const DirectionArrow = styled.span<{ $angle: number }>`
107107
font-family: "Segoe UI Symbol", "Noto Color Emoji", sans-serif;
108108
`;
109109

110-
interface WindData {
111-
speed: number;
112-
u: number;
113-
v: number;
110+
const DataContainer = styled(Space)`
111+
display: flex;
112+
flex-wrap: wrap;
113+
gap: 8px;
114+
width: 100%;
115+
`;
116+
117+
const DataGroup = styled.div`
118+
display: flex;
119+
align-items: center;
120+
gap: 8px;
121+
flex-wrap: wrap;
122+
`;
123+
124+
const ToggleButton = styled(Button)`
125+
font-size: 12px;
126+
padding: 2px 8px;
127+
height: 24px;
128+
`;
129+
130+
interface WindData extends WindDataAtLonLat {
114131
direction?: number;
115132
}
116133

@@ -122,6 +139,7 @@ interface SpeedQueryProps {
122139
export const SpeedQuery: React.FC<SpeedQueryProps> = ({ windLayer, viewer }) => {
123140
const [queryResult, setQueryResult] = useState<WindData | null>(null);
124141
const [location, setLocation] = useState<{ lon: number; lat: number } | null>(null);
142+
const [showInterpolated, setShowInterpolated] = useState(true);
125143

126144
const calculateWindDirection = (u: number, v: number): number => {
127145
// 使用 atan2 计算角度,注意参数顺序:atan2(y, x)
@@ -158,8 +176,9 @@ export const SpeedQuery: React.FC<SpeedQueryProps> = ({ windLayer, viewer }) =>
158176
const result = windLayer.getDataAtLonLat(lon, lat);
159177
setLocation({ lon, lat });
160178

161-
if (result && typeof result.u === 'number' && typeof result.v === 'number') {
162-
const direction = calculateWindDirection(result.u, result.v);
179+
if (result) {
180+
const data = showInterpolated ? result.interpolated : result.original;
181+
const direction = calculateWindDirection(data.u, data.v);
163182
setQueryResult({ ...result, direction });
164183
} else {
165184
setQueryResult(null);
@@ -171,14 +190,15 @@ export const SpeedQuery: React.FC<SpeedQueryProps> = ({ windLayer, viewer }) =>
171190
}
172191
};
173192

174-
// 支持移动端触摸
175193
handler.setInputAction(handleClick, ScreenSpaceEventType.LEFT_CLICK);
176194
handler.setInputAction(handleClick, ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
177195

178196
return () => {
179197
handler.destroy();
180198
};
181-
}, [viewer, windLayer]);
199+
}, [viewer, windLayer, showInterpolated]);
200+
201+
const currentData = queryResult ? (showInterpolated ? queryResult.interpolated : queryResult.original) : null;
182202

183203
return (
184204
<Container>
@@ -190,30 +210,45 @@ export const SpeedQuery: React.FC<SpeedQueryProps> = ({ windLayer, viewer }) =>
190210
)}
191211

192212
{location && (
193-
<Space split={<Divider type="vertical" style={{ margin: '0 4px' }} />}>
194-
<DataItem>
195-
📍 {location.lon.toFixed(1)}°, {location.lat.toFixed(1)}°
196-
</DataItem>
213+
<DataContainer>
214+
<DataGroup>
215+
<DataItem>
216+
📍 {location.lon.toFixed(1)}°, {location.lat.toFixed(1)}°
217+
</DataItem>
218+
219+
{queryResult && currentData && (
220+
<ToggleButton
221+
type={showInterpolated ? "primary" : "default"}
222+
onClick={() => setShowInterpolated(!showInterpolated)}
223+
>
224+
{showInterpolated ? "Interpolated" : "Original"}
225+
</ToggleButton>
226+
)}
227+
</DataGroup>
197228

198229
{!queryResult && (
199230
<Text type="secondary" style={{ fontSize: '13px' }}>No data</Text>
200231
)}
201232

202-
{queryResult && (
203-
<>
204-
<DataItem>
205-
💨 {queryResult.speed.toFixed(1)} m/s
206-
</DataItem>
207-
<DataItem>
208-
<DirectionArrow $angle={(queryResult.direction || 0) - 90}></DirectionArrow>
209-
{' '}{queryResult.direction?.toFixed(0)}° ({getCardinalDirection(queryResult.direction || 0)})
233+
{queryResult && currentData && (
234+
<DataGroup>
235+
<DataItem title="Wind Speed">
236+
💨 Speed: {currentData.speed.toFixed(1)} m/s
210237
</DataItem>
211-
<DataItem>
212-
UV: {queryResult.u.toFixed(1)}, {queryResult.v.toFixed(1)}
238+
239+
{currentData.speed > 0 && (
240+
<DataItem title="Wind Direction">
241+
<DirectionArrow $angle={(queryResult.direction || 0) - 90}></DirectionArrow>
242+
{' '}Direction: {queryResult.direction?.toFixed(0)}° ({getCardinalDirection(queryResult.direction || 0)})
243+
</DataItem>
244+
)}
245+
246+
<DataItem title="UV Vector">
247+
UV Vector: {currentData.u.toFixed(3)}, {currentData.v.toFixed(3)}
213248
</DataItem>
214-
</>
249+
</DataGroup>
215250
)}
216-
</Space>
251+
</DataContainer>
217252
)}
218253
</QueryInfo>
219254

example/src/pages/earth.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const defaultOptions: Partial<WindLayerOptions> = {
2929
dropRateBump: 0.01,
3030
speedFactor: 1.0,
3131
lineWidth: 10.0,
32-
colors: colorSchemes[2].colors,
32+
colors: colorSchemes[3].colors,
3333
flipY: true,
3434
useViewerBounds: true,
3535
};

packages/cesium-wind-layer/CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# cesium-wind-layer
22

3+
## 0.5.0
4+
5+
### Minor Changes
6+
7+
- feat: enhance wind layer visualization and data query
8+
9+
- Improve particle system performance and coverage
10+
11+
- Fix particle distribution in certain areas
12+
- Optimize particle generation algorithm
13+
- Adjust particle visibility calculation
14+
- Add 5% buffer to view range for smoother transitions
15+
16+
- Enhance wind data query functionality
17+
- Refactor WindDataAtLonLat interface with clear structure
18+
- Add bilinear interpolation for wind data
19+
- Separate original and interpolated data in query results
20+
- Add detailed JSDoc comments for better type documentation
21+
22+
BREAKING CHANGE: WindDataAtLonLat interface structure has changed. Now returns data in 'original' and 'interpolated' sub-objects.
23+
324
## 0.4.3
425

526
### Patch Changes

packages/cesium-wind-layer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cesium-wind-layer",
3-
"version": "0.4.3",
3+
"version": "0.5.0",
44
"publishConfig": {
55
"access": "public"
66
},

packages/cesium-wind-layer/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ interface WindLayerOptions {
109109
| `show: boolean` | Get or set the visibility of the wind layer |
110110
| `updateWindData(data: WindData)` | Update the wind field data |
111111
| `updateOptions(options: Partial<WindLayerOptions>)` | Update the options of the wind layer |
112-
| `getDataAtLonLat(lon: number, lat: number): { u: number, v: number, speed: number }` | Get the wind data at a specific longitude and latitude |
112+
| `getDataAtLonLat(lon: number, lat: number): WindDataAtLonLat \| null` | Get the wind data at a specific longitude and latitude, returns both original and interpolated values. Returns null if coordinates are outside bounds |
113113
| `zoomTo(duration?: number)` | Zoom the camera to fit the wind field extent |
114114
| `isDestroyed(): boolean` | Check if the wind layer has been destroyed |
115115
| `destroy()` | Clean up resources and destroy the wind layer |

packages/cesium-wind-layer/readme.zh-CN.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const windData = {
6767

6868
// 使用配置创建风场图层
6969
const windLayer = new WindLayer(viewer, windData, {
70-
particlesTextureSize: 256, // 粒子系统的纹理大小
70+
particlesTextureSize: 100, // 粒子系统的纹理大小
7171
particleHeight: 1000, // 粒子距地面高度
7272
lineWidth: 10.0, // 粒子轨迹宽度
7373
speedFactor: 1.0, // 速度倍数
@@ -88,8 +88,8 @@ const windLayer = new WindLayer(viewer, windData, {
8888

8989
```typescript
9090
interface WindLayerOptions {
91-
particlesTextureSize: number; // 粒子纹理大小默认:256
92-
particleHeight: number; // 粒子高度(默认:1000
91+
particlesTextureSize: number; // 粒子纹理大小,决定粒子最大数量(size * size)(默认:100
92+
particleHeight: number; // 粒子距地面高度(默认:0
9393
lineWidth: number; // 粒子线宽(默认:3.0)
9494
speedFactor: number; // 速度倍数(默认:1.0)
9595
dropRate: number; // 粒子消失率(默认:0.003)
@@ -109,7 +109,7 @@ interface WindLayerOptions {
109109
| `show: boolean` | 获取或设置风场图层的可见性 |
110110
| `updateWindData(data: WindData)` | 更新风场数据 |
111111
| `updateOptions(options: Partial<WindLayerOptions>)` | 更新风场图层的选项 |
112-
| `getDataAtLonLat(lon: number, lat: number): { u: number, v: number, speed: number }` | 获取指定经纬度的风场数据 |
112+
| `getDataAtLonLat(lon: number, lat: number): WindDataAtLonLat \| null` | 获取指定经纬度的风场数据,返回原始值和插值结果。如果坐标超出范围则返回 null |
113113
| `zoomTo(duration?: number)` | 缩放相机以适应风场范围 |
114114
| `isDestroyed(): boolean` | 检查风场图层是否已被销毁 |
115115
| `destroy()` | 清理资源并销毁风场图层 |

packages/cesium-wind-layer/src/index.ts

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import {
77
Rectangle
88
} from 'cesium';
99

10-
import { WindLayerOptions, WindData } from './types';
10+
import { WindLayerOptions, WindData, WindDataAtLonLat } from './types';
1111
import { WindParticleSystem } from './windParticleSystem';
1212

13+
export * from './types';
14+
1315
export class WindLayer {
1416
private _show: boolean = true;
1517
private _resized: boolean = false;
@@ -78,7 +80,7 @@ export class WindLayer {
7880
this.viewerParameters = {
7981
lonRange: new Cartesian2(-180, 180),
8082
latRange: new Cartesian2(-90, 90),
81-
pixelSize: 2000.0,
83+
pixelSize: 1000.0,
8284
sceneMode: this.scene.mode
8385
};
8486
this.updateViewerParameters();
@@ -131,7 +133,7 @@ export class WindLayer {
131133
* @param {number} lat - The latitude.
132134
* @returns {Object} - An object containing the u, v, and speed values at the specified coordinates.
133135
*/
134-
getDataAtLonLat(lon: number, lat: number): { u: number, v: number, speed: number } | null {
136+
getDataAtLonLat(lon: number, lat: number): WindDataAtLonLat | null {
135137
const { bounds, width, height, u, v, speed } = this.windData;
136138
const { flipY } = this.options;
137139

@@ -140,25 +142,66 @@ export class WindLayer {
140142
return null;
141143
}
142144

143-
const x = Math.floor((lon - bounds.west) / (bounds.east - bounds.west) * (width - 1));
144-
let y = Math.floor((lat - bounds.south) / (bounds.north - bounds.south) * (height - 1));
145+
// Calculate normalized coordinates
146+
const xNorm = (lon - bounds.west) / (bounds.east - bounds.west) * (width - 1);
147+
let yNorm = (lat - bounds.south) / (bounds.north - bounds.south) * (height - 1);
145148

146149
// Apply flipY if enabled
147150
if (flipY) {
148-
y = height - 1 - y;
151+
yNorm = height - 1 - yNorm;
149152
}
150153

151-
// Ensure x and y are within the array bounds
152-
if (x < 0 || x >= width || y < 0 || y >= height) {
153-
return null;
154-
}
154+
// Get exact grid point for original values
155+
const x = Math.floor(xNorm);
156+
const y = Math.floor(yNorm);
157+
158+
// Get the four surrounding grid points for interpolation
159+
const x0 = Math.floor(xNorm);
160+
const x1 = Math.min(x0 + 1, width - 1);
161+
const y0 = Math.floor(yNorm);
162+
const y1 = Math.min(y0 + 1, height - 1);
163+
164+
// Calculate interpolation weights
165+
const wx = xNorm - x0;
166+
const wy = yNorm - y0;
155167

168+
// Get indices
156169
const index = y * width + x;
170+
const i00 = y0 * width + x0;
171+
const i10 = y0 * width + x1;
172+
const i01 = y1 * width + x0;
173+
const i11 = y1 * width + x1;
174+
175+
// Bilinear interpolation for u component
176+
const u00 = u.array[i00];
177+
const u10 = u.array[i10];
178+
const u01 = u.array[i01];
179+
const u11 = u.array[i11];
180+
const uInterp = (1 - wx) * (1 - wy) * u00 + wx * (1 - wy) * u10 +
181+
(1 - wx) * wy * u01 + wx * wy * u11;
182+
183+
// Bilinear interpolation for v component
184+
const v00 = v.array[i00];
185+
const v10 = v.array[i10];
186+
const v01 = v.array[i01];
187+
const v11 = v.array[i11];
188+
const vInterp = (1 - wx) * (1 - wy) * v00 + wx * (1 - wy) * v10 +
189+
(1 - wx) * wy * v01 + wx * wy * v11;
190+
191+
// Calculate interpolated speed
192+
const interpolatedSpeed = Math.sqrt(uInterp * uInterp + vInterp * vInterp);
157193

158194
return {
159-
u: u.array[index],
160-
v: v.array[index],
161-
speed: speed.array[index]
195+
original: {
196+
u: u.array[index],
197+
v: v.array[index],
198+
speed: speed.array[index],
199+
},
200+
interpolated: {
201+
u: uInterp,
202+
v: vInterp,
203+
speed: interpolatedSpeed,
204+
}
162205
};
163206
}
164207

@@ -200,7 +243,7 @@ export class WindLayer {
200243
maxLat = Math.max(maxLat, lat);
201244
}
202245

203-
if (!isOutsideGlobe) {
246+
if (!isOutsideGlobe) { // -30 degrees in radians
204247
// Calculate intersection with data bounds
205248
const lonRange = new Cartesian2(
206249
Math.max(this.windData.bounds.west, minLon),
@@ -222,7 +265,6 @@ export class WindLayer {
222265

223266
this.viewerParameters.lonRange = lonRange;
224267
this.viewerParameters.latRange = latRange;
225-
226268
// Calculate pixelSize based on the visible range
227269
const dataLonRange = this.windData.bounds.east - this.windData.bounds.west;
228270
const dataLatRange = this.windData.bounds.north - this.windData.bounds.south;
@@ -234,8 +276,9 @@ export class WindLayer {
234276

235277
// Map the ratio to a pixelSize value between 0 and 1000
236278
const pixelSize = 1000 * visibleRatio;
237-
238-
this.viewerParameters.pixelSize = 5 + Math.max(0, Math.min(1000, pixelSize));
279+
if (pixelSize > 0) {
280+
this.viewerParameters.pixelSize = Math.max(0, Math.min(1000, pixelSize));
281+
}
239282
}
240283

241284

0 commit comments

Comments
 (0)