autumnplot-gl 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +156 -0
- package/lib/AutumnTypes.d.ts +37 -0
- package/lib/AutumnTypes.js +4 -0
- package/lib/Barbs.d.ts +56 -0
- package/lib/Barbs.js +155 -0
- package/lib/BillboardCollection.d.ts +17 -0
- package/lib/BillboardCollection.js +149 -0
- package/lib/ColorBar.d.ts +70 -0
- package/lib/ColorBar.js +183 -0
- package/lib/Colormap.d.ts +70 -0
- package/lib/Colormap.js +137 -0
- package/lib/Contour.d.ts +71 -0
- package/lib/Contour.js +172 -0
- package/lib/ContourFill.d.ts +58 -0
- package/lib/ContourFill.js +125 -0
- package/lib/Hodographs.d.ts +51 -0
- package/lib/Hodographs.js +152 -0
- package/lib/Map.d.ts +34 -0
- package/lib/Map.js +120 -0
- package/lib/Paintball.d.ts +56 -0
- package/lib/Paintball.js +104 -0
- package/lib/PlotComponent.d.ts +20 -0
- package/lib/PlotComponent.js +6 -0
- package/lib/PlotLayer.d.ts +96 -0
- package/lib/PlotLayer.js +131 -0
- package/lib/PlotLayer.worker.d.ts +18 -0
- package/lib/PlotLayer.worker.js +343 -0
- package/lib/PolylineCollection.d.ts +17 -0
- package/lib/PolylineCollection.js +92 -0
- package/lib/RawField.d.ts +194 -0
- package/lib/RawField.js +340 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +20 -0
- package/lib/utils.d.ts +9 -0
- package/lib/utils.js +103 -0
- package/package.json +37 -0
package/lib/ColorBar.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
;
|
|
2
|
+
const createElement = (tagname, attributes, parent) => {
|
|
3
|
+
const elem = document.createElementNS('http://www.w3.org/2000/svg', tagname);
|
|
4
|
+
if (attributes !== undefined) {
|
|
5
|
+
Object.entries(attributes).forEach(([k, v]) => {
|
|
6
|
+
elem.setAttribute(k, v.toString());
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
if (parent !== undefined) {
|
|
10
|
+
parent.appendChild(elem);
|
|
11
|
+
}
|
|
12
|
+
return elem;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Make an SVG containing a color bar. The color bar can either be oriented horizontal or vertical, and a label can be provided.
|
|
16
|
+
* @param colormap - The color map to use
|
|
17
|
+
* @param opts - The options for creating the color bar
|
|
18
|
+
* @returns An SVGElement containing the color bar image.
|
|
19
|
+
* @example
|
|
20
|
+
* // Create the color bar
|
|
21
|
+
* const svg = makeColorBar(color_map, {label: 'Wind Speed (kts)', orientation: 'horizontal',
|
|
22
|
+
* fontface: 'Trebuchet MS'});
|
|
23
|
+
*
|
|
24
|
+
* // Add colorbar to the page
|
|
25
|
+
* document.getElementById('colorbar-container').appendChild(svg);
|
|
26
|
+
*/
|
|
27
|
+
function makeColorBar(colormap, opts) {
|
|
28
|
+
const label = opts.label || "";
|
|
29
|
+
const ticks = opts.ticks || colormap.levels;
|
|
30
|
+
const orientation = opts.orientation || 'vertical';
|
|
31
|
+
const fontface = opts.fontface || 'sans-serif';
|
|
32
|
+
const tick_dir = opts.tick_direction || (orientation == 'vertical' ? 'left' : 'bottom');
|
|
33
|
+
if (orientation == 'vertical' && (tick_dir == 'top' || tick_dir == 'bottom') ||
|
|
34
|
+
orientation == 'horizontal' && (tick_dir == 'left' || tick_dir == 'right')) {
|
|
35
|
+
throw `tick_direction of '${tick_dir} doesn't match an orientation of ${orientation}`;
|
|
36
|
+
}
|
|
37
|
+
const getNChar = (n) => {
|
|
38
|
+
return n.toString().length;
|
|
39
|
+
};
|
|
40
|
+
const chars_left = getNChar(ticks[0]);
|
|
41
|
+
const chars_right = getNChar(ticks[ticks.length - 1]);
|
|
42
|
+
const bar_long_size = 600;
|
|
43
|
+
const bar_cross_size = bar_long_size / 9;
|
|
44
|
+
const bar_long_pad = orientation == 'horizontal' ? Math.max(chars_left, chars_right) * 6 : 5;
|
|
45
|
+
const bar_cross_pad = 3;
|
|
46
|
+
const bar_thickness = 10;
|
|
47
|
+
let height, width, bar_left, bar_top, bar_width, bar_height;
|
|
48
|
+
if (orientation == 'vertical') {
|
|
49
|
+
height = bar_long_size;
|
|
50
|
+
width = bar_cross_size;
|
|
51
|
+
bar_left = tick_dir == 'left' ? bar_cross_size - bar_cross_pad - bar_thickness : bar_cross_pad;
|
|
52
|
+
bar_top = bar_long_pad;
|
|
53
|
+
bar_width = bar_thickness;
|
|
54
|
+
bar_height = bar_long_size - 2 * bar_long_pad;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
width = bar_long_size;
|
|
58
|
+
height = bar_cross_size - 6;
|
|
59
|
+
bar_left = bar_long_pad;
|
|
60
|
+
bar_top = tick_dir == 'bottom' ? bar_cross_pad : bar_cross_size - 6 - bar_cross_pad - bar_thickness;
|
|
61
|
+
bar_height = bar_thickness;
|
|
62
|
+
bar_width = bar_long_size - 2 * bar_long_pad;
|
|
63
|
+
}
|
|
64
|
+
const n_colors = colormap.colors.length;
|
|
65
|
+
const root = createElement('svg', { width: width, height: height });
|
|
66
|
+
const gbar = createElement('g', {}, root);
|
|
67
|
+
let gtickattrs;
|
|
68
|
+
if (orientation == 'vertical') {
|
|
69
|
+
gtickattrs = tick_dir == 'left' ? { 'text-anchor': 'end', transform: `translate(${bar_left}, ${bar_top})` } :
|
|
70
|
+
{ transform: `translate(${bar_left + bar_width}, ${bar_top})` };
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
gtickattrs = tick_dir == 'bottom' ? { 'text-anchor': 'middle', transform: `translate(${bar_left}, ${bar_top + bar_height})` } :
|
|
74
|
+
{ 'text-anchor': 'middle', transform: `translate(${bar_left}, ${bar_top})` };
|
|
75
|
+
}
|
|
76
|
+
const gticks = createElement('g', gtickattrs, root);
|
|
77
|
+
colormap.colors.forEach((color, icolor) => {
|
|
78
|
+
let attrs = {};
|
|
79
|
+
if (orientation == 'vertical') {
|
|
80
|
+
attrs = {
|
|
81
|
+
x: bar_left, y: bar_top + bar_height * (1 - (icolor + 1) / n_colors), width: bar_width, height: bar_height / n_colors
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
attrs = { x: bar_left + bar_width * icolor / n_colors, y: bar_top, width: bar_width / n_colors, height: bar_height };
|
|
86
|
+
}
|
|
87
|
+
createElement('rect', { ...attrs, fill: color.color, opacity: color.opacity }, gbar);
|
|
88
|
+
});
|
|
89
|
+
ticks.forEach(level => {
|
|
90
|
+
const ilevel = colormap.levels.indexOf(level);
|
|
91
|
+
const tickattrs = orientation == 'vertical' ? { transform: `translate(0, ${bar_height * (1 - ilevel / n_colors)})` } :
|
|
92
|
+
{ transform: `translate(${bar_width * ilevel / n_colors}, 0)` };
|
|
93
|
+
const gtick = createElement('g', tickattrs, gticks);
|
|
94
|
+
let lineattrs;
|
|
95
|
+
if (orientation == 'vertical') {
|
|
96
|
+
lineattrs = tick_dir == 'left' ? { x2: -6 } : { x2: 6 };
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
lineattrs = tick_dir == 'bottom' ? { y2: 6 } : { y2: -6 };
|
|
100
|
+
}
|
|
101
|
+
createElement('line', { ...lineattrs, stroke: '#000000', 'stroke-width': 1.5 }, gtick);
|
|
102
|
+
let textattrs;
|
|
103
|
+
if (orientation == 'vertical') {
|
|
104
|
+
textattrs = tick_dir == 'left' ? { x: -9, dy: '0.32em' } : { x: 9, dy: '0.32em' };
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
textattrs = tick_dir == 'bottom' ? { y: 9, dy: '0.8em' } : { y: -9, dy: '0em' };
|
|
108
|
+
}
|
|
109
|
+
const text = createElement('text', { ...textattrs, fill: '#000000', style: `font-family: ${fontface};` }, gtick);
|
|
110
|
+
text.textContent = level.toString();
|
|
111
|
+
});
|
|
112
|
+
const outline_attrs = {
|
|
113
|
+
x: bar_left,
|
|
114
|
+
y: bar_top,
|
|
115
|
+
width: bar_width,
|
|
116
|
+
height: bar_height,
|
|
117
|
+
stroke: '#000000',
|
|
118
|
+
'stroke-width': 1.5,
|
|
119
|
+
fill: 'none'
|
|
120
|
+
};
|
|
121
|
+
createElement('rect', outline_attrs, root);
|
|
122
|
+
let labelattrs;
|
|
123
|
+
if (orientation == 'vertical') {
|
|
124
|
+
labelattrs = tick_dir == 'left' ? { transform: `translate(15, ${height / 2}) rotate(-90)` } : { transform: `translate(${width - 6}, ${height / 2}) rotate(-90)` };
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
labelattrs = tick_dir == 'bottom' ? { transform: `translate(${width / 2}, ${height - 5})` } : { transform: `translate(${width / 2}, 15)` };
|
|
128
|
+
}
|
|
129
|
+
const label_elem = createElement('text', { ...labelattrs, fill: '#000000', 'text-anchor': 'middle', style: `font-family: ${fontface};` }, root);
|
|
130
|
+
label_elem.textContent = label;
|
|
131
|
+
return root;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Make an SVG containing a color key for a paintball plot. The key can be split over any number of columns.
|
|
135
|
+
* @param colors - A list of colors
|
|
136
|
+
* @param labels - The labels corresponding to each color
|
|
137
|
+
* @param opts - The options for creating the color key
|
|
138
|
+
* @returns An SVGElement containing the color bar image.
|
|
139
|
+
* @example
|
|
140
|
+
* // Create the color key
|
|
141
|
+
* const svg = makePaintballKey(colors, labels, {n_cols: 2, fontface: 'Trebuchet MS'});
|
|
142
|
+
*
|
|
143
|
+
* // Add the color key to the page
|
|
144
|
+
* document.getElementById('pb-key-container').appendChild(svg);
|
|
145
|
+
*/
|
|
146
|
+
function makePaintballKey(colors, labels, opts) {
|
|
147
|
+
if (colors.length != labels.length) {
|
|
148
|
+
throw `Mismatch between the number of colors (${colors.length}) and the number of labels (${labels.length})`;
|
|
149
|
+
}
|
|
150
|
+
opts = opts === undefined ? {} : opts;
|
|
151
|
+
const n_cols = opts.n_cols === undefined ? 1 : opts.n_cols;
|
|
152
|
+
const fontface = opts.fontface === undefined ? 'sans-serif' : opts.fontface;
|
|
153
|
+
let height, width;
|
|
154
|
+
const swatch_width = 20;
|
|
155
|
+
const swatch_height = 20;
|
|
156
|
+
const swatch_text_pad = 3;
|
|
157
|
+
const swatch_text_space = 100;
|
|
158
|
+
const swatch_width_pad = 5;
|
|
159
|
+
const swatch_height_pad = 5;
|
|
160
|
+
const n_rows = Math.ceil(colors.length / n_cols);
|
|
161
|
+
width = (swatch_width + swatch_text_pad + swatch_text_space) * n_cols + swatch_width_pad * (n_cols + 1);
|
|
162
|
+
height = swatch_height * n_rows + swatch_height_pad * (n_rows + 1);
|
|
163
|
+
const root = createElement('svg', { width: width, height: height });
|
|
164
|
+
const gbar = createElement('g', {}, root);
|
|
165
|
+
colors.forEach((color, icolor) => {
|
|
166
|
+
const label = labels[icolor];
|
|
167
|
+
const irow = icolor % n_rows;
|
|
168
|
+
const icol = Math.floor(icolor / n_rows);
|
|
169
|
+
let opacity = 1.;
|
|
170
|
+
if (typeof color != 'string') {
|
|
171
|
+
opacity = color.opacity;
|
|
172
|
+
color = color.color;
|
|
173
|
+
}
|
|
174
|
+
const x = swatch_width_pad + icol * (swatch_width + swatch_text_pad + swatch_text_space + swatch_width_pad);
|
|
175
|
+
const y = swatch_height_pad + irow * (swatch_height + swatch_height_pad);
|
|
176
|
+
createElement('rect', { x: x, y: y, fill: color, opacity: opacity, width: swatch_width, height: swatch_height }, gbar);
|
|
177
|
+
let textattrs = { x: x + swatch_width + swatch_text_pad, y: y + swatch_height / 2, dy: '0.32em' };
|
|
178
|
+
const text = createElement('text', { ...textattrs, fill: '#000000', style: `font-size: 0.8em; font-family: ${fontface};` }, gbar);
|
|
179
|
+
text.textContent = label;
|
|
180
|
+
});
|
|
181
|
+
return root;
|
|
182
|
+
}
|
|
183
|
+
export { makeColorBar, makePaintballKey };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
interface Color {
|
|
2
|
+
/** The color as a hex color string */
|
|
3
|
+
color: string;
|
|
4
|
+
/** The opacity as a number from 0 to 1 */
|
|
5
|
+
opacity: number;
|
|
6
|
+
}
|
|
7
|
+
/** A mapping from values to colors */
|
|
8
|
+
declare class ColorMap {
|
|
9
|
+
readonly levels: number[];
|
|
10
|
+
readonly colors: Color[];
|
|
11
|
+
/**
|
|
12
|
+
* Create a color map
|
|
13
|
+
* @param levels - The list of levels. The number of levels should always be one more than the number of colors.
|
|
14
|
+
* @param colors - A list of colors
|
|
15
|
+
*/
|
|
16
|
+
constructor(levels: number[], colors: Color[] | string[]);
|
|
17
|
+
/**
|
|
18
|
+
* @returns an array of hex color strings
|
|
19
|
+
*/
|
|
20
|
+
getColors(): string[];
|
|
21
|
+
/**
|
|
22
|
+
* @returns an array of opacities, one for each color in the color map
|
|
23
|
+
*/
|
|
24
|
+
getOpacities(): number[];
|
|
25
|
+
/**
|
|
26
|
+
* Make a new color map with different opacities. The opacities are set by func.
|
|
27
|
+
* @param func - A function which takes the two levels associated with a color (an upper and lower bound) and returns an opacity in the range from 0 to 1.
|
|
28
|
+
* @returns A new color map
|
|
29
|
+
*/
|
|
30
|
+
withOpacity(func: (level_lower: number, level_upper: number) => number): ColorMap;
|
|
31
|
+
/**
|
|
32
|
+
* Create a diverging color map using two input colors
|
|
33
|
+
* @param color1 - The color corresponding to the lowest value in the color map
|
|
34
|
+
* @param color2 - The color corresponding to the highest value in the color map
|
|
35
|
+
* @param level_min - The lowest value in the color map
|
|
36
|
+
* @param level_max - The highest value in the color map
|
|
37
|
+
* @param n_colors - The number of colors to use
|
|
38
|
+
* @returns a Colormap object
|
|
39
|
+
*/
|
|
40
|
+
static diverging(color1: string, color2: string, level_min: number, level_max: number, n_colors: number): ColorMap;
|
|
41
|
+
}
|
|
42
|
+
declare const pw_speed500mb: ColorMap;
|
|
43
|
+
declare const pw_speed850mb: ColorMap;
|
|
44
|
+
declare const pw_cape: ColorMap;
|
|
45
|
+
declare const pw_t2m: ColorMap;
|
|
46
|
+
declare const pw_td2m: ColorMap;
|
|
47
|
+
/**
|
|
48
|
+
* Create a diverging red/blue colormap, where red corresponds to the lowest value and blue corresponds to the highest value
|
|
49
|
+
* @param level_min - The lowest value in the color map
|
|
50
|
+
* @param level_max - The highest value in the color map
|
|
51
|
+
* @param n_colors - The number of colors
|
|
52
|
+
* @returns a Colormap object
|
|
53
|
+
*/
|
|
54
|
+
declare const redblue: (level_min: number, level_max: number, n_colors: number) => ColorMap;
|
|
55
|
+
/**
|
|
56
|
+
* Create a diverging blue/red colormap, where blue corresponds to the lowest value and red corresponds to the highest value
|
|
57
|
+
* @param level_min - The lowest value in the color map
|
|
58
|
+
* @param level_max - The highest value in the color map
|
|
59
|
+
* @param n_colors - The number of colors
|
|
60
|
+
* @returns a Colormap object
|
|
61
|
+
*/
|
|
62
|
+
declare const bluered: (level_min: number, level_max: number, n_colors: number) => ColorMap;
|
|
63
|
+
/**
|
|
64
|
+
* Make a canvas image corresponding to a color map
|
|
65
|
+
* @param colormap - The color map to use
|
|
66
|
+
* @returns A canvas element containing each color of the color map
|
|
67
|
+
*/
|
|
68
|
+
declare function makeTextureImage(colormap: ColorMap): HTMLCanvasElement;
|
|
69
|
+
export { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, makeTextureImage };
|
|
70
|
+
export type { Color };
|
package/lib/Colormap.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { hex2rgb, hsv2rgb, rgb2hex, rgb2hsv } from "./utils";
|
|
2
|
+
const spd500_colormap_data = { "levels": [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140], "colors": [ "#e6f4ff", "#dbf0fe", "#d1ebfe", "#c6e7fd", "#bce3fd", "#b1dffc", "#a7dbfc", "#9cd6fb", "#92d2fb", "#87cefa", "#84c2f6", "#81b7f1", "#7eabed", "#7ba0e8", "#7994e4", "#7688df", "#737ddb", "#7071d6", "#6d66d2", "#6a5acd", "#7660cf", "#8366d0", "#8f6cd2", "#9c72d3", "#a878d5", "#b47ed6", "#c184d8", "#cd8ad9", "#da90db", "#e696dc", "#e390d9", "#e08ad6", "#dd84d3", "#da7ed0", "#d778cd", "#d472ca", "#d16cc7", "#ce66c4", "#cb60c1", "#c85abe", "#c453ba", "#c04cb6", "#bc45b2", "#b83eae", "#b437aa", "#b030a6", "#ac29a2", "#a8229e", "#a41b9a", "#a01496", "#a41080", "#a80e75", "#ac0e75", "#b00c6a", "#b4085f", "#b80649", "#bc043e", "#c00233", "#c80028", "#c80028", "#ca042a", "#cc082c", "#d00c2c", "#d21432", "#d41834", "#d41834", "#d61c36", "#da243a", "#dc283c", "#de2c3e", "#e03040", "#e23442", "#e43844", "#e63c46", "#e84048", "#ea444a", "#ec484c", "#ee4c4e", "#f05050", "#f16052", "#f27054", "#f38056", "#f49058", "#f5a05a", "#f6b05c", "#f7c05e", "#f8d060", "#f9e062", "#faf064", "#f7eb61", "#f4e65e", "#f1e15b", "#eedc58", "#ebd755", "#e8d252", "#e5cd4f", "#e2c84c", "#dfc349", "#dcbe46", "#d9b943", "#d6b440", "#d3af3d", "#d0aa3a", "#cda537", "#caa034", "#c79b31", "#c4962e", "#c1912b", "#be8c28", "#bb8725", "#b88222", "#b57d1f", "#b2781c", "#af7319", "#ac6e16", "#a96913", "#a66410", "#a35f0d", "#a05a0a", "#a05a0a" ] }
|
|
3
|
+
const spd850_colormap_data = { "levels": [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80], "colors": [ "#f0f8ff", "#dbf0fe", "#c6e7fd", "#b1dffc", "#9cd6fb", "#87cefa", "#81b7f1", "#7ba0e8", "#7688df", "#7071d6", "#6a5acd", "#8366d0", "#9c72d3", "#b47ed6", "#cd8ad9", "#e696dc", "#e08ad6", "#da7ed0", "#d472ca", "#ce66c4", "#c85abe", "#c04cb6", "#b83eae", "#b030a6", "#a8229e", "#a01496", "#a81080", "#b00c6a", "#b8043e", "#c80028", "#c80028", "#d01030", "#d41834", "#d82038", "#dc283c", "#e03040", "#e43844", "#e84048", "#ec484c", "#f05050", "#f27054", "#f49058", "#f6b05c", "#f8d060", "#faf064", "#f4e65e", "#eedc58", "#e8d252", "#e2c84c", "#dcbe46", "#d6b440", "#d0aa3a", "#caa034", "#c4962e", "#be8c28", "#b88222", "#b2781c", "#ac6e16", "#a66410", "#a05a0a" ] }
|
|
4
|
+
const cape_colormap_data = { "levels": [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000, 4100, 4200, 4300, 4400, 4500, 4600, 4700, 4800, 4900, 5000, 5100, 5200, 5300, 5400, 5500, 5600, 5700, 5800, 5900, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000], "colors": [ "#ffffff", "#f0f0f0", "#e1e1e1", "#d2d2d2", "#c3c3c3", "#a5a5a5", "#969696", "#878787", "#787878", "#696969", "#37536a", "#436075", "#506d80", "#5c7a8b", "#698796", "#7594a2", "#82a1ad", "#8eaeb8", "#9bbbc3", "#a7c8ce", "#e9dd96", "#e8d186", "#e7c575", "#e6b865", "#e5ac54", "#e5a044", "#e49433", "#e38723", "#e27b12", "#e16f02", "#dc4110", "#d33b17", "#ca351e", "#c12e25", "#b8282c", "#af2234", "#a61c3b", "#9d1542", "#940f49", "#8b0950", "#73088a", "#7e1894", "#8a289f", "#9538a9", "#a148b3", "#ac59be", "#b869c8", "#c379d2", "#cf89dd", "#da99e7", "#e9bec3", "#e3b0b7", "#dda3ac", "#d795a0", "#d18894", "#ca7a89", "#c46d7d", "#be5f71", "#b85266", "#b2445a", "#893d48", "#8f4752", "#96525b", "#9c5c65", "#a2676f", "#a97178", "#af7c82", "#b6868b" ] }
|
|
5
|
+
const t2m_colormap_data = { "levels": [-60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120], "colors": [ "#235877", "#2a5f7c", "#316782", "#396e87", "#40768c", "#477d91", "#4e8497", "#558c9c", "#5d93a1", "#649ba6", "#6ba2ac", "#72a9b1", "#79b1b6", "#81b8bb", "#88c0c1", "#8fc7c6", "#96cecb", "#9dd6d0", "#a5ddd6", "#ace5db", "#b3ece0", "#b3ece0", "#b1e7df", "#b0e1dd", "#aedcdc", "#add7da", "#abd2d9", "#aaccd8", "#a8c7d6", "#a7c2d5", "#a5bcd4", "#a4b7d2", "#a2b2d1", "#9fa7ce", "#9ea2cd", "#9c9dcb", "#9c9dcb", "#9b97ca", "#9992c8", "#988dc7", "#9688c6", "#9582c4", "#9278c2", "#9073c0", "#8f6dbf", "#8d68bd", "#8c63bc", "#8a5dbb", "#8958b9", "#8753b8", "#864eb6", "#8448b5", "#8343b4", "#813eb2", "#8038b1", "#7e33b0", "#7d33ae", "#7b29ad", "#7a23ab", "#781eaa", "#a037af", "#a443b3", "#a74fb7", "#ab5cbb", "#af68bf", "#b374c3", "#b680c7", "#ba8dcc", "#be99d0", "#c1a5d4", "#c5b1d8", "#c9bddc", "#cdcae0", "#d0d6e4", "#d4e2e8", "#deecf2", "#d1e2ee", "#c5d9ea", "#b8cfe6", "#acc5e3", "#9fbbdf", "#92b2db", "#86a8d7", "#799ed3", "#6c94cf", "#608bcb", "#5381c7", "#4777c4", "#3a6dc0", "#2d64bc", "#215ab8", "#1450b4", "#0f4455", "#1c4e5a", "#2a585f", "#376363", "#456d68", "#52776d", "#5f8172", "#6d8c77", "#7a967c", "#88a080", "#95aa85", "#a3b58a", "#b0bf8f", "#bdc994", "#cbd399", "#d8de9d", "#e6e8a2", "#f3f2a7", "#f8eea2", "#f0e199", "#e8d591", "#e1c888", "#d9bc80", "#d1af77", "#c9a36f", "#c19666", "#ba8a5e", "#b27d55", "#aa714d", "#a26444", "#9b583c", "#8b3f2b", "#833222", "#7b261a", "#7b261a", "#741911", "#6c0d09", "#640000", "#5f0000", "#630507", "#670a0e", "#6c0f15", "#70141c", "#741824", "#781d2b", "#7d2232", "#812739", "#852c40", "#73372d", "#7a4036", "#80493f", "#875349", "#8e5c52", "#94655b", "#9b6e64", "#a88177", "#af8a80", "#b69389", "#bd9c92", "#c3a69c", "#caafa5", "#d1b8ae", "#d7c1b7", "#decac0", "#e5d4ca", "#ebddd3", "#f2e6dc", "#e8dfd6", "#e0d7cf", "#d8d0c8", "#d0c8c0", "#c8c0b9", "#c0b9b2", "#b7b1ab", "#afa9a4", "#a7a29c", "#9f9a95", "#97938e", "#8f8b87", "#878380", "#7f7c78", "#777471", "#6f6c6a", "#666563", "#5e5d5c", "#565554", "#4e4e4d", "#464646" ] }
|
|
6
|
+
const td2m_colormap_data = { "levels": [-40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], "colors": [ "#986d4d", "#966c4c", "#946b4c", "#926a4b", "#90694b", "#8e684a", "#8c664a", "#8a6549", "#886448", "#866348", "#846247", "#826147", "#806046", "#7e5f46", "#7c5e45", "#7a5d44", "#785b44", "#765a43", "#745943", "#725842", "#715742", "#6f5641", "#6d5540", "#6b5440", "#69533f", "#67523f", "#65503e", "#634f3d", "#614e3d", "#5f4d3c", "#5d4c3c", "#5b4b3b", "#594a3b", "#57493a", "#554839", "#534739", "#514538", "#4f4438", "#4d4337", "#4b4237", "#494136", "#4d4334", "#514738", "#564c3c", "#5a5041", "#5e5545", "#625949", "#675e4d", "#6b6251", "#6f6755", "#746b5a", "#78705e", "#7c7462", "#807966", "#857d6a", "#89826f", "#8d8673", "#928b77", "#968f7b", "#9a947f", "#9e9883", "#a39d88", "#a7a18c", "#aba690", "#afaa94", "#b8b39c", "#b8b39c", "#bcb8a1", "#c1bca5", "#c9c5ad", "#c9cab1", "#d2ceb6", "#d2ceb6", "#d6d3ba", "#dfdcc2", "#e3e0c6", "#e7e5ca", "#ebe9cf", "#f0eed3", "#f4f2d7", "#e6f5e6", "#d7f0d7", "#c8eac8", "#b9e5b9", "#aadfaa", "#9bda9b", "#8cd48c", "#7dcf7d", "#6ec96e", "#5fc45f", "#30ae30", "#2ca32c", "#279927", "#238e23", "#1e831e", "#1a791a", "#156e15", "#116311", "#0c590c", "#084e08", "#61a3af", "#5896a0", "#508992", "#477b83", "#3e6e74", "#366166", "#2d5457", "#244648", "#1c393a", "#132c2b", "#66669a", "#605e94", "#59568e", "#534e88", "#4d4682", "#463e7c", "#403676", "#3a2e70", "#33266a", "#2d1e64", "#724071", "#784573", "#7d4b75", "#835076", "#885678", "#8e5b7a", "#93617c", "#99667d", "#9e6c7f", "#a47181" ] }
|
|
7
|
+
function isColor(obj) {
|
|
8
|
+
return (typeof obj == 'object') && 'color' in obj && 'opacity' in obj;
|
|
9
|
+
}
|
|
10
|
+
/** A mapping from values to colors */
|
|
11
|
+
class ColorMap {
|
|
12
|
+
/**
|
|
13
|
+
* Create a color map
|
|
14
|
+
* @param levels - The list of levels. The number of levels should always be one more than the number of colors.
|
|
15
|
+
* @param colors - A list of colors
|
|
16
|
+
*/
|
|
17
|
+
constructor(levels, colors) {
|
|
18
|
+
if (levels.length != colors.length + 1) {
|
|
19
|
+
throw `Mismatch between number of levels (${levels.length}) and number of colors (${colors.length}; expected ${levels.length - 1})`;
|
|
20
|
+
}
|
|
21
|
+
this.levels = levels;
|
|
22
|
+
this.colors = colors.map(c => isColor(c) ? c : { 'color': c, 'opacity': 1. });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @returns an array of hex color strings
|
|
26
|
+
*/
|
|
27
|
+
getColors() {
|
|
28
|
+
return this.colors.map(s => s['color']);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @returns an array of opacities, one for each color in the color map
|
|
32
|
+
*/
|
|
33
|
+
getOpacities() {
|
|
34
|
+
return this.colors.map(s => s['opacity']);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Make a new color map with different opacities. The opacities are set by func.
|
|
38
|
+
* @param func - A function which takes the two levels associated with a color (an upper and lower bound) and returns an opacity in the range from 0 to 1.
|
|
39
|
+
* @returns A new color map
|
|
40
|
+
*/
|
|
41
|
+
withOpacity(func) {
|
|
42
|
+
const new_colors = this.colors.map((c, ic) => { return { 'color': c['color'], 'opacity': func(this.levels[ic], this.levels[ic + 1]) }; });
|
|
43
|
+
return new ColorMap(this.levels, new_colors);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a diverging color map using two input colors
|
|
47
|
+
* @param color1 - The color corresponding to the lowest value in the color map
|
|
48
|
+
* @param color2 - The color corresponding to the highest value in the color map
|
|
49
|
+
* @param level_min - The lowest value in the color map
|
|
50
|
+
* @param level_max - The highest value in the color map
|
|
51
|
+
* @param n_colors - The number of colors to use
|
|
52
|
+
* @returns a Colormap object
|
|
53
|
+
*/
|
|
54
|
+
static diverging(color1, color2, level_min, level_max, n_colors) {
|
|
55
|
+
const stops = [];
|
|
56
|
+
const levels = [];
|
|
57
|
+
const level_step = (level_max - level_min) / (n_colors - 1);
|
|
58
|
+
const crossover = (level_max + level_min) / 2;
|
|
59
|
+
const crossover_hsv = [0, 0, 0.9];
|
|
60
|
+
const color1_hsv = rgb2hsv(hex2rgb(color1));
|
|
61
|
+
const color2_hsv = rgb2hsv(hex2rgb(color2));
|
|
62
|
+
for (let istop = 0; istop < n_colors; istop++) {
|
|
63
|
+
const level = level_min + istop * level_step;
|
|
64
|
+
let h, s, v;
|
|
65
|
+
let interp_fac;
|
|
66
|
+
if (level < crossover) {
|
|
67
|
+
interp_fac = (crossover - level) / (crossover - level_min);
|
|
68
|
+
[h, s, v] = [
|
|
69
|
+
color1_hsv[0],
|
|
70
|
+
crossover_hsv[1] + (color1_hsv[1] - crossover_hsv[1]) * interp_fac,
|
|
71
|
+
crossover_hsv[2] + (color1_hsv[2] - crossover_hsv[2]) * interp_fac
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
else if (level >= crossover) {
|
|
75
|
+
interp_fac = (level - crossover) / (level_max - crossover);
|
|
76
|
+
[h, s, v] = [
|
|
77
|
+
color2_hsv[0],
|
|
78
|
+
crossover_hsv[1] + (color2_hsv[1] - crossover_hsv[1]) * interp_fac,
|
|
79
|
+
crossover_hsv[2] + (color2_hsv[2] - crossover_hsv[2]) * interp_fac
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
const color = rgb2hex(hsv2rgb([h, s, v]));
|
|
83
|
+
stops.push({ 'color': color, 'opacity': Math.min(2 * interp_fac, 1) });
|
|
84
|
+
}
|
|
85
|
+
for (let ilev = 0; ilev <= n_colors; ilev++) {
|
|
86
|
+
const level_step = (level_max - level_min) / n_colors;
|
|
87
|
+
levels.push(level_min + ilev * level_step);
|
|
88
|
+
}
|
|
89
|
+
return new ColorMap(levels, stops);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Some built-in colormaps
|
|
93
|
+
const pw_speed500mb = new ColorMap(spd500_colormap_data.levels, spd500_colormap_data.colors).withOpacity((levl, levu) => Math.min((levu - 20) / 10, 1.));
|
|
94
|
+
const pw_speed850mb = new ColorMap(spd850_colormap_data.levels, spd850_colormap_data.colors).withOpacity((levl, levu) => Math.min((levu - 20) / 10, 1.));
|
|
95
|
+
const pw_cape = new ColorMap(cape_colormap_data.levels, cape_colormap_data.colors).withOpacity((levl, levu) => Math.min(levu / 1000., 1.));
|
|
96
|
+
const pw_t2m = new ColorMap(t2m_colormap_data.levels, t2m_colormap_data.colors);
|
|
97
|
+
const pw_td2m = new ColorMap(td2m_colormap_data.levels, td2m_colormap_data.colors);
|
|
98
|
+
/**
|
|
99
|
+
* Create a diverging red/blue colormap, where red corresponds to the lowest value and blue corresponds to the highest value
|
|
100
|
+
* @param level_min - The lowest value in the color map
|
|
101
|
+
* @param level_max - The highest value in the color map
|
|
102
|
+
* @param n_colors - The number of colors
|
|
103
|
+
* @returns a Colormap object
|
|
104
|
+
*/
|
|
105
|
+
const redblue = (level_min, level_max, n_colors) => {
|
|
106
|
+
return ColorMap.diverging('#ff0000', '#0000ff', level_min, level_max, n_colors);
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Create a diverging blue/red colormap, where blue corresponds to the lowest value and red corresponds to the highest value
|
|
110
|
+
* @param level_min - The lowest value in the color map
|
|
111
|
+
* @param level_max - The highest value in the color map
|
|
112
|
+
* @param n_colors - The number of colors
|
|
113
|
+
* @returns a Colormap object
|
|
114
|
+
*/
|
|
115
|
+
const bluered = (level_min, level_max, n_colors) => {
|
|
116
|
+
return ColorMap.diverging('#0000ff', '#ff0000', level_min, level_max, n_colors);
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Make a canvas image corresponding to a color map
|
|
120
|
+
* @param colormap - The color map to use
|
|
121
|
+
* @returns A canvas element containing each color of the color map
|
|
122
|
+
*/
|
|
123
|
+
function makeTextureImage(colormap) {
|
|
124
|
+
const cmap_image = document.createElement('canvas');
|
|
125
|
+
cmap_image.width = colormap.colors.length;
|
|
126
|
+
cmap_image.height = 1;
|
|
127
|
+
let ctx = cmap_image.getContext('2d');
|
|
128
|
+
colormap.colors.forEach((stop, istop) => {
|
|
129
|
+
if (ctx === null) {
|
|
130
|
+
throw "Could not get rendering context for colormap image canvas";
|
|
131
|
+
}
|
|
132
|
+
ctx.fillStyle = stop['color'] + Math.round(stop['opacity'] * 255).toString(16);
|
|
133
|
+
ctx.fillRect(istop, 0, 1, 1);
|
|
134
|
+
});
|
|
135
|
+
return cmap_image;
|
|
136
|
+
}
|
|
137
|
+
export { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, makeTextureImage };
|
package/lib/Contour.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { WebGLAnyRenderingContext } from './AutumnTypes';
|
|
2
|
+
import { MapType } from './Map';
|
|
3
|
+
import { PlotComponent } from './PlotComponent';
|
|
4
|
+
import { RawScalarField } from './RawField';
|
|
5
|
+
import { WGLBuffer, WGLProgram, WGLTexture } from 'autumn-wgl';
|
|
6
|
+
interface ContourOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The color of the contours as a hex color string
|
|
9
|
+
* @default '#000000'
|
|
10
|
+
*/
|
|
11
|
+
color?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The contour interval for drawing contours at regular intervals
|
|
14
|
+
* @default 1
|
|
15
|
+
*/
|
|
16
|
+
interval?: number;
|
|
17
|
+
/**
|
|
18
|
+
* A list of arbitrary levels (up to 40) to contour. This overrides the `interval` option.
|
|
19
|
+
* @default Draw contours at regular intervals given by the `interval` option.
|
|
20
|
+
*/
|
|
21
|
+
levels?: number[];
|
|
22
|
+
/**
|
|
23
|
+
* A function to thin the contours based on zoom level. The function should take a zoom level and return a number `n` that means to only show every
|
|
24
|
+
* `n`th contour.
|
|
25
|
+
* @default Don't thin the contours on any zoom level
|
|
26
|
+
*/
|
|
27
|
+
thinner?: (zoom: number) => number;
|
|
28
|
+
}
|
|
29
|
+
interface ContourGLElems {
|
|
30
|
+
map: MapType;
|
|
31
|
+
program: WGLProgram;
|
|
32
|
+
vertices: WGLBuffer;
|
|
33
|
+
grid_cell_size: WGLBuffer;
|
|
34
|
+
fill_texture: WGLTexture;
|
|
35
|
+
texcoords: WGLBuffer;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A field of contoured data. The contours can optionally be thinned based on map zoom level.
|
|
39
|
+
* @example
|
|
40
|
+
* // Create a contoured height field, with black contours every 30 m (assuming the height field is in
|
|
41
|
+
* // meters) and only using every other contour when the map zoom level is less than 5.
|
|
42
|
+
* const contours = new Contour(height_field, {color: '#000000', interval: 30,
|
|
43
|
+
* thinner: zoom => zoom < 5 ? 2 : 1});
|
|
44
|
+
*/
|
|
45
|
+
declare class Contour extends PlotComponent {
|
|
46
|
+
readonly field: RawScalarField;
|
|
47
|
+
readonly color: [number, number, number];
|
|
48
|
+
readonly interval: number;
|
|
49
|
+
readonly levels: number[];
|
|
50
|
+
readonly thinner: (zoom: number) => number;
|
|
51
|
+
/** @private */
|
|
52
|
+
gl_elems: ContourGLElems | null;
|
|
53
|
+
/**
|
|
54
|
+
* Create a contoured field
|
|
55
|
+
* @param field - The field to contour
|
|
56
|
+
* @param opts - Options for creating the contours
|
|
57
|
+
*/
|
|
58
|
+
constructor(field: RawScalarField, opts: ContourOptions);
|
|
59
|
+
/**
|
|
60
|
+
* @internal
|
|
61
|
+
* Add the contours to a map
|
|
62
|
+
*/
|
|
63
|
+
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
* Render the contours
|
|
67
|
+
*/
|
|
68
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
69
|
+
}
|
|
70
|
+
export default Contour;
|
|
71
|
+
export type { ContourOptions };
|
package/lib/Contour.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { isWebGL2Ctx } from './AutumnTypes';
|
|
2
|
+
import { PlotComponent } from './PlotComponent';
|
|
3
|
+
import { hex2rgba } from './utils';
|
|
4
|
+
import { WGLProgram, WGLTexture } from 'autumn-wgl';
|
|
5
|
+
const contour_vertex_shader_src = `uniform mat4 u_matrix;
|
|
6
|
+
|
|
7
|
+
attribute vec2 a_pos;
|
|
8
|
+
attribute float a_grid_cell_size;
|
|
9
|
+
attribute vec2 a_tex_coord;
|
|
10
|
+
|
|
11
|
+
varying highp vec2 v_tex_coord;
|
|
12
|
+
varying highp float v_grid_cell_size;
|
|
13
|
+
varying highp float v_map_scale_fac;
|
|
14
|
+
|
|
15
|
+
void main() {
|
|
16
|
+
gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
|
|
17
|
+
v_tex_coord = a_tex_coord;
|
|
18
|
+
v_grid_cell_size = a_grid_cell_size;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
highp float lat = 2. * atan(exp(a_pos.y / 6371229.0)) - 3.1414592654 / 2.;
|
|
22
|
+
v_map_scale_fac = cos(lat);
|
|
23
|
+
}`
|
|
24
|
+
const contour_fragment_shader_src = `#extension GL_OES_standard_derivatives : enable
|
|
25
|
+
#define MAX_N_CONTOURS 40
|
|
26
|
+
|
|
27
|
+
varying highp vec2 v_tex_coord;
|
|
28
|
+
varying highp float v_grid_cell_size;
|
|
29
|
+
varying highp float v_map_scale_fac;
|
|
30
|
+
|
|
31
|
+
uniform sampler2D u_fill_sampler;
|
|
32
|
+
uniform highp float u_contour_interval;
|
|
33
|
+
uniform highp float u_contour_levels[MAX_N_CONTOURS];
|
|
34
|
+
uniform int u_num_contours;
|
|
35
|
+
uniform lowp float u_line_cutoff;
|
|
36
|
+
uniform lowp vec3 u_color;
|
|
37
|
+
uniform lowp vec2 u_step_size;
|
|
38
|
+
uniform lowp float u_zoom_fac;
|
|
39
|
+
|
|
40
|
+
void main() {
|
|
41
|
+
highp float field_val = texture2D(u_fill_sampler, v_tex_coord).r;
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
lowp vec2 ihat = vec2(u_step_size.x, 0.0);
|
|
45
|
+
lowp vec2 jhat = vec2(0.0, u_step_size.y);
|
|
46
|
+
highp float fv_xp1 = texture2D(u_fill_sampler, v_tex_coord + ihat).r;
|
|
47
|
+
highp float fv_xm1 = texture2D(u_fill_sampler, v_tex_coord - ihat).r;
|
|
48
|
+
highp float fv_yp1 = texture2D(u_fill_sampler, v_tex_coord + jhat).r;
|
|
49
|
+
highp float fv_ym1 = texture2D(u_fill_sampler, v_tex_coord - jhat).r;
|
|
50
|
+
highp float fwidth_field = sqrt(((fv_xp1 - fv_xm1) * (fv_xp1 - fv_xm1) + (fv_yp1 - fv_ym1) * (fv_yp1 - fv_ym1)) / (5e5 * v_grid_cell_size));
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
lowp float plot_val;
|
|
54
|
+
|
|
55
|
+
if (u_num_contours > 0) {
|
|
56
|
+
highp float min_contour_diff = u_contour_levels[1] - u_contour_levels[0];
|
|
57
|
+
bool assigned_contour = false;
|
|
58
|
+
highp float low_contour;
|
|
59
|
+
highp float high_contour;
|
|
60
|
+
highp float highest_contour;
|
|
61
|
+
|
|
62
|
+
for (int ncnt = 0; ncnt < MAX_N_CONTOURS; ncnt++) {
|
|
63
|
+
if (ncnt >= u_num_contours) { break; }
|
|
64
|
+
|
|
65
|
+
min_contour_diff = min(min_contour_diff, u_contour_levels[ncnt + 1] - u_contour_levels[ncnt]);
|
|
66
|
+
|
|
67
|
+
if (u_contour_levels[ncnt] < field_val && field_val < u_contour_levels[ncnt + 1]) {
|
|
68
|
+
assigned_contour = true;
|
|
69
|
+
|
|
70
|
+
low_contour = u_contour_levels[ncnt];
|
|
71
|
+
high_contour = u_contour_levels[ncnt + 1];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
highest_contour = u_contour_levels[ncnt];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!assigned_contour) {
|
|
78
|
+
if (field_val < u_contour_levels[0]) {
|
|
79
|
+
plot_val = (u_contour_levels[0] - field_val) / min_contour_diff;
|
|
80
|
+
}
|
|
81
|
+
else if (field_val > highest_contour) {
|
|
82
|
+
plot_val = (field_val - highest_contour) / min_contour_diff;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
plot_val = min(field_val - low_contour, high_contour - field_val) / min_contour_diff;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
plot_val = fract(field_val / u_contour_interval);
|
|
91
|
+
if (plot_val > 0.5) plot_val = 1.0 - plot_val;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
plot_val = plot_val / (max(0.001, fwidth_field / (u_zoom_fac * 0.125)));
|
|
95
|
+
|
|
96
|
+
if (plot_val > u_line_cutoff) discard;
|
|
97
|
+
|
|
98
|
+
gl_FragColor = vec4(u_color, 1. - (plot_val * plot_val / (u_line_cutoff * u_line_cutoff)));
|
|
99
|
+
}`
|
|
100
|
+
/**
|
|
101
|
+
* A field of contoured data. The contours can optionally be thinned based on map zoom level.
|
|
102
|
+
* &example
|
|
103
|
+
* // Create a contoured height field, with black contours every 30 m (assuming the height field is in
|
|
104
|
+
* // meters) and only using every other contour when the map zoom level is less than 5.
|
|
105
|
+
* const contours = new Contour(height_field, {color: '#000000', interval: 30,
|
|
106
|
+
* thinner: zoom => zoom < 5 ? 2 : 1});
|
|
107
|
+
*/
|
|
108
|
+
class Contour extends PlotComponent {
|
|
109
|
+
/**
|
|
110
|
+
* Create a contoured field
|
|
111
|
+
* ¶m field - The field to contour
|
|
112
|
+
* ¶m opts - Options for creating the contours
|
|
113
|
+
*/
|
|
114
|
+
constructor(field, opts) {
|
|
115
|
+
super();
|
|
116
|
+
this.field = field;
|
|
117
|
+
this.interval = opts.interval || 1;
|
|
118
|
+
this.levels = opts.levels || [];
|
|
119
|
+
const color = hex2rgba(opts.color || '#000000');
|
|
120
|
+
this.color = [color[0], color[1], color[2]];
|
|
121
|
+
this.thinner = opts.thinner || (() => 1);
|
|
122
|
+
this.gl_elems = null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* &internal
|
|
126
|
+
* Add the contours to a map
|
|
127
|
+
*/
|
|
128
|
+
async onAdd(map, gl) {
|
|
129
|
+
// Basic procedure for these contours from https://www.shadertoy.com/view/lltBWM
|
|
130
|
+
gl.getExtension('OES_texture_float');
|
|
131
|
+
gl.getExtension('OES_texture_float_linear');
|
|
132
|
+
gl.getExtension('OES_standard_derivatives');
|
|
133
|
+
const program = new WGLProgram(gl, contour_vertex_shader_src, contour_fragment_shader_src);
|
|
134
|
+
const { vertices: verts_buf, texcoords: tex_coords_buf, cellsize: cellsize_buf } = await this.field.grid.getWGLBuffers(gl);
|
|
135
|
+
const vertices = verts_buf;
|
|
136
|
+
const texcoords = tex_coords_buf;
|
|
137
|
+
const grid_cell_size = cellsize_buf;
|
|
138
|
+
const format = isWebGL2Ctx(gl) ? gl.R32F : gl.LUMINANCE;
|
|
139
|
+
const fill_image = { 'format': format, 'type': gl.FLOAT,
|
|
140
|
+
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.data,
|
|
141
|
+
'mag_filter': gl.LINEAR,
|
|
142
|
+
};
|
|
143
|
+
const fill_texture = new WGLTexture(gl, fill_image);
|
|
144
|
+
this.gl_elems = {
|
|
145
|
+
map: map, program: program, vertices: vertices, texcoords: texcoords, grid_cell_size: grid_cell_size, fill_texture: fill_texture
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* &internal
|
|
150
|
+
* Render the contours
|
|
151
|
+
*/
|
|
152
|
+
render(gl, matrix) {
|
|
153
|
+
if (this.gl_elems === null)
|
|
154
|
+
return;
|
|
155
|
+
const gl_elems = this.gl_elems;
|
|
156
|
+
const zoom = gl_elems.map.getZoom();
|
|
157
|
+
const intv = this.thinner(zoom) * this.interval;
|
|
158
|
+
const cutoff = 0.5 / intv;
|
|
159
|
+
const step_size = [0.25 / this.field.grid.ni, 0.25 / this.field.grid.nj];
|
|
160
|
+
const zoom_fac = Math.pow(2, zoom);
|
|
161
|
+
let uniforms = { 'u_contour_interval': intv, 'u_line_cutoff': cutoff, 'u_color': this.color, 'u_step_size': step_size, 'u_zoom_fac': zoom_fac,
|
|
162
|
+
'u_matrix': matrix, 'u_num_contours': 0, 'u_contour_levels': [0] };
|
|
163
|
+
if (this.levels.length > 0) {
|
|
164
|
+
uniforms = { ...uniforms, 'u_num_contours': this.levels.length, 'u_contour_levels': this.levels };
|
|
165
|
+
}
|
|
166
|
+
gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_grid_cell_size': gl_elems.grid_cell_size, 'a_tex_coord': gl_elems.texcoords }, uniforms, { 'u_fill_sampler': gl_elems.fill_texture });
|
|
167
|
+
gl.enable(gl.BLEND);
|
|
168
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
169
|
+
gl_elems.program.draw();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export default Contour;
|