autumnplot-gl 2.2.2 → 3.0.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/README.md +65 -8
- package/dist/110.autumnplot-gl.js +2 -0
- package/dist/110.autumnplot-gl.js.map +1 -0
- package/dist/autumnplot-gl.js +3 -0
- package/dist/autumnplot-gl.js.LICENSE.txt +12 -0
- package/dist/autumnplot-gl.js.map +1 -0
- package/dist/marchingsquares.wasm +0 -0
- package/lib/AutumnTypes.d.ts +14 -13
- package/lib/Barbs.d.ts +8 -3
- package/lib/Barbs.js +14 -1
- package/lib/BillboardCollection.d.ts +11 -7
- package/lib/BillboardCollection.js +54 -31
- package/lib/ColorBar.js +51 -7
- package/lib/Colormap.d.ts +14 -3
- package/lib/Colormap.js +63 -12
- package/lib/Contour.d.ts +73 -16
- package/lib/Contour.js +175 -146
- package/lib/ContourCreator.d.ts +18 -0
- package/lib/ContourCreator.js +59 -0
- package/lib/Fill.d.ts +17 -5
- package/lib/Fill.js +60 -37
- package/lib/Grid.d.ts +167 -0
- package/lib/Grid.js +339 -0
- package/lib/Hodographs.d.ts +13 -5
- package/lib/Hodographs.js +52 -67
- package/lib/Map.d.ts +14 -3
- package/lib/Map.js +9 -0
- package/lib/Paintball.d.ts +9 -3
- package/lib/Paintball.js +28 -10
- package/lib/PlotComponent.d.ts +5 -5
- package/lib/PlotLayer.d.ts +12 -12
- package/lib/PlotLayer.js +16 -14
- package/lib/PlotLayer.worker.d.ts +2 -2
- package/lib/PlotLayer.worker.js +102 -66
- package/lib/PolylineCollection.d.ts +20 -9
- package/lib/PolylineCollection.js +158 -32
- package/lib/RawField.d.ts +11 -167
- package/lib/RawField.js +37 -383
- package/lib/TextCollection.d.ts +31 -0
- package/lib/TextCollection.js +295 -0
- package/lib/cpp/marchingsquares.d.ts +6 -0
- package/lib/cpp/marchingsquares.js +4152 -0
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +47 -0
- package/lib/index.d.ts +13 -6
- package/lib/index.js +12 -3
- package/lib/utils.d.ts +5 -3
- package/lib/utils.js +17 -6
- package/package.json +14 -9
package/lib/Fill.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
|
|
2
|
-
import { makeTextureImage } from './Colormap';
|
|
2
|
+
import { makeIndexMap, makeTextureImage } from './Colormap';
|
|
3
3
|
import { WGLProgram, WGLTexture } from 'autumn-wgl';
|
|
4
|
-
import {
|
|
4
|
+
import { hex2rgb } from './utils';
|
|
5
5
|
const contourfill_vertex_shader_src = `uniform mat4 u_matrix;
|
|
6
6
|
uniform int u_offset;
|
|
7
7
|
|
|
@@ -25,6 +25,8 @@ uniform sampler2D u_cmap_nonlin_sampler;
|
|
|
25
25
|
uniform highp float u_cmap_min;
|
|
26
26
|
uniform highp float u_cmap_max;
|
|
27
27
|
uniform highp float u_opacity;
|
|
28
|
+
uniform highp vec4 u_underflow_color;
|
|
29
|
+
uniform highp vec4 u_overflow_color;
|
|
28
30
|
uniform int u_n_index;
|
|
29
31
|
|
|
30
32
|
void main() {
|
|
@@ -32,13 +34,19 @@ void main() {
|
|
|
32
34
|
highp float fill_val = texture2D(u_fill_sampler, v_tex_coord).r;
|
|
33
35
|
lowp float normed_val = (fill_val - u_cmap_min) / (u_cmap_max - u_cmap_min);
|
|
34
36
|
|
|
35
|
-
|
|
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));
|
|
37
48
|
}
|
|
38
49
|
|
|
39
|
-
normed_val = index_buffer + normed_val * (1. - 2. * index_buffer);
|
|
40
|
-
highp float nonlin_val = texture2D(u_cmap_nonlin_sampler, vec2(normed_val, 0.5)).r;
|
|
41
|
-
lowp vec4 color = texture2D(u_cmap_sampler, vec2(nonlin_val, 0.5));
|
|
42
50
|
color.a = color.a * u_opacity;
|
|
43
51
|
gl_FragColor = color;
|
|
44
52
|
}`
|
|
@@ -49,28 +57,32 @@ class PlotComponentFill extends PlotComponent {
|
|
|
49
57
|
this.cmap = opts.cmap;
|
|
50
58
|
this.opacity = opts.opacity || 1.;
|
|
51
59
|
this.cmap_image = makeTextureImage(this.cmap);
|
|
52
|
-
|
|
53
|
-
const n_lev = levels.length - 1;
|
|
54
|
-
// Build a texture to account for nonlinear colormaps (basically inverts the relationship between
|
|
55
|
-
// the normalized index and the normalized level)
|
|
56
|
-
const n_nonlin = 101;
|
|
57
|
-
const map_norm = [];
|
|
58
|
-
for (let i = 0; i < n_nonlin; i++) {
|
|
59
|
-
map_norm.push(i / (n_nonlin - 1));
|
|
60
|
-
}
|
|
61
|
-
const input_norm = levels.map((lev, ilev) => ilev / n_lev);
|
|
62
|
-
const cmap_norm = levels.map(lev => (lev - levels[0]) / (levels[n_lev] - levels[0]));
|
|
63
|
-
const inv_cmap_norm = map_norm.map(lev => {
|
|
64
|
-
let jlev;
|
|
65
|
-
for (jlev = 0; !(cmap_norm[jlev] <= lev && lev <= cmap_norm[jlev + 1]); jlev++) { }
|
|
66
|
-
const alpha = (lev - cmap_norm[jlev]) / (cmap_norm[jlev + 1] - cmap_norm[jlev]);
|
|
67
|
-
return input_norm[jlev] * (1 - alpha) + input_norm[jlev + 1] * alpha;
|
|
68
|
-
});
|
|
69
|
-
this.index_map = new Float16Array(inv_cmap_norm);
|
|
60
|
+
this.index_map = makeIndexMap(this.cmap);
|
|
70
61
|
this.gl_elems = null;
|
|
62
|
+
this.fill_texture = null;
|
|
71
63
|
this.image_mag_filter = null;
|
|
72
64
|
this.cmap_mag_filter = null;
|
|
73
65
|
}
|
|
66
|
+
async updateField(field) {
|
|
67
|
+
this.field = field;
|
|
68
|
+
if (this.gl_elems === null)
|
|
69
|
+
return;
|
|
70
|
+
const gl = this.gl_elems.gl;
|
|
71
|
+
const map = this.gl_elems.map;
|
|
72
|
+
const tex_data = this.field.getTextureData();
|
|
73
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, !(tex_data instanceof Float32Array));
|
|
74
|
+
const fill_image = { 'format': format, 'type': type,
|
|
75
|
+
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': tex_data,
|
|
76
|
+
'mag_filter': this.image_mag_filter, 'row_alignment': row_alignment,
|
|
77
|
+
};
|
|
78
|
+
if (this.fill_texture === null) {
|
|
79
|
+
this.fill_texture = new WGLTexture(gl, fill_image);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.fill_texture.setImageData(fill_image);
|
|
83
|
+
}
|
|
84
|
+
map.triggerRepaint();
|
|
85
|
+
}
|
|
74
86
|
async onAdd(map, gl) {
|
|
75
87
|
// Basic procedure for the filled contours inspired by https://blog.mbq.me/webgl-weather-globe/
|
|
76
88
|
if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
|
|
@@ -80,12 +92,6 @@ class PlotComponentFill extends PlotComponent {
|
|
|
80
92
|
const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
|
|
81
93
|
const vertices = verts_buf;
|
|
82
94
|
const texcoords = tex_coords_buf;
|
|
83
|
-
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, this.field.isFloat16());
|
|
84
|
-
const fill_image = { 'format': format, 'type': type,
|
|
85
|
-
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.getTextureData(),
|
|
86
|
-
'mag_filter': this.image_mag_filter, 'row_alignment': row_alignment,
|
|
87
|
-
};
|
|
88
|
-
const fill_texture = new WGLTexture(gl, fill_image);
|
|
89
95
|
const cmap_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.cmap_image, 'mag_filter': this.cmap_mag_filter };
|
|
90
96
|
const cmap_texture = new WGLTexture(gl, cmap_image);
|
|
91
97
|
const { format: format_nonlin, type: type_nonlin, row_alignment: row_alignment_nonlin } = getGLFormatTypeAlignment(gl, true);
|
|
@@ -96,18 +102,21 @@ class PlotComponentFill extends PlotComponent {
|
|
|
96
102
|
};
|
|
97
103
|
const cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
|
|
98
104
|
this.gl_elems = {
|
|
99
|
-
program: program, vertices: vertices, texcoords: texcoords,
|
|
100
|
-
|
|
105
|
+
gl: gl, map: map, program: program, vertices: vertices, texcoords: texcoords,
|
|
106
|
+
cmap_texture: cmap_texture, cmap_nonlin_texture: cmap_nonlin_texture,
|
|
101
107
|
};
|
|
108
|
+
this.updateField(this.field);
|
|
102
109
|
}
|
|
103
110
|
render(gl, matrix) {
|
|
104
|
-
if (this.gl_elems === null)
|
|
111
|
+
if (this.gl_elems === null || this.fill_texture === null)
|
|
105
112
|
return;
|
|
106
113
|
const gl_elems = this.gl_elems;
|
|
107
114
|
if (matrix instanceof Float32Array)
|
|
108
115
|
matrix = [...matrix];
|
|
116
|
+
const underflow_color = this.cmap.underflow_color === null ? [0, 0, 0, 0] : hex2rgb(this.cmap.underflow_color.color).concat(this.cmap.underflow_color.opacity);
|
|
117
|
+
const overflow_color = this.cmap.overflow_color === null ? [0, 0, 0, 0] : hex2rgb(this.cmap.overflow_color.color).concat(this.cmap.overflow_color.opacity);
|
|
109
118
|
gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_cmap_min': this.cmap.levels[0], 'u_cmap_max': this.cmap.levels[this.cmap.levels.length - 1], 'u_matrix': matrix, 'u_opacity': this.opacity,
|
|
110
|
-
'u_n_index': this.index_map.length, 'u_offset': 0 }, { 'u_fill_sampler':
|
|
119
|
+
'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 });
|
|
111
120
|
gl.enable(gl.BLEND);
|
|
112
121
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
113
122
|
gl_elems.program.draw();
|
|
@@ -134,6 +143,13 @@ class Raster extends PlotComponentFill {
|
|
|
134
143
|
constructor(field, opts) {
|
|
135
144
|
super(field, opts);
|
|
136
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Update the data displayed as a raster plot
|
|
148
|
+
* @param field - The new field to display as a raster plot
|
|
149
|
+
*/
|
|
150
|
+
async updateField(field) {
|
|
151
|
+
await super.updateField(field);
|
|
152
|
+
}
|
|
137
153
|
/**
|
|
138
154
|
* @internal
|
|
139
155
|
* Add the raster plot to a map
|
|
@@ -141,7 +157,7 @@ class Raster extends PlotComponentFill {
|
|
|
141
157
|
async onAdd(map, gl) {
|
|
142
158
|
this.image_mag_filter = gl.NEAREST;
|
|
143
159
|
this.cmap_mag_filter = gl.LINEAR;
|
|
144
|
-
super.onAdd(map, gl);
|
|
160
|
+
await super.onAdd(map, gl);
|
|
145
161
|
}
|
|
146
162
|
/**
|
|
147
163
|
* @internal
|
|
@@ -166,6 +182,13 @@ class ContourFill extends PlotComponentFill {
|
|
|
166
182
|
constructor(field, opts) {
|
|
167
183
|
super(field, opts);
|
|
168
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Update the data displayed as filled contours
|
|
187
|
+
* @param field - The new field to display as filled contours
|
|
188
|
+
*/
|
|
189
|
+
async updateField(field) {
|
|
190
|
+
await super.updateField(field);
|
|
191
|
+
}
|
|
169
192
|
/**
|
|
170
193
|
* @internal
|
|
171
194
|
* Add the filled contours to a map
|
|
@@ -173,7 +196,7 @@ class ContourFill extends PlotComponentFill {
|
|
|
173
196
|
async onAdd(map, gl) {
|
|
174
197
|
this.image_mag_filter = gl.LINEAR;
|
|
175
198
|
this.cmap_mag_filter = gl.NEAREST;
|
|
176
|
-
super.onAdd(map, gl);
|
|
199
|
+
await super.onAdd(map, gl);
|
|
177
200
|
}
|
|
178
201
|
/**
|
|
179
202
|
* @internal
|
package/lib/Grid.d.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { WGLBuffer, WGLTexture } from "autumn-wgl";
|
|
2
|
+
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
3
|
+
interface EarthCoords {
|
|
4
|
+
lons: Float32Array;
|
|
5
|
+
lats: Float32Array;
|
|
6
|
+
}
|
|
7
|
+
interface GridCoords {
|
|
8
|
+
x: Float32Array;
|
|
9
|
+
y: Float32Array;
|
|
10
|
+
}
|
|
11
|
+
type GridType = 'latlon' | 'latlonrot' | 'lcc';
|
|
12
|
+
declare abstract class Grid {
|
|
13
|
+
readonly type: GridType;
|
|
14
|
+
readonly ni: number;
|
|
15
|
+
readonly nj: number;
|
|
16
|
+
readonly is_conformal: boolean;
|
|
17
|
+
private readonly buffer_cache;
|
|
18
|
+
private readonly billboard_buffer_cache;
|
|
19
|
+
private readonly vector_rotation_cache;
|
|
20
|
+
constructor(type: GridType, is_conformal: boolean, ni: number, nj: number);
|
|
21
|
+
abstract copy(opts?: {
|
|
22
|
+
ni?: number;
|
|
23
|
+
nj?: number;
|
|
24
|
+
}): Grid;
|
|
25
|
+
abstract getEarthCoords(): EarthCoords;
|
|
26
|
+
abstract getGridCoords(): GridCoords;
|
|
27
|
+
abstract transform(x: number, y: number, opts?: {
|
|
28
|
+
inverse?: boolean;
|
|
29
|
+
}): [number, number];
|
|
30
|
+
abstract getThinnedGrid(thin_x: number, thin_y: number): Grid;
|
|
31
|
+
getWGLBuffers(gl: WebGLAnyRenderingContext): Promise<{
|
|
32
|
+
vertices: WGLBuffer;
|
|
33
|
+
texcoords: WGLBuffer;
|
|
34
|
+
cellsize: WGLBuffer;
|
|
35
|
+
}>;
|
|
36
|
+
getWGLBillboardBuffers(gl: WebGLAnyRenderingContext, thin_fac: number, max_zoom: number): Promise<{
|
|
37
|
+
vertices: WGLBuffer;
|
|
38
|
+
texcoords: WGLBuffer;
|
|
39
|
+
}>;
|
|
40
|
+
getVectorRotationTexture(gl: WebGLAnyRenderingContext): {
|
|
41
|
+
rotation: WGLTexture;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** A plate carree (a.k.a. lat/lon) grid with uniform grid spacing */
|
|
45
|
+
declare class PlateCarreeGrid extends Grid {
|
|
46
|
+
readonly ll_lon: number;
|
|
47
|
+
readonly ll_lat: number;
|
|
48
|
+
readonly ur_lon: number;
|
|
49
|
+
readonly ur_lat: number;
|
|
50
|
+
private readonly ll_cache;
|
|
51
|
+
private readonly gc_cache;
|
|
52
|
+
/**
|
|
53
|
+
* Create a plate carree grid
|
|
54
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
55
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
56
|
+
* @param ll_lon - The longitude of the lower left corner of the grid
|
|
57
|
+
* @param ll_lat - The latitude of the lower left corner of the grid
|
|
58
|
+
* @param ur_lon - The longitude of the upper right corner of the grid
|
|
59
|
+
* @param ur_lat - The latitude of the upper right corner of the grid
|
|
60
|
+
*/
|
|
61
|
+
constructor(ni: number, nj: number, ll_lon: number, ll_lat: number, ur_lon: number, ur_lat: number);
|
|
62
|
+
copy(opts?: {
|
|
63
|
+
ni?: number;
|
|
64
|
+
nj?: number;
|
|
65
|
+
ll_lon?: number;
|
|
66
|
+
ll_lat?: number;
|
|
67
|
+
ur_lon?: number;
|
|
68
|
+
ur_lat?: number;
|
|
69
|
+
}): PlateCarreeGrid;
|
|
70
|
+
/**
|
|
71
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
72
|
+
*/
|
|
73
|
+
getEarthCoords(): EarthCoords;
|
|
74
|
+
getGridCoords(): GridCoords;
|
|
75
|
+
transform(x: number, y: number, opts?: {
|
|
76
|
+
inverse?: boolean;
|
|
77
|
+
}): [number, number];
|
|
78
|
+
getThinnedGrid(thin_x: number, thin_y: number): PlateCarreeGrid;
|
|
79
|
+
}
|
|
80
|
+
/** A rotated lat-lon (plate carree) grid with uniform grid spacing */
|
|
81
|
+
declare class PlateCarreeRotatedGrid extends Grid {
|
|
82
|
+
readonly np_lon: number;
|
|
83
|
+
readonly np_lat: number;
|
|
84
|
+
readonly lon_shift: number;
|
|
85
|
+
readonly ll_lon: number;
|
|
86
|
+
readonly ll_lat: number;
|
|
87
|
+
readonly ur_lon: number;
|
|
88
|
+
readonly ur_lat: number;
|
|
89
|
+
private readonly llrot;
|
|
90
|
+
private readonly ll_cache;
|
|
91
|
+
private readonly gc_cache;
|
|
92
|
+
/**
|
|
93
|
+
* Create a Lambert conformal conic grid
|
|
94
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
95
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
96
|
+
* @param np_lon - The longitude of the north pole for the rotated grid
|
|
97
|
+
* @param np_lat - The latitude of the north pole for the rotated grid
|
|
98
|
+
* @param lon_shift - The angle around the rotated north pole to shift the central meridian
|
|
99
|
+
* @param ll_lon - The longitude of the lower left corner of the grid (on the rotated earth)
|
|
100
|
+
* @param ll_lat - The latitude of the lower left corner of the grid (on the rotated earth)
|
|
101
|
+
* @param ur_lon - The longitude of the upper right corner of the grid (on the rotated earth)
|
|
102
|
+
* @param ur_lat - The latitude of the upper right corner of the grid (on the rotated earth)
|
|
103
|
+
*/
|
|
104
|
+
constructor(ni: number, nj: number, np_lon: number, np_lat: number, lon_shift: number, ll_lon: number, ll_lat: number, ur_lon: number, ur_lat: number);
|
|
105
|
+
copy(opts?: {
|
|
106
|
+
ni?: number;
|
|
107
|
+
nj?: number;
|
|
108
|
+
ll_lon?: number;
|
|
109
|
+
ll_lat?: number;
|
|
110
|
+
ur_lon?: number;
|
|
111
|
+
ur_lat?: number;
|
|
112
|
+
}): PlateCarreeRotatedGrid;
|
|
113
|
+
/**
|
|
114
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
115
|
+
*/
|
|
116
|
+
getEarthCoords(): EarthCoords;
|
|
117
|
+
getGridCoords(): GridCoords;
|
|
118
|
+
transform(x: number, y: number, opts?: {
|
|
119
|
+
inverse?: boolean;
|
|
120
|
+
}): [number, number];
|
|
121
|
+
getThinnedGrid(thin_x: number, thin_y: number): PlateCarreeRotatedGrid;
|
|
122
|
+
}
|
|
123
|
+
/** A Lambert conformal conic grid with uniform grid spacing */
|
|
124
|
+
declare class LambertGrid extends Grid {
|
|
125
|
+
readonly lon_0: number;
|
|
126
|
+
readonly lat_0: number;
|
|
127
|
+
readonly lat_std: [number, number];
|
|
128
|
+
readonly ll_x: number;
|
|
129
|
+
readonly ll_y: number;
|
|
130
|
+
readonly ur_x: number;
|
|
131
|
+
readonly ur_y: number;
|
|
132
|
+
private readonly lcc;
|
|
133
|
+
private readonly ll_cache;
|
|
134
|
+
private readonly gc_cache;
|
|
135
|
+
/**
|
|
136
|
+
* Create a Lambert conformal conic grid
|
|
137
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
138
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
139
|
+
* @param lon_0 - The standard longitude for the projection; this is also the center longitude for the projection
|
|
140
|
+
* @param lat_0 - The center latitude for the projection
|
|
141
|
+
* @param lat_std - The standard latitudes for the projection
|
|
142
|
+
* @param ll_x - The x coordinate in projection space of the lower-left corner of the grid
|
|
143
|
+
* @param ll_y - The y coordinate in projection space of the lower-left corner of the grid
|
|
144
|
+
* @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
|
|
145
|
+
* @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
|
|
146
|
+
*/
|
|
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
|
+
copy(opts?: {
|
|
149
|
+
ni?: number;
|
|
150
|
+
nj?: number;
|
|
151
|
+
ll_x?: number;
|
|
152
|
+
ll_y?: number;
|
|
153
|
+
ur_x?: number;
|
|
154
|
+
ur_y?: number;
|
|
155
|
+
}): LambertGrid;
|
|
156
|
+
/**
|
|
157
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
158
|
+
*/
|
|
159
|
+
getEarthCoords(): EarthCoords;
|
|
160
|
+
getGridCoords(): GridCoords;
|
|
161
|
+
transform(x: number, y: number, opts?: {
|
|
162
|
+
inverse?: boolean;
|
|
163
|
+
}): [number, number];
|
|
164
|
+
getThinnedGrid(thin_x: number, thin_y: number): LambertGrid;
|
|
165
|
+
}
|
|
166
|
+
export { Grid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid };
|
|
167
|
+
export type { GridType };
|
package/lib/Grid.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { Float16Array } from "@petamoriken/float16";
|
|
2
|
+
import { WGLBuffer, WGLTexture } from "autumn-wgl";
|
|
3
|
+
import { lambertConformalConic, rotateSphere } from "./Map";
|
|
4
|
+
import { getGLFormatTypeAlignment, layer_worker } from "./PlotComponent";
|
|
5
|
+
import { Cache } from "./utils";
|
|
6
|
+
async function makeWGLDomainBuffers(gl, grid, native_grid) {
|
|
7
|
+
native_grid = native_grid !== undefined ? native_grid : grid;
|
|
8
|
+
const texcoord_margin_r = 1 / (2 * native_grid.ni);
|
|
9
|
+
const texcoord_margin_s = 1 / (2 * native_grid.nj);
|
|
10
|
+
const grid_cell_size_multiplier = (grid.ni * grid.nj) / (native_grid.ni * native_grid.nj);
|
|
11
|
+
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
|
|
12
|
+
const domain_coords = await layer_worker.makeDomainVerticesAndTexCoords(field_lats, field_lons, grid.ni, grid.nj, texcoord_margin_r, texcoord_margin_s);
|
|
13
|
+
for (let icd = 0; icd < domain_coords['grid_cell_size'].length; icd++) {
|
|
14
|
+
domain_coords['grid_cell_size'][icd] *= grid_cell_size_multiplier;
|
|
15
|
+
}
|
|
16
|
+
const vertices = new WGLBuffer(gl, domain_coords['vertices'], 2, gl.TRIANGLE_STRIP);
|
|
17
|
+
const texcoords = new WGLBuffer(gl, domain_coords['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
18
|
+
const grid_cell_size = new WGLBuffer(gl, domain_coords['grid_cell_size'], 1, gl.TRIANGLE_STRIP);
|
|
19
|
+
return { 'vertices': vertices, 'texcoords': texcoords, 'cellsize': grid_cell_size };
|
|
20
|
+
}
|
|
21
|
+
async function makeWGLBillboardBuffers(gl, grid, thin_fac, max_zoom) {
|
|
22
|
+
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
|
|
23
|
+
const bb_elements = await layer_worker.makeBBElements(field_lats, field_lons, grid.ni, grid.nj, thin_fac, max_zoom);
|
|
24
|
+
const vertices = new WGLBuffer(gl, bb_elements['pts'], 3, gl.TRIANGLE_STRIP);
|
|
25
|
+
const texcoords = new WGLBuffer(gl, bb_elements['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
26
|
+
return { 'vertices': vertices, 'texcoords': texcoords };
|
|
27
|
+
}
|
|
28
|
+
function makeVectorRotationTexture(gl, grid) {
|
|
29
|
+
const coords = grid.getEarthCoords();
|
|
30
|
+
if (!grid.is_conformal) {
|
|
31
|
+
// If the grid is non-conformal, we need a fully general change of basis from grid coordinates to earth coordinates. This is not supported for now, so warn about it.
|
|
32
|
+
console.warn('Vector rotations for non-conformal projections are not supported. The output may look incorrect.');
|
|
33
|
+
}
|
|
34
|
+
const rot_vals = new Float16Array(coords.lats.length);
|
|
35
|
+
for (let icd = 0; icd < coords.lats.length; icd++) {
|
|
36
|
+
const lon = coords.lons[icd];
|
|
37
|
+
const lat = coords.lats[icd];
|
|
38
|
+
const [x, y] = grid.transform(lon, lat);
|
|
39
|
+
const [x_pertlon, y_pertlon] = grid.transform(lon + 0.01, lat);
|
|
40
|
+
rot_vals[icd] = Math.atan2(y_pertlon - y, x_pertlon - x);
|
|
41
|
+
}
|
|
42
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, true);
|
|
43
|
+
const rot_img = {
|
|
44
|
+
format: format, type: type, row_alignment: row_alignment, image: new Uint16Array(rot_vals.buffer),
|
|
45
|
+
width: grid.ni, height: grid.nj, mag_filter: gl.LINEAR
|
|
46
|
+
};
|
|
47
|
+
const rot_tex = new WGLTexture(gl, rot_img);
|
|
48
|
+
return { 'rotation': rot_tex };
|
|
49
|
+
}
|
|
50
|
+
class Grid {
|
|
51
|
+
constructor(type, is_conformal, ni, nj) {
|
|
52
|
+
this.type = type;
|
|
53
|
+
this.is_conformal = is_conformal;
|
|
54
|
+
this.ni = ni;
|
|
55
|
+
this.nj = nj;
|
|
56
|
+
this.buffer_cache = new Cache((gl) => {
|
|
57
|
+
const new_ni = Math.max(Math.floor(this.ni / 50), 20);
|
|
58
|
+
const new_nj = Math.max(Math.floor(this.nj / 50), 20);
|
|
59
|
+
return makeWGLDomainBuffers(gl, this.copy({ ni: new_ni, nj: new_nj }), this);
|
|
60
|
+
});
|
|
61
|
+
this.billboard_buffer_cache = new Cache((gl, thin_fac, max_zoom) => {
|
|
62
|
+
return makeWGLBillboardBuffers(gl, this, thin_fac, max_zoom);
|
|
63
|
+
});
|
|
64
|
+
this.vector_rotation_cache = new Cache((gl) => {
|
|
65
|
+
return makeVectorRotationTexture(gl, this);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async getWGLBuffers(gl) {
|
|
69
|
+
return await this.buffer_cache.getValue(gl);
|
|
70
|
+
}
|
|
71
|
+
async getWGLBillboardBuffers(gl, thin_fac, max_zoom) {
|
|
72
|
+
return await this.billboard_buffer_cache.getValue(gl, thin_fac, max_zoom);
|
|
73
|
+
}
|
|
74
|
+
getVectorRotationTexture(gl) {
|
|
75
|
+
return this.vector_rotation_cache.getValue(gl);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** A plate carree (a.k.a. lat/lon) grid with uniform grid spacing */
|
|
79
|
+
class PlateCarreeGrid extends Grid {
|
|
80
|
+
/**
|
|
81
|
+
* Create a plate carree grid
|
|
82
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
83
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
84
|
+
* @param ll_lon - The longitude of the lower left corner of the grid
|
|
85
|
+
* @param ll_lat - The latitude of the lower left corner of the grid
|
|
86
|
+
* @param ur_lon - The longitude of the upper right corner of the grid
|
|
87
|
+
* @param ur_lat - The latitude of the upper right corner of the grid
|
|
88
|
+
*/
|
|
89
|
+
constructor(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat) {
|
|
90
|
+
super('latlon', true, ni, nj);
|
|
91
|
+
this.ll_lon = ll_lon;
|
|
92
|
+
this.ll_lat = ll_lat;
|
|
93
|
+
this.ur_lon = ur_lon;
|
|
94
|
+
this.ur_lat = ur_lat;
|
|
95
|
+
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
96
|
+
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
97
|
+
this.ll_cache = new Cache(() => {
|
|
98
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
99
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
100
|
+
for (let i = 0; i < this.ni; i++) {
|
|
101
|
+
for (let j = 0; j < this.nj; j++) {
|
|
102
|
+
const idx = i + j * this.ni;
|
|
103
|
+
lons[idx] = this.ll_lon + i * dlon;
|
|
104
|
+
lats[idx] = this.ll_lat + j * dlat;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { 'lons': lons, 'lats': lats };
|
|
108
|
+
});
|
|
109
|
+
this.gc_cache = new Cache(() => {
|
|
110
|
+
const x = new Float32Array(this.ni);
|
|
111
|
+
const y = new Float32Array(this.nj);
|
|
112
|
+
for (let i = 0; i < this.ni; i++) {
|
|
113
|
+
x[i] = this.ll_lon + i * dlon;
|
|
114
|
+
}
|
|
115
|
+
for (let j = 0; j < this.nj; j++) {
|
|
116
|
+
y[j] = this.ll_lat + j * dlat;
|
|
117
|
+
}
|
|
118
|
+
return { x: x, y: y };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
copy(opts) {
|
|
122
|
+
opts = opts !== undefined ? opts : {};
|
|
123
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
124
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
125
|
+
const ll_lon = opts.ll_lon !== undefined ? opts.ll_lon : this.ll_lon;
|
|
126
|
+
const ll_lat = opts.ll_lat !== undefined ? opts.ll_lat : this.ll_lat;
|
|
127
|
+
const ur_lon = opts.ur_lon !== undefined ? opts.ur_lon : this.ur_lon;
|
|
128
|
+
const ur_lat = opts.ur_lat !== undefined ? opts.ur_lat : this.ur_lat;
|
|
129
|
+
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
133
|
+
*/
|
|
134
|
+
getEarthCoords() {
|
|
135
|
+
return this.ll_cache.getValue();
|
|
136
|
+
}
|
|
137
|
+
getGridCoords() {
|
|
138
|
+
return this.gc_cache.getValue();
|
|
139
|
+
}
|
|
140
|
+
transform(x, y, opts) {
|
|
141
|
+
return [x, y];
|
|
142
|
+
}
|
|
143
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
144
|
+
const dlon = (this.ur_lon - this.ll_lon) / this.ni;
|
|
145
|
+
const dlat = (this.ur_lat - this.ll_lat) / this.nj;
|
|
146
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
147
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
148
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
149
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
150
|
+
const ll_lon = this.ll_lon;
|
|
151
|
+
const ll_lat = this.ll_lat;
|
|
152
|
+
const ur_lon = this.ur_lon - ni_remove * dlon;
|
|
153
|
+
const ur_lat = this.ur_lat - nj_remove * dlat;
|
|
154
|
+
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** A rotated lat-lon (plate carree) grid with uniform grid spacing */
|
|
158
|
+
class PlateCarreeRotatedGrid extends Grid {
|
|
159
|
+
/**
|
|
160
|
+
* Create a Lambert conformal conic grid
|
|
161
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
162
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
163
|
+
* @param np_lon - The longitude of the north pole for the rotated grid
|
|
164
|
+
* @param np_lat - The latitude of the north pole for the rotated grid
|
|
165
|
+
* @param lon_shift - The angle around the rotated north pole to shift the central meridian
|
|
166
|
+
* @param ll_lon - The longitude of the lower left corner of the grid (on the rotated earth)
|
|
167
|
+
* @param ll_lat - The latitude of the lower left corner of the grid (on the rotated earth)
|
|
168
|
+
* @param ur_lon - The longitude of the upper right corner of the grid (on the rotated earth)
|
|
169
|
+
* @param ur_lat - The latitude of the upper right corner of the grid (on the rotated earth)
|
|
170
|
+
*/
|
|
171
|
+
constructor(ni, nj, np_lon, np_lat, lon_shift, ll_lon, ll_lat, ur_lon, ur_lat) {
|
|
172
|
+
super('latlonrot', true, ni, nj);
|
|
173
|
+
this.np_lon = np_lon;
|
|
174
|
+
this.np_lat = np_lat;
|
|
175
|
+
this.lon_shift = lon_shift;
|
|
176
|
+
this.ll_lon = ll_lon;
|
|
177
|
+
this.ll_lat = ll_lat;
|
|
178
|
+
this.ur_lon = ur_lon;
|
|
179
|
+
this.ur_lat = ur_lat;
|
|
180
|
+
this.llrot = rotateSphere({ np_lon: np_lon, np_lat: np_lat, lon_shift: lon_shift });
|
|
181
|
+
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
182
|
+
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
183
|
+
this.ll_cache = new Cache(() => {
|
|
184
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
185
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
186
|
+
for (let i = 0; i < this.ni; i++) {
|
|
187
|
+
const lon_p = this.ll_lon + i * dlon;
|
|
188
|
+
for (let j = 0; j < this.nj; j++) {
|
|
189
|
+
const lat_p = this.ll_lat + j * dlat;
|
|
190
|
+
const [lon, lat] = this.llrot(lon_p, lat_p);
|
|
191
|
+
const idx = i + j * this.ni;
|
|
192
|
+
lons[idx] = lon;
|
|
193
|
+
lats[idx] = lat;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { lons: lons, lats: lats };
|
|
197
|
+
});
|
|
198
|
+
this.gc_cache = new Cache(() => {
|
|
199
|
+
const x = new Float32Array(this.ni);
|
|
200
|
+
const y = new Float32Array(this.nj);
|
|
201
|
+
for (let i = 0; i < this.ni; i++) {
|
|
202
|
+
x[i] = this.ll_lon + i * dlon;
|
|
203
|
+
}
|
|
204
|
+
for (let j = 0; j < this.nj; j++) {
|
|
205
|
+
y[j] = this.ll_lat + j * dlat;
|
|
206
|
+
}
|
|
207
|
+
return { x: x, y: y };
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
copy(opts) {
|
|
211
|
+
opts = opts !== undefined ? opts : {};
|
|
212
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
213
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
214
|
+
const ll_lon = opts.ll_lon !== undefined ? opts.ll_lon : this.ll_lon;
|
|
215
|
+
const ll_lat = opts.ll_lat !== undefined ? opts.ll_lat : this.ll_lat;
|
|
216
|
+
const ur_lon = opts.ur_lon !== undefined ? opts.ur_lon : this.ur_lon;
|
|
217
|
+
const ur_lat = opts.ur_lat !== undefined ? opts.ur_lat : this.ur_lat;
|
|
218
|
+
return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
222
|
+
*/
|
|
223
|
+
getEarthCoords() {
|
|
224
|
+
return this.ll_cache.getValue();
|
|
225
|
+
}
|
|
226
|
+
getGridCoords() {
|
|
227
|
+
return this.gc_cache.getValue();
|
|
228
|
+
}
|
|
229
|
+
transform(x, y, opts) {
|
|
230
|
+
opts = opts === undefined ? {} : opts;
|
|
231
|
+
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
232
|
+
return this.llrot(x, y, { inverse: !inverse });
|
|
233
|
+
}
|
|
234
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
235
|
+
const dlon = (this.ur_lon - this.ll_lon) / this.ni;
|
|
236
|
+
const dlat = (this.ur_lat - this.ll_lat) / this.nj;
|
|
237
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
238
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
239
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
240
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
241
|
+
const ll_lon = this.ll_lon;
|
|
242
|
+
const ll_lat = this.ll_lat;
|
|
243
|
+
const ur_lon = this.ur_lon - ni_remove * dlon;
|
|
244
|
+
const ur_lat = this.ur_lat - nj_remove * dlat;
|
|
245
|
+
return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** A Lambert conformal conic grid with uniform grid spacing */
|
|
249
|
+
class LambertGrid extends Grid {
|
|
250
|
+
/**
|
|
251
|
+
* Create a Lambert conformal conic grid
|
|
252
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
253
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
254
|
+
* @param lon_0 - The standard longitude for the projection; this is also the center longitude for the projection
|
|
255
|
+
* @param lat_0 - The center latitude for the projection
|
|
256
|
+
* @param lat_std - The standard latitudes for the projection
|
|
257
|
+
* @param ll_x - The x coordinate in projection space of the lower-left corner of the grid
|
|
258
|
+
* @param ll_y - The y coordinate in projection space of the lower-left corner of the grid
|
|
259
|
+
* @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
|
|
260
|
+
* @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
|
|
261
|
+
*/
|
|
262
|
+
constructor(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ur_x, ur_y) {
|
|
263
|
+
super('lcc', true, ni, nj);
|
|
264
|
+
this.lon_0 = lon_0;
|
|
265
|
+
this.lat_0 = lat_0;
|
|
266
|
+
this.lat_std = lat_std;
|
|
267
|
+
this.ll_x = ll_x;
|
|
268
|
+
this.ll_y = ll_y;
|
|
269
|
+
this.ur_x = ur_x;
|
|
270
|
+
this.ur_y = ur_y;
|
|
271
|
+
this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
|
|
272
|
+
const dx = (this.ur_x - this.ll_x) / (this.ni - 1);
|
|
273
|
+
const dy = (this.ur_y - this.ll_y) / (this.nj - 1);
|
|
274
|
+
this.ll_cache = new Cache(() => {
|
|
275
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
276
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
277
|
+
for (let i = 0; i < this.ni; i++) {
|
|
278
|
+
const x = this.ll_x + i * dx;
|
|
279
|
+
for (let j = 0; j < this.nj; j++) {
|
|
280
|
+
const y = this.ll_y + j * dy;
|
|
281
|
+
const [lon, lat] = this.lcc(x, y, { inverse: true });
|
|
282
|
+
const idx = i + j * this.ni;
|
|
283
|
+
lons[idx] = lon;
|
|
284
|
+
lats[idx] = lat;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { lons: lons, lats: lats };
|
|
288
|
+
});
|
|
289
|
+
this.gc_cache = new Cache(() => {
|
|
290
|
+
const x = new Float32Array(this.ni);
|
|
291
|
+
const y = new Float32Array(this.nj);
|
|
292
|
+
for (let i = 0; i < this.ni; i++) {
|
|
293
|
+
x[i] = this.ll_x + i * dx;
|
|
294
|
+
}
|
|
295
|
+
for (let j = 0; j < this.nj; j++) {
|
|
296
|
+
y[j] = this.ll_y + j * dy;
|
|
297
|
+
}
|
|
298
|
+
return { x: x, y: y };
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
copy(opts) {
|
|
302
|
+
opts = opts !== undefined ? opts : {};
|
|
303
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
304
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
305
|
+
const ll_x = opts.ll_x !== undefined ? opts.ll_x : this.ll_x;
|
|
306
|
+
const ll_y = opts.ll_y !== undefined ? opts.ll_y : this.ll_y;
|
|
307
|
+
const ur_x = opts.ur_x !== undefined ? opts.ur_x : this.ur_x;
|
|
308
|
+
const ur_y = opts.ur_y !== undefined ? opts.ur_y : this.ur_y;
|
|
309
|
+
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
313
|
+
*/
|
|
314
|
+
getEarthCoords() {
|
|
315
|
+
return this.ll_cache.getValue();
|
|
316
|
+
}
|
|
317
|
+
getGridCoords() {
|
|
318
|
+
return this.gc_cache.getValue();
|
|
319
|
+
}
|
|
320
|
+
transform(x, y, opts) {
|
|
321
|
+
opts = opts === undefined ? {} : opts;
|
|
322
|
+
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
323
|
+
return this.lcc(x, y, { inverse: inverse });
|
|
324
|
+
}
|
|
325
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
326
|
+
const dx = (this.ur_x - this.ll_x) / this.ni;
|
|
327
|
+
const dy = (this.ur_y - this.ll_y) / this.nj;
|
|
328
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
329
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
330
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
331
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
332
|
+
const ll_x = this.ll_x;
|
|
333
|
+
const ll_y = this.ll_y;
|
|
334
|
+
const ur_x = this.ur_x - ni_remove * dx;
|
|
335
|
+
const ur_y = this.ur_y - nj_remove * dy;
|
|
336
|
+
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
export { Grid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid };
|