autumnplot-gl 3.1.0 → 3.2.0

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/lib/Colormap.js CHANGED
@@ -1,4 +1,3 @@
1
- import { hex2rgb, hsv2rgb, rgb2hex, rgb2hsv } from "./utils";
2
1
  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
2
  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
3
  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" ] }
@@ -6,9 +5,37 @@ const t2m_colormap_data = { "levels": [-60, -59, -58, -57, -56, -55, -54, -5
6
5
  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
6
  const nws_storm_clear_refl_colormap_data = { "levels": [ -15, -14.53608247, -14.07216495, -13.60824742, -13.1443299, -12.68041237, -12.21649485, -11.75257732, -11.28865979, -10.82474227, -10.36082474, -9.89690722, -9.43298969, -8.96907216, -8.50515464, -8.04123711, -7.57731959, -7.11340206, -6.64948454, -6.18556701, -5.72164948, -5.25773196, -4.79381443, -4.32989691, -3.86597938, -3.40206186, -2.93814433, -2.4742268, -2.01030928, -1.54639175, -1.08247423, -0.6185567, -0.15463918, 0.30927835, 0.77319588, 1.2371134, 1.70103093, 2.16494845, 2.62886598, 3.09278351, 3.55670103, 4.02061856, 4.48453608, 4.94845361, 5.41237113, 5.87628866, 6.34020619, 6.80412371, 7.26804124, 7.73195876, 8.19587629, 8.65979381, 9.12371134, 9.58762887, 10.05154639, 10.51546392, 10.97938144, 11.44329897, 11.90721649, 12.37113402, 12.83505155, 13.29896907, 13.7628866, 14.22680412, 14.69072165, 15.15463918, 15.6185567, 16.08247423, 16.54639175, 17.01030928, 17.4742268, 17.93814433, 18.40206186, 18.86597938, 19.32989691, 19.79381443, 20.25773196, 20.72164948, 21.18556701, 21.64948454, 22.11340206, 22.57731959, 23.04123711, 23.50515464, 23.96907216, 24.43298969, 24.89690722, 25.36082474, 25.82474227, 26.28865979, 26.75257732, 27.21649485, 27.68041237, 28.1443299, 28.60824742, 29.07216495, 29.53608247, 30, 30.46391753, 30.92783505, 31.39175258, 31.8556701, 32.31958763, 32.78350515, 33.24742268, 33.71134021, 34.17525773, 34.63917526, 35.10309278, 35.56701031, 36.03092784, 36.49484536, 36.95876289, 37.42268041, 37.88659794, 38.35051546, 38.81443299, 39.27835052, 39.74226804, 40.20618557, 40.67010309, 41.13402062, 41.59793814, 42.06185567, 42.5257732, 42.98969072, 43.45360825, 43.91752577, 44.3814433, 44.84536082, 45.30927835, 45.77319588, 46.2371134, 46.70103093, 47.16494845, 47.62886598, 48.09278351, 48.55670103, 49.02061856, 49.48453608, 49.94845361, 50.41237113, 50.87628866, 51.34020619, 51.80412371, 52.26804124, 52.73195876, 53.19587629, 53.65979381, 54.12371134, 54.58762887, 55.05154639, 55.51546392, 55.97938144, 56.44329897, 56.90721649, 57.37113402, 57.83505155, 58.29896907, 58.7628866, 59.22680412, 59.69072165, 60.15463918, 60.6185567, 61.08247423, 61.54639175, 62.01030928, 62.4742268, 62.93814433, 63.40206186, 63.86597938, 64.32989691, 64.79381443, 65.25773196, 65.72164948, 66.18556701, 66.64948454, 67.11340206, 67.57731959, 68.04123711, 68.50515464, 68.96907216, 69.43298969, 69.89690722, 70.36082474, 70.82474227, 71.28865979, 71.75257732, 72.21649485, 72.68041237, 73.1443299, 73.60824742, 74.07216495, 74.53608247, 75 ], "colors": [ "#959052", "#979356", "#9c9a60", "#a09c64", "#a3a067", "#a8a570", "#aaa875", "#acac79", "#b1b182", "#b5b587", "#b6b88c", "#bcbd93", "#bfc199", "#c1c39c", "#c1c39c", "#c6caa5", "#cacdaa", "#cccfaf", "#cfd1b3", "#cccfb3", "#c8ccb3", "#c6c8b3", "#bfc3b3", "#bfc3b3", "#bcc1b3", "#b8bdb3", "#b5bab3", "#afb5b3", "#acb3b3", "#aaafb3", "#a7aeb3", "#a3aab3", "#a0a8b3", "#9ca5b3", "#9aa1b3", "#97a0b3", "#939cb3", "#909ab3", "#939ab5", "#8c95b3", "#8791b1", "#838eb1", "#808caf", "#7c89af", "#7785ae", "#7482ac", "#7080aa", "#6b7caa", "#6275a8", "#5e72a7", "#5e72a7", "#5b70a5", "#566da3", "#5269a3", "#4f67a1", "#4b64a1", "#4660a0", "#425d9e", "#4260a1", "#4467a5", "#486eaa", "#4975ae", "#4d7cb1", "#4f83b5", "#508aba", "#5491bf", "#5699c3", "#599ec6", "#5ba5ca", "#5daccf", "#60b3d4", "#62bad8", "#64c1db", "#67c8df", "#69cfe4", "#6ed6e8", "#67d6d6", "#60d6c4", "#59d6b3", "#52d6a1", "#4bd690", "#42d67e", "#3bd66d", "#34d65b", "#11d418", "#11d116", "#0fcd16", "#0fc816", "#0fc316", "#0fbf15", "#0fbc15", "#0fb613", "#0eb313", "#0eaf13", "#0eaa13", "#0ca511", "#0ca111", "#0c9e11", "#0c9911", "#0c950f", "#0c900f", "#0a8c0f", "#0a870f", "#0a830e", "#0a800e", "#0a7c0c", "#0a770c", "#08720c", "#086e0c", "#086b0a", "#08660a", "#08620a", "#085d08", "#1c6708", "#317208", "#467c08", "#5b8707", "#6e9107", "#839c05", "#97a805", "#acb105", "#c1bc05", "#d6c603", "#e9d103", "#ffe200", "#ffd800", "#ffd300", "#ffc800", "#ffc300", "#ffba00", "#ffb500", "#ffb000", "#ffac00", "#ffa700", "#ff9e00", "#ff9900", "#ff9300", "#ff8900", "#ff8500", "#ff7f00", "#ff0000", "#f70000", "#f00000", "#e90000", "#e20000", "#db0000", "#d40000", "#cd0000", "#c60000", "#bf0000", "#b80000", "#b10000", "#aa0000", "#a30000", "#9a0000", "#930000", "#8c0000", "#850000", "#7e0000", "#770000", "#700000", "#ffffff", "#fff4ff", "#ffe9ff", "#ffdfff", "#ffd4ff", "#ffc8ff", "#ffbdff", "#ffb3ff", "#ffa8ff", "#ff9cff", "#ff91ff", "#ff75ff", "#fb6bfd", "#f960f9", "#f656f6", "#f24bf4", "#ef3ff0", "#ed36ef", "#e92aeb", "#e61fe8", "#e416e6", "#e10ae2", "#b100ff", "#ac00fb", "#a300f6", "#9a00f4", "#9300ef", "#8700e9", "#8200e8", "#7900e2", "#7200dd", "#6900db", "#6200d6" ] }
8
7
  import { Float16Array } from "@petamoriken/float16";
9
- function isColor(obj) {
10
- return (typeof obj == 'object') && 'color' in obj && 'opacity' in obj;
11
- }
8
+ import { WGLTexture } from "autumn-wgl";
9
+ import { getGLFormatTypeAlignment } from "./PlotComponent";
10
+ import { Color } from "./Color";
11
+ const colormap_shader_src = `
12
+ uniform sampler2D u_cmap_sampler;
13
+ uniform sampler2D u_cmap_nonlin_sampler;
14
+ uniform highp float u_cmap_min;
15
+ uniform highp float u_cmap_max;
16
+ uniform highp vec4 u_underflow_color;
17
+ uniform highp vec4 u_overflow_color;
18
+ uniform int u_n_index;
19
+
20
+ lowp vec4 apply_colormap(highp float value) {
21
+ lowp float normed_val = (value - u_cmap_min) / (u_cmap_max - u_cmap_min);
22
+
23
+ lowp vec4 color;
24
+ if (normed_val < 0.0) {
25
+ color = u_underflow_color;
26
+ }
27
+ else if (normed_val > 1.0) {
28
+ color = u_overflow_color;
29
+ }
30
+ else {
31
+ lowp float index_buffer = 1. / (2. * float(u_n_index));
32
+ normed_val = index_buffer + normed_val * (1. - 2. * index_buffer);
33
+ highp float nonlin_val = texture2D(u_cmap_nonlin_sampler, vec2(normed_val, 0.5)).r;
34
+ color = texture2D(u_cmap_sampler, vec2(nonlin_val, 0.5));
35
+ }
36
+
37
+ return color;
38
+ }`
12
39
  /** A mapping from values to colors */
13
40
  class ColorMap {
14
41
  /**
@@ -21,7 +48,7 @@ class ColorMap {
21
48
  if (levels.length != colors.length + 1) {
22
49
  throw `Mismatch between number of levels (${levels.length}) and number of colors (${colors.length}; expected ${levels.length - 1})`;
23
50
  }
24
- const normalizeColor = (c) => isColor(c) ? c : { 'color': c, 'opacity': 1. };
51
+ const normalizeColor = (c) => c instanceof Color ? c : Color.fromHex(c);
25
52
  this.levels = levels;
26
53
  this.colors = colors.map(c => normalizeColor(c));
27
54
  opts = opts === undefined ? {} : opts;
@@ -32,13 +59,13 @@ class ColorMap {
32
59
  * @returns an array of hex color strings
33
60
  */
34
61
  getColors() {
35
- return this.colors.map(s => s['color']);
62
+ return this.colors.map(s => s.toRGBHex());
36
63
  }
37
64
  /**
38
65
  * @returns an array of opacities, one for each color in the color map
39
66
  */
40
67
  getOpacities() {
41
- return this.colors.map(s => s['opacity']);
68
+ return this.colors.map(s => s.a);
42
69
  }
43
70
  /**
44
71
  * Make a new color map with different opacities. The opacities are set by func.
@@ -54,7 +81,7 @@ class ColorMap {
54
81
  const level_lower = this.levels[ic];
55
82
  const level_upper = this.levels[ic + 1];
56
83
  const new_opacity = func(level_lower, level_upper);
57
- const new_color = { color: color.color, opacity: new_opacity };
84
+ const new_color = color.withOpacity(new_opacity);
58
85
  if (new_opacity > 0) {
59
86
  if (new_levels[new_levels.length - 1] != level_lower)
60
87
  new_levels.push(level_lower);
@@ -65,13 +92,13 @@ class ColorMap {
65
92
  if (this.underflow_color !== null) {
66
93
  const underflow_opacity = func(this.levels[0], this.levels[0]);
67
94
  if (underflow_opacity > 0) {
68
- opts.underflow_color = { color: this.underflow_color.color, opacity: underflow_opacity };
95
+ opts.underflow_color = this.underflow_color.withOpacity(underflow_opacity);
69
96
  }
70
97
  }
71
98
  if (this.overflow_color !== null) {
72
99
  const overflow_opacity = func(this.levels[this.levels.length - 1], this.levels[this.levels.length - 1]);
73
100
  if (overflow_opacity > 0) {
74
- opts.overflow_color = { color: this.overflow_color.color, opacity: overflow_opacity };
101
+ opts.overflow_color = this.overflow_color.withOpacity(overflow_opacity);
75
102
  }
76
103
  }
77
104
  return new ColorMap(new_levels, new_colors, opts);
@@ -91,30 +118,30 @@ class ColorMap {
91
118
  const level_step = (level_max - level_min) / (n_colors - 1);
92
119
  const crossover = (level_max + level_min) / 2;
93
120
  const crossover_hsv = [0, 0, 0.9];
94
- const color1_hsv = rgb2hsv(hex2rgb(color1));
95
- const color2_hsv = rgb2hsv(hex2rgb(color2));
121
+ const color1_hsv = Color.fromHex(color1).toHSVTuple();
122
+ const color2_hsv = Color.fromHex(color2).toHSVTuple();
123
+ const interp_fac_power = 1.5;
96
124
  for (let istop = 0; istop < n_colors; istop++) {
97
125
  const level = level_min + istop * level_step;
98
126
  let h, s, v;
99
127
  let interp_fac;
100
128
  if (level < crossover) {
101
- interp_fac = (crossover - level) / (crossover - level_min);
129
+ interp_fac = Math.pow((crossover - level) / (crossover - level_min), interp_fac_power);
102
130
  [h, s, v] = [
103
131
  color1_hsv[0],
104
132
  crossover_hsv[1] + (color1_hsv[1] - crossover_hsv[1]) * interp_fac,
105
133
  crossover_hsv[2] + (color1_hsv[2] - crossover_hsv[2]) * interp_fac
106
134
  ];
107
135
  }
108
- else if (level >= crossover) {
109
- interp_fac = (level - crossover) / (level_max - crossover);
136
+ else {
137
+ interp_fac = Math.pow((level - crossover) / (level_max - crossover), interp_fac_power);
110
138
  [h, s, v] = [
111
139
  color2_hsv[0],
112
140
  crossover_hsv[1] + (color2_hsv[1] - crossover_hsv[1]) * interp_fac,
113
141
  crossover_hsv[2] + (color2_hsv[2] - crossover_hsv[2]) * interp_fac
114
142
  ];
115
143
  }
116
- const color = rgb2hex(hsv2rgb([h, s, v]));
117
- stops.push({ 'color': color, 'opacity': Math.min(2 * interp_fac, 1) });
144
+ stops.push(Color.fromHSVTuple([h, s, v]).withOpacity(Math.min(2 * interp_fac, 1)));
118
145
  }
119
146
  for (let ilev = 0; ilev <= n_colors; ilev++) {
120
147
  const level_step = (level_max - level_min) / n_colors;
@@ -161,6 +188,40 @@ const redblue = (level_min, level_max, n_colors) => {
161
188
  const bluered = (level_min, level_max, n_colors) => {
162
189
  return ColorMap.diverging('#0000ff', '#ff0000', level_min, level_max, n_colors);
163
190
  };
191
+ const N_INDEX_MAP = 101;
192
+ class ColorMapGPUInterface {
193
+ constructor(colormap) {
194
+ this.colormap = colormap;
195
+ this.gl_elems = null;
196
+ }
197
+ static applyShader(shader_src) {
198
+ return colormap_shader_src + "\n" + shader_src;
199
+ }
200
+ setupShaderVariables(gl, mag_filter) {
201
+ const index_map = makeIndexMap(this.colormap);
202
+ const cmap_image = makeTextureImage(this.colormap);
203
+ const { format: format_nonlin, type: type_nonlin, row_alignment: row_alignment_nonlin } = getGLFormatTypeAlignment(gl, true);
204
+ const cmap_image_spec = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': cmap_image, 'mag_filter': mag_filter };
205
+ const cmap_texture = new WGLTexture(gl, cmap_image_spec);
206
+ const cmap_nonlin_image = { 'format': format_nonlin, 'type': type_nonlin,
207
+ 'width': index_map.length, 'height': 1,
208
+ 'image': new Uint16Array(index_map.buffer),
209
+ 'mag_filter': gl.LINEAR, 'row_alignment': row_alignment_nonlin,
210
+ };
211
+ const cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
212
+ this.gl_elems = { cmap_texture: cmap_texture, cmap_nonlin_texture: cmap_nonlin_texture };
213
+ }
214
+ bindShaderVariables(program) {
215
+ if (this.gl_elems === null)
216
+ return;
217
+ const cmap = this.colormap;
218
+ const underflow_color = cmap.underflow_color === null ? [0, 0, 0, 0] : cmap.underflow_color.toRGBATuple();
219
+ const overflow_color = cmap.overflow_color === null ? [0, 0, 0, 0] : cmap.overflow_color.toRGBATuple();
220
+ program.setUniforms({ 'u_cmap_min': cmap.levels[0], 'u_cmap_max': cmap.levels[cmap.levels.length - 1],
221
+ 'u_n_index': N_INDEX_MAP, 'u_underflow_color': underflow_color, 'u_overflow_color': overflow_color });
222
+ program.bindTextures({ 'u_cmap_sampler': this.gl_elems.cmap_texture, 'u_cmap_nonlin_sampler': this.gl_elems.cmap_nonlin_texture });
223
+ }
224
+ }
164
225
  /**
165
226
  * Make a canvas image corresponding to a color map
166
227
  * @param colormap - The color map to use
@@ -175,7 +236,7 @@ function makeTextureImage(colormap) {
175
236
  if (ctx === null) {
176
237
  throw "Could not get rendering context for colormap image canvas";
177
238
  }
178
- ctx.fillStyle = stop['color'] + Math.round(stop['opacity'] * 255).toString(16);
239
+ ctx.fillStyle = stop.toRGBAHex();
179
240
  ctx.fillRect(istop, 0, 1, 1);
180
241
  });
181
242
  return cmap_image;
@@ -183,7 +244,7 @@ function makeTextureImage(colormap) {
183
244
  function makeIndexMap(colormap) {
184
245
  // Build a texture to account for nonlinear colormaps (basically inverts the relationship between
185
246
  // the normalized index and the normalized level)
186
- const n_nonlin = 101;
247
+ const n_nonlin = N_INDEX_MAP;
187
248
  const map_norm = [];
188
249
  for (let i = 0; i < n_nonlin; i++) {
189
250
  map_norm.push(i / (n_nonlin - 1));
@@ -200,4 +261,4 @@ function makeIndexMap(colormap) {
200
261
  });
201
262
  return new Float16Array(inv_cmap_norm);
202
263
  }
203
- export { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, nws_storm_clear_refl, makeTextureImage, makeIndexMap };
264
+ export { ColorMap, ColorMapGPUInterface, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, nws_storm_clear_refl };
package/lib/Contour.d.ts CHANGED
@@ -2,22 +2,43 @@ import { TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
2
2
  import { MapLikeType } from './Map';
3
3
  import { PlotComponent } from './PlotComponent';
4
4
  import { RawScalarField } from './RawField';
5
+ import { LineStyle } from './PolylineCollection';
6
+ import { ColorMap } from './Colormap';
5
7
  interface ContourOptions {
6
8
  /**
7
9
  * The color of the contours as a hex color string
8
10
  * @default '#000000'
9
11
  */
10
12
  color?: string;
13
+ /**
14
+ * A color map to use to color the contours. Specifying a colormap overrides the color option.
15
+ * @default null
16
+ */
17
+ cmap?: ColorMap | null;
11
18
  /**
12
19
  * The contour interval for drawing contours at regular intervals
13
20
  * @default 1
14
21
  */
15
22
  interval?: number;
16
23
  /**
17
- * A list of arbitrary levels (up to 40) to contour. This overrides the `interval` option.
18
- * @default Draw contours at regular intervals given by the `interval` option.
24
+ * A list of arbitrary levels to contour. This overrides the `interval` option.
25
+ * @default null
26
+ */
27
+ levels?: number[] | null;
28
+ /**
29
+ * The width of the line in pixels. This could be either a number or a function that takes a contour level as a number and returns a line width. This
30
+ * can be used to vary the width of the contours by value.
31
+ * @example level => level >= 100 ? 3 : 1.5
32
+ * @default 2
33
+ */
34
+ line_width?: number | ((level: number) => number);
35
+ /**
36
+ * The style to use for the line. This can be either a LineStyle or a function that takes a contour level as a number and returns a LineStyle. This
37
+ * can be used to vary the contours by value.
38
+ * @example level => level < 0 ? '--' : '-'
39
+ * @default '-'
19
40
  */
20
- levels?: number[];
41
+ line_style?: LineStyle | ((level: number) => LineStyle);
21
42
  }
22
43
  /**
23
44
  * A field of contoured data.
package/lib/Contour.js CHANGED
@@ -1,13 +1,17 @@
1
1
  import { LngLat } from './Map';
2
2
  import { PlotComponent } from './PlotComponent';
3
- import { PolylineCollection } from './PolylineCollection';
3
+ import { PolylineCollection, isLineStyle } from './PolylineCollection';
4
4
  import { TextCollection } from './TextCollection';
5
- import { hex2rgb, normalizeOptions } from './utils';
5
+ import { Color } from './Color';
6
+ import { normalizeOptions } from './utils';
6
7
  import { kdTree } from 'kd-tree-javascript';
7
8
  const contour_opt_defaults = {
8
9
  color: '#000000',
10
+ cmap: null,
9
11
  interval: 1,
10
- levels: undefined
12
+ levels: null,
13
+ line_width: 2,
14
+ line_style: '-'
11
15
  };
12
16
  /**
13
17
  * A field of contoured data.
@@ -39,14 +43,51 @@ class Contour extends PlotComponent {
39
43
  return;
40
44
  const gl = this.gl_elems.gl;
41
45
  const contour_data = await this.getContours();
42
- const line_data = Object.values(contour_data).flat().map(c => {
43
- return { vertices: c };
46
+ const line_data = [];
47
+ // Make contour data and sort them by line width and line style
48
+ Object.entries(contour_data).forEach(([cv, cd]) => {
49
+ const cv_ = parseFloat(cv);
50
+ const contour_style = isLineStyle(this.opts.line_style) ? this.opts.line_style : this.opts.line_style(cv_);
51
+ const contour_width = typeof this.opts.line_width === 'number' ? this.opts.line_width : this.opts.line_width(cv_);
52
+ const polyline_data = cd.map(c => {
53
+ const ld = { vertices: c };
54
+ if (this.opts.cmap !== null) {
55
+ ld.data = c.map(() => cv_);
56
+ }
57
+ return ld;
58
+ });
59
+ const line_data_filtered = line_data.filter(ld => ld.line_style == contour_style && ld.line_width == contour_width);
60
+ let contour_line_data;
61
+ if (line_data_filtered.length == 0) {
62
+ contour_line_data = { data: [], line_width: contour_width, line_style: contour_style };
63
+ line_data.push(contour_line_data);
64
+ }
65
+ else {
66
+ contour_line_data = line_data_filtered[0];
67
+ }
68
+ contour_line_data.data = contour_line_data.data.concat(polyline_data);
69
+ });
70
+ // Make one PolylineCollection for each combination of line width and line style
71
+ const promises = line_data.map(async (ld) => {
72
+ const plc_opts = { line_width: ld.line_width, line_style: ld.line_style };
73
+ if (this.opts.cmap !== null) {
74
+ plc_opts.cmap = this.opts.cmap;
75
+ }
76
+ else {
77
+ plc_opts.color = this.opts.color;
78
+ }
79
+ return await PolylineCollection.make(gl, ld.data, plc_opts);
80
+ });
81
+ Promise.all(promises).then(values => {
82
+ if (this.gl_elems === null)
83
+ return;
84
+ this.contours = values;
85
+ this.gl_elems.map.triggerRepaint();
44
86
  });
45
- this.contours = await PolylineCollection.make(gl, line_data, { line_width: 2, color: this.opts.color });
46
- this.gl_elems.map.triggerRepaint();
47
87
  }
48
88
  async getContours() {
49
- return await this.field.getContours({ interval: this.opts.interval, levels: this.opts.levels });
89
+ const levels = this.opts.levels === null ? undefined : this.opts.levels;
90
+ return await this.field.getContours({ interval: this.opts.interval, levels: levels });
50
91
  }
51
92
  /**
52
93
  * @internal
@@ -73,7 +114,7 @@ class Contour extends PlotComponent {
73
114
  const map_height = gl_elems.map.getCanvas().height;
74
115
  const bearing = gl_elems.map.getBearing();
75
116
  const pitch = gl_elems.map.getPitch();
76
- this.contours.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
117
+ this.contours.forEach(cnt => cnt.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch));
77
118
  }
78
119
  }
79
120
  const contour_label_opt_defaults = {
@@ -103,6 +144,8 @@ class ContourLabels extends PlotComponent {
103
144
  const gl = this.gl_elems.gl;
104
145
  const map_style = map.getStyle();
105
146
  const font_url_template = this.opts.font_url_template == '' ? map_style.glyphs : this.opts.font_url_template;
147
+ if (font_url_template === undefined)
148
+ throw "The map style doesn't have any glyph information. Please pass the font_url_template option to ContourLabels";
106
149
  const font_url = font_url_template.replace('{range}', '0-255').replace('{fontstack}', this.opts.font_face);
107
150
  const label_pos = [];
108
151
  const contour_data = await this.contours.getContours();
@@ -154,6 +197,9 @@ class ContourLabels extends PlotComponent {
154
197
  });
155
198
  });
156
199
  const tree = new kdTree(label_pos, (a, b) => Math.hypot(a.lon - b.lon, a.lat - b.lat), ['lon', 'lat']);
200
+ if (min_label_lon === null || min_label_lat === null || max_label_lon === null || max_label_lat === null) {
201
+ return;
202
+ }
157
203
  const { x: min_label_x, y: max_label_y } = new LngLat(min_label_lon, min_label_lat).toMercatorCoord();
158
204
  const { x: max_label_x, y: min_label_y } = new LngLat(max_label_lon, max_label_lat).toMercatorCoord();
159
205
  const thin_grid_width = max_label_x - min_label_x;
@@ -184,7 +230,7 @@ class ContourLabels extends PlotComponent {
184
230
  const tc_opts = {
185
231
  horizontal_align: 'center', vertical_align: 'middle', font_size: this.opts.font_size,
186
232
  halo: this.opts.halo,
187
- text_color: hex2rgb(this.opts.text_color), halo_color: hex2rgb(this.opts.halo_color),
233
+ text_color: Color.fromHex(this.opts.text_color), halo_color: Color.fromHex(this.opts.halo_color),
188
234
  };
189
235
  this.text_collection = await TextCollection.make(gl, label_pos, font_url, tc_opts);
190
236
  map.triggerRepaint();
package/lib/Fill.d.ts CHANGED
@@ -24,8 +24,7 @@ interface RasterOptions {
24
24
  declare class PlotComponentFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
25
25
  private field;
26
26
  readonly opts: Required<ContourFillOptions>;
27
- private readonly cmap_image;
28
- private readonly index_map;
27
+ private readonly cmap_gpu;
29
28
  private gl_elems;
30
29
  private fill_texture;
31
30
  protected image_mag_filter: number | null;
package/lib/Fill.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
2
- import { ColorMap, makeIndexMap, makeTextureImage } from './Colormap';
2
+ import { ColorMap, ColorMapGPUInterface } from './Colormap';
3
3
  import { WGLProgram, WGLTexture } from 'autumn-wgl';
4
- import { hex2rgb, normalizeOptions } from './utils';
4
+ import { normalizeOptions } from './utils';
5
5
  const contourfill_vertex_shader_src = `uniform mat4 u_matrix;
6
6
  uniform int u_offset;
7
7
 
@@ -20,33 +20,20 @@ void main() {
20
20
  const contourfill_fragment_shader_src = `varying highp vec2 v_tex_coord;
21
21
 
22
22
  uniform sampler2D u_fill_sampler;
23
- uniform sampler2D u_cmap_sampler;
24
- uniform sampler2D u_cmap_nonlin_sampler;
25
- uniform highp float u_cmap_min;
26
- uniform highp float u_cmap_max;
27
23
  uniform highp float u_opacity;
28
- uniform highp vec4 u_underflow_color;
29
- uniform highp vec4 u_overflow_color;
30
- uniform int u_n_index;
24
+
25
+ bool isnan(highp float val) {
26
+ return ( val < 0.0 || 0.0 < val || val == 0.0 ) ? false : true;
27
+ }
31
28
 
32
29
  void main() {
33
- lowp float index_buffer = 1. / (2. * float(u_n_index));
34
30
  highp float fill_val = texture2D(u_fill_sampler, v_tex_coord).r;
35
- lowp float normed_val = (fill_val - u_cmap_min) / (u_cmap_max - u_cmap_min);
36
-
37
- lowp vec4 color;
38
- if (normed_val < 0.0) {
39
- color = u_underflow_color;
40
- }
41
- else if (normed_val > 1.0) {
42
- color = u_overflow_color;
43
- }
44
- else {
45
- normed_val = index_buffer + normed_val * (1. - 2. * index_buffer);
46
- highp float nonlin_val = texture2D(u_cmap_nonlin_sampler, vec2(normed_val, 0.5)).r;
47
- color = texture2D(u_cmap_sampler, vec2(nonlin_val, 0.5));
31
+
32
+ if (isnan(fill_val)) {
33
+ discard;
48
34
  }
49
35
 
36
+ lowp vec4 color = apply_colormap(fill_val);
50
37
  color.a = color.a * u_opacity;
51
38
  gl_FragColor = color;
52
39
  }`
@@ -64,8 +51,7 @@ class PlotComponentFill extends PlotComponent {
64
51
  super();
65
52
  this.field = field;
66
53
  this.opts = normalizeOptions(opts, contour_fill_opt_defaults);
67
- this.cmap_image = makeTextureImage(this.opts.cmap);
68
- this.index_map = makeIndexMap(this.opts.cmap);
54
+ this.cmap_gpu = new ColorMapGPUInterface(this.opts.cmap);
69
55
  this.gl_elems = null;
70
56
  this.fill_texture = null;
71
57
  this.image_mag_filter = null;
@@ -73,6 +59,9 @@ class PlotComponentFill extends PlotComponent {
73
59
  }
74
60
  async updateField(field) {
75
61
  this.field = field;
62
+ if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
63
+ throw `Implement magnification filtes in a subclass`;
64
+ }
76
65
  if (this.gl_elems === null)
77
66
  return;
78
67
  const gl = this.gl_elems.gl;
@@ -96,22 +85,11 @@ class PlotComponentFill extends PlotComponent {
96
85
  if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
97
86
  throw `Implement magnification filtes in a subclass`;
98
87
  }
99
- const program = new WGLProgram(gl, contourfill_vertex_shader_src, contourfill_fragment_shader_src);
100
- const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
101
- const vertices = verts_buf;
102
- const texcoords = tex_coords_buf;
103
- const cmap_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.cmap_image, 'mag_filter': this.cmap_mag_filter };
104
- const cmap_texture = new WGLTexture(gl, cmap_image);
105
- const { format: format_nonlin, type: type_nonlin, row_alignment: row_alignment_nonlin } = getGLFormatTypeAlignment(gl, true);
106
- const cmap_nonlin_image = { 'format': format_nonlin, 'type': type_nonlin,
107
- 'width': this.index_map.length, 'height': 1,
108
- 'image': new Uint16Array(this.index_map.buffer),
109
- 'mag_filter': gl.LINEAR, 'row_alignment': row_alignment_nonlin,
110
- };
111
- const cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
88
+ const program = new WGLProgram(gl, contourfill_vertex_shader_src, ColorMapGPUInterface.applyShader(contourfill_fragment_shader_src));
89
+ const { vertices: vertices, texcoords: texcoords } = await this.field.grid.getWGLBuffers(gl);
90
+ this.cmap_gpu.setupShaderVariables(gl, this.cmap_mag_filter);
112
91
  this.gl_elems = {
113
92
  gl: gl, map: map, program: program, vertices: vertices, texcoords: texcoords,
114
- cmap_texture: cmap_texture, cmap_nonlin_texture: cmap_nonlin_texture,
115
93
  };
116
94
  this.updateField(this.field);
117
95
  }
@@ -121,11 +99,8 @@ class PlotComponentFill extends PlotComponent {
121
99
  const gl_elems = this.gl_elems;
122
100
  if (matrix instanceof Float32Array)
123
101
  matrix = [...matrix];
124
- const cmap = this.opts.cmap;
125
- const underflow_color = cmap.underflow_color === null ? [0, 0, 0, 0] : hex2rgb(cmap.underflow_color.color).concat(cmap.underflow_color.opacity);
126
- const overflow_color = cmap.overflow_color === null ? [0, 0, 0, 0] : hex2rgb(cmap.overflow_color.color).concat(cmap.overflow_color.opacity);
127
- gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_cmap_min': cmap.levels[0], 'u_cmap_max': cmap.levels[cmap.levels.length - 1], 'u_matrix': matrix, 'u_opacity': this.opts.opacity,
128
- 'u_n_index': this.index_map.length, 'u_underflow_color': underflow_color, 'u_overflow_color': overflow_color, 'u_offset': 0 }, { 'u_fill_sampler': this.fill_texture, 'u_cmap_sampler': gl_elems.cmap_texture, 'u_cmap_nonlin_sampler': gl_elems.cmap_nonlin_texture });
102
+ gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_matrix': matrix, 'u_opacity': this.opts.opacity, 'u_offset': 0 }, { 'u_fill_sampler': this.fill_texture });
103
+ this.cmap_gpu.bindShaderVariables(gl_elems.program);
129
104
  gl.enable(gl.BLEND);
130
105
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
131
106
  gl_elems.program.draw();
package/lib/Grid.d.ts CHANGED
@@ -145,6 +145,7 @@ declare class LambertGrid extends Grid {
145
145
  * @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
146
146
  */
147
147
  constructor(ni: number, nj: number, lon_0: number, lat_0: number, lat_std: [number, number], ll_x: number, ll_y: number, ur_x: number, ur_y: number);
148
+ static fromLLCornerLonLat(ni: number, nj: number, lon_0: number, lat_0: number, lat_std: [number, number], ll_lon: number, ll_lat: number, dx: number, dy: number): LambertGrid;
148
149
  copy(opts?: {
149
150
  ni?: number;
150
151
  nj?: number;
package/lib/Grid.js CHANGED
@@ -298,6 +298,11 @@ class LambertGrid extends Grid {
298
298
  return { x: x, y: y };
299
299
  });
300
300
  }
301
+ static fromLLCornerLonLat(ni, nj, lon_0, lat_0, lat_std, ll_lon, ll_lat, dx, dy) {
302
+ const lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
303
+ const [ll_x, ll_y] = lcc(ll_lon, ll_lat);
304
+ return new LambertGrid(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ll_x + ni * dx, ll_y + nj * dy);
305
+ }
301
306
  copy(opts) {
302
307
  opts = opts !== undefined ? opts : {};
303
308
  const ni = opts.ni !== undefined ? opts.ni : this.ni;
@@ -319,7 +324,7 @@ class LambertGrid extends Grid {
319
324
  }
320
325
  transform(x, y, opts) {
321
326
  opts = opts === undefined ? {} : opts;
322
- const inverse = 'inverse' in opts ? opts.inverse : false;
327
+ const inverse = opts.inverse === undefined ? false : opts.inverse;
323
328
  return this.lcc(x, y, { inverse: inverse });
324
329
  }
325
330
  getThinnedGrid(thin_x, thin_y) {
@@ -2,9 +2,11 @@ import { PlotComponent } from "./PlotComponent";
2
2
  import { MapLikeType } from "./Map";
3
3
  import { RawProfileField } from "./RawField";
4
4
  import { WebGLAnyRenderingContext } from "./AutumnTypes";
5
+ import { ColorMap } from "./Colormap";
5
6
  interface HodographOptions {
6
7
  /**
7
8
  * The color of the hodograph plot background as a hex string
9
+ * @default '#000000'
8
10
  */
9
11
  bgcolor?: string;
10
12
  /**
@@ -13,13 +15,28 @@ interface HodographOptions {
13
15
  * @default 1
14
16
  */
15
17
  thin_fac?: number;
18
+ /**
19
+ * The width of the hodograph line in pixels
20
+ * @default 2.5
21
+ */
22
+ hodo_line_width: number;
23
+ /**
24
+ * The width of the lines on the background in pixels
25
+ * @default 1.5
26
+ */
27
+ background_line_width: number;
28
+ /**
29
+ * The colormap to use for the heights on the hodograph. Default is a yellow-blue colormap.
30
+ */
31
+ height_cmap: ColorMap;
16
32
  }
17
- /** A class representing a a field of hodograph plots */
33
+ /** A class representing a field of hodograph plots */
18
34
  declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapType> {
19
35
  private profile_field;
20
36
  readonly opts: Required<HodographOptions>;
21
37
  private gl_elems;
22
38
  private line_elems;
39
+ private hodo_bg_texture;
23
40
  private readonly hodo_scale;
24
41
  private readonly bg_size;
25
42
  /**