Skip to content

Commit c8b44c1

Browse files
authored
Merge pull request #53 from open-meteo/add-convective-cloud-colorscales
feat: add support for convective cloud color scales
2 parents 013c7c2 + 8b14f13 commit c8b44c1

File tree

7 files changed

+92
-40
lines changed

7 files changed

+92
-40
lines changed

src/lib/utils/color-scales.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { interpolateHsl, color } from 'd3';
2+
import type { ColorScale, Variable } from '../../types';
23

34
function interpolateColorScaleHSL(colors: Array<string>, steps: number) {
45
const segments = colors.length - 1;
@@ -27,13 +28,6 @@ function interpolateColorScaleHSL(colors: Array<string>, steps: number) {
2728
return rgbArray;
2829
}
2930

30-
type ColorScale = {
31-
min: number;
32-
max: number;
33-
scalefactor: number;
34-
colors: number[][];
35-
};
36-
3731
type ColorScales = {
3832
[key: string]: ColorScale;
3933
};
@@ -46,7 +40,18 @@ const precipScale: ColorScale = {
4640
...interpolateColorScaleHSL(['blue', 'green'], 5), // 0 to 5mm
4741
...interpolateColorScaleHSL(['green', 'orange'], 5), // 5 to 10mm
4842
...interpolateColorScaleHSL(['orange', 'red'], 10) // 10 to 20mm
49-
]
43+
],
44+
interpolationMethod: 'hermite2d'
45+
};
46+
47+
const convectiveCloudScale: ColorScale = {
48+
min: 0,
49+
max: 6000,
50+
scalefactor: 1,
51+
colors: [
52+
...interpolateColorScaleHSL(['#c0392b', '#d35400', '#f1c40f', '#16a085', '#2980b9'], 6000)
53+
],
54+
interpolationMethod: 'quintic2d'
5055
};
5156

5257
export const colorScales: ColorScales = {
@@ -59,16 +64,20 @@ export const colorScales: ColorScales = {
5964
['#009392', '#39b185', '#9ccb86', '#e9e29c', '#eeb479', '#e88471', '#cf597e'],
6065
4000
6166
)
62-
]
67+
],
68+
interpolationMethod: 'hermite2d'
6369
},
6470
cloud: {
6571
min: 0,
6672
max: 100,
6773
scalefactor: 1,
6874
colors: [
6975
...interpolateColorScaleHSL(['#FFF', '#c3c2c2'], 100) // 0 to 100%
70-
]
76+
],
77+
interpolationMethod: 'hermite2d'
7178
},
79+
convective_cloud_top: convectiveCloudScale,
80+
convective_cloud_base: convectiveCloudScale,
7281
precipitation: precipScale,
7382
pressure: {
7483
min: 950,
@@ -77,7 +86,8 @@ export const colorScales: ColorScales = {
7786
colors: [
7887
...interpolateColorScaleHSL(['#4444FF', '#FFFFFF'], 25), // 950 to 1000hPa
7988
...interpolateColorScaleHSL(['#FFFFFF', '#FF4444'], 25) // 1000hPa to 1050hPa
80-
]
89+
],
90+
interpolationMethod: 'hermite2d'
8191
},
8292
rain: precipScale,
8393
relative: {
@@ -89,7 +99,8 @@ export const colorScales: ColorScales = {
8999
['#009392', '#39b185', '#9ccb86', '#e9e29c', '#eeb479', '#e88471', '#cf597e'].reverse(),
90100
100
91101
)
92-
]
102+
],
103+
interpolationMethod: 'hermite2d'
93104
},
94105
shortwave: {
95106
min: 0,
@@ -100,7 +111,8 @@ export const colorScales: ColorScales = {
100111
['#009392', '#39b185', '#9ccb86', '#e9e29c', '#eeb479', '#e88471', '#cf597e'],
101112
1000
102113
)
103-
]
114+
],
115+
interpolationMethod: 'hermite2d'
104116
},
105117
temperature: {
106118
min: -40,
@@ -112,7 +124,8 @@ export const colorScales: ColorScales = {
112124
...interpolateColorScaleHSL(['green', 'orange'], 12), // 0° to 28°
113125
...interpolateColorScaleHSL(['orange', 'red'], 14), // 28° to 42°
114126
...interpolateColorScaleHSL(['red', 'purple'], 18) // 42° to 60°
115-
]
127+
],
128+
interpolationMethod: 'hermite2d'
116129
},
117130
thunderstorm: {
118131
min: 0,
@@ -122,7 +135,8 @@ export const colorScales: ColorScales = {
122135
...interpolateColorScaleHSL(['blue', 'green'], 33), //
123136
...interpolateColorScaleHSL(['green', 'orange'], 33), //
124137
...interpolateColorScaleHSL(['orange', 'red'], 34) //
125-
]
138+
],
139+
interpolationMethod: 'hermite2d'
126140
},
127141
uv: {
128142
min: 0,
@@ -133,7 +147,8 @@ export const colorScales: ColorScales = {
133147
['#009392', '#39b185', '#9ccb86', '#e9e29c', '#eeb479', '#e88471', '#cf597e'],
134148
12
135149
)
136-
]
150+
],
151+
interpolationMethod: 'hermite2d'
137152
},
138153
wind: {
139154
min: 0,
@@ -143,6 +158,15 @@ export const colorScales: ColorScales = {
143158
...interpolateColorScaleHSL(['blue', 'green'], 10), // 0 to 10kn
144159
...interpolateColorScaleHSL(['green', 'orange'], 10), // 10 to 20kn
145160
...interpolateColorScaleHSL(['orange', 'red'], 20) // 20 to 40kn
146-
]
161+
],
162+
interpolationMethod: 'hermite2d'
147163
}
148164
};
165+
166+
export function getColorScale(variable: Variable) {
167+
return (
168+
colorScales[variable.value] ??
169+
colorScales[variable.value.split('_')[0]] ??
170+
colorScales['temperature']
171+
);
172+
}

