@developmentseed/deck.gl-raster 0.1.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/dist/index.js ADDED
@@ -0,0 +1,723 @@
1
+ import proj4 from 'proj4';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, { get: all[name], enumerable: true });
7
+ };
8
+
9
+ // src/reprojection/index.ts
10
+ var reprojection_exports = {};
11
+ __export(reprojection_exports, {
12
+ RasterReprojector: () => RasterReprojector,
13
+ affine: () => affine_exports,
14
+ extractGeotiffReprojectors: () => extractGeotiffReprojectors,
15
+ fromGeoTransform: () => fromGeoTransform
16
+ });
17
+
18
+ // src/reprojection/delatin.ts
19
+ var SAMPLE_POINTS = [
20
+ [1 / 3, 1 / 3, 1 / 3],
21
+ // centroid
22
+ [0.5, 0.5, 0],
23
+ // edge 0–1
24
+ [0.5, 0, 0.5],
25
+ // edge 0–2
26
+ [0, 0.5, 0.5]
27
+ // edge 1–2
28
+ ];
29
+ var RasterReprojector = class {
30
+ reprojectors;
31
+ width;
32
+ height;
33
+ /**
34
+ * UV vertex coordinates (x, y), i.e.
35
+ * [x0, y0, x1, y1, ...]
36
+ *
37
+ * These coordinates are floats that range from [0, 1] in both X and Y.
38
+ */
39
+ uvs;
40
+ /**
41
+ * XY Positions in output CRS, computed via exact forward reprojection.
42
+ */
43
+ exactOutputPositions;
44
+ /**
45
+ * triangle vertex indices
46
+ */
47
+ triangles;
48
+ _halfedges;
49
+ /**
50
+ * The UV texture coordinates of candidates found from
51
+ * `findReprojectionCandidate`.
52
+ *
53
+ * Maybe in the future we'll want to store the barycentric coordinates instead
54
+ * of just the uv coordinates?
55
+ */
56
+ _candidatesUV;
57
+ _queueIndices;
58
+ _queue;
59
+ _errors;
60
+ _pending;
61
+ _pendingLen;
62
+ constructor(reprojectors, width, height = width) {
63
+ this.reprojectors = reprojectors;
64
+ this.width = width;
65
+ this.height = height;
66
+ this.uvs = [];
67
+ this.exactOutputPositions = [];
68
+ this.triangles = [];
69
+ this._halfedges = [];
70
+ this._candidatesUV = [];
71
+ this._queueIndices = [];
72
+ this._queue = [];
73
+ this._errors = [];
74
+ this._pending = [];
75
+ this._pendingLen = 0;
76
+ const u1 = 1;
77
+ const v1 = 1;
78
+ const p0 = this._addPoint(0, 0);
79
+ const p1 = this._addPoint(u1, 0);
80
+ const p2 = this._addPoint(0, v1);
81
+ const p3 = this._addPoint(u1, v1);
82
+ const t0 = this._addTriangle(p3, p0, p2, -1, -1, -1);
83
+ this._addTriangle(p0, p3, p1, t0, -1, -1);
84
+ this._flush();
85
+ }
86
+ // refine the mesh until its maximum error gets below the given one
87
+ run(maxError = 1) {
88
+ while (this.getMaxError() > maxError) {
89
+ this.refine();
90
+ }
91
+ }
92
+ // refine the mesh with a single point
93
+ refine() {
94
+ this._step();
95
+ this._flush();
96
+ }
97
+ // max error of the current mesh
98
+ getMaxError() {
99
+ return this._errors[0];
100
+ }
101
+ // rasterize and queue all triangles that got added or updated in _step
102
+ _flush() {
103
+ for (let i = 0; i < this._pendingLen; i++) {
104
+ const t = this._pending[i];
105
+ this._findReprojectionCandidate(t);
106
+ }
107
+ this._pendingLen = 0;
108
+ }
109
+ // Original, upstream implementation of FindCandidate:
110
+ // // rasterize a triangle, find its max error, and queue it for processing
111
+ // private _findCandidate(
112
+ // p0x: number,
113
+ // p0y: number,
114
+ // p1x: number,
115
+ // p1y: number,
116
+ // p2x: number,
117
+ // p2y: number,
118
+ // t: number,
119
+ // ) {
120
+ // // triangle bounding box
121
+ // const minX = Math.min(p0x, p1x, p2x);
122
+ // const minY = Math.min(p0y, p1y, p2y);
123
+ // const maxX = Math.max(p0x, p1x, p2x);
124
+ // const maxY = Math.max(p0y, p1y, p2y);
125
+ // // forward differencing variables
126
+ // let w00 = orient(p1x, p1y, p2x, p2y, minX, minY);
127
+ // let w01 = orient(p2x, p2y, p0x, p0y, minX, minY);
128
+ // let w02 = orient(p0x, p0y, p1x, p1y, minX, minY);
129
+ // const a01 = p1y - p0y;
130
+ // const b01 = p0x - p1x;
131
+ // const a12 = p2y - p1y;
132
+ // const b12 = p1x - p2x;
133
+ // const a20 = p0y - p2y;
134
+ // const b20 = p2x - p0x;
135
+ // // pre-multiplied z values at vertices
136
+ // const a = orient(p0x, p0y, p1x, p1y, p2x, p2y);
137
+ // const z0 = this.heightAt(p0x, p0y) / a;
138
+ // const z1 = this.heightAt(p1x, p1y) / a;
139
+ // const z2 = this.heightAt(p2x, p2y) / a;
140
+ // // iterate over pixels in bounding box
141
+ // let maxError = 0;
142
+ // let mx = 0;
143
+ // let my = 0;
144
+ // for (let y = minY; y <= maxY; y++) {
145
+ // // compute starting offset
146
+ // let dx = 0;
147
+ // if (w00 < 0 && a12 !== 0) {
148
+ // dx = Math.max(dx, Math.floor(-w00 / a12));
149
+ // }
150
+ // if (w01 < 0 && a20 !== 0) {
151
+ // dx = Math.max(dx, Math.floor(-w01 / a20));
152
+ // }
153
+ // if (w02 < 0 && a01 !== 0) {
154
+ // dx = Math.max(dx, Math.floor(-w02 / a01));
155
+ // }
156
+ // let w0 = w00 + a12 * dx;
157
+ // let w1 = w01 + a20 * dx;
158
+ // let w2 = w02 + a01 * dx;
159
+ // let wasInside = false;
160
+ // for (let x = minX + dx; x <= maxX; x++) {
161
+ // // check if inside triangle
162
+ // if (w0 >= 0 && w1 >= 0 && w2 >= 0) {
163
+ // wasInside = true;
164
+ // // compute z using barycentric coordinates
165
+ // const z = z0 * w0 + z1 * w1 + z2 * w2;
166
+ // const dz = Math.abs(z - this.heightAt(x, y));
167
+ // if (dz > maxError) {
168
+ // maxError = dz;
169
+ // mx = x;
170
+ // my = y;
171
+ // }
172
+ // } else if (wasInside) {
173
+ // break;
174
+ // }
175
+ // w0 += a12;
176
+ // w1 += a20;
177
+ // w2 += a01;
178
+ // }
179
+ // w00 += b12;
180
+ // w01 += b20;
181
+ // w02 += b01;
182
+ // }
183
+ // if (
184
+ // (mx === p0x && my === p0y) ||
185
+ // (mx === p1x && my === p1y) ||
186
+ // (mx === p2x && my === p2y)
187
+ // ) {
188
+ // maxError = 0;
189
+ // }
190
+ // // update triangle metadata
191
+ // this._candidatesUV[2 * t] = mx;
192
+ // this._candidatesUV[2 * t + 1] = my;
193
+ // // add triangle to priority queue
194
+ // this._queuePush(t, maxError);
195
+ // }
196
+ /**
197
+ * Conversion of upstream's `_findCandidate` for reprojection error handling.
198
+ *
199
+ * @param {number} t The index (into `this.triangles`) of the pending triangle to process.
200
+ *
201
+ * @return {void} Doesn't return; instead modifies internal state.
202
+ */
203
+ _findReprojectionCandidate(t) {
204
+ const a2 = 2 * this.triangles[t * 3 + 0];
205
+ const b2 = 2 * this.triangles[t * 3 + 1];
206
+ const c = 2 * this.triangles[t * 3 + 2];
207
+ const p0u = this.uvs[a2];
208
+ const p0v = this.uvs[a2 + 1];
209
+ const p1u = this.uvs[b2];
210
+ const p1v = this.uvs[b2 + 1];
211
+ const p2u = this.uvs[c];
212
+ const p2v = this.uvs[c + 1];
213
+ const out0x = this.exactOutputPositions[a2];
214
+ const out0y = this.exactOutputPositions[a2 + 1];
215
+ const out1x = this.exactOutputPositions[b2];
216
+ const out1y = this.exactOutputPositions[b2 + 1];
217
+ const out2x = this.exactOutputPositions[c];
218
+ const out2y = this.exactOutputPositions[c + 1];
219
+ let maxError = 0;
220
+ let maxErrorU = 0;
221
+ let maxErrorV = 0;
222
+ for (const samplePoint of SAMPLE_POINTS) {
223
+ const uvSampleU = barycentricMix(
224
+ p0u,
225
+ p1u,
226
+ p2u,
227
+ samplePoint[0],
228
+ samplePoint[1],
229
+ samplePoint[2]
230
+ );
231
+ const uvSampleV = barycentricMix(
232
+ p0v,
233
+ p1v,
234
+ p2v,
235
+ samplePoint[0],
236
+ samplePoint[1],
237
+ samplePoint[2]
238
+ );
239
+ const outSampleX = barycentricMix(
240
+ out0x,
241
+ out1x,
242
+ out2x,
243
+ samplePoint[0],
244
+ samplePoint[1],
245
+ samplePoint[2]
246
+ );
247
+ const outSampleY = barycentricMix(
248
+ out0y,
249
+ out1y,
250
+ out2y,
251
+ samplePoint[0],
252
+ samplePoint[1],
253
+ samplePoint[2]
254
+ );
255
+ const pixelExactX = uvSampleU * (this.width - 1);
256
+ const pixelExactY = uvSampleV * (this.height - 1);
257
+ const inputCRSSampled = this.reprojectors.inverseReproject(
258
+ outSampleX,
259
+ outSampleY
260
+ );
261
+ const pixelSampled = this.reprojectors.inputCRSToPixel(
262
+ inputCRSSampled[0],
263
+ inputCRSSampled[1]
264
+ );
265
+ const dx = pixelExactX - pixelSampled[0];
266
+ const dy = pixelExactY - pixelSampled[1];
267
+ const err = Math.hypot(dx, dy);
268
+ if (err > maxError) {
269
+ maxError = err;
270
+ maxErrorU = uvSampleU;
271
+ maxErrorV = uvSampleV;
272
+ }
273
+ }
274
+ if (maxErrorU === p0u && maxErrorV === p0v || maxErrorU === p1u && maxErrorV === p1v || maxErrorU === p2u && maxErrorV === p2v) {
275
+ maxError = 0;
276
+ }
277
+ this._candidatesUV[2 * t] = maxErrorU;
278
+ this._candidatesUV[2 * t + 1] = maxErrorV;
279
+ this._queuePush(t, maxError);
280
+ }
281
+ // process the next triangle in the queue, splitting it with a new point
282
+ _step() {
283
+ const t = this._queuePop();
284
+ const e0 = t * 3 + 0;
285
+ const e1 = t * 3 + 1;
286
+ const e2 = t * 3 + 2;
287
+ const p0 = this.triangles[e0];
288
+ const p1 = this.triangles[e1];
289
+ const p2 = this.triangles[e2];
290
+ const au = this.uvs[2 * p0];
291
+ const av = this.uvs[2 * p0 + 1];
292
+ const bu = this.uvs[2 * p1];
293
+ const bv = this.uvs[2 * p1 + 1];
294
+ const cu = this.uvs[2 * p2];
295
+ const cv = this.uvs[2 * p2 + 1];
296
+ const pu = this._candidatesUV[2 * t];
297
+ const pv = this._candidatesUV[2 * t + 1];
298
+ const pn = this._addPoint(pu, pv);
299
+ if (orient(au, av, bu, bv, pu, pv) === 0) {
300
+ this._handleCollinear(pn, e0);
301
+ } else if (orient(bu, bv, cu, cv, pu, pv) === 0) {
302
+ this._handleCollinear(pn, e1);
303
+ } else if (orient(cu, cv, au, av, pu, pv) === 0) {
304
+ this._handleCollinear(pn, e2);
305
+ } else {
306
+ const h0 = this._halfedges[e0];
307
+ const h1 = this._halfedges[e1];
308
+ const h2 = this._halfedges[e2];
309
+ const t0 = this._addTriangle(p0, p1, pn, h0, -1, -1, e0);
310
+ const t1 = this._addTriangle(p1, p2, pn, h1, -1, t0 + 1);
311
+ const t2 = this._addTriangle(p2, p0, pn, h2, t0 + 2, t1 + 1);
312
+ this._legalize(t0);
313
+ this._legalize(t1);
314
+ this._legalize(t2);
315
+ }
316
+ }
317
+ // add coordinates for a new vertex
318
+ _addPoint(u, v) {
319
+ const i = this.uvs.length >> 1;
320
+ this.uvs.push(u, v);
321
+ const pixelX = u * (this.width - 1);
322
+ const pixelY = v * (this.height - 1);
323
+ const inputPosition = this.reprojectors.pixelToInputCRS(pixelX, pixelY);
324
+ const exactOutputPosition = this.reprojectors.forwardReproject(
325
+ inputPosition[0],
326
+ inputPosition[1]
327
+ );
328
+ this.exactOutputPositions.push(
329
+ exactOutputPosition[0],
330
+ exactOutputPosition[1]
331
+ );
332
+ return i;
333
+ }
334
+ // add or update a triangle in the mesh
335
+ _addTriangle(a2, b2, c, ab, bc, ca, e2 = this.triangles.length) {
336
+ const t = e2 / 3;
337
+ this.triangles[e2 + 0] = a2;
338
+ this.triangles[e2 + 1] = b2;
339
+ this.triangles[e2 + 2] = c;
340
+ this._halfedges[e2 + 0] = ab;
341
+ this._halfedges[e2 + 1] = bc;
342
+ this._halfedges[e2 + 2] = ca;
343
+ if (ab >= 0) {
344
+ this._halfedges[ab] = e2 + 0;
345
+ }
346
+ if (bc >= 0) {
347
+ this._halfedges[bc] = e2 + 1;
348
+ }
349
+ if (ca >= 0) {
350
+ this._halfedges[ca] = e2 + 2;
351
+ }
352
+ this._candidatesUV[2 * t + 0] = 0;
353
+ this._candidatesUV[2 * t + 1] = 0;
354
+ this._queueIndices[t] = -1;
355
+ this._pending[this._pendingLen++] = t;
356
+ return e2;
357
+ }
358
+ _legalize(a2) {
359
+ const b2 = this._halfedges[a2];
360
+ if (b2 < 0) {
361
+ return;
362
+ }
363
+ const a0 = a2 - a2 % 3;
364
+ const b0 = b2 - b2 % 3;
365
+ const al = a0 + (a2 + 1) % 3;
366
+ const ar = a0 + (a2 + 2) % 3;
367
+ const bl = b0 + (b2 + 2) % 3;
368
+ const br = b0 + (b2 + 1) % 3;
369
+ const p0 = this.triangles[ar];
370
+ const pr = this.triangles[a2];
371
+ const pl = this.triangles[al];
372
+ const p1 = this.triangles[bl];
373
+ const uvs = this.uvs;
374
+ if (!inCircle(
375
+ uvs[2 * p0],
376
+ uvs[2 * p0 + 1],
377
+ uvs[2 * pr],
378
+ uvs[2 * pr + 1],
379
+ uvs[2 * pl],
380
+ uvs[2 * pl + 1],
381
+ uvs[2 * p1],
382
+ uvs[2 * p1 + 1]
383
+ )) {
384
+ return;
385
+ }
386
+ const hal = this._halfedges[al];
387
+ const har = this._halfedges[ar];
388
+ const hbl = this._halfedges[bl];
389
+ const hbr = this._halfedges[br];
390
+ this._queueRemove(a0 / 3);
391
+ this._queueRemove(b0 / 3);
392
+ const t0 = this._addTriangle(p0, p1, pl, -1, hbl, hal, a0);
393
+ const t1 = this._addTriangle(p1, p0, pr, t0, har, hbr, b0);
394
+ this._legalize(t0 + 1);
395
+ this._legalize(t1 + 2);
396
+ }
397
+ // handle a case where new vertex is on the edge of a triangle
398
+ _handleCollinear(pn, a2) {
399
+ const a0 = a2 - a2 % 3;
400
+ const al = a0 + (a2 + 1) % 3;
401
+ const ar = a0 + (a2 + 2) % 3;
402
+ const p0 = this.triangles[ar];
403
+ const pr = this.triangles[a2];
404
+ const pl = this.triangles[al];
405
+ const hal = this._halfedges[al];
406
+ const har = this._halfedges[ar];
407
+ const b2 = this._halfedges[a2];
408
+ if (b2 < 0) {
409
+ const t02 = this._addTriangle(pn, p0, pr, -1, har, -1, a0);
410
+ const t12 = this._addTriangle(p0, pn, pl, t02, -1, hal);
411
+ this._legalize(t02 + 1);
412
+ this._legalize(t12 + 2);
413
+ return;
414
+ }
415
+ const b0 = b2 - b2 % 3;
416
+ const bl = b0 + (b2 + 2) % 3;
417
+ const br = b0 + (b2 + 1) % 3;
418
+ const p1 = this.triangles[bl];
419
+ const hbl = this._halfedges[bl];
420
+ const hbr = this._halfedges[br];
421
+ this._queueRemove(b0 / 3);
422
+ const t0 = this._addTriangle(p0, pr, pn, har, -1, -1, a0);
423
+ const t1 = this._addTriangle(pr, p1, pn, hbr, -1, t0 + 1, b0);
424
+ const t2 = this._addTriangle(p1, pl, pn, hbl, -1, t1 + 1);
425
+ const t3 = this._addTriangle(pl, p0, pn, hal, t0 + 2, t2 + 1);
426
+ this._legalize(t0);
427
+ this._legalize(t1);
428
+ this._legalize(t2);
429
+ this._legalize(t3);
430
+ }
431
+ // priority queue methods
432
+ _queuePush(t, error) {
433
+ const i = this._queue.length;
434
+ this._queueIndices[t] = i;
435
+ this._queue.push(t);
436
+ this._errors.push(error);
437
+ this._queueUp(i);
438
+ }
439
+ _queuePop() {
440
+ const n = this._queue.length - 1;
441
+ this._queueSwap(0, n);
442
+ this._queueDown(0, n);
443
+ return this._queuePopBack();
444
+ }
445
+ _queuePopBack() {
446
+ const t = this._queue.pop();
447
+ this._errors.pop();
448
+ this._queueIndices[t] = -1;
449
+ return t;
450
+ }
451
+ _queueRemove(t) {
452
+ const i = this._queueIndices[t];
453
+ if (i < 0) {
454
+ const it = this._pending.indexOf(t);
455
+ if (it !== -1) {
456
+ this._pending[it] = this._pending[--this._pendingLen];
457
+ } else {
458
+ throw new Error("Broken triangulation (something went wrong).");
459
+ }
460
+ return;
461
+ }
462
+ const n = this._queue.length - 1;
463
+ if (n !== i) {
464
+ this._queueSwap(i, n);
465
+ if (!this._queueDown(i, n)) {
466
+ this._queueUp(i);
467
+ }
468
+ }
469
+ this._queuePopBack();
470
+ }
471
+ _queueLess(i, j) {
472
+ return this._errors[i] > this._errors[j];
473
+ }
474
+ _queueSwap(i, j) {
475
+ const pi = this._queue[i];
476
+ const pj = this._queue[j];
477
+ this._queue[i] = pj;
478
+ this._queue[j] = pi;
479
+ this._queueIndices[pi] = j;
480
+ this._queueIndices[pj] = i;
481
+ const e2 = this._errors[i];
482
+ this._errors[i] = this._errors[j];
483
+ this._errors[j] = e2;
484
+ }
485
+ _queueUp(j0) {
486
+ let j = j0;
487
+ while (true) {
488
+ const i = j - 1 >> 1;
489
+ if (i === j || !this._queueLess(j, i)) {
490
+ break;
491
+ }
492
+ this._queueSwap(i, j);
493
+ j = i;
494
+ }
495
+ }
496
+ _queueDown(i0, n) {
497
+ let i = i0;
498
+ while (true) {
499
+ const j1 = 2 * i + 1;
500
+ if (j1 >= n || j1 < 0) {
501
+ break;
502
+ }
503
+ const j2 = j1 + 1;
504
+ let j = j1;
505
+ if (j2 < n && this._queueLess(j2, j1)) {
506
+ j = j2;
507
+ }
508
+ if (!this._queueLess(j, i)) {
509
+ break;
510
+ }
511
+ this._queueSwap(i, j);
512
+ i = j;
513
+ }
514
+ return i > i0;
515
+ }
516
+ };
517
+ function orient(ax, ay, bx, by, cx, cy) {
518
+ return (bx - cx) * (ay - cy) - (by - cy) * (ax - cx);
519
+ }
520
+ function inCircle(ax, ay, bx, by, cx, cy, px, py) {
521
+ const dx = ax - px;
522
+ const dy = ay - py;
523
+ const ex = bx - px;
524
+ const ey = by - py;
525
+ const fx = cx - px;
526
+ const fy = cy - py;
527
+ const ap = dx * dx + dy * dy;
528
+ const bp = ex * ex + ey * ey;
529
+ const cp = fx * fx + fy * fy;
530
+ return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0;
531
+ }
532
+ function barycentricMix(a2, b2, c, t0, t1, t2) {
533
+ return t0 * a2 + t1 * b2 + t2 * c;
534
+ }
535
+
536
+ // src/reprojection/affine.ts
537
+ var affine_exports = {};
538
+ __export(affine_exports, {
539
+ applyAffine: () => applyAffine,
540
+ invertGeoTransform: () => invertGeoTransform
541
+ });
542
+ function invertGeoTransform(gt) {
543
+ if (isDegenerate(gt)) {
544
+ throw new Error("Cannot invert degenerate transform");
545
+ }
546
+ const idet = 1 / determinant(gt);
547
+ const [sa, sb, sc, sd, se, sf] = gt;
548
+ const ra = se * idet;
549
+ const rb = -sb * idet;
550
+ const rd = -sd * idet;
551
+ const re = sa * idet;
552
+ return [
553
+ ra,
554
+ rb,
555
+ -sc * ra - sf * rb,
556
+ rd,
557
+ re,
558
+ -sc * rd - sf * re
559
+ ];
560
+ }
561
+ function isDegenerate(gt) {
562
+ return determinant(gt) === 0;
563
+ }
564
+ function determinant(gt) {
565
+ return a(gt) * e(gt) - b(gt) * d(gt);
566
+ }
567
+ function a(gt) {
568
+ return gt[0];
569
+ }
570
+ function b(gt) {
571
+ return gt[1];
572
+ }
573
+ function d(gt) {
574
+ return gt[3];
575
+ }
576
+ function e(gt) {
577
+ return gt[4];
578
+ }
579
+ function applyAffine(x, y, gt) {
580
+ const [a2, b2, c, d2, e2, f] = gt;
581
+ return [a2 * x + b2 * y + c, d2 * x + e2 * y + f];
582
+ }
583
+ var OGC_84 = {
584
+ $schema: "https://proj.org/schemas/v0.7/projjson.schema.json",
585
+ type: "GeographicCRS",
586
+ name: "WGS 84 (CRS84)",
587
+ datum_ensemble: {
588
+ name: "World Geodetic System 1984 ensemble",
589
+ members: [
590
+ {
591
+ name: "World Geodetic System 1984 (Transit)",
592
+ id: { authority: "EPSG", code: 1166 }
593
+ },
594
+ {
595
+ name: "World Geodetic System 1984 (G730)",
596
+ id: { authority: "EPSG", code: 1152 }
597
+ },
598
+ {
599
+ name: "World Geodetic System 1984 (G873)",
600
+ id: { authority: "EPSG", code: 1153 }
601
+ },
602
+ {
603
+ name: "World Geodetic System 1984 (G1150)",
604
+ id: { authority: "EPSG", code: 1154 }
605
+ },
606
+ {
607
+ name: "World Geodetic System 1984 (G1674)",
608
+ id: { authority: "EPSG", code: 1155 }
609
+ },
610
+ {
611
+ name: "World Geodetic System 1984 (G1762)",
612
+ id: { authority: "EPSG", code: 1156 }
613
+ },
614
+ {
615
+ name: "World Geodetic System 1984 (G2139)",
616
+ id: { authority: "EPSG", code: 1309 }
617
+ }
618
+ ],
619
+ ellipsoid: {
620
+ name: "WGS 84",
621
+ semi_major_axis: 6378137,
622
+ inverse_flattening: 298.257223563
623
+ },
624
+ accuracy: "2.0",
625
+ id: { authority: "EPSG", code: 6326 }
626
+ },
627
+ coordinate_system: {
628
+ subtype: "ellipsoidal",
629
+ axis: [
630
+ {
631
+ name: "Geodetic longitude",
632
+ abbreviation: "Lon",
633
+ direction: "east",
634
+ unit: "degree"
635
+ },
636
+ {
637
+ name: "Geodetic latitude",
638
+ abbreviation: "Lat",
639
+ direction: "north",
640
+ unit: "degree"
641
+ }
642
+ ]
643
+ },
644
+ scope: "Not known.",
645
+ area: "World.",
646
+ bbox: {
647
+ south_latitude: -90,
648
+ west_longitude: -180,
649
+ north_latitude: 90,
650
+ east_longitude: 180
651
+ },
652
+ // @ts-expect-error - proj4 types are incomplete
653
+ id: { authority: "OGC", code: "CRS84" }
654
+ };
655
+ async function extractGeotiffReprojectors(tiff, outputCrs = OGC_84) {
656
+ const image = await tiff.getImage();
657
+ const geoKeys = image.getGeoKeys();
658
+ const projectionCode = geoKeys.ProjectedCSTypeGeoKey || geoKeys.GeographicTypeGeoKey || null;
659
+ const baseGeotransform = extractGeotransform(image);
660
+ const sourceProjection = await getProjjson(projectionCode);
661
+ if (sourceProjection === null) {
662
+ throw new Error(
663
+ "Could not determine source projection from GeoTIFF geo keys"
664
+ );
665
+ }
666
+ const converter = proj4(sourceProjection, outputCrs);
667
+ const { pixelToInputCRS, inputCRSToPixel } = fromGeoTransform(baseGeotransform);
668
+ return {
669
+ pixelToInputCRS,
670
+ inputCRSToPixel,
671
+ forwardReproject: (x, y) => converter.forward([x, y], false),
672
+ inverseReproject: (x, y) => converter.inverse([x, y], false)
673
+ };
674
+ }
675
+ function fromGeoTransform(geotransform) {
676
+ const inverseGeotransform = invertGeoTransform(geotransform);
677
+ return {
678
+ pixelToInputCRS: (x, y) => applyAffine(x, y, geotransform),
679
+ inputCRSToPixel: (x, y) => applyAffine(x, y, inverseGeotransform)
680
+ };
681
+ }
682
+ async function getProjjson(projectionCode) {
683
+ if (projectionCode === null) {
684
+ return null;
685
+ }
686
+ const url = `https://epsg.io/${projectionCode}.json`;
687
+ const response = await fetch(url);
688
+ if (!response.ok) {
689
+ throw new Error(`Failed to fetch projection data from ${url}`);
690
+ }
691
+ const data = await response.json();
692
+ return data;
693
+ }
694
+ function extractGeotransform(image) {
695
+ const origin = image.getOrigin();
696
+ const resolution = image.getResolution();
697
+ const fileDirectory = image.getFileDirectory();
698
+ const modelTransformation = fileDirectory.ModelTransformation;
699
+ let b2 = 0;
700
+ let d2 = 0;
701
+ if (modelTransformation && modelTransformation.length >= 16) {
702
+ b2 = modelTransformation[1];
703
+ d2 = modelTransformation[4];
704
+ }
705
+ return [
706
+ resolution[0],
707
+ // a: pixel width
708
+ b2,
709
+ // b: row rotation
710
+ origin[0],
711
+ // c: x origin
712
+ d2,
713
+ // d: column rotation
714
+ resolution[1],
715
+ // e: pixel height (often negative)
716
+ origin[1]
717
+ // f: y origin
718
+ ];
719
+ }
720
+
721
+ export { reprojection_exports as reprojection };
722
+ //# sourceMappingURL=index.js.map
723
+ //# sourceMappingURL=index.js.map