@gisatcz/deckgl-geolib 1.12.0-dev.4 → 2.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 +67 -64
- package/dist/cjs/index.js +631 -641
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.min.js +3 -3
- package/dist/cjs/index.min.js.map +1 -1
- package/dist/{esm/types/cogtiles/cogtiles.d.ts → cjs/types/core/CogTiles.d.ts} +12 -9
- package/dist/cjs/types/{geoimage/geoimage.d.ts → core/GeoImage.d.ts} +6 -6
- package/dist/cjs/types/core/index.d.ts +2 -0
- package/dist/cjs/types/index.d.ts +2 -11
- package/dist/{esm/types/cogbitmaplayer → cjs/types/layers}/CogBitmapLayer.d.ts +3 -5
- package/dist/cjs/types/{cogterrainlayer → layers}/CogTerrainLayer.d.ts +13 -8
- package/dist/cjs/types/layers/index.d.ts +2 -0
- package/dist/esm/index.js +628 -641
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.min.js +3 -3
- package/dist/esm/index.min.js.map +1 -1
- package/dist/{cjs/types/cogtiles/cogtiles.d.ts → esm/types/core/CogTiles.d.ts} +12 -9
- package/dist/esm/types/{geoimage/geoimage.d.ts → core/GeoImage.d.ts} +6 -6
- package/dist/esm/types/core/index.d.ts +2 -0
- package/dist/esm/types/index.d.ts +2 -11
- package/dist/{cjs/types/cogbitmaplayer → esm/types/layers}/CogBitmapLayer.d.ts +3 -5
- package/dist/esm/types/{cogterrainlayer → layers}/CogTerrainLayer.d.ts +13 -8
- package/dist/esm/types/layers/index.d.ts +2 -0
- package/package.json +40 -4
- package/.eslintignore +0 -2
- package/.eslintrc.cjs +0 -3
- package/CHANGELOG.md +0 -166
- package/rollup.config.mjs +0 -77
- package/src/cogbitmaplayer/CogBitmapLayer.ts +0 -337
- package/src/cogbitmaplayer/README.md +0 -113
- package/src/cogterrainlayer/CogTerrainLayer.ts +0 -445
- package/src/cogterrainlayer/README.md +0 -118
- package/src/cogtiles/README.md +0 -72
- package/src/cogtiles/cogtiles.ts +0 -420
- package/src/cogtiles/lzw.js +0 -256
- package/src/geoimage/README.md +0 -129
- package/src/geoimage/delatin/index.ts +0 -495
- package/src/geoimage/geoimage.ts +0 -599
- package/src/geoimage/helpers/skirt.ts +0 -171
- package/src/index.ts +0 -11
- package/src/utilities/tileurls.ts +0 -21
- package/tsconfig.json +0 -6
- /package/dist/cjs/types/{geoimage → core}/delatin/index.d.ts +0 -0
- /package/dist/cjs/types/{geoimage → core}/helpers/skirt.d.ts +0 -0
- /package/dist/cjs/types/{utilities → utils}/tileurls.d.ts +0 -0
- /package/dist/esm/types/{geoimage → core}/delatin/index.d.ts +0 -0
- /package/dist/esm/types/{geoimage → core}/helpers/skirt.d.ts +0 -0
- /package/dist/esm/types/{utilities → utils}/tileurls.d.ts +0 -0
package/src/cogtiles/cogtiles.ts
DELETED
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
/* eslint 'max-len': [1, { code: 100, comments: 999, ignoreStrings: true, ignoreUrls: true }] */
|
|
2
|
-
// COG loading
|
|
3
|
-
import { fromUrl, GeoTIFF, GeoTIFFImage } from 'geotiff';
|
|
4
|
-
|
|
5
|
-
// Image compression support
|
|
6
|
-
import { worldToLngLat } from '@math.gl/web-mercator';
|
|
7
|
-
|
|
8
|
-
// Bitmap styling
|
|
9
|
-
import GeoImage, { GeoImageOptions } from '../geoimage/geoimage.ts';
|
|
10
|
-
|
|
11
|
-
export type Bounds = [minX: number, minY: number, maxX: number, maxY: number];
|
|
12
|
-
|
|
13
|
-
const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
|
|
14
|
-
const EARTH_HALF_CIRCUMFERENCE = EARTH_CIRCUMFERENCE / 2;
|
|
15
|
-
const webMercatorOrigin = [-20037508.342789244, 20037508.342789244];
|
|
16
|
-
const webMercatorRes0 = 156543.03125;
|
|
17
|
-
|
|
18
|
-
const CogTilesGeoImageOptionsDefaults = {
|
|
19
|
-
blurredTexture: true,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
class CogTiles {
|
|
23
|
-
cog: GeoTIFF;
|
|
24
|
-
|
|
25
|
-
cogZoomLookup = [0];
|
|
26
|
-
|
|
27
|
-
cogResolutionLookup = [0];
|
|
28
|
-
|
|
29
|
-
cogOrigin = [0, 0];
|
|
30
|
-
|
|
31
|
-
zoomRange = [0, 0];
|
|
32
|
-
|
|
33
|
-
tileSize: number;
|
|
34
|
-
|
|
35
|
-
bounds: Bounds;
|
|
36
|
-
|
|
37
|
-
loaded: boolean = false;
|
|
38
|
-
|
|
39
|
-
geo: GeoImage = new GeoImage();
|
|
40
|
-
|
|
41
|
-
options: GeoImageOptions;
|
|
42
|
-
|
|
43
|
-
constructor(options: GeoImageOptions) {
|
|
44
|
-
this.options = { ...CogTilesGeoImageOptionsDefaults, ...options };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async initializeCog(url: string) {
|
|
48
|
-
this.cog = await fromUrl(url);
|
|
49
|
-
const image = await this.cog.getImage(); // by default, the first image is read.
|
|
50
|
-
this.cogOrigin = image.getOrigin();
|
|
51
|
-
this.options.noDataValue ??= this.getNoDataValue(image);
|
|
52
|
-
this.options.format ??= this.getDataTypeFromTags(image);
|
|
53
|
-
this.options.numOfChannels = this.getNumberOfChannels(image);
|
|
54
|
-
this.options.planarConfig = this.getPlanarConfiguration(image);
|
|
55
|
-
[this.cogZoomLookup, this.cogResolutionLookup] = await this.buildCogZoomResolutionLookup(this.cog);
|
|
56
|
-
this.tileSize = image.getTileWidth();
|
|
57
|
-
this.zoomRange = this.calculateZoomRange(image, await this.cog.getImageCount());
|
|
58
|
-
this.bounds = this.calculateBoundsAsLatLon(image);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
getZoomRange() {
|
|
62
|
-
return this.zoomRange;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
calculateZoomRange(img: GeoTIFFImage, imgCount: number) {
|
|
66
|
-
const maxZoom = this.getZoomLevelFromResolution(img.getTileWidth(), img.getResolution()[0]);
|
|
67
|
-
const minZoom = maxZoom - (imgCount - 1);
|
|
68
|
-
|
|
69
|
-
return [minZoom, maxZoom];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
calculateBoundsAsLatLon(image: GeoTIFFImage) {
|
|
73
|
-
const bbox = image.getBoundingBox();
|
|
74
|
-
|
|
75
|
-
const minX = Math.min(bbox[0], bbox[2]);
|
|
76
|
-
const maxX = Math.max(bbox[0], bbox[2]);
|
|
77
|
-
const minY = Math.min(bbox[1], bbox[3]);
|
|
78
|
-
const maxY = Math.max(bbox[1], bbox[3]);
|
|
79
|
-
|
|
80
|
-
const minXYDeg = this.getLatLon([minX, minY]);
|
|
81
|
-
const maxXYDeg = this.getLatLon([maxX, maxY]);
|
|
82
|
-
|
|
83
|
-
return [minXYDeg[0], minXYDeg[1], maxXYDeg[0], maxXYDeg[1]] as [number, number, number, number];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
getZoomLevelFromResolution(tileSize: number, resolution: number) {
|
|
87
|
-
return Math.round(Math.log2(EARTH_CIRCUMFERENCE / (resolution * tileSize)));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
getBoundsAsLatLon() {
|
|
91
|
-
return this.bounds;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
getLatLon(input: number[]) {
|
|
95
|
-
const ax = EARTH_HALF_CIRCUMFERENCE + input[0];
|
|
96
|
-
const ay = -(EARTH_HALF_CIRCUMFERENCE + (input[1] - EARTH_CIRCUMFERENCE));
|
|
97
|
-
|
|
98
|
-
const cartesianPosition = [
|
|
99
|
-
ax * (512 / EARTH_CIRCUMFERENCE),
|
|
100
|
-
ay * (512 / EARTH_CIRCUMFERENCE),
|
|
101
|
-
];
|
|
102
|
-
const cartographicPosition = worldToLngLat(cartesianPosition);
|
|
103
|
-
const cartographicPositionAdjusted = [cartographicPosition[0], -cartographicPosition[1]];
|
|
104
|
-
|
|
105
|
-
return cartographicPositionAdjusted;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Builds lookup tables for zoom levels and estimated resolutions from a Cloud Optimized GeoTIFF (COG) object.
|
|
110
|
-
*
|
|
111
|
-
* It is assumed that inn web mapping, COG data is visualized in the Web Mercator coordinate system.
|
|
112
|
-
* At zoom level 0, the Web Mercator resolution is defined by the constant `webMercatorRes0`
|
|
113
|
-
* (e.g., 156543.03125 m/pixel). At each subsequent zoom level, this resolution is halved.
|
|
114
|
-
*
|
|
115
|
-
* This function calculates, for each image (overview) in the COG, its estimated resolution and
|
|
116
|
-
* corresponding zoom level based on the base image's resolution and width.
|
|
117
|
-
*
|
|
118
|
-
* @param {object} cog - A Cloud Optimized GeoTIFF object loaded via geotiff.js.
|
|
119
|
-
* @returns {Promise<[number[], number[]]>} A promise resolving to a tuple of two arrays:
|
|
120
|
-
* - The first array (`zoomLookup`) maps each image index to its computed zoom level.
|
|
121
|
-
* - The second array (`resolutionLookup`) maps each image index to its estimated resolution (m/pixel).
|
|
122
|
-
*/
|
|
123
|
-
async buildCogZoomResolutionLookup(cog) {
|
|
124
|
-
// Retrieve the total number of images (overviews) in the COG.
|
|
125
|
-
const imageCount = await cog.getImageCount();
|
|
126
|
-
|
|
127
|
-
// Use the first image as the base reference.
|
|
128
|
-
const baseImage = await cog.getImage(0);
|
|
129
|
-
const baseResolution = baseImage.getResolution()[0]; // Resolution (m/pixel) of the base image.
|
|
130
|
-
const baseWidth = baseImage.getWidth();
|
|
131
|
-
|
|
132
|
-
// Initialize arrays to store the zoom level and resolution for each image.
|
|
133
|
-
const zoomLookup = [];
|
|
134
|
-
const resolutionLookup = [];
|
|
135
|
-
|
|
136
|
-
// Iterate over each image (overview) in the COG.
|
|
137
|
-
for (let idx = 0; idx < imageCount; idx++) {
|
|
138
|
-
const image = await cog.getImage(idx);
|
|
139
|
-
const width = image.getWidth();
|
|
140
|
-
|
|
141
|
-
// Calculate the scale factor relative to the base image.
|
|
142
|
-
const scaleFactor = baseWidth / width;
|
|
143
|
-
const estimatedResolution = baseResolution * scaleFactor;
|
|
144
|
-
|
|
145
|
-
// Calculate the zoom level using the Web Mercator resolution standard:
|
|
146
|
-
// webMercatorRes0 is the resolution at zoom level 0; each zoom level halves the resolution.
|
|
147
|
-
const zoomLevel = Math.round(Math.log2(webMercatorRes0 / estimatedResolution));
|
|
148
|
-
// console.log(`buildCogZoomResolutionLookup: Image index ${idx}: Estimated Resolution = ${estimatedResolution} m/pixel, Zoom Level = ${zoomLevel}`);
|
|
149
|
-
|
|
150
|
-
zoomLookup[idx] = zoomLevel;
|
|
151
|
-
resolutionLookup[idx] = estimatedResolution;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return [zoomLookup, resolutionLookup];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Determines the appropriate image index from the Cloud Optimized GeoTIFF (COG)
|
|
159
|
-
* that best matches a given zoom level.
|
|
160
|
-
*
|
|
161
|
-
* This function utilizes precomputed lookup tables (`cogZoomLookup`) that map
|
|
162
|
-
* each image index in the COG to its corresponding zoom level. It ensures that
|
|
163
|
-
* the selected image index provides the closest resolution to the desired zoom level.
|
|
164
|
-
*
|
|
165
|
-
* @param {number} zoom - The target zoom level for which the image index is sought.
|
|
166
|
-
* @returns {number} The index of the image in the COG that best matches the specified zoom level.
|
|
167
|
-
*/
|
|
168
|
-
getImageIndexForZoomLevel(zoom) {
|
|
169
|
-
// Retrieve the minimum and maximum zoom levels from the lookup table.
|
|
170
|
-
const minZoom = this.cogZoomLookup[this.cogZoomLookup.length - 1];
|
|
171
|
-
const maxZoom = this.cogZoomLookup[0];
|
|
172
|
-
if (zoom > maxZoom) return 0;
|
|
173
|
-
if (zoom < minZoom) return this.cogZoomLookup.length - 1;
|
|
174
|
-
|
|
175
|
-
// For zoom levels within the available range, find the exact or closest matching index.
|
|
176
|
-
const exactMatchIndex = this.cogZoomLookup.indexOf(zoom);
|
|
177
|
-
if (exactMatchIndex === -1) {
|
|
178
|
-
// TO DO improve the condition if the match index is not found
|
|
179
|
-
console.log('getImageIndexForZoomLevel: error in retrieving image by zoom index');
|
|
180
|
-
}
|
|
181
|
-
return exactMatchIndex;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async getTileFromImage(tileX, tileY, zoom) {
|
|
185
|
-
const imageIndex = this.getImageIndexForZoomLevel(zoom);
|
|
186
|
-
const targetImage = await this.cog.getImage(imageIndex);
|
|
187
|
-
|
|
188
|
-
// Ensure the image is tiled
|
|
189
|
-
const tileWidth = targetImage.getTileWidth();
|
|
190
|
-
const tileHeight = targetImage.getTileHeight();
|
|
191
|
-
if (!tileWidth || !tileHeight) {
|
|
192
|
-
throw new Error('The image is not tiled.');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Calculate the map offset between the global Web Mercator origin and the COG's origin.
|
|
196
|
-
// (Difference in map units.)
|
|
197
|
-
// if X offset is large and positive (COG is far to the right of global origin)
|
|
198
|
-
// if Y offset is large and positive (COG is far below global origin — expected)
|
|
199
|
-
const offsetXMap = this.cogOrigin[0] - webMercatorOrigin[0];
|
|
200
|
-
const offsetYMap = webMercatorOrigin[1] - this.cogOrigin[1];
|
|
201
|
-
|
|
202
|
-
const tileResolution = (EARTH_CIRCUMFERENCE / tileWidth) / 2 ** zoom;
|
|
203
|
-
const cogResolution = this.cogResolutionLookup[imageIndex];
|
|
204
|
-
|
|
205
|
-
// Convert map offsets into pixel offsets.
|
|
206
|
-
const offsetXPixel = Math.floor(offsetXMap / tileResolution);
|
|
207
|
-
const offsetYPixel = Math.floor(offsetYMap / tileResolution);
|
|
208
|
-
|
|
209
|
-
// Calculate the pixel boundaries for the tile.
|
|
210
|
-
const window = [
|
|
211
|
-
tileX * tileWidth - offsetXPixel, // startX
|
|
212
|
-
tileY * tileHeight - offsetYPixel, // startY
|
|
213
|
-
(tileX + 1) * tileWidth - offsetXPixel, // endX (exclusive)
|
|
214
|
-
(tileY + 1) * tileHeight - offsetYPixel, // endY (exclusive)
|
|
215
|
-
];
|
|
216
|
-
|
|
217
|
-
const [windowStartX, windowStartY, windowEndX, windowEndY] = window;
|
|
218
|
-
|
|
219
|
-
const imageHeight = targetImage.getHeight();
|
|
220
|
-
const imageWidth = targetImage.getWidth();
|
|
221
|
-
|
|
222
|
-
// Determine the effective (valid) window inside the image:
|
|
223
|
-
const effectiveStartX = Math.max(0, windowStartX);
|
|
224
|
-
const effectiveStartY = Math.max(0, windowStartY);
|
|
225
|
-
const effectiveEndX = windowEndX;
|
|
226
|
-
const effectiveEndY = windowEndY;
|
|
227
|
-
|
|
228
|
-
// Calculate how many pixels are missing from the left and top due to negative windowStart.
|
|
229
|
-
const missingLeft = Math.max(0, 0 - windowStartX);
|
|
230
|
-
const missingTop = Math.max(0, 0 - windowStartY);
|
|
231
|
-
|
|
232
|
-
// Read only the valid window from the image.
|
|
233
|
-
const validWindow = [effectiveStartX, effectiveStartY, effectiveEndX, effectiveEndY];
|
|
234
|
-
|
|
235
|
-
// Read the raster data for the tile window with shifted origin.
|
|
236
|
-
if (missingLeft > 0 || missingTop > 0) {
|
|
237
|
-
// Prepare the final tile buffer and fill it with noDataValue.
|
|
238
|
-
const tileBuffer = this.createTileBuffer(this.options.format, tileWidth);
|
|
239
|
-
tileBuffer.fill(this.options.noDataValue);
|
|
240
|
-
|
|
241
|
-
// Calculate the width of the valid window.
|
|
242
|
-
const validWidth = Math.min(imageWidth, effectiveEndX - effectiveStartX);
|
|
243
|
-
const validHeight = Math.min(imageHeight, effectiveEndY - effectiveStartY);
|
|
244
|
-
|
|
245
|
-
// if the valid window is smaller than tile size, it gets the image size width and height, thus validRasterData.width must be used as below
|
|
246
|
-
const validRasterData = await targetImage.readRasters({ window: validWindow });
|
|
247
|
-
|
|
248
|
-
// FOR MULTI-BAND - the result is one array with sequentially typed bands, firstly all data for the band 0, then for band 1
|
|
249
|
-
// I think this is less practical then the commented solution above, but I do it so it works with the code in geoimage.ts in deck.gl-geoimage in function getColorValue.
|
|
250
|
-
const validImageData = Array(validRasterData.length * validRasterData[0].length);
|
|
251
|
-
validImageData.fill(this.options.noDataValue);
|
|
252
|
-
|
|
253
|
-
// Place the valid pixel data into the tile buffer.
|
|
254
|
-
for (let band = 0; band < validRasterData.length; band++) {
|
|
255
|
-
for (let row = 0; row < validHeight; row++) {
|
|
256
|
-
for (let col = 0; col < validWidth; col++) {
|
|
257
|
-
// Compute the destination position in the tile buffer.
|
|
258
|
-
// We shift by the number of missing pixels (if any) at the top/left.
|
|
259
|
-
const destRow = missingTop + row;
|
|
260
|
-
const destCol = missingLeft + col;
|
|
261
|
-
if (destRow < tileWidth && destCol < tileHeight) {
|
|
262
|
-
tileBuffer[destRow * tileWidth + destCol] = validRasterData[band][row * validRasterData.width + col];
|
|
263
|
-
} else {
|
|
264
|
-
console.log('error in assigning data to tile buffer');
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
tileBuffer.forEach((rasterValue, index) => {
|
|
269
|
-
validImageData[index * this.options.numOfChannels + band] = rasterValue;
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
return [validImageData];
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Read the raster data for the non shifted tile window.
|
|
276
|
-
const tileData = await targetImage.readRasters({ window, interleave: true });
|
|
277
|
-
// console.log(`data that starts at the left top corner of the tile ${tileX}, ${tileY}`);
|
|
278
|
-
return [tileData];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async getTile(x: number, y: number, z: number, bounds:Bounds, meshMaxError: number) {
|
|
282
|
-
const tileData = await this.getTileFromImage(x, y, z);
|
|
283
|
-
|
|
284
|
-
return this.geo.getMap({
|
|
285
|
-
rasters: [tileData[0]],
|
|
286
|
-
width: this.tileSize,
|
|
287
|
-
height: this.tileSize,
|
|
288
|
-
bounds,
|
|
289
|
-
}, this.options, meshMaxError);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Determines the data type (e.g., "Int32", "Float64") of a GeoTIFF image
|
|
294
|
-
* by reading its TIFF tags.
|
|
295
|
-
*
|
|
296
|
-
* @param {GeoTIFFImage} image - A GeoTIFF.js image.
|
|
297
|
-
* @returns {Promise<string>} - A string representing the data type.
|
|
298
|
-
*/
|
|
299
|
-
getDataTypeFromTags(image) {
|
|
300
|
-
// Retrieve the file directory containing TIFF tags.
|
|
301
|
-
const fileDirectory = image.getFileDirectory();
|
|
302
|
-
|
|
303
|
-
// In GeoTIFF, BitsPerSample (tag 258) and SampleFormat (tag 339) provide the type info.
|
|
304
|
-
// They can be either a single number or an array if there are multiple samples.
|
|
305
|
-
const sampleFormat = fileDirectory.SampleFormat; // Tag 339
|
|
306
|
-
const bitsPerSample = fileDirectory.BitsPerSample; // Tag 258
|
|
307
|
-
|
|
308
|
-
// If multiple bands exist, we assume all bands share the same type.
|
|
309
|
-
const format = (sampleFormat && typeof sampleFormat.length === 'number' && sampleFormat.length > 0)
|
|
310
|
-
? sampleFormat[0]
|
|
311
|
-
: sampleFormat;
|
|
312
|
-
|
|
313
|
-
const bits = (bitsPerSample && typeof bitsPerSample.length === 'number' && bitsPerSample.length > 0)
|
|
314
|
-
? bitsPerSample[0]
|
|
315
|
-
: bitsPerSample;
|
|
316
|
-
|
|
317
|
-
// Map the sample format to its corresponding type string.
|
|
318
|
-
// The common definitions are:
|
|
319
|
-
// 1: Unsigned integer
|
|
320
|
-
// 2: Signed integer
|
|
321
|
-
// 3: Floating point
|
|
322
|
-
let typePrefix;
|
|
323
|
-
if (format === 1) {
|
|
324
|
-
typePrefix = 'UInt';
|
|
325
|
-
} else if (format === 2) {
|
|
326
|
-
typePrefix = 'Int';
|
|
327
|
-
} else if (format === 3) {
|
|
328
|
-
typePrefix = 'Float';
|
|
329
|
-
} else {
|
|
330
|
-
typePrefix = 'Unknown';
|
|
331
|
-
}
|
|
332
|
-
// console.log(`data type ${typePrefix}${bits}`);
|
|
333
|
-
return `${typePrefix}${bits}`;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Extracts the noData value from a GeoTIFF.js image.
|
|
338
|
-
* Returns the noData value as a number if available, otherwise undefined.
|
|
339
|
-
*
|
|
340
|
-
* @param {GeoTIFFImage} image - The GeoTIFF.js image.
|
|
341
|
-
* @returns {number|undefined} The noData value as a number, or undefined if not available.
|
|
342
|
-
*/
|
|
343
|
-
getNoDataValue(image) {
|
|
344
|
-
// Attempt to retrieve the noData value via the GDAL method.
|
|
345
|
-
const noDataRaw = image.getGDALNoData();
|
|
346
|
-
|
|
347
|
-
if (noDataRaw === undefined || noDataRaw === null) {
|
|
348
|
-
console.log('noDataValue is undefined or null,raster might be displayed incorrectly.');
|
|
349
|
-
// No noData value is defined
|
|
350
|
-
return undefined;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// In geotiff.js, the noData value is typically returned as a string.
|
|
354
|
-
// Clean up the string by removing any null characters or extra whitespace.
|
|
355
|
-
const cleanedValue = String(noDataRaw).replace(/\0/g, '').trim();
|
|
356
|
-
|
|
357
|
-
const parsedValue = Number(cleanedValue);
|
|
358
|
-
return Number.isNaN(parsedValue) ? undefined : parsedValue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Retrieves the number of channels (samples per pixel) in a GeoTIFF image.
|
|
363
|
-
*
|
|
364
|
-
* @param {GeoTIFFImage} image - A GeoTIFFImage object from which to extract the number of channels.
|
|
365
|
-
* @returns {number} The number of channels in the image.
|
|
366
|
-
*/
|
|
367
|
-
getNumberOfChannels(image) {
|
|
368
|
-
return image.getSamplesPerPixel();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Retrieves the PlanarConfiguration value from a GeoTIFF image.
|
|
373
|
-
*
|
|
374
|
-
* @param {GeoTIFFImage} image - The GeoTIFF image object.
|
|
375
|
-
* @returns {number} The PlanarConfiguration value (1 for Chunky format, 2 for Planar format).
|
|
376
|
-
*/
|
|
377
|
-
getPlanarConfiguration(image) {
|
|
378
|
-
// Access the PlanarConfiguration tag directly
|
|
379
|
-
const planarConfiguration = image.fileDirectory.PlanarConfiguration;
|
|
380
|
-
|
|
381
|
-
// If the tag is not present, default to 1 (Chunky format)
|
|
382
|
-
if (planarConfiguration !== 1 && planarConfiguration !== 2) {
|
|
383
|
-
throw new Error('Invalid planar configuration.');
|
|
384
|
-
}
|
|
385
|
-
return planarConfiguration;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Creates a tile buffer of the specified size using a typed array corresponding to the provided data type.
|
|
390
|
-
*
|
|
391
|
-
* @param {string} dataType - A string specifying the data type (e.g., "Int32", "Float64", "UInt16", etc.).
|
|
392
|
-
* @param {number} tileSize - The width/height of the square tile.
|
|
393
|
-
* @returns {TypedArray} A typed array buffer of length tileSize * tileSize.
|
|
394
|
-
*/
|
|
395
|
-
createTileBuffer(dataType, tileSize) {
|
|
396
|
-
const length = tileSize * tileSize;
|
|
397
|
-
switch (dataType) {
|
|
398
|
-
case 'UInt8':
|
|
399
|
-
return new Uint8Array(length);
|
|
400
|
-
case 'Int8':
|
|
401
|
-
return new Int8Array(length);
|
|
402
|
-
case 'UInt16':
|
|
403
|
-
return new Uint16Array(length);
|
|
404
|
-
case 'Int16':
|
|
405
|
-
return new Int16Array(length);
|
|
406
|
-
case 'UInt32':
|
|
407
|
-
return new Uint32Array(length);
|
|
408
|
-
case 'Int32':
|
|
409
|
-
return new Int32Array(length);
|
|
410
|
-
case 'Float32':
|
|
411
|
-
return new Float32Array(length);
|
|
412
|
-
case 'Float64':
|
|
413
|
-
return new Float64Array(length);
|
|
414
|
-
default:
|
|
415
|
-
throw new Error(`Unsupported data type: ${dataType}`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export default CogTiles;
|
package/src/cogtiles/lzw.js
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-mixed-operators */
|
|
2
|
-
/* eslint-disable max-len */
|
|
3
|
-
/* eslint-disable no-bitwise */
|
|
4
|
-
/* eslint-disable no-param-reassign */
|
|
5
|
-
/* eslint-disable no-plusplus */
|
|
6
|
-
/* eslint-disable max-classes-per-file */
|
|
7
|
-
/* eslint 'max-len': [1, { code: 100, comments: 999, ignoreStrings: true, ignoreUrls: true }] */
|
|
8
|
-
|
|
9
|
-
// LITERALLY STOLEN CODE FROM GITHUB
|
|
10
|
-
// NOT MY RESPONSIBILITY
|
|
11
|
-
|
|
12
|
-
function decodeRowAcc(row, stride) {
|
|
13
|
-
let length = row.length - stride;
|
|
14
|
-
let offset = 0;
|
|
15
|
-
do {
|
|
16
|
-
for (let i = stride; i > 0; i--) {
|
|
17
|
-
row[offset + stride] += row[offset];
|
|
18
|
-
offset++;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
length -= stride;
|
|
22
|
-
} while (length > 0);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function decodeRowFloatingPoint(row, stride, bytesPerSample) {
|
|
26
|
-
let index = 0;
|
|
27
|
-
let count = row.length;
|
|
28
|
-
const wc = count / bytesPerSample;
|
|
29
|
-
|
|
30
|
-
while (count > stride) {
|
|
31
|
-
for (let i = stride; i > 0; --i) {
|
|
32
|
-
row[index + stride] += row[index];
|
|
33
|
-
++index;
|
|
34
|
-
}
|
|
35
|
-
count -= stride;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const copy = row.slice();
|
|
39
|
-
for (let i = 0; i < wc; ++i) {
|
|
40
|
-
for (let b = 0; b < bytesPerSample; ++b) {
|
|
41
|
-
row[(bytesPerSample * i) + b] = copy[((bytesPerSample - b - 1) * wc) + i];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function applyPredictor(
|
|
47
|
-
block,
|
|
48
|
-
predictor,
|
|
49
|
-
width,
|
|
50
|
-
height,
|
|
51
|
-
bitsPerSample,
|
|
52
|
-
planarConfiguration,
|
|
53
|
-
) {
|
|
54
|
-
if (!predictor || predictor === 1) {
|
|
55
|
-
return block;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
for (let i = 0; i < bitsPerSample.length; ++i) {
|
|
59
|
-
if (bitsPerSample[i] % 8 !== 0) {
|
|
60
|
-
throw new Error('When decoding with predictor, only multiple of 8 bits are supported.');
|
|
61
|
-
}
|
|
62
|
-
if (bitsPerSample[i] !== bitsPerSample[0]) {
|
|
63
|
-
throw new Error('When decoding with predictor, all samples must have the same size.');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const bytesPerSample = bitsPerSample[0] / 8;
|
|
68
|
-
const stride = planarConfiguration === 2 ? 1 : bitsPerSample.length;
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < height; ++i) {
|
|
71
|
-
// Last strip will be truncated if height % stripHeight != 0
|
|
72
|
-
if (i * stride * width * bytesPerSample >= block.byteLength) {
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
let row;
|
|
76
|
-
if (predictor === 2) { // horizontal prediction
|
|
77
|
-
switch (bitsPerSample[0]) {
|
|
78
|
-
case 8:
|
|
79
|
-
row = new Uint8Array(block, i * stride * width * bytesPerSample, stride * width * bytesPerSample);
|
|
80
|
-
break;
|
|
81
|
-
case 16:
|
|
82
|
-
row = new Uint16Array(block, i * stride * width * bytesPerSample, stride * width * bytesPerSample / 2);
|
|
83
|
-
break;
|
|
84
|
-
case 32:
|
|
85
|
-
row = new Uint32Array(block, i * stride * width * bytesPerSample, stride * width * bytesPerSample / 4);
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
throw new Error(`Predictor 2 not allowed with ${bitsPerSample[0]} bits per sample.`);
|
|
89
|
-
}
|
|
90
|
-
decodeRowAcc(row, stride, bytesPerSample);
|
|
91
|
-
} else if (predictor === 3) { // horizontal floating point
|
|
92
|
-
row = new Uint8Array(block, i * stride * width * bytesPerSample, stride * width * bytesPerSample);
|
|
93
|
-
decodeRowFloatingPoint(row, stride, bytesPerSample);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return block;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
class BaseDecoder {
|
|
100
|
-
async decode(fileDirectory, buffer) {
|
|
101
|
-
const decoded = await this.decodeBlock(buffer);
|
|
102
|
-
const predictor = fileDirectory.Predictor || 1;
|
|
103
|
-
if (predictor !== 1) {
|
|
104
|
-
const isTiled = !fileDirectory.StripOffsets;
|
|
105
|
-
const tileWidth = isTiled ? fileDirectory.TileWidth : fileDirectory.ImageWidth;
|
|
106
|
-
const tileHeight = isTiled
|
|
107
|
-
? fileDirectory.TileLength
|
|
108
|
-
: (
|
|
109
|
-
fileDirectory.RowsPerStrip || fileDirectory.ImageLength
|
|
110
|
-
);
|
|
111
|
-
return applyPredictor(
|
|
112
|
-
decoded,
|
|
113
|
-
predictor,
|
|
114
|
-
tileWidth,
|
|
115
|
-
tileHeight,
|
|
116
|
-
fileDirectory.BitsPerSample,
|
|
117
|
-
fileDirectory.PlanarConfiguration,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
return decoded;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const MIN_BITS = 9;
|
|
125
|
-
const CLEAR_CODE = 256; // clear code
|
|
126
|
-
const EOI_CODE = 257; // end of information
|
|
127
|
-
const MAX_BYTELENGTH = 12;
|
|
128
|
-
|
|
129
|
-
function getByte(array, position, length) {
|
|
130
|
-
const d = position % 8;
|
|
131
|
-
const a = Math.floor(position / 8);
|
|
132
|
-
const de = 8 - d;
|
|
133
|
-
const ef = (position + length) - ((a + 1) * 8);
|
|
134
|
-
let fg = (8 * (a + 2)) - (position + length);
|
|
135
|
-
const dg = ((a + 2) * 8) - position;
|
|
136
|
-
fg = Math.max(0, fg);
|
|
137
|
-
if (a >= array.length) {
|
|
138
|
-
console.warn('ran off the end of the buffer before finding EOI_CODE (end on input code)');
|
|
139
|
-
return EOI_CODE;
|
|
140
|
-
}
|
|
141
|
-
let chunk1 = array[a] & ((2 ** (8 - d)) - 1);
|
|
142
|
-
chunk1 <<= (length - de);
|
|
143
|
-
let chunks = chunk1;
|
|
144
|
-
if (a + 1 < array.length) {
|
|
145
|
-
let chunk2 = array[a + 1] >>> fg;
|
|
146
|
-
chunk2 <<= Math.max(0, (length - dg));
|
|
147
|
-
chunks += chunk2;
|
|
148
|
-
}
|
|
149
|
-
if (ef > 8 && a + 2 < array.length) {
|
|
150
|
-
const hi = ((a + 3) * 8) - (position + length);
|
|
151
|
-
const chunk3 = array[a + 2] >>> hi;
|
|
152
|
-
chunks += chunk3;
|
|
153
|
-
}
|
|
154
|
-
return chunks;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function appendReversed(dest, source) {
|
|
158
|
-
for (let i = source.length - 1; i >= 0; i--) {
|
|
159
|
-
dest.push(source[i]);
|
|
160
|
-
}
|
|
161
|
-
return dest;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function decompress(input) {
|
|
165
|
-
const dictionaryIndex = new Uint16Array(4093);
|
|
166
|
-
const dictionaryChar = new Uint8Array(4093);
|
|
167
|
-
for (let i = 0; i <= 257; i++) {
|
|
168
|
-
dictionaryIndex[i] = 4096;
|
|
169
|
-
dictionaryChar[i] = i;
|
|
170
|
-
}
|
|
171
|
-
let dictionaryLength = 258;
|
|
172
|
-
let byteLength = MIN_BITS;
|
|
173
|
-
let position = 0;
|
|
174
|
-
|
|
175
|
-
function initDictionary() {
|
|
176
|
-
dictionaryLength = 258;
|
|
177
|
-
byteLength = MIN_BITS;
|
|
178
|
-
}
|
|
179
|
-
function getNext(array) {
|
|
180
|
-
const byte = getByte(array, position, byteLength);
|
|
181
|
-
position += byteLength;
|
|
182
|
-
return byte;
|
|
183
|
-
}
|
|
184
|
-
function addToDictionary(i, c) {
|
|
185
|
-
dictionaryChar[dictionaryLength] = c;
|
|
186
|
-
dictionaryIndex[dictionaryLength] = i;
|
|
187
|
-
dictionaryLength++;
|
|
188
|
-
return dictionaryLength - 1;
|
|
189
|
-
}
|
|
190
|
-
function getDictionaryReversed(n) {
|
|
191
|
-
const rev = [];
|
|
192
|
-
for (let i = n; i !== 4096; i = dictionaryIndex[i]) {
|
|
193
|
-
rev.push(dictionaryChar[i]);
|
|
194
|
-
}
|
|
195
|
-
return rev;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const result = [];
|
|
199
|
-
initDictionary();
|
|
200
|
-
const array = new Uint8Array(input);
|
|
201
|
-
let code = getNext(array);
|
|
202
|
-
let oldCode;
|
|
203
|
-
while (code !== EOI_CODE) {
|
|
204
|
-
if (code === CLEAR_CODE) {
|
|
205
|
-
initDictionary();
|
|
206
|
-
code = getNext(array);
|
|
207
|
-
while (code === CLEAR_CODE) {
|
|
208
|
-
code = getNext(array);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (code === EOI_CODE) {
|
|
212
|
-
break;
|
|
213
|
-
} else if (code > CLEAR_CODE) {
|
|
214
|
-
throw new Error(`corrupted code at scanline ${code}`);
|
|
215
|
-
} else {
|
|
216
|
-
const val = getDictionaryReversed(code);
|
|
217
|
-
appendReversed(result, val);
|
|
218
|
-
oldCode = code;
|
|
219
|
-
}
|
|
220
|
-
} else if (code < dictionaryLength) {
|
|
221
|
-
const val = getDictionaryReversed(code);
|
|
222
|
-
appendReversed(result, val);
|
|
223
|
-
addToDictionary(oldCode, val[val.length - 1]);
|
|
224
|
-
oldCode = code;
|
|
225
|
-
} else {
|
|
226
|
-
const oldVal = getDictionaryReversed(oldCode);
|
|
227
|
-
if (!oldVal) {
|
|
228
|
-
throw new Error(
|
|
229
|
-
`Bogus entry. Not in dictionary,
|
|
230
|
-
${oldCode} / ${dictionaryLength},
|
|
231
|
-
position: ${position}`,
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
appendReversed(result, oldVal);
|
|
235
|
-
result.push(oldVal[oldVal.length - 1]);
|
|
236
|
-
addToDictionary(oldCode, oldVal[oldVal.length - 1]);
|
|
237
|
-
oldCode = code;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (dictionaryLength + 1 >= (2 ** byteLength)) {
|
|
241
|
-
if (byteLength === MAX_BYTELENGTH) {
|
|
242
|
-
oldCode = undefined;
|
|
243
|
-
} else {
|
|
244
|
-
byteLength++;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
code = getNext(array);
|
|
248
|
-
}
|
|
249
|
-
return new Uint8Array(result);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export default class LZWDecoder extends BaseDecoder {
|
|
253
|
-
decodeBlock(buffer) {
|
|
254
|
-
return decompress(buffer, false).buffer;
|
|
255
|
-
}
|
|
256
|
-
}
|