@gisatcz/deckgl-geolib 1.12.0-dev.1 → 1.12.0-dev.3
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/cjs/index.js +3374 -13043
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.min.js +3 -4
- package/dist/cjs/index.min.js.map +1 -1
- package/dist/cjs/types/cogbitmaplayer/CogBitmapLayer.d.ts +1 -3
- package/dist/cjs/types/cogtiles/cogtiles.d.ts +102 -16
- package/dist/cjs/types/geoimage/geoimage.d.ts +1 -0
- package/dist/esm/index.js +3374 -13043
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.min.js +3 -4
- package/dist/esm/index.min.js.map +1 -1
- package/dist/esm/types/cogbitmaplayer/CogBitmapLayer.d.ts +1 -3
- package/dist/esm/types/cogtiles/cogtiles.d.ts +102 -16
- package/dist/esm/types/geoimage/geoimage.d.ts +1 -0
- package/package.json +1 -2
- package/src/cogbitmaplayer/CogBitmapLayer.ts +17 -6
- package/src/cogterrainlayer/CogTerrainLayer.ts +2 -2
- package/src/cogtiles/cogtiles.ts +319 -215
- package/src/geoimage/geoimage.ts +8 -6
package/src/cogtiles/cogtiles.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/* eslint 'max-len': [1, { code: 100, comments: 999, ignoreStrings: true, ignoreUrls: true }] */
|
|
2
2
|
// COG loading
|
|
3
|
-
import {
|
|
4
|
-
import { SourceHttp } from '@chunkd/source-http';
|
|
3
|
+
import { fromUrl, GeoTIFF, GeoTIFFImage } from 'geotiff';
|
|
5
4
|
|
|
6
5
|
// Image compression support
|
|
7
|
-
import { inflate } from 'pako';
|
|
8
|
-
import jpeg from 'jpeg-js';
|
|
9
6
|
import { worldToLngLat } from '@math.gl/web-mercator';
|
|
10
|
-
import LZWDecoder from './lzw';
|
|
11
7
|
|
|
12
8
|
// Bitmap styling
|
|
13
9
|
import GeoImage, { GeoImageOptions } from '../geoimage/geoimage.ts';
|
|
@@ -16,28 +12,32 @@ export type Bounds = [minX: number, minY: number, maxX: number, maxY: number];
|
|
|
16
12
|
|
|
17
13
|
const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
|
|
18
14
|
const EARTH_HALF_CIRCUMFERENCE = EARTH_CIRCUMFERENCE / 2;
|
|
15
|
+
const webMercatorOrigin = [-20037508.342789244, 20037508.342789244];
|
|
16
|
+
const webMercatorRes0 = 156543.03125;
|
|
19
17
|
|
|
20
18
|
const CogTilesGeoImageOptionsDefaults = {
|
|
21
19
|
blurredTexture: true,
|
|
22
20
|
};
|
|
23
21
|
|
|
24
22
|
class CogTiles {
|
|
25
|
-
cog:
|
|
23
|
+
cog: GeoTIFF;
|
|
24
|
+
|
|
25
|
+
cogZoomLookup = [0];
|
|
26
|
+
|
|
27
|
+
cogResolutionLookup = [0];
|
|
28
|
+
|
|
29
|
+
cogOrigin = [0, 0];
|
|
26
30
|
|
|
27
31
|
zoomRange = [0, 0];
|
|
28
32
|
|
|
29
33
|
tileSize: number;
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
lowestOriginTileSize = 0;
|
|
35
|
+
bounds: Bounds;
|
|
34
36
|
|
|
35
37
|
loaded: boolean = false;
|
|
36
38
|
|
|
37
39
|
geo: GeoImage = new GeoImage();
|
|
38
40
|
|
|
39
|
-
lzw: LZWDecoder = new LZWDecoder();
|
|
40
|
-
|
|
41
41
|
options: GeoImageOptions;
|
|
42
42
|
|
|
43
43
|
constructor(options: GeoImageOptions) {
|
|
@@ -45,48 +45,32 @@ class CogTiles {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async initializeCog(url: string) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
this.tileSize = this.getTileSize(this.cog);
|
|
62
|
-
|
|
63
|
-
this.lowestOriginTileOffset = this.getImageTileIndex(
|
|
64
|
-
this.cog.images[this.cog.images.length - 1],
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
this.zoomRange = this.getZoomRange(this.cog);
|
|
68
|
-
|
|
69
|
-
return this.cog;
|
|
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);
|
|
70
59
|
}
|
|
71
60
|
|
|
72
|
-
|
|
73
|
-
return
|
|
61
|
+
getZoomRange() {
|
|
62
|
+
return this.zoomRange;
|
|
74
63
|
}
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const minZoom = this.getZoomLevelFromResolution(
|
|
80
|
-
cog.images[cog.images.length - 1].tileSize.width,
|
|
81
|
-
img.resolution[0],
|
|
82
|
-
);
|
|
83
|
-
const maxZoom = minZoom + (cog.images.length - 1);
|
|
65
|
+
calculateZoomRange(img: GeoTIFFImage, imgCount: number) {
|
|
66
|
+
const maxZoom = this.getZoomLevelFromResolution(img.getTileWidth(), img.getResolution()[0]);
|
|
67
|
+
const minZoom = maxZoom - (imgCount - 1);
|
|
84
68
|
|
|
85
69
|
return [minZoom, maxZoom];
|
|
86
70
|
}
|
|
87
71
|
|
|
88
|
-
|
|
89
|
-
const
|
|
72
|
+
calculateBoundsAsLatLon(image: GeoTIFFImage) {
|
|
73
|
+
const bbox = image.getBoundingBox();
|
|
90
74
|
|
|
91
75
|
const minX = Math.min(bbox[0], bbox[2]);
|
|
92
76
|
const maxX = Math.max(bbox[0], bbox[2]);
|
|
@@ -99,40 +83,14 @@ class CogTiles {
|
|
|
99
83
|
return [minXYDeg[0], minXYDeg[1], maxXYDeg[0], maxXYDeg[1]] as [number, number, number, number];
|
|
100
84
|
}
|
|
101
85
|
|
|
102
|
-
getOriginAsLatLon(cog: Tiff) {
|
|
103
|
-
const { origin } = cog.images[cog.images.length - 1];
|
|
104
|
-
return this.getLatLon(origin);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
getImageTileIndex(img: TiffImage) {
|
|
108
|
-
const ax = EARTH_HALF_CIRCUMFERENCE + img.origin[0];
|
|
109
|
-
const ay = -(EARTH_HALF_CIRCUMFERENCE + (img.origin[1] - EARTH_CIRCUMFERENCE));
|
|
110
|
-
// let mpt = img.resolution[0] * img.tileSize.width;
|
|
111
|
-
|
|
112
|
-
const mpt = img.tileSize.width * this.getResolutionFromZoomLevel(
|
|
113
|
-
img.tileSize.width,
|
|
114
|
-
this.getZoomLevelFromResolution(
|
|
115
|
-
img.tileSize.width,
|
|
116
|
-
img.resolution[0],
|
|
117
|
-
),
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const ox = Math.round(ax / mpt);
|
|
121
|
-
const oy = Math.round(ay / mpt);
|
|
122
|
-
|
|
123
|
-
const oz = this.getZoomLevelFromResolution(img.tileSize.width, img.resolution[0]);
|
|
124
|
-
|
|
125
|
-
return [ox, oy, oz];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
getResolutionFromZoomLevel(tileSize: number, z: number) {
|
|
129
|
-
return (EARTH_CIRCUMFERENCE / tileSize) / (2 ** z);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
86
|
getZoomLevelFromResolution(tileSize: number, resolution: number) {
|
|
133
87
|
return Math.round(Math.log2(EARTH_CIRCUMFERENCE / (resolution * tileSize)));
|
|
134
88
|
}
|
|
135
89
|
|
|
90
|
+
getBoundsAsLatLon() {
|
|
91
|
+
return this.bounds;
|
|
92
|
+
}
|
|
93
|
+
|
|
136
94
|
getLatLon(input: number[]) {
|
|
137
95
|
const ax = EARTH_HALF_CIRCUMFERENCE + input[0];
|
|
138
96
|
const ay = -(EARTH_HALF_CIRCUMFERENCE + (input[1] - EARTH_CIRCUMFERENCE));
|
|
@@ -147,169 +105,315 @@ class CogTiles {
|
|
|
147
105
|
return cartographicPositionAdjusted;
|
|
148
106
|
}
|
|
149
107
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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;
|
|
162
152
|
}
|
|
163
|
-
const tilesX = img.tileCount.x;
|
|
164
|
-
const tilesY = img.tileCount.y;
|
|
165
|
-
// console.log("------OFFSET IS------ " + offset[0] + " ; " + offset[1])
|
|
166
|
-
|
|
167
|
-
const ox = offset[0];
|
|
168
|
-
const oy = offset[1];
|
|
169
153
|
|
|
170
|
-
|
|
154
|
+
return [zoomLookup, resolutionLookup];
|
|
155
|
+
}
|
|
171
156
|
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
}
|
|
174
183
|
|
|
175
|
-
|
|
176
|
-
|
|
184
|
+
async getTileFromImage(tileX, tileY, zoom) {
|
|
185
|
+
const imageIndex = this.getImageIndexForZoomLevel(zoom);
|
|
186
|
+
const targetImage = await this.cog.getImage(imageIndex);
|
|
177
187
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
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.');
|
|
184
193
|
}
|
|
185
194
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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;
|
|
192
270
|
});
|
|
193
|
-
bitsPerSample = c;
|
|
194
|
-
} else {
|
|
195
|
-
[bitsPerSample] = bitsPerSample;
|
|
196
271
|
}
|
|
272
|
+
return [validImageData];
|
|
197
273
|
}
|
|
198
274
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
// console.log(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (x - ox >= 0 && y - oy >= 0 && x - ox < tilesX && y - oy < tilesY) {
|
|
205
|
-
// console.log(`getting tile: ${[x - ox, y - oy]}`);
|
|
206
|
-
const tile = await img.getTile((x - ox), (y - oy));
|
|
207
|
-
// console.time("Request to data time: ")
|
|
208
|
-
|
|
209
|
-
switch (img.compression) {
|
|
210
|
-
case 'image/jpeg':
|
|
211
|
-
decoded = jpeg.decode(tile!.bytes, { useTArray: true });
|
|
212
|
-
break;
|
|
213
|
-
case 'application/deflate':
|
|
214
|
-
decoded = await inflate(tile!.bytes);
|
|
215
|
-
break;
|
|
216
|
-
case 'application/lzw':
|
|
217
|
-
decoded = this.lzw.decodeBlock(tile!.bytes.buffer);
|
|
218
|
-
break;
|
|
219
|
-
default:
|
|
220
|
-
console.warn(`Unexpected compression method: ${img.compression}`);
|
|
221
|
-
}
|
|
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
|
+
}
|
|
222
280
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
case 'int8':
|
|
234
|
-
decompressedFormatted = new Int8Array(decoded.buffer); break;
|
|
235
|
-
case 'int16':
|
|
236
|
-
decompressedFormatted = new Int16Array(decoded.buffer); break;
|
|
237
|
-
case 'int32':
|
|
238
|
-
decompressedFormatted = new Int32Array(decoded.buffer); break;
|
|
239
|
-
case 'float32':
|
|
240
|
-
decompressedFormatted = new Float32Array(decoded.buffer); break;
|
|
241
|
-
case 'float64':
|
|
242
|
-
decompressedFormatted = new Float64Array(decoded.buffer); break;
|
|
243
|
-
default: decompressedFormatted = null;
|
|
244
|
-
}
|
|
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
|
+
}
|
|
245
291
|
|
|
246
|
-
|
|
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
|
+
}
|
|
247
335
|
|
|
248
|
-
|
|
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
|
+
}
|
|
249
352
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
height: this.tileSize,
|
|
254
|
-
bounds,
|
|
255
|
-
}, this.options, meshMaxError);
|
|
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();
|
|
256
356
|
|
|
257
|
-
|
|
357
|
+
const parsedValue = Number(cleanedValue);
|
|
358
|
+
return Number.isNaN(parsedValue) ? undefined : parsedValue;
|
|
359
|
+
}
|
|
258
360
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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();
|
|
262
369
|
}
|
|
263
370
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
case 32: dataType = 'uint32'; break;
|
|
278
|
-
default: dataType = null;
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
|
-
case 2: // Signed integer
|
|
282
|
-
switch (uniqueBitsPerSample) {
|
|
283
|
-
case 8: dataType = 'int8'; break;
|
|
284
|
-
case 16: dataType = 'int16'; break;
|
|
285
|
-
case 32: dataType = 'int32'; break;
|
|
286
|
-
default: dataType = null;
|
|
287
|
-
}
|
|
288
|
-
break;
|
|
289
|
-
case 3: // Floating point
|
|
290
|
-
switch (uniqueBitsPerSample) {
|
|
291
|
-
case 32: dataType = 'float32'; break;
|
|
292
|
-
case 64: dataType = 'float64'; break;
|
|
293
|
-
default: dataType = null;
|
|
294
|
-
}
|
|
295
|
-
break;
|
|
296
|
-
default:
|
|
297
|
-
throw new Error('Unknown data format.');
|
|
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.');
|
|
298
384
|
}
|
|
299
|
-
|
|
300
|
-
return dataType;
|
|
385
|
+
return planarConfiguration;
|
|
301
386
|
}
|
|
302
387
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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}`);
|
|
311
416
|
}
|
|
312
|
-
return undefined;
|
|
313
417
|
}
|
|
314
418
|
}
|
|
315
419
|
|
package/src/geoimage/geoimage.ts
CHANGED
|
@@ -45,7 +45,8 @@ export type GeoImageOptions = {
|
|
|
45
45
|
clampToTerrain?: ClampToTerrainOptions | boolean, // terrainDrawMode: 'drape',
|
|
46
46
|
terrainColor?: Array<number> | chroma.Color,
|
|
47
47
|
terrainSkirtHeight?: number,
|
|
48
|
-
terrainMinValue?: number
|
|
48
|
+
terrainMinValue?: number,
|
|
49
|
+
planarConfig?: number,
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export const DefaultGeoImageOptions: GeoImageOptions = {
|
|
@@ -77,6 +78,7 @@ export const DefaultGeoImageOptions: GeoImageOptions = {
|
|
|
77
78
|
terrainColor: [133, 133, 133, 255],
|
|
78
79
|
terrainSkirtHeight: 100,
|
|
79
80
|
terrainMinValue: 0,
|
|
81
|
+
planarConfig: undefined,
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
export default class GeoImage {
|
|
@@ -291,9 +293,9 @@ export default class GeoImage {
|
|
|
291
293
|
const size = width * height * 4;
|
|
292
294
|
// const size = width * height;
|
|
293
295
|
|
|
294
|
-
if (!options.noDataValue) {
|
|
295
|
-
|
|
296
|
-
}
|
|
296
|
+
// if (!options.noDataValue) {
|
|
297
|
+
// console.log('Missing noData value. Raster might be displayed incorrectly.');
|
|
298
|
+
// }
|
|
297
299
|
optionsLocal.unidentifiedColor = this.getColorFromChromaType(optionsLocal.unidentifiedColor);
|
|
298
300
|
optionsLocal.nullColor = this.getColorFromChromaType(optionsLocal.nullColor);
|
|
299
301
|
optionsLocal.clippedColor = this.getColorFromChromaType(optionsLocal.clippedColor);
|
|
@@ -387,8 +389,8 @@ export default class GeoImage {
|
|
|
387
389
|
optionsLocal.colorScaleValueRange = this.getMinMax(channel, optionsLocal);
|
|
388
390
|
// console.log('data min: ' + optionsLocal.rangeMin + ', max: ' + optionsLocal.rangeMax);
|
|
389
391
|
}
|
|
390
|
-
const numOfChannels = channel.length / (width * height);
|
|
391
|
-
const colorData = this.getColorValue(channel, optionsLocal, size, numOfChannels);
|
|
392
|
+
// const numOfChannels = channel.length / (width * height);
|
|
393
|
+
const colorData = this.getColorValue(channel, optionsLocal, size, optionsLocal.numOfChannels);
|
|
392
394
|
colorData.forEach((value, index) => {
|
|
393
395
|
imageData.data[index] = value;
|
|
394
396
|
});
|