src/lib/utils/math.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const getDerivative = (fPrev: number, fNext: number) => {
5555
};
5656

5757
export const interpolate2DHermite = (
58-
values: TypedArray,
58+
values: Float32Array<ArrayBufferLike>,
5959
nx: number,
6060
index: number,
6161
xFraction: number,

src/lib/utils/variables.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,13 @@ for (const height of heights) {
8484
variables.push({ value: `wind_${height}m`, label: `Wind ${height}m` });
8585
}
8686

87-
export const hideZero = ['precipitation', 'cloud_cover', 'rain'];
87+
export const hideZero = [
88+
'precipitation',
89+
'cloud_cover',
90+
'rain',
91+
'convective_cloud_top',
92+
'convective_cloud_base'
93+
];
8894
export const requestMultiple = [
8995
'wind_10m',
9096
'wind_40m',

src/om-protocol.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import {
1212
} from '$lib/utils/math';
1313

1414
import { domains } from '$lib/utils/domains';
15-
import { variables } from '$lib//utils/variables';
15+
import { variables } from '$lib/utils/variables';
1616

1717
import TileWorker from './worker?worker';
1818

1919
import type { TileJSON, TileIndex, Domain, Variable, Bounds, Range } from './types';
2020
import { DynamicProjection, ProjectionGrid, type Projection } from '$lib/utils/projection';
2121
import { OMapsFileReader } from './omaps-reader';
22+
import { getColorScale } from '$lib/utils/color-scales';
2223

2324
let dark = false;
2425
let partial = false;
@@ -91,6 +92,7 @@ export const getValueFromLatLong = (
9192

9293
const getTile = async ({ z, x, y }: TileIndex, omUrl: string): Promise<ImageBitmap> => {
9394
const key = `${omUrl}/${TILE_SIZE}/${z}/${x}/${y}`;
95+
const colorScale = getColorScale(variable);
9496

9597
const worker = new TileWorker();
9698

@@ -103,6 +105,7 @@ const getTile = async ({ z, x, y }: TileIndex, omUrl: string): Promise<ImageBitm
103105
data,
104106
domain,
105107
variable,
108+
colorScale,
106109
ranges,
107110
dark: dark,
108111
mapBounds: mapBounds

src/routes/+page.svelte

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import * as Select from '$lib/components/ui/select';
2525
import * as Drawer from '$lib/components/ui/drawer';
2626
27-
import { colorScales } from '$lib/utils/color-scales';
27+
import { getColorScale } from '$lib/utils/color-scales';
2828
2929
let partial = $state(false);
3030
let showScale = $state(true);
@@ -508,12 +508,7 @@
508508
let selectedVariable = $derived(variable.value);
509509
510510
let colorScale = $derived.by(() => {
511-
for (let [cs, value] of Object.entries(colorScales)) {
512-
if (variable.value.startsWith(cs)) {
513-
return value;
514-
}
515-
}
516-
return colorScales['temperature'];
511+
return getColorScale(variable);
517512
});
518513
519514
let colors = $derived(colorScale.colors.reverse());

src/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ export type Variable = {
4646
label: string;
4747
};
4848

49+
export type ColorScale = {
50+
min: number;
51+
max: number;
52+
scalefactor: number;
53+
colors: number[][];
54+
interpolationMethod: InterpolationMethod;
55+
};
56+
57+
export type InterpolationMethod = 'hermite2d' | 'quintic2d';
58+
59+
export type Interpolator = (
60+
values: Float32Array<ArrayBufferLike>,
61+
nx: number,
62+
index: number,
63+
xFraction: number,
64+
yFraction: number
65+
) => number;
66+
4967
export interface Domain {
5068
value: string;
5169
label: string;

src/worker.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { colorScales } from './lib/utils/color-scales';
2-
31
import { hideZero, drawOnTiles } from './lib/utils/variables';
42

53
import { DynamicProjection, ProjectionGrid, type Projection } from './lib/utils/projection';
@@ -10,12 +8,12 @@ import {
108
getIndexFromLatLong,
119
interpolateLinear,
1210
interpolate2DHermite,
11+
quinticHermite2D,
1312
degreesToRadians
1413
} from './lib/utils/math';
1514

16-
import type { TypedArray } from '@openmeteo/file-reader';
1715
import type { IconListPixels } from './lib/utils/icons';
18-
import type { Domain, IndexAndFractions } from './types';
16+
import type { ColorScale, Domain, IndexAndFractions, Interpolator } from './types';
1917

2018
const TILE_SIZE = Number(import.meta.env.VITE_TILE_SIZE) * 2;
2119
const OPACITY = Number(import.meta.env.VITE_TILE_OPACITY);
@@ -83,11 +81,11 @@ const OPACITY = Number(import.meta.env.VITE_TILE_OPACITY);
8381
// }
8482
// };
8583

86-
const getColor = (v: string, px: number): number[] => {
87-
return colorsObj.colors[
84+
const getColor = (colorScale: ColorScale, px: number): number[] => {
85+
return colorScale.colors[
8886
Math.min(
89-
colorsObj.colors.length - 1,
90-
Math.max(0, Math.floor((px - colorsObj.min) / colorsObj.scalefactor))
87+
colorScale.colors.length - 1,
88+
Math.max(0, Math.floor((px - colorScale.min) / colorScale.scalefactor))
9189
)
9290
];
9391
};
@@ -134,7 +132,6 @@ const getIndexAndFractions = (
134132
);
135133
};
136134

137-
let colorsObj;
138135
self.onmessage = async (message) => {
139136
if (message.data.type == 'GT') {
140137
const key = message.data.key;
@@ -146,13 +143,12 @@ self.onmessage = async (message) => {
146143

147144
const domain = message.data.domain;
148145
const variable = message.data.variable;
146+
const colorScale: ColorScale = message.data.colorScale;
149147

150148
const pixels = TILE_SIZE * TILE_SIZE;
151149
const rgba = new Uint8ClampedArray(pixels * 4);
152150
const dark = message.data.dark;
153151

154-
colorsObj = colorScales[variable.value.split('_')[0]] ?? colorScales['temperature'];
155-
156152
let projectionGrid = null;
157153
if (domain.grid.projection) {
158154
const projectionName = domain.grid.projection.name;
@@ -163,6 +159,16 @@ self.onmessage = async (message) => {
163159
projectionGrid = new ProjectionGrid(projection, domain.grid, ranges);
164160
}
165161

162+
let interpolator: Interpolator;
163+
if (colorScale.interpolationMethod === 'hermite2d') {
164+
interpolator = interpolate2DHermite;
165+
} else if (colorScale.interpolationMethod === 'quintic2d') {
166+
interpolator = quinticHermite2D;
167+
} else {
168+
// default is hermite2d
169+
interpolator = interpolate2DHermite;
170+
}
171+
166172
for (let i = 0; i < TILE_SIZE; i++) {
167173
const lat = tile2lat(y + i / TILE_SIZE, z);
168174
for (let j = 0; j < TILE_SIZE; j++) {
@@ -177,7 +183,7 @@ self.onmessage = async (message) => {
177183
ranges
178184
);
179185

180-
let px = interpolate2DHermite(
186+
let px = interpolator(
181187
values,
182188
ranges[1]['end'] - ranges[1]['start'],
183189
index,
@@ -197,7 +203,7 @@ self.onmessage = async (message) => {
197203
rgba[4 * ind + 2] = 0;
198204
rgba[4 * ind + 3] = 0;
199205
} else {
200-
const color = getColor(variable.value, px);
206+
const color = getColor(colorScale, px);
201207

202208
if (color) {
203209
rgba[4 * ind] = color[0];

0 commit comments

Comments
 (0)