mapshaper 0.7.13 → 0.7.14

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/www/mapshaper.js CHANGED
@@ -1443,6 +1443,10 @@
1443
1443
  return layerHasPaths(lyr) || layerHasPoints(lyr);
1444
1444
  }
1445
1445
 
1446
+ function layerHasRaster(lyr) {
1447
+ return !!(lyr && lyr.raster_type && lyr.raster);
1448
+ }
1449
+
1446
1450
  function layerIsGeometric(lyr) {
1447
1451
  return !!lyr.geometry_type; // only checks type, includes empty layers
1448
1452
  }
@@ -4772,6 +4776,628 @@
4772
4776
  pathIsRectangle: pathIsRectangle
4773
4777
  });
4774
4778
 
4779
+ var DEFAULT_MAX_PREVIEW_PIXELS = 4e6;
4780
+
4781
+ function getRasterGrid(raster) {
4782
+ return raster && (raster.grid || raster);
4783
+ }
4784
+
4785
+ function getRasterView(raster) {
4786
+ return raster && (raster.view || raster);
4787
+ }
4788
+
4789
+ function getRasterPreview(raster) {
4790
+ var view = getRasterView(raster);
4791
+ return view && (view.preview || raster.preview);
4792
+ }
4793
+
4794
+ function getRasterBBox(raster) {
4795
+ var grid = getRasterGrid(raster);
4796
+ return grid && grid.bbox || raster && raster.bbox || null;
4797
+ }
4798
+
4799
+ function getRasterTransform(raster) {
4800
+ var grid = getRasterGrid(raster);
4801
+ return grid && grid.transform || raster && raster.transform || null;
4802
+ }
4803
+
4804
+ function getRasterWidth(raster) {
4805
+ var grid = getRasterGrid(raster);
4806
+ return grid && grid.width || 0;
4807
+ }
4808
+
4809
+ function getRasterHeight(raster) {
4810
+ var grid = getRasterGrid(raster);
4811
+ return grid && grid.height || 0;
4812
+ }
4813
+
4814
+ function getRasterBandCount(raster) {
4815
+ var grid = getRasterGrid(raster);
4816
+ return grid && grid.bands || 0;
4817
+ }
4818
+
4819
+ function getRasterPixelType(raster) {
4820
+ var grid = getRasterGrid(raster);
4821
+ return grid && grid.pixelType || null;
4822
+ }
4823
+
4824
+ function copyRasterData(raster) {
4825
+ var copy = utils.extend({}, raster);
4826
+ if (raster.grid) {
4827
+ copy.grid = copyRasterGrid(raster.grid);
4828
+ }
4829
+ if (raster.view) {
4830
+ copy.view = copyRasterView(raster.view);
4831
+ }
4832
+ if (raster.derivation) {
4833
+ copy.derivation = utils.extend({}, raster.derivation);
4834
+ if (raster.derivation.bands) copy.derivation.bands = copyObjectOrArray(raster.derivation.bands);
4835
+ }
4836
+ if (raster.source) copy.source = utils.extend({}, raster.source);
4837
+
4838
+ // Back-compat for the first raster model.
4839
+ if (raster.pixels) copy.pixels = copyTypedArray(raster.pixels);
4840
+ if (raster.preview) copy.preview = copyRasterPreview(raster.preview);
4841
+ if (raster.bbox) copy.bbox = raster.bbox.concat();
4842
+ if (raster.transform) copy.transform = copyObjectOrArray(raster.transform);
4843
+ return copy;
4844
+ }
4845
+
4846
+ function copyRasterGrid(grid) {
4847
+ var copy = utils.extend({}, grid);
4848
+ if (grid.samples) copy.samples = copyTypedArray(grid.samples);
4849
+ if (grid.sampleBands) copy.sampleBands = grid.sampleBands.concat();
4850
+ if (grid.bbox) copy.bbox = grid.bbox.concat();
4851
+ if (grid.transform) copy.transform = copyObjectOrArray(grid.transform);
4852
+ return copy;
4853
+ }
4854
+
4855
+ function copyRasterView(view) {
4856
+ var copy = utils.extend({}, view);
4857
+ if (view.recipe) copy.recipe = copyObjectOrArray(view.recipe);
4858
+ if (view.preview) copy.preview = copyRasterPreview(view.preview);
4859
+ if (view.scalingStats) copy.scalingStats = copyObjectOrArray(view.scalingStats);
4860
+ return copy;
4861
+ }
4862
+
4863
+ function copyRasterPreview(preview) {
4864
+ var copy = utils.extend({}, preview);
4865
+ delete copy.canvas;
4866
+ if (preview.pixels) copy.pixels = copyTypedArray(preview.pixels);
4867
+ return copy;
4868
+ }
4869
+
4870
+ function copyTypedArray(arr) {
4871
+ if (Array.isArray(arr)) return arr.map(copyTypedArray);
4872
+ return arr && arr.slice ? arr.slice() : arr;
4873
+ }
4874
+
4875
+ function createRasterPreview(raster, opts) {
4876
+ opts = opts || {};
4877
+ var grid = getRasterGrid(raster);
4878
+ var recipe = getRasterViewRecipe(grid, raster.view && raster.view.recipe, opts);
4879
+ var stats = getRasterViewScalingStats(raster, recipe);
4880
+ var maxPixels = opts.maxPixels || opts.raster_max_pixels || opts.rasterMaxPixels || DEFAULT_MAX_PREVIEW_PIXELS;
4881
+ var scale = Math.min(1, Math.sqrt(maxPixels / (grid.width * grid.height)));
4882
+ var width = Math.max(1, Math.round(grid.width * scale));
4883
+ var height = Math.max(1, Math.round(grid.height * scale));
4884
+ return renderRasterPreview(grid, recipe, width, height, stats);
4885
+ }
4886
+
4887
+ function getRasterViewRecipe(grid, recipeArg, opts) {
4888
+ var recipe = Object.assign({}, recipeArg || {});
4889
+ var scaling = opts && opts.scaling || recipe.scaling || getDefaultRasterScaling(grid);
4890
+ var scaleRange = opts && (opts.scale_range || opts.scaleRange) || recipe.scaleRange;
4891
+ var percentileRange = opts && (opts.percentile_range || opts.percentileRange) || recipe.percentileRange;
4892
+ if (scaling != 'none' && scaling != 'minmax' && scaling != 'percentile') {
4893
+ stop$1('Unsupported raster scaling method:', scaling);
4894
+ }
4895
+ return Object.assign(recipe, {
4896
+ type: recipe.type || (grid.bands >= 3 ? 'rgb' : 'gray'),
4897
+ bands: recipe.bands || grid.sampleBands || null,
4898
+ scaling: scaling,
4899
+ scaleRange: parseRangeOption(scaleRange, [0, 100], 'scale-range'),
4900
+ percentileRange: parseRangeOption(percentileRange, [2, 98], 'percentile-range')
4901
+ });
4902
+ }
4903
+
4904
+ function renderRasterPreview(grid, recipe, width, height, statsArg) {
4905
+ recipe = getRasterViewRecipe(grid, recipe);
4906
+ if (useFastRawEightBitRendering(grid, recipe)) {
4907
+ return renderRawEightBitPreview(grid, width, height, null);
4908
+ }
4909
+ return renderRasterGridPreview(grid, recipe, width, height, statsArg || getRasterScalingStats(grid, recipe), null);
4910
+ }
4911
+
4912
+ function renderRasterViewportPreview(grid, recipe, bbox, width, height, statsArg) {
4913
+ recipe = getRasterViewRecipe(grid, recipe);
4914
+ if (!intersectBboxes(grid.bbox, bbox)) return null;
4915
+ if (useFastRawEightBitRendering(grid, recipe)) {
4916
+ return Object.assign(renderRawEightBitPreview(grid, width, height, bbox), {
4917
+ bbox: bbox.concat()
4918
+ });
4919
+ }
4920
+ return Object.assign(renderRasterGridPreview(grid, recipe, width, height, statsArg || getRasterScalingStats(grid, recipe), bbox), {
4921
+ bbox: bbox.concat()
4922
+ });
4923
+ }
4924
+
4925
+ function getRasterScalingStats(grid, recipe) {
4926
+ recipe = getRasterViewRecipe(grid, recipe);
4927
+ return getScalingStats(grid.samples, grid.bands, grid.nodata, recipe);
4928
+ }
4929
+
4930
+ function getRasterViewScalingStats(raster, recipeArg) {
4931
+ var grid = getRasterGrid(raster);
4932
+ var view = getRasterView(raster);
4933
+ var recipe = getRasterViewRecipe(grid, recipeArg || view && view.recipe);
4934
+ var key, cached, stats;
4935
+ if (recipe.scaling == 'none') return null;
4936
+ key = getRasterScalingStatsKey(grid, recipe);
4937
+ cached = view && view.scalingStats;
4938
+ if (cached && cached.key == key) return cached.stats;
4939
+ stats = getRasterScalingStats(grid, recipe);
4940
+ if (view) {
4941
+ view.scalingStats = {
4942
+ key: key,
4943
+ stats: stats
4944
+ };
4945
+ }
4946
+ return stats;
4947
+ }
4948
+
4949
+ function renderRasterGridPreview(grid, recipe, width, height, statsArg, sourceBbox) {
4950
+ var samples = grid.samples;
4951
+ var bands = grid.bands;
4952
+ var noData = grid.nodata;
4953
+ var pixels = new Uint8ClampedArray(width * height * 4);
4954
+ var stats = statsArg || getScalingStats(samples, bands, noData, recipe);
4955
+ var sourceRange = recipe.scaling == 'none' ? getPixelTypeRange(grid.pixelType) : null;
4956
+ var displayRange = getDisplayRange(recipe.scaleRange);
4957
+ var src, dest, val, isNoData, j;
4958
+ for (var y = 0; y < height; y++) {
4959
+ for (var x = 0; x < width; x++) {
4960
+ src = getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands);
4961
+ dest = (y * width + x) * 4;
4962
+ isNoData = noData !== null && noData !== undefined && allSamplesAreNoData(samples, src, bands, noData);
4963
+ if (bands == 1) {
4964
+ val = scaleSample(samples[src], stats && stats[0], sourceRange, displayRange);
4965
+ pixels[dest] = val;
4966
+ pixels[dest + 1] = val;
4967
+ pixels[dest + 2] = val;
4968
+ pixels[dest + 3] = isNoData ? 0 : 255;
4969
+ } else {
4970
+ for (j = 0; j < 3; j++) {
4971
+ pixels[dest + j] = scaleSample(samples[src + j], stats && stats[j], sourceRange, displayRange);
4972
+ }
4973
+ pixels[dest + 3] = isNoData ? 0 : bands >= 4 ? scaleSample(samples[src + 3], stats && stats[3], sourceRange, [0, 255]) : 255;
4974
+ }
4975
+ }
4976
+ }
4977
+ return {
4978
+ width: width,
4979
+ height: height,
4980
+ bands: 4,
4981
+ pixelType: 'uint8',
4982
+ colorModel: 'rgba',
4983
+ pixels: pixels
4984
+ };
4985
+ }
4986
+
4987
+ function renderRawEightBitPreview(grid, width, height, sourceBbox) {
4988
+ var samples = grid.samples;
4989
+ var bands = grid.bands;
4990
+ var pixels = new Uint8ClampedArray(width * height * 4);
4991
+ var src, dest, val;
4992
+ for (var y = 0; y < height; y++) {
4993
+ for (var x = 0; x < width; x++) {
4994
+ src = getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands);
4995
+ dest = (y * width + x) * 4;
4996
+ if (bands == 1) {
4997
+ val = samples[src];
4998
+ pixels[dest] = val;
4999
+ pixels[dest + 1] = val;
5000
+ pixels[dest + 2] = val;
5001
+ pixels[dest + 3] = 255;
5002
+ } else {
5003
+ pixels[dest] = samples[src];
5004
+ pixels[dest + 1] = samples[src + 1];
5005
+ pixels[dest + 2] = samples[src + 2];
5006
+ pixels[dest + 3] = bands >= 4 ? samples[src + 3] : 255;
5007
+ }
5008
+ }
5009
+ }
5010
+ return {
5011
+ width: width,
5012
+ height: height,
5013
+ bands: 4,
5014
+ pixelType: 'uint8',
5015
+ colorModel: 'rgba',
5016
+ pixels: pixels
5017
+ };
5018
+ }
5019
+
5020
+ function getPreviewSourceOffset(grid, sourceBbox, x, y, width, height, bands) {
5021
+ var sx, sy, mapX, mapY, rb;
5022
+ if (!sourceBbox) {
5023
+ sy = Math.min(grid.height - 1, Math.floor((y + 0.5) * grid.height / height));
5024
+ sx = Math.min(grid.width - 1, Math.floor((x + 0.5) * grid.width / width));
5025
+ } else {
5026
+ rb = grid.bbox;
5027
+ mapX = sourceBbox[0] + (x + 0.5) / width * (sourceBbox[2] - sourceBbox[0]);
5028
+ mapY = sourceBbox[3] - (y + 0.5) / height * (sourceBbox[3] - sourceBbox[1]);
5029
+ sx = Math.max(0, Math.min(grid.width - 1, Math.floor((mapX - rb[0]) / (rb[2] - rb[0]) * grid.width)));
5030
+ sy = Math.max(0, Math.min(grid.height - 1, Math.floor((rb[3] - mapY) / (rb[3] - rb[1]) * grid.height)));
5031
+ }
5032
+ return (sy * grid.width + sx) * bands;
5033
+ }
5034
+
5035
+ function clipRasterToBBox(lyr, bbox, opts) {
5036
+ var raster = lyr.raster;
5037
+ var grid = getRasterGrid(raster);
5038
+ var clipBbox = intersectBboxes(grid.bbox, bbox);
5039
+ if (!clipBbox) {
5040
+ warn('Raster clipping rectangle does not intersect the raster layer');
5041
+ return false;
5042
+ }
5043
+ var crop = getRasterCrop(grid, clipBbox);
5044
+ if (crop.width === 0 || crop.height === 0) {
5045
+ warn('Raster clipping rectangle does not intersect the raster layer');
5046
+ return false;
5047
+ }
5048
+ noteLayerWillChange(lyr, {operation: 'clipRasterToBBox', unit: 'raster'});
5049
+ raster.grid = cropRasterGrid(grid, crop, clipBbox);
5050
+ raster.view = raster.view || {};
5051
+ raster.view.preview = createRasterPreview(raster, opts || {});
5052
+ clearLegacyRasterFields(raster);
5053
+ markLayerChanged(lyr, {operation: 'clipRasterToBBox', unit: 'raster'});
5054
+ return true;
5055
+ }
5056
+
5057
+ function intersectBboxes(a, b) {
5058
+ var bbox = [
5059
+ Math.max(a[0], b[0]),
5060
+ Math.max(a[1], b[1]),
5061
+ Math.min(a[2], b[2]),
5062
+ Math.min(a[3], b[3])
5063
+ ];
5064
+ return bbox[2] <= bbox[0] || bbox[3] <= bbox[1] ? null : bbox;
5065
+ }
5066
+
5067
+ function getRasterCrop(grid, bbox) {
5068
+ var rb = grid.bbox;
5069
+ var x0 = Math.max(0, Math.floor((bbox[0] - rb[0]) / (rb[2] - rb[0]) * grid.width));
5070
+ var x1 = Math.min(grid.width, Math.ceil((bbox[2] - rb[0]) / (rb[2] - rb[0]) * grid.width));
5071
+ var y0 = Math.max(0, Math.floor((rb[3] - bbox[3]) / (rb[3] - rb[1]) * grid.height));
5072
+ var y1 = Math.min(grid.height, Math.ceil((rb[3] - bbox[1]) / (rb[3] - rb[1]) * grid.height));
5073
+ return {
5074
+ x: x0,
5075
+ y: y0,
5076
+ width: Math.max(0, x1 - x0),
5077
+ height: Math.max(0, y1 - y0)
5078
+ };
5079
+ }
5080
+
5081
+ function cropRasterGrid(grid, crop, bbox) {
5082
+ var samples = new grid.samples.constructor(crop.width * crop.height * grid.bands);
5083
+ var rowCount = crop.width * grid.bands;
5084
+ var src, dest;
5085
+ for (var y = 0; y < crop.height; y++) {
5086
+ src = ((crop.y + y) * grid.width + crop.x) * grid.bands;
5087
+ dest = y * rowCount;
5088
+ samples.set(grid.samples.subarray(src, src + rowCount), dest);
5089
+ }
5090
+ return Object.assign({}, grid, {
5091
+ width: crop.width,
5092
+ height: crop.height,
5093
+ samples: samples,
5094
+ bbox: bbox,
5095
+ transform: updateTransformForBBox(grid.transform, bbox, crop.width, crop.height)
5096
+ });
5097
+ }
5098
+
5099
+ function getRasterLayerBounds(lyr) {
5100
+ var bbox = lyr.raster && getRasterBBox(lyr.raster);
5101
+ return bbox && bbox.length == 4 ? new Bounds(bbox) : null;
5102
+ }
5103
+
5104
+ function requireRasterLayer(lyr) {
5105
+ if (!lyr || !lyr.raster_type || !lyr.raster) {
5106
+ stop$1('Expected a raster layer');
5107
+ }
5108
+ }
5109
+
5110
+ function clearLegacyRasterFields(raster) {
5111
+ delete raster.width;
5112
+ delete raster.height;
5113
+ delete raster.bands;
5114
+ delete raster.pixelType;
5115
+ delete raster.nodata;
5116
+ delete raster.bbox;
5117
+ delete raster.transform;
5118
+ delete raster.colorModel;
5119
+ delete raster.preview;
5120
+ delete raster.pixels;
5121
+ }
5122
+
5123
+ function updateTransformForBBox(transform, bbox, width, height) {
5124
+ if (!transform) return null;
5125
+ return [
5126
+ (bbox[2] - bbox[0]) / width,
5127
+ 0,
5128
+ bbox[0],
5129
+ 0,
5130
+ -(bbox[3] - bbox[1]) / height,
5131
+ bbox[3]
5132
+ ];
5133
+ }
5134
+
5135
+ function getBandStats(data, bands, noData) {
5136
+ var stats = [];
5137
+ for (var band = 0; band < bands; band++) {
5138
+ stats[band] = getSingleBandStats(data, bands, band, noData);
5139
+ }
5140
+ return stats;
5141
+ }
5142
+
5143
+ function getSingleBandStats(data, bands, band, noData) {
5144
+ var min = Infinity;
5145
+ var max = -Infinity;
5146
+ var val;
5147
+ for (var i = band; i < data.length; i += bands) {
5148
+ val = data[i];
5149
+ if (!isValidBandValue(val, noData)) continue;
5150
+ if (val < min) min = val;
5151
+ if (val > max) max = val;
5152
+ }
5153
+ return min < Infinity && max > min ? {min: min, max: max} : {min: 0, max: 255};
5154
+ }
5155
+
5156
+ function getSharedBandStats(data, bands, noData) {
5157
+ var stats = getBandStats(data, bands, noData);
5158
+ var min = Math.min(stats[0].min, stats[1].min, stats[2].min);
5159
+ var max = Math.max(stats[0].max, stats[1].max, stats[2].max);
5160
+ var shared = max > min ? {min: min, max: max} : {min: 0, max: 255};
5161
+ stats[0] = stats[1] = stats[2] = shared;
5162
+ return stats;
5163
+ }
5164
+
5165
+ function getScalingStats(data, bands, noData, recipe) {
5166
+ var shared = recipe.type == 'rgb' && bands >= 3;
5167
+ if (recipe.scaling == 'minmax') {
5168
+ return shared ? getSharedBandStats(data, bands, noData) : getBandStats(data, bands, noData);
5169
+ }
5170
+ if (recipe.scaling == 'percentile') {
5171
+ return shared ? getSharedPercentileStats(data, bands, noData, recipe.percentileRange) :
5172
+ getBandPercentileStats(data, bands, noData, recipe.percentileRange);
5173
+ }
5174
+ return null;
5175
+ }
5176
+
5177
+ function scaleSample(val, stats, sourceRange, displayRange) {
5178
+ var range = stats || sourceRange || {min: 0, max: 255};
5179
+ var pct = range.max > range.min ? (val - range.min) / (range.max - range.min) : 0;
5180
+ var scaled = displayRange[0] + pct * (displayRange[1] - displayRange[0]);
5181
+ return Math.max(0, Math.min(255, Math.round(scaled)));
5182
+ }
5183
+
5184
+ function getDefaultRasterScaling(grid, recipe) {
5185
+ return isEightBitPixelType(grid.pixelType) ? 'none' : 'percentile';
5186
+ }
5187
+
5188
+ function parseRangeOption(val, def, name) {
5189
+ var range;
5190
+ if (val == null || val === '') return def;
5191
+ range = Array.isArray(val) ? val : String(val).split(',');
5192
+ if (range.length != 2) {
5193
+ stop$1('Expected ' + name + '= to contain two comma-separated numbers');
5194
+ }
5195
+ range = range.map(Number);
5196
+ if (!isFinite(range[0]) || !isFinite(range[1]) || range[0] < 0 || range[1] > 100 || range[1] < range[0]) {
5197
+ stop$1('Expected ' + name + '= values between 0 and 100');
5198
+ }
5199
+ return range;
5200
+ }
5201
+
5202
+ function getDisplayRange(scaleRange) {
5203
+ return [
5204
+ scaleRange[0] / 100 * 255,
5205
+ scaleRange[1] / 100 * 255
5206
+ ];
5207
+ }
5208
+
5209
+ function getPixelTypeRange(pixelType) {
5210
+ switch (pixelType) {
5211
+ case 'uint8': return {min: 0, max: 255};
5212
+ case 'uint16': return {min: 0, max: 65535};
5213
+ case 'uint32': return {min: 0, max: 4294967295};
5214
+ case 'int8': return {min: -128, max: 127};
5215
+ case 'int16': return {min: -32768, max: 32767};
5216
+ case 'int32': return {min: -2147483648, max: 2147483647};
5217
+ }
5218
+ return null;
5219
+ }
5220
+
5221
+ function isEightBitPixelType(pixelType) {
5222
+ return pixelType == 'uint8' || pixelType == 'int8';
5223
+ }
5224
+
5225
+ function useFastRawEightBitRendering(grid, recipe) {
5226
+ return grid.pixelType == 'uint8' &&
5227
+ recipe.scaling == 'none' &&
5228
+ recipe.scaleRange[0] === 0 &&
5229
+ recipe.scaleRange[1] === 100 &&
5230
+ (recipe.type == 'gray' && grid.bands == 1 || recipe.type == 'rgb' && grid.bands >= 3) &&
5231
+ (grid.nodata === null || grid.nodata === undefined);
5232
+ }
5233
+
5234
+ function getRasterScalingStatsKey(grid, recipe) {
5235
+ return [
5236
+ grid.width,
5237
+ grid.height,
5238
+ grid.bands,
5239
+ grid.pixelType,
5240
+ grid.samples && grid.samples.length,
5241
+ grid.nodata,
5242
+ recipe.type,
5243
+ recipe.scaling,
5244
+ recipe.scaleRange && recipe.scaleRange.join(','),
5245
+ recipe.percentileRange && recipe.percentileRange.join(',')
5246
+ ].join('|');
5247
+ }
5248
+
5249
+ function getBandPercentileStats(data, bands, noData, range) {
5250
+ var stats = [];
5251
+ for (var band = 0; band < bands; band++) {
5252
+ stats[band] = getPercentileStats(data, bands, [band], noData, range);
5253
+ }
5254
+ return stats;
5255
+ }
5256
+
5257
+ function getSharedPercentileStats(data, bands, noData, range) {
5258
+ var stats = getBandPercentileStats(data, bands, noData, range);
5259
+ var shared = getPercentileStats(data, bands, [0, 1, 2], noData, range);
5260
+ stats[0] = stats[1] = stats[2] = shared;
5261
+ return stats;
5262
+ }
5263
+
5264
+ function getPercentileStats(data, bands, bandIds, noData, range) {
5265
+ var integerRange = getSmallIntegerRange(data);
5266
+ return integerRange ?
5267
+ getHistogramPercentileStats(data, bands, bandIds, noData, range, integerRange) :
5268
+ getApproxHistogramPercentileStats(data, bands, bandIds, noData, range);
5269
+ }
5270
+
5271
+ function getSmallIntegerRange(data) {
5272
+ if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) return {min: 0, max: 255};
5273
+ if (data instanceof Int8Array) return {min: -128, max: 127};
5274
+ if (data instanceof Uint16Array) return {min: 0, max: 65535};
5275
+ if (data instanceof Int16Array) return {min: -32768, max: 32767};
5276
+ return null;
5277
+ }
5278
+
5279
+ function getHistogramPercentileStats(data, bands, bandIds, noData, range, integerRange) {
5280
+ var counts = new Uint32Array(integerRange.max - integerRange.min + 1);
5281
+ var count = 0, val;
5282
+ forEachBandValue(data, bands, bandIds, noData, function(v) {
5283
+ val = v - integerRange.min;
5284
+ counts[val]++;
5285
+ count++;
5286
+ });
5287
+ return count > 0 ? {
5288
+ min: getHistogramPercentileValue(counts, integerRange.min, count, range[0]),
5289
+ max: getHistogramPercentileValue(counts, integerRange.min, count, range[1])
5290
+ } : {min: 0, max: 255};
5291
+ }
5292
+
5293
+ function getApproxHistogramPercentileStats(data, bands, bandIds, noData, range) {
5294
+ var bounds = getBandValueBounds(data, bands, bandIds, noData);
5295
+ var binCount = 65536;
5296
+ var counts, count, scale, maxBin;
5297
+ if (!bounds || bounds.max <= bounds.min) {
5298
+ return bounds || {min: 0, max: 255};
5299
+ }
5300
+ counts = new Uint32Array(binCount);
5301
+ count = 0;
5302
+ scale = (binCount - 1) / (bounds.max - bounds.min);
5303
+ maxBin = binCount - 1;
5304
+ forEachBandValue(data, bands, bandIds, noData, function(val) {
5305
+ counts[Math.max(0, Math.min(maxBin, Math.floor((val - bounds.min) * scale)))]++;
5306
+ count++;
5307
+ });
5308
+ return count > 0 ? {
5309
+ min: getApproxHistogramPercentileValue(counts, bounds, count, range[0]),
5310
+ max: getApproxHistogramPercentileValue(counts, bounds, count, range[1])
5311
+ } : {min: 0, max: 255};
5312
+ }
5313
+
5314
+ function getBandValueBounds(data, bands, bandIds, noData) {
5315
+ var min = Infinity;
5316
+ var max = -Infinity;
5317
+ forEachBandValue(data, bands, bandIds, noData, function(val) {
5318
+ if (val < min) min = val;
5319
+ if (val > max) max = val;
5320
+ });
5321
+ return min < Infinity ? {min: min, max: max} : null;
5322
+ }
5323
+
5324
+ function getHistogramPercentileValue(counts, offset, count, pct) {
5325
+ var target = getPercentileRank(count, pct);
5326
+ var sum = 0;
5327
+ for (var i = 0; i < counts.length; i++) {
5328
+ sum += counts[i];
5329
+ if (sum > target) return i + offset;
5330
+ }
5331
+ return counts.length - 1 + offset;
5332
+ }
5333
+
5334
+ function getApproxHistogramPercentileValue(counts, bounds, count, pct) {
5335
+ var bin = getHistogramPercentileValue(counts, 0, count, pct);
5336
+ return bounds.min + bin / (counts.length - 1) * (bounds.max - bounds.min);
5337
+ }
5338
+
5339
+ function forEachBandValue(data, bands, bandIds, noData, cb) {
5340
+ var val;
5341
+ for (var i = 0; i < data.length; i += bands) {
5342
+ for (var j = 0; j < bandIds.length; j++) {
5343
+ val = data[i + bandIds[j]];
5344
+ if (!isValidBandValue(val, noData)) continue;
5345
+ cb(val);
5346
+ }
5347
+ }
5348
+ }
5349
+
5350
+ function isValidBandValue(val, noData) {
5351
+ return isFinite(val) && (noData === null || noData === undefined || val != noData);
5352
+ }
5353
+
5354
+ function getPercentileRank(count, pct) {
5355
+ return Math.max(0, Math.min(count - 1, Math.floor((count - 1) * pct / 100)));
5356
+ }
5357
+
5358
+ function allSamplesAreNoData(data, offset, bands, noData) {
5359
+ var n = Math.min(bands, 3);
5360
+ for (var i = 0; i < n; i++) {
5361
+ if (data[offset + i] != noData) return false;
5362
+ }
5363
+ return true;
5364
+ }
5365
+
5366
+ function copyObjectOrArray(obj) {
5367
+ return obj && obj.concat ? obj.concat() : utils.extend({}, obj);
5368
+ }
5369
+
5370
+ var RasterUtils = /*#__PURE__*/Object.freeze({
5371
+ __proto__: null,
5372
+ clipRasterToBBox: clipRasterToBBox,
5373
+ copyRasterData: copyRasterData,
5374
+ copyRasterGrid: copyRasterGrid,
5375
+ copyRasterPreview: copyRasterPreview,
5376
+ copyRasterView: copyRasterView,
5377
+ copyTypedArray: copyTypedArray,
5378
+ createRasterPreview: createRasterPreview,
5379
+ cropRasterGrid: cropRasterGrid,
5380
+ getRasterBBox: getRasterBBox,
5381
+ getRasterBandCount: getRasterBandCount,
5382
+ getRasterCrop: getRasterCrop,
5383
+ getRasterGrid: getRasterGrid,
5384
+ getRasterHeight: getRasterHeight,
5385
+ getRasterLayerBounds: getRasterLayerBounds,
5386
+ getRasterPixelType: getRasterPixelType,
5387
+ getRasterPreview: getRasterPreview,
5388
+ getRasterScalingStats: getRasterScalingStats,
5389
+ getRasterScalingStatsKey: getRasterScalingStatsKey,
5390
+ getRasterTransform: getRasterTransform,
5391
+ getRasterView: getRasterView,
5392
+ getRasterViewRecipe: getRasterViewRecipe,
5393
+ getRasterViewScalingStats: getRasterViewScalingStats,
5394
+ getRasterWidth: getRasterWidth,
5395
+ intersectBboxes: intersectBboxes,
5396
+ renderRasterPreview: renderRasterPreview,
5397
+ renderRasterViewportPreview: renderRasterViewportPreview,
5398
+ requireRasterLayer: requireRasterLayer
5399
+ });
5400
+
4775
5401
  // Insert a column of values into a (new or existing) data field
4776
5402
  function insertFieldValues(lyr, fieldName, values) {
4777
5403
  var size = getFeatureCount(lyr) || values.length,
@@ -4864,7 +5490,9 @@
4864
5490
 
4865
5491
  function getFeatureCount(lyr) {
4866
5492
  var count = 0;
4867
- if (lyr.data) {
5493
+ if (layerHasRaster(lyr)) {
5494
+ count = 1;
5495
+ } else if (lyr.data) {
4868
5496
  count = lyr.data.size();
4869
5497
  } else if (lyr.shapes) {
4870
5498
  count = lyr.shapes.length;
@@ -5052,6 +5680,7 @@
5052
5680
  if (lyr.shapes) {
5053
5681
  copy.shapes = cloneShapes(lyr.shapes);
5054
5682
  }
5683
+ if (lyr.raster) copy.raster = copyRasterData(lyr.raster);
5055
5684
  return copy;
5056
5685
  }
5057
5686
 
@@ -5087,10 +5716,13 @@
5087
5716
  bounds = getPointBounds$1(lyr.shapes);
5088
5717
  } else if (lyr.geometry_type == 'polygon' || lyr.geometry_type == 'polyline') {
5089
5718
  bounds = getPathBounds(lyr.shapes, arcs);
5719
+ } else if (layerHasRaster(lyr)) {
5720
+ bounds = getRasterLayerBounds(lyr);
5090
5721
  } else ;
5091
5722
  return bounds;
5092
5723
  }
5093
5724
 
5725
+
5094
5726
  function isolateLayer(layer, dataset) {
5095
5727
  return utils.defaults({
5096
5728
  layers: dataset.layers.filter(function(lyr) {return lyr == layer;})
@@ -5129,6 +5761,7 @@
5129
5761
  layerHasNonNullShapes: layerHasNonNullShapes,
5130
5762
  layerHasPaths: layerHasPaths,
5131
5763
  layerHasPoints: layerHasPoints,
5764
+ layerHasRaster: layerHasRaster,
5132
5765
  layerIsEmpty: layerIsEmpty,
5133
5766
  layerIsGeometric: layerIsGeometric,
5134
5767
  layerIsRectangle: layerIsRectangle,
@@ -8260,6 +8893,12 @@
8260
8893
  });
8261
8894
  }
8262
8895
 
8896
+ function datasetHasRaster(dataset) {
8897
+ return utils.some(dataset.layers, function(lyr) {
8898
+ return layerHasRaster(lyr);
8899
+ });
8900
+ }
8901
+
8263
8902
  function datasetHasPaths(dataset) {
8264
8903
  return utils.some(dataset.layers, function(lyr) {
8265
8904
  return layerHasPaths(lyr);
@@ -8377,6 +9016,7 @@
8377
9016
  copyDatasetInfo: copyDatasetInfo,
8378
9017
  datasetHasGeometry: datasetHasGeometry,
8379
9018
  datasetHasPaths: datasetHasPaths,
9019
+ datasetHasRaster: datasetHasRaster,
8380
9020
  datasetIsEmpty: datasetIsEmpty,
8381
9021
  getDatasetBounds: getDatasetBounds,
8382
9022
  mergeDatasetInfo: mergeDatasetInfo,
@@ -8489,8 +9129,14 @@
8489
9129
  function guessInputFileType(file) {
8490
9130
  var ext = getFileExtension(file || '').toLowerCase(),
8491
9131
  type = null;
8492
- if (ext == 'dbf' || ext == 'shp' || ext == 'kml' || ext == 'svg' || ext == 'fgb' || ext == 'gpkg') {
9132
+ if (ext == 'dbf' || ext == 'shp' || ext == 'kml' || ext == 'svg' || ext == 'fgb' || ext == 'gpkg' || ext == 'png') {
8493
9133
  type = ext;
9134
+ } else if (ext == 'jpg' || ext == 'jpeg') {
9135
+ type = 'jpeg';
9136
+ } else if (ext == 'tif' || ext == 'tiff') {
9137
+ type = 'geotiff';
9138
+ } else if (isWorldFileExtension(ext)) {
9139
+ type = 'world';
8494
9140
  } else if (ext == 'parquet' || ext == 'geoparquet') {
8495
9141
  type = 'parquet';
8496
9142
  } else if (isAuxiliaryInputFileType(ext)) {
@@ -8507,7 +9153,15 @@
8507
9153
 
8508
9154
  // File types that can be imported but are not convertible to datasets
8509
9155
  function isAuxiliaryInputFileType(type) {
8510
- return type == 'prj' || type == 'shx' || type == 'cpg';
9156
+ return type == 'prj' || type == 'shx' || type == 'cpg' || type == 'world';
9157
+ }
9158
+
9159
+ function isRasterImageInputType(type) {
9160
+ return type == 'png' || type == 'jpeg';
9161
+ }
9162
+
9163
+ function isWorldFileExtension(ext) {
9164
+ return /^(tfw|pgw|pngw|jgw|jpw|jpgw|jpegw|wld)$/i.test(ext || '');
8511
9165
  }
8512
9166
 
8513
9167
  function guessInputContentType(content) {
@@ -8634,7 +9288,9 @@
8634
9288
  function isSupportedBinaryInputType(path) {
8635
9289
  var ext = getFileExtension(path).toLowerCase();
8636
9290
  return ext == 'shp' || ext == 'shx' || ext == 'dbf' ||
8637
- ext == 'fgb' || ext == 'gpkg' || ext == 'parquet' || ext == 'geoparquet' || ext == PACKAGE_EXT; // GUI also supports zip files
9291
+ ext == 'fgb' || ext == 'gpkg' || ext == 'parquet' || ext == 'geoparquet' ||
9292
+ ext == 'tif' || ext == 'tiff' || ext == 'png' || ext == 'jpg' || ext == 'jpeg' ||
9293
+ ext == PACKAGE_EXT; // GUI also supports zip files
8638
9294
  }
8639
9295
 
8640
9296
  function isImportableAsBinary(path) {
@@ -8663,7 +9319,9 @@
8663
9319
  isKmzFile: isKmzFile,
8664
9320
  isPackageFile: isPackageFile,
8665
9321
  isPotentialCommandFile: isPotentialCommandFile,
9322
+ isRasterImageInputType: isRasterImageInputType,
8666
9323
  isSupportedBinaryInputType: isSupportedBinaryInputType,
9324
+ isWorldFileExtension: isWorldFileExtension,
8667
9325
  isZipFile: isZipFile,
8668
9326
  looksLikeContentFile: looksLikeContentFile,
8669
9327
  looksLikeImportableFile: looksLikeImportableFile,
@@ -19041,6 +19699,8 @@
19041
19699
  var obj;
19042
19700
  if (layerHasFurniture(lyr)) {
19043
19701
  obj = exportFurnitureLayerForSVG(lyr, frame, opts);
19702
+ } else if (layerHasRaster(lyr)) {
19703
+ obj = exportRasterLayerForSVG(lyr, frame, opts);
19044
19704
  } else {
19045
19705
  obj = exportLayerForSVG(lyr, dataset, opts);
19046
19706
  }
@@ -19157,6 +19817,141 @@ ${svg}
19157
19817
  return layerObj;
19158
19818
  }
19159
19819
 
19820
+ function exportRasterLayerForSVG(lyr, frame, opts) {
19821
+ var raster = lyr.raster;
19822
+ var clipped = clipRasterPreviewToFrame(raster, frame);
19823
+ var bbox = transformRasterBboxForSVG(clipped.bbox, frame);
19824
+ var href;
19825
+ var layerObj = getEmptyLayerForSVG(lyr, opts);
19826
+ if (!clipped.preview.width || !clipped.preview.height) {
19827
+ layerObj.children = [];
19828
+ return layerObj;
19829
+ }
19830
+ href = encodeRasterPreview(clipped.preview, opts);
19831
+ layerObj.children = [{
19832
+ tag: 'image',
19833
+ properties: {
19834
+ x: bbox[0],
19835
+ y: bbox[1],
19836
+ width: bbox[2] - bbox[0],
19837
+ height: bbox[3] - bbox[1],
19838
+ href: href,
19839
+ preserveAspectRatio: 'none'
19840
+ }
19841
+ }];
19842
+ return layerObj;
19843
+ }
19844
+
19845
+ function clipRasterPreviewToFrame(raster, frame) {
19846
+ var rasterBbox = getRasterBBox(raster);
19847
+ var preview = getRasterPreview(raster);
19848
+ var rasterBounds = new Bounds(rasterBbox);
19849
+ var clipBounds = new Bounds(frame.bbox);
19850
+ var bbox = intersectBboxes(rasterBounds.toArray(), clipBounds.toArray()) || [0, 0, 0, 0];
19851
+ var crop = getRasterPreviewCrop(rasterBbox, preview, bbox);
19852
+ return {
19853
+ bbox: bbox,
19854
+ preview: cropRasterPreview(preview, crop)
19855
+ };
19856
+ }
19857
+
19858
+ function getRasterPreviewCrop(rasterBbox, preview, bbox) {
19859
+ var rb = rasterBbox;
19860
+ var p = preview;
19861
+ var x0 = Math.max(0, Math.floor((bbox[0] - rb[0]) / (rb[2] - rb[0]) * p.width));
19862
+ var x1 = Math.min(p.width, Math.ceil((bbox[2] - rb[0]) / (rb[2] - rb[0]) * p.width));
19863
+ var y0 = Math.max(0, Math.floor((rb[3] - bbox[3]) / (rb[3] - rb[1]) * p.height));
19864
+ var y1 = Math.min(p.height, Math.ceil((rb[3] - bbox[1]) / (rb[3] - rb[1]) * p.height));
19865
+ return {
19866
+ x: x0,
19867
+ y: y0,
19868
+ width: Math.max(0, x1 - x0),
19869
+ height: Math.max(0, y1 - y0)
19870
+ };
19871
+ }
19872
+
19873
+ function cropRasterPreview(preview, crop) {
19874
+ if (crop.x === 0 && crop.y === 0 && crop.width == preview.width && crop.height == preview.height) {
19875
+ return preview;
19876
+ }
19877
+ var pixels = new Uint8ClampedArray(crop.width * crop.height * 4);
19878
+ var src, dest, rowBytes = crop.width * 4;
19879
+ for (var y = 0; y < crop.height; y++) {
19880
+ src = ((crop.y + y) * preview.width + crop.x) * 4;
19881
+ dest = y * rowBytes;
19882
+ pixels.set(preview.pixels.subarray(src, src + rowBytes), dest);
19883
+ }
19884
+ return Object.assign({}, preview, {
19885
+ width: crop.width,
19886
+ height: crop.height,
19887
+ pixels: pixels
19888
+ });
19889
+ }
19890
+
19891
+ function transformRasterBboxForSVG(bbox, frame) {
19892
+ var srcBounds = new Bounds(frame.bbox);
19893
+ var destBounds = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
19894
+ srcBounds.fillOut(destBounds.width() / destBounds.height());
19895
+ var fwd = srcBounds.getTransform(destBounds, frame.invert_y);
19896
+ var p1 = fwd.transform(bbox[0], bbox[3]);
19897
+ var p2 = fwd.transform(bbox[2], bbox[1]);
19898
+ return [p1[0], p1[1], p2[0], p2[1]];
19899
+ }
19900
+
19901
+ function encodeRasterPreview(preview, opts) {
19902
+ var format = getSvgRasterFormat(preview, opts);
19903
+ var data = format == 'png' ? encodePng(preview) : encodeJpeg(preview, opts);
19904
+ return 'data:image/' + format + ';base64,' + data;
19905
+ }
19906
+
19907
+ function getSvgRasterFormat(preview, opts) {
19908
+ var fmt = opts.svg_raster_format || opts.raster_format || null;
19909
+ if (fmt) return fmt == 'jpg' ? 'jpeg' : fmt;
19910
+ return previewHasTransparency(preview) ? 'png' : 'jpeg';
19911
+ }
19912
+
19913
+ function previewHasTransparency(preview) {
19914
+ var pixels = preview.pixels;
19915
+ for (var i = 3; i < pixels.length; i += 4) {
19916
+ if (pixels[i] < 255) return true;
19917
+ }
19918
+ return false;
19919
+ }
19920
+
19921
+ function encodeJpeg(preview, opts) {
19922
+ if (runningInBrowser() && typeof document != 'undefined') {
19923
+ return encodeWithCanvas(preview, 'image/jpeg', opts.svg_raster_quality || 0.85);
19924
+ }
19925
+ var jpeg = require$1('jpeg-js');
19926
+ var quality = Math.round((opts.svg_raster_quality || 0.85) * 100);
19927
+ return Buffer.from(jpeg.encode({
19928
+ data: Buffer.from(preview.pixels),
19929
+ width: preview.width,
19930
+ height: preview.height
19931
+ }, quality).data).toString('base64');
19932
+ }
19933
+
19934
+ function encodePng(preview) {
19935
+ if (runningInBrowser() && typeof document != 'undefined') {
19936
+ return encodeWithCanvas(preview, 'image/png');
19937
+ }
19938
+ var PNG = require$1('pngjs').PNG;
19939
+ var png = new PNG({width: preview.width, height: preview.height});
19940
+ png.data = Buffer.from(preview.pixels);
19941
+ return Buffer.from(PNG.sync.write(png)).toString('base64');
19942
+ }
19943
+
19944
+ function encodeWithCanvas(preview, type, quality) {
19945
+ var canvas = document.createElement('canvas');
19946
+ var ctx = canvas.getContext('2d');
19947
+ var dataUrl;
19948
+ canvas.width = preview.width;
19949
+ canvas.height = preview.height;
19950
+ ctx.putImageData(new ImageData(preview.pixels, preview.width, preview.height), 0, 0);
19951
+ dataUrl = canvas.toDataURL(type, quality);
19952
+ return dataUrl.split(',')[1];
19953
+ }
19954
+
19160
19955
  function exportSymbolsForSVG(lyr, dataset, opts) {
19161
19956
  // TODO: convert geojson features one at a time
19162
19957
  var d = utils.defaults({layers: [lyr]}, dataset);
@@ -19373,6 +20168,7 @@ ${svg}
19373
20168
  exportDataAttributesForSVG: exportDataAttributesForSVG,
19374
20169
  exportFurnitureLayerForSVG: exportFurnitureLayerForSVG,
19375
20170
  exportLayerForSVG: exportLayerForSVG,
20171
+ exportRasterLayerForSVG: exportRasterLayerForSVG,
19376
20172
  exportSVG: exportSVG,
19377
20173
  featureHasLabel: featureHasLabel,
19378
20174
  featureHasSvgSymbol: featureHasSvgSymbol,
@@ -22609,6 +23405,8 @@ ${svg}
22609
23405
  name: lyr.name || null,
22610
23406
  geometry_type: lyr.geometry_type || null,
22611
23407
  shapes: lyr.shapes || null,
23408
+ raster_type: lyr.raster_type || null,
23409
+ raster: lyr.raster ? exportRasterData(lyr.raster) : null,
22612
23410
  data: data,
22613
23411
  menu_order: lyr.menu_order || null,
22614
23412
  pinned: lyr.pinned || opts.show_all || false,
@@ -22616,6 +23414,37 @@ ${svg}
22616
23414
  };
22617
23415
  }
22618
23416
 
23417
+ function exportRasterData(raster) {
23418
+ var copy = Object.assign({}, raster);
23419
+ if (raster.grid) {
23420
+ copy.grid = Object.assign({}, raster.grid);
23421
+ if (raster.grid.samples) {
23422
+ copy.grid.samples = typedArrayToBuffer(raster.grid.samples);
23423
+ }
23424
+ }
23425
+ if (raster.view) {
23426
+ copy.view = Object.assign({}, raster.view);
23427
+ if (raster.view.preview) {
23428
+ copy.view.preview = Object.assign({}, raster.view.preview);
23429
+ delete copy.view.preview.canvas;
23430
+ if (raster.view.preview.pixels) {
23431
+ copy.view.preview.pixels = typedArrayToBuffer(raster.view.preview.pixels);
23432
+ }
23433
+ }
23434
+ }
23435
+ if (raster.preview) {
23436
+ copy.preview = Object.assign({}, raster.preview);
23437
+ delete copy.preview.canvas;
23438
+ if (raster.preview.pixels) {
23439
+ copy.preview.pixels = typedArrayToBuffer(raster.preview.pixels);
23440
+ }
23441
+ }
23442
+ if (raster.pixels) {
23443
+ copy.pixels = typedArrayToBuffer(raster.pixels);
23444
+ }
23445
+ return copy;
23446
+ }
23447
+
22619
23448
  function exportInfo(info) {
22620
23449
  info = Object.assign({}, info);
22621
23450
  if (info.crs && !info.crs_string && !info.wkt1) {
@@ -26256,7 +27085,7 @@ ${svg}
26256
27085
 
26257
27086
  var writerPromise = null;
26258
27087
  var zstdPromise = null;
26259
- var dynamicImportModule$1 = Function('id', 'return import(id)');
27088
+ var dynamicImportModule$2 = Function('id', 'return import(id)');
26260
27089
 
26261
27090
  async function exportGeoParquet(dataset, opts, filenameOverride) {
26262
27091
  var writer = await loadGeoParquetWriter();
@@ -26471,7 +27300,7 @@ ${svg}
26471
27300
  return mod;
26472
27301
  }
26473
27302
  if (!writerPromise) {
26474
- writerPromise = dynamicImportModule$1('hyparquet-writer');
27303
+ writerPromise = dynamicImportModule$2('hyparquet-writer');
26475
27304
  }
26476
27305
  var nodeMod = await writerPromise;
26477
27306
  return nodeMod.default && !nodeMod.parquetWriteBuffer ? nodeMod.default : nodeMod;
@@ -26540,7 +27369,7 @@ ${svg}
26540
27369
  mod = require$1('@bokuweb/zstd-wasm');
26541
27370
  } else {
26542
27371
  if (!zstdPromise) {
26543
- zstdPromise = dynamicImportModule$1('@bokuweb/zstd-wasm');
27372
+ zstdPromise = dynamicImportModule$2('@bokuweb/zstd-wasm');
26544
27373
  }
26545
27374
  mod = await zstdPromise;
26546
27375
  }
@@ -26663,6 +27492,7 @@ ${svg}
26663
27492
  async function exportDatasets(datasets, opts) {
26664
27493
  var format = getOutputFormat(datasets[0], opts);
26665
27494
  var files;
27495
+ validateRasterExportFormat(datasets, format);
26666
27496
  if (format != 'geoparquet' && (opts.compression || opts.level !== undefined)) {
26667
27497
  error('The compression= and level= options only apply to GeoParquet output');
26668
27498
  }
@@ -26732,6 +27562,18 @@ ${svg}
26732
27562
  return files;
26733
27563
  }
26734
27564
 
27565
+ function validateRasterExportFormat(datasets, format) {
27566
+ if (!datasetsHaveRasterLayers(datasets)) return;
27567
+ if (format == 'svg' || format == PACKAGE_EXT) return;
27568
+ stop$1('Raster layers can only be exported as SVG or ' + PACKAGE_EXT + ' files');
27569
+ }
27570
+
27571
+ function datasetsHaveRasterLayers(datasets) {
27572
+ return datasets.some(function(dataset) {
27573
+ return dataset.layers && dataset.layers.some(layerHasRaster);
27574
+ });
27575
+ }
27576
+
26735
27577
  // Return an array of objects with 'filename' and 'content' members.
26736
27578
  //
26737
27579
  function exportFileContent(dataset, opts) {
@@ -26744,6 +27586,7 @@ ${svg}
26744
27586
  } else if (!exporter) {
26745
27587
  error('Unknown output format:', outFmt);
26746
27588
  }
27589
+ validateRasterExportFormat([dataset], outFmt);
26747
27590
 
26748
27591
  // shallow-copy dataset and layers, so layers can be renamed for export
26749
27592
  dataset = utils.defaults({
@@ -26819,7 +27662,11 @@ ${svg}
26819
27662
  // Throw errors for various error conditions
26820
27663
  function validateLayerData(layers) {
26821
27664
  layers.forEach(function(lyr) {
26822
- if (!lyr.geometry_type) {
27665
+ if (layerHasRaster(lyr)) {
27666
+ if (!getRasterBBox(lyr.raster) || !getRasterPreview(lyr.raster) && !(getRasterGrid(lyr.raster) && getRasterGrid(lyr.raster).samples)) {
27667
+ error('A raster layer is missing preview data or bounds');
27668
+ }
27669
+ } else if (!lyr.geometry_type) {
26823
27670
  // allowing data-only layers
26824
27671
  if (lyr.shapes && utils.some(lyr.shapes, function(o) {
26825
27672
  return !!o;
@@ -29108,6 +29955,18 @@ ${svg}
29108
29955
  type: 'strings',
29109
29956
  describe: '[GPKG] comma-sep. list of layers to import'
29110
29957
  })
29958
+ .option('scaling', {
29959
+ describe: '[raster] raster display scaling method: none,minmax,percentile'
29960
+ })
29961
+ .option('scale-range', {
29962
+ describe: '[raster] display intensity range in percent, e.g. 0,100'
29963
+ })
29964
+ .option('percentile-range', {
29965
+ describe: '[raster] input percentile range for percentile scaling, e.g. 2,98'
29966
+ })
29967
+ .option('rendition', {
29968
+ describe: '[GeoTIFF] import a GeoTIFF rendition: full,overview-1,etc.'
29969
+ })
29111
29970
  .option('geometry-type', {
29112
29971
  // undocumented; GeoJSON import rejects all but one kind of geometry
29113
29972
  // describe: '[GeoJSON] Import one kind of geometry (point|polygon|polyline)'
@@ -34446,7 +35305,7 @@ ${svg}
34446
35305
 
34447
35306
  var hyparquetPromise = null;
34448
35307
  var compressorsPromise = null;
34449
- var dynamicImportModule = Function('id', 'return import(id)');
35308
+ var dynamicImportModule$1 = Function('id', 'return import(id)');
34450
35309
  var mproj = null;
34451
35310
 
34452
35311
  async function importGeoParquet(input, optsArg) {
@@ -34493,7 +35352,7 @@ ${svg}
34493
35352
  return mod;
34494
35353
  }
34495
35354
  if (!hyparquetPromise) {
34496
- hyparquetPromise = dynamicImportModule('hyparquet');
35355
+ hyparquetPromise = dynamicImportModule$1('hyparquet');
34497
35356
  }
34498
35357
  var nodeMod = await hyparquetPromise;
34499
35358
  return nodeMod.default && !nodeMod.parquetReadObjects ? nodeMod.default : nodeMod;
@@ -34504,7 +35363,7 @@ ${svg}
34504
35363
  return getHyparquetCompressors(require$1('hyparquet-compressors'));
34505
35364
  }
34506
35365
  if (!compressorsPromise) {
34507
- compressorsPromise = dynamicImportModule('hyparquet-compressors');
35366
+ compressorsPromise = dynamicImportModule$1('hyparquet-compressors');
34508
35367
  }
34509
35368
  return getHyparquetCompressors(await compressorsPromise);
34510
35369
  }
@@ -34833,6 +35692,555 @@ ${svg}
34833
35692
  };
34834
35693
  }
34835
35694
 
35695
+ var geotiffPromise = null;
35696
+ var dynamicImportModule = Function('id', 'return import(id)');
35697
+ var DEFAULT_MAX_IMPORT_PIXELS = 16e6;
35698
+
35699
+ async function importGeoTIFF(input, optsArg) {
35700
+ var opts = optsArg || {};
35701
+ var geotiff = await loadGeoTIFFLib();
35702
+ var source = getGeoTIFFSource(input);
35703
+ var tiff = await openGeoTIFF(source, geotiff);
35704
+ var sourceImage = await tiff.getImage();
35705
+ var importImage = await selectGeoTIFFImportImage(tiff, sourceImage, opts);
35706
+ var imported = await importGeoTIFFImage(importImage, input, opts, sourceImage);
35707
+ var dataset = {
35708
+ info: {
35709
+ raster_sources: [imported.source]
35710
+ },
35711
+ layers: [{
35712
+ name: input && input.filename ? getFileBase(input.filename) : null,
35713
+ raster_type: 'grid',
35714
+ raster: imported.raster
35715
+ }]
35716
+ };
35717
+ await importGeoTIFFCrs(dataset, sourceImage);
35718
+ return dataset;
35719
+ }
35720
+
35721
+ async function loadGeoTIFFLib() {
35722
+ var mod;
35723
+ if (runningInBrowser()) {
35724
+ mod = require$1('geotiff');
35725
+ if (!mod || !mod.fromArrayBuffer) {
35726
+ stop$1('GeoTIFF library is not loaded');
35727
+ }
35728
+ return mod;
35729
+ }
35730
+ if (!geotiffPromise) {
35731
+ geotiffPromise = dynamicImportModule('geotiff');
35732
+ }
35733
+ mod = await geotiffPromise;
35734
+ return mod.default && !mod.fromArrayBuffer ? mod.default : mod;
35735
+ }
35736
+
35737
+ async function openGeoTIFF(source, geotiff) {
35738
+ if (!source) {
35739
+ stop$1('Missing GeoTIFF source data');
35740
+ }
35741
+ return geotiff.fromArrayBuffer(source);
35742
+ }
35743
+
35744
+ function getGeoTIFFSource(input) {
35745
+ var content = input && input.content;
35746
+ if (!content) return null;
35747
+ if (content instanceof ArrayBuffer) return content;
35748
+ return content.buffer.slice(content.byteOffset || 0, (content.byteOffset || 0) + content.byteLength);
35749
+ }
35750
+
35751
+ async function importGeoTIFFImage(importImage, input, opts, sourceImage) {
35752
+ var image = importImage.image;
35753
+ var width = importImage.width;
35754
+ var height = importImage.height;
35755
+ var samplesPerPixel = image.getSamplesPerPixel();
35756
+ var samples = getDisplaySamples(samplesPerPixel);
35757
+ var imageBbox = getImageBoundingBox(sourceImage);
35758
+ repairDeferredOffsetArrays(image);
35759
+ var data = await readGeoTIFFSamples(image, samples, width, height);
35760
+ var noData = getNoDataValue(image);
35761
+ var sourceId = getSourceId(input);
35762
+ var raster = {
35763
+ sourceId: sourceId,
35764
+ grid: {
35765
+ width: width,
35766
+ height: height,
35767
+ bands: samples.length,
35768
+ pixelType: getPixelType(image),
35769
+ samples: data,
35770
+ sampleBands: samples,
35771
+ nodata: noData,
35772
+ bbox: imageBbox,
35773
+ transform: getImageTransformForSize(sourceImage, width, height, imageBbox)
35774
+ },
35775
+ derivation: {
35776
+ type: samples.length >= 3 ? 'rgb' : 'gray',
35777
+ sourceId: sourceId,
35778
+ bands: samples
35779
+ },
35780
+ view: {
35781
+ recipe: {
35782
+ type: samples.length >= 3 ? 'rgb' : 'gray',
35783
+ bands: samples
35784
+ }
35785
+ }
35786
+ };
35787
+ raster.view.recipe = getRasterViewRecipe(raster.grid, raster.view.recipe, opts);
35788
+ raster.view.preview = createRasterPreview(raster, opts);
35789
+ return {
35790
+ raster: raster,
35791
+ source: getSourceInfo$1(input, sourceId, sourceImage)
35792
+ };
35793
+ }
35794
+
35795
+ async function selectGeoTIFFImportImage(tiff, sourceImage, opts) {
35796
+ var maxPixels = getMaxImportPixels(opts);
35797
+ var renditions = await getGeoTIFFRenditions(tiff, sourceImage);
35798
+ var source = renditions[0];
35799
+ var best = getRequestedRendition(renditions, opts.rendition);
35800
+ var bestPixels;
35801
+ if (!best) {
35802
+ best = source;
35803
+ bestPixels = best.width * best.height;
35804
+ if (bestPixels > maxPixels) {
35805
+ best = getAutomaticRendition(renditions, maxPixels);
35806
+ bestPixels = best.width * best.height;
35807
+ if (bestPixels > maxPixels) {
35808
+ best = getResizedImportImage(best, maxPixels);
35809
+ }
35810
+ }
35811
+ }
35812
+ if (renditions.length > 1 && (runningInBrowser() || opts.rendition)) {
35813
+ message(getRenditionsMessage(renditions, best));
35814
+ }
35815
+ if (best.slug != 'full' || best.width != source.width || best.height != source.height) {
35816
+ warnOnce(getImportRenditionMessage(best, source));
35817
+ }
35818
+ return best;
35819
+ }
35820
+
35821
+ async function getGeoTIFFRenditions(tiff, sourceImage) {
35822
+ var imageCount = await tiff.getImageCount();
35823
+ var renditions = [getRenditionInfo(sourceImage, 0)];
35824
+ var image;
35825
+ for (var i = 1; i < imageCount; i++) {
35826
+ image = await tiff.getImage(i);
35827
+ renditions.push(getRenditionInfo(image, i));
35828
+ }
35829
+ return renditions;
35830
+ }
35831
+
35832
+ function getRenditionInfo(image, index) {
35833
+ return {
35834
+ image: image,
35835
+ index: index,
35836
+ slug: index === 0 ? 'full' : 'overview-' + index,
35837
+ width: image.getWidth(),
35838
+ height: image.getHeight()
35839
+ };
35840
+ }
35841
+
35842
+ function getRequestedRendition(renditions, slug) {
35843
+ var match;
35844
+ if (!slug) return null;
35845
+ slug = String(slug);
35846
+ match = renditions.find(function(rendition) {
35847
+ return rendition.slug == slug || rendition.width + 'x' + rendition.height == slug;
35848
+ });
35849
+ if (!match) {
35850
+ stop$1('Unknown GeoTIFF rendition:', slug + '.', 'Use one of:', renditions.map(function(rendition) {
35851
+ return rendition.slug;
35852
+ }).join(','));
35853
+ }
35854
+ return match;
35855
+ }
35856
+
35857
+ function getAutomaticRendition(renditions, maxPixels) {
35858
+ var best = renditions[0];
35859
+ var bestPixels = best.width * best.height;
35860
+ var rendition, pixels;
35861
+ for (var i = 1; i < renditions.length; i++) {
35862
+ rendition = renditions[i];
35863
+ pixels = rendition.width * rendition.height;
35864
+ if (pixels <= maxPixels && (bestPixels > maxPixels || pixels > bestPixels)) {
35865
+ best = rendition;
35866
+ bestPixels = pixels;
35867
+ } else if (bestPixels > maxPixels && pixels < bestPixels) {
35868
+ best = rendition;
35869
+ bestPixels = pixels;
35870
+ }
35871
+ }
35872
+ return best;
35873
+ }
35874
+
35875
+ function getRenditionsMessage(renditions, selected) {
35876
+ var lines = ['GeoTIFF renditions:'];
35877
+ renditions.forEach(function(rendition) {
35878
+ lines.push(' ' + rendition.slug + ': ' + rendition.width + 'x' + rendition.height +
35879
+ (rendition.slug == selected.slug ? ' [selected]' : ''));
35880
+ });
35881
+ lines.push('Use import option rendition=<slug> to select a different rendition.');
35882
+ return lines.join('\n');
35883
+ }
35884
+
35885
+ function getImportRenditionMessage(importImage, source) {
35886
+ if (importImage.resampled) {
35887
+ return 'Using resampled GeoTIFF ' + (importImage.sourceSlug == 'full' ? 'full-resolution image' : 'rendition ' + importImage.sourceSlug) +
35888
+ ' for import: ' + importImage.width + 'x' + importImage.height +
35889
+ ' (source: ' + source.width + 'x' + source.height + ').';
35890
+ }
35891
+ return 'Using reduced-resolution GeoTIFF rendition for import: ' + importImage.slug + ' ' +
35892
+ importImage.width + 'x' + importImage.height + ' (source: ' + source.width + 'x' + source.height + ').';
35893
+ }
35894
+
35895
+ function getResizedImportImage(importImage, maxPixels) {
35896
+ var scale = Math.min(1, Math.sqrt(maxPixels / (importImage.width * importImage.height)));
35897
+ return Object.assign({}, importImage, {
35898
+ sourceSlug: importImage.slug,
35899
+ slug: importImage.slug + '-resampled',
35900
+ resampled: true,
35901
+ width: Math.max(1, Math.round(importImage.width * scale)),
35902
+ height: Math.max(1, Math.round(importImage.height * scale))
35903
+ });
35904
+ }
35905
+
35906
+ function getMaxImportPixels(opts) {
35907
+ return opts.maxPixels || opts.raster_max_pixels || opts.rasterMaxPixels ||
35908
+ DEFAULT_MAX_IMPORT_PIXELS;
35909
+ }
35910
+
35911
+ async function readGeoTIFFSamples(image, samples, width, height) {
35912
+ var opts = {
35913
+ interleave: true,
35914
+ width: width,
35915
+ height: height
35916
+ };
35917
+ if (useRGBRead(image, samples)) {
35918
+ return image.readRGB(opts);
35919
+ }
35920
+ opts.samples = samples;
35921
+ return image.readRasters(opts);
35922
+ }
35923
+
35924
+ function useRGBRead(image, samples) {
35925
+ return samples.length == 3 && getPhotometricInterpretation(image) == 6 && image.readRGB;
35926
+ }
35927
+
35928
+ function getDisplaySamples(samplesPerPixel) {
35929
+ if (samplesPerPixel >= 4) return [0, 1, 2, 3];
35930
+ if (samplesPerPixel >= 3) return [0, 1, 2];
35931
+ return [0];
35932
+ }
35933
+
35934
+ function getNoDataValue(image) {
35935
+ var val = image.getGDALNoData && image.getGDALNoData();
35936
+ return val == null || val === '' ? null : +val;
35937
+ }
35938
+
35939
+ function getPixelType(image) {
35940
+ var fmt = image.getSampleFormat && image.getSampleFormat();
35941
+ var bits = image.getBitsPerSample && image.getBitsPerSample();
35942
+ if (Array.isArray(fmt)) fmt = fmt[0];
35943
+ if (Array.isArray(bits)) bits = bits[0];
35944
+ var type = fmt == 3 ? 'float' : fmt == 2 ? 'int' : 'uint';
35945
+ return bits ? type + bits : type;
35946
+ }
35947
+
35948
+ function getImageBoundingBox(image) {
35949
+ try {
35950
+ return image.getBoundingBox().map(Number);
35951
+ } catch(e) {
35952
+ stop$1('GeoTIFF is missing georeferencing metadata');
35953
+ }
35954
+ }
35955
+
35956
+ function getImageTransformForSize(image, width, height, bbox) {
35957
+ var sourceBbox = bbox || getImageBoundingBox(image);
35958
+ return [
35959
+ (sourceBbox[2] - sourceBbox[0]) / width,
35960
+ 0,
35961
+ sourceBbox[0],
35962
+ 0,
35963
+ (sourceBbox[1] - sourceBbox[3]) / height,
35964
+ sourceBbox[3]
35965
+ ];
35966
+ }
35967
+
35968
+ function getImageTransform(image) {
35969
+ var origin = image.getOrigin && image.getOrigin();
35970
+ var resolution = image.getResolution && image.getResolution();
35971
+ if (!origin || !resolution) return null;
35972
+ return [resolution[0], 0, origin[0], 0, resolution[1], origin[1]];
35973
+ }
35974
+
35975
+ function getPhotometricInterpretation(image) {
35976
+ var fileDirectory = image && image.fileDirectory;
35977
+ return fileDirectory && fileDirectory.actualizedFields && fileDirectory.actualizedFields.get(262);
35978
+ }
35979
+
35980
+ function repairDeferredOffsetArrays(image) {
35981
+ var fileDirectory = image && image.fileDirectory;
35982
+ var arrays = fileDirectory && fileDirectory.deferredArrays;
35983
+ if (!arrays || !fileDirectory.actualizedFields) return;
35984
+ // Work around geotiff.js decoding big-endian deferred offset/count arrays as little-endian.
35985
+ [273, 279, 324, 325].forEach(function(tag) {
35986
+ var deferred = arrays.get(tag);
35987
+ var values;
35988
+ if (!deferred || deferred.littleEndian) return;
35989
+ values = decodeDeferredIntegerArray(deferred);
35990
+ if (!values) return;
35991
+ fileDirectory.actualizedFields.set(tag, values);
35992
+ arrays.delete(tag);
35993
+ });
35994
+ }
35995
+
35996
+ function decodeDeferredIntegerArray(deferred) {
35997
+ var source = deferred.source && deferred.source.arrayBuffer;
35998
+ var offset = deferred.arrayOffset;
35999
+ var length = deferred.length;
36000
+ var itemSize = deferred.itemSize;
36001
+ var view, values;
36002
+ if (!source || !length || !itemSize) return null;
36003
+ view = new DataView(source, offset, length * itemSize);
36004
+ if (itemSize == 2) {
36005
+ values = new Uint16Array(length);
36006
+ for (var i = 0; i < length; i++) values[i] = view.getUint16(i * itemSize, false);
36007
+ return values;
36008
+ }
36009
+ if (itemSize == 4) {
36010
+ values = new Uint32Array(length);
36011
+ for (var j = 0; j < length; j++) values[j] = view.getUint32(j * itemSize, false);
36012
+ return values;
36013
+ }
36014
+ if (itemSize == 8 && typeof view.getBigUint64 == 'function') {
36015
+ values = [];
36016
+ for (var k = 0; k < length; k++) values[k] = Number(view.getBigUint64(k * itemSize, false));
36017
+ return values;
36018
+ }
36019
+ return null;
36020
+ }
36021
+
36022
+ function getSourceId(input) {
36023
+ return input && input.filename ? getFileBase(input.filename) : 'geotiff-source';
36024
+ }
36025
+
36026
+ function getSourceInfo$1(input, sourceId, image) {
36027
+ var content = input && input.content;
36028
+ return {
36029
+ id: sourceId,
36030
+ filename: input && input.filename || null,
36031
+ byteLength: content && content.byteLength || null,
36032
+ storage: runningInBrowser() ? 'indexeddb-pending' : 'path',
36033
+ width: image.getWidth(),
36034
+ height: image.getHeight(),
36035
+ bands: image.getSamplesPerPixel(),
36036
+ pixelType: getPixelType(image),
36037
+ bbox: getImageBoundingBox(image),
36038
+ transform: getImageTransform(image)
36039
+ };
36040
+ }
36041
+
36042
+ async function importGeoTIFFCrs(dataset, image) {
36043
+ var crsString = getGeoTIFFCrsString(image);
36044
+ if (!crsString) {
36045
+ warnOnce('Unable to import CRS from GeoTIFF metadata');
36046
+ return;
36047
+ }
36048
+ await initProjLibrary({crs: crsString});
36049
+ try {
36050
+ setDatasetCrsInfo(dataset, getCrsInfo(crsString));
36051
+ } catch(e) {
36052
+ dataset.info = Object.assign(dataset.info || {}, {crs_string: crsString});
36053
+ }
36054
+ }
36055
+
36056
+ function getGeoTIFFCrsString(image) {
36057
+ var keys = image.getGeoKeys && image.getGeoKeys() || {};
36058
+ var code = keys.ProjectedCSTypeGeoKey || keys.GeographicTypeGeoKey;
36059
+ return code ? 'EPSG:' + code : null;
36060
+ }
36061
+
36062
+ async function importImageRaster(input, optsArg) {
36063
+ var opts = optsArg || {};
36064
+ var imageType = input.png ? 'png' : input.jpeg ? 'jpeg' : null;
36065
+ var imageInput = input[imageType];
36066
+ var decoded = await decodeImage(imageInput, imageType);
36067
+ var world = parseWorldFile(input.world && input.world.content);
36068
+ var sourceId = getFileBase(imageInput.filename || imageType);
36069
+ var transform, bbox, raster, dataset;
36070
+ if (!world) {
36071
+ stop$1('Image raster import requires a world file');
36072
+ }
36073
+ transform = getWorldTransform(world);
36074
+ bbox = getWorldFileBBox(transform, decoded.width, decoded.height);
36075
+ raster = {
36076
+ sourceId: sourceId,
36077
+ grid: {
36078
+ width: decoded.width,
36079
+ height: decoded.height,
36080
+ bands: decoded.bands,
36081
+ pixelType: 'uint8',
36082
+ samples: decoded.samples,
36083
+ sampleBands: decoded.sampleBands,
36084
+ nodata: null,
36085
+ bbox: bbox,
36086
+ transform: transform
36087
+ },
36088
+ derivation: {
36089
+ type: decoded.bands >= 3 ? 'rgb' : 'gray',
36090
+ sourceId: sourceId,
36091
+ bands: decoded.sampleBands
36092
+ },
36093
+ view: {
36094
+ recipe: {
36095
+ type: decoded.bands >= 3 ? 'rgb' : 'gray',
36096
+ bands: decoded.sampleBands
36097
+ }
36098
+ }
36099
+ };
36100
+ raster.view.recipe = getRasterViewRecipe(raster.grid, raster.view.recipe, opts);
36101
+ raster.view.preview = createRasterPreview(raster, opts);
36102
+ dataset = {
36103
+ info: {
36104
+ raster_sources: [getSourceInfo(imageInput, sourceId, imageType, input)]
36105
+ },
36106
+ layers: [{
36107
+ name: imageInput.filename ? getFileBase(imageInput.filename) : null,
36108
+ raster_type: 'grid',
36109
+ raster: raster
36110
+ }]
36111
+ };
36112
+ importImageCrs(dataset, input.prj);
36113
+ return dataset;
36114
+ }
36115
+
36116
+ async function decodeImage(input, imageType) {
36117
+ if (runningInBrowser()) {
36118
+ return decodeImageInBrowser(input.content, imageType);
36119
+ }
36120
+ return imageType == 'png' ? decodePng(input.content) : decodeJpeg(input.content);
36121
+ }
36122
+
36123
+ function decodePng(content) {
36124
+ var png = require$1('pngjs').PNG.sync.read(Buffer.from(content));
36125
+ return rgbaToImageData(png.data, png.width, png.height, true);
36126
+ }
36127
+
36128
+ function decodeJpeg(content) {
36129
+ var jpeg = require$1('jpeg-js');
36130
+ var image = jpeg.decode(Buffer.from(content), {useTArray: true});
36131
+ return rgbaToImageData(image.data, image.width, image.height, false);
36132
+ }
36133
+
36134
+ async function decodeImageInBrowser(content, imageType) {
36135
+ var blob = new Blob([content], {type: imageType == 'png' ? 'image/png' : 'image/jpeg'});
36136
+ var bitmap = await createImageBitmap(blob);
36137
+ var canvas = document.createElement('canvas');
36138
+ var ctx, data;
36139
+ canvas.width = bitmap.width;
36140
+ canvas.height = bitmap.height;
36141
+ ctx = canvas.getContext('2d');
36142
+ ctx.drawImage(bitmap, 0, 0);
36143
+ data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
36144
+ if (bitmap.close) bitmap.close();
36145
+ return rgbaToImageData(data, canvas.width, canvas.height, imageType == 'png');
36146
+ }
36147
+
36148
+ function rgbaToImageData(rgba, width, height, keepAlpha) {
36149
+ var bands = keepAlpha ? 4 : 3;
36150
+ var samples = new Uint8Array(width * height * bands);
36151
+ var src, dest;
36152
+ for (var i = 0, n = width * height; i < n; i++) {
36153
+ src = i * 4;
36154
+ dest = i * bands;
36155
+ samples[dest] = rgba[src];
36156
+ samples[dest + 1] = rgba[src + 1];
36157
+ samples[dest + 2] = rgba[src + 2];
36158
+ if (keepAlpha) samples[dest + 3] = rgba[src + 3];
36159
+ }
36160
+ return {
36161
+ width: width,
36162
+ height: height,
36163
+ bands: bands,
36164
+ samples: samples,
36165
+ sampleBands: bands == 4 ? [0, 1, 2, 3] : [0, 1, 2]
36166
+ };
36167
+ }
36168
+
36169
+ function parseWorldFile(content) {
36170
+ if (!content) return null;
36171
+ var vals = String(content).trim().split(/\s+/).map(Number);
36172
+ if (vals.length < 6 || vals.some(function(val) { return !isFinite(val); })) {
36173
+ stop$1('Invalid world file');
36174
+ }
36175
+ return vals.slice(0, 6);
36176
+ }
36177
+
36178
+ function getWorldTransform(world) {
36179
+ var a = world[0], d = world[1], b = world[2], e = world[3],
36180
+ c = world[4], f = world[5];
36181
+ return [
36182
+ a,
36183
+ b,
36184
+ c - a / 2 - b / 2,
36185
+ d,
36186
+ e,
36187
+ f - d / 2 - e / 2
36188
+ ];
36189
+ }
36190
+
36191
+ function getWorldFileBBox(transform, width, height) {
36192
+ var corners = [
36193
+ transformPoint(transform, 0, 0),
36194
+ transformPoint(transform, width, 0),
36195
+ transformPoint(transform, width, height),
36196
+ transformPoint(transform, 0, height)
36197
+ ];
36198
+ var xs = corners.map(function(p) { return p[0]; });
36199
+ var ys = corners.map(function(p) { return p[1]; });
36200
+ return [
36201
+ Math.min.apply(null, xs),
36202
+ Math.min.apply(null, ys),
36203
+ Math.max.apply(null, xs),
36204
+ Math.max.apply(null, ys)
36205
+ ];
36206
+ }
36207
+
36208
+ function transformPoint(t, col, row) {
36209
+ return [
36210
+ t[0] * col + t[1] * row + t[2],
36211
+ t[3] * col + t[4] * row + t[5]
36212
+ ];
36213
+ }
36214
+
36215
+ function importImageCrs(dataset, prj) {
36216
+ var wkt = prj && prj.content;
36217
+ if (!wkt) {
36218
+ warn('Image raster is missing a .prj file; CRS is unknown');
36219
+ return;
36220
+ }
36221
+ try {
36222
+ setDatasetCrsInfo(dataset, {
36223
+ wkt1: wkt,
36224
+ crs: parsePrj(wkt)
36225
+ });
36226
+ } catch(e) {
36227
+ dataset.info.wkt1 = wkt;
36228
+ }
36229
+ }
36230
+
36231
+ function getSourceInfo(input, sourceId, imageType, group) {
36232
+ var content = input && input.content;
36233
+ return {
36234
+ id: sourceId,
36235
+ type: imageType,
36236
+ filename: input && input.filename || null,
36237
+ byteLength: content && content.byteLength || null,
36238
+ storage: runningInBrowser() ? 'indexeddb-pending' : 'path',
36239
+ worldFile: group.world && group.world.filename || null,
36240
+ prjFile: group.prj && group.prj.filename || null
36241
+ };
36242
+ }
36243
+
34836
36244
  var INHERITED_STYLE_KEYS = [
34837
36245
  'fill', 'fill-opacity',
34838
36246
  'stroke', 'stroke-width', 'stroke-opacity', 'stroke-dasharray',
@@ -35786,6 +37194,10 @@ ${svg}
35786
37194
  stop$1("GeoPackage import requires async import path");
35787
37195
  } else if (obj.parquet) {
35788
37196
  stop$1("GeoParquet import requires async import path");
37197
+ } else if (obj.geotiff) {
37198
+ stop$1("GeoTIFF import requires async import path");
37199
+ } else if (obj.png || obj.jpeg) {
37200
+ stop$1("Image raster import requires async import path");
35789
37201
  }
35790
37202
 
35791
37203
  return finalizeImportedDataset(dataset, dataFmt, data, opts);
@@ -35806,6 +37218,14 @@ ${svg}
35806
37218
  dataFmt = 'geoparquet';
35807
37219
  data = obj.parquet;
35808
37220
  dataset = await importGeoParquet(data, opts);
37221
+ } else if (obj.geotiff) {
37222
+ dataFmt = 'geotiff';
37223
+ data = obj.geotiff;
37224
+ dataset = await importGeoTIFF(data, opts);
37225
+ } else if (obj.png || obj.jpeg) {
37226
+ dataFmt = obj.png ? 'png' : 'jpeg';
37227
+ data = obj[dataFmt];
37228
+ dataset = await importImageRaster(obj, opts);
35809
37229
  } else {
35810
37230
  return importContent(obj, opts);
35811
37231
  }
@@ -36007,11 +37427,63 @@ ${svg}
36007
37427
  if (data) {
36008
37428
  data = importTable(data);
36009
37429
  }
37430
+ if (lyr.raster) {
37431
+ lyr.raster = importRasterData(lyr.raster);
37432
+ }
36010
37433
  return Object.assign(lyr, {
36011
37434
  data: lyr.data ? new DataTable(data) : null
36012
37435
  });
36013
37436
  }
36014
37437
 
37438
+ function importRasterData(raster) {
37439
+ var copy = Object.assign({}, raster);
37440
+ if (raster.grid) {
37441
+ copy.grid = Object.assign({}, raster.grid);
37442
+ if (raster.grid.samples) {
37443
+ copy.grid.samples = restoreRasterSamples(raster.grid.samples, raster.grid.pixelType);
37444
+ }
37445
+ }
37446
+ if (raster.view) {
37447
+ copy.view = Object.assign({}, raster.view);
37448
+ if (raster.view.preview) {
37449
+ copy.view.preview = Object.assign({}, raster.view.preview);
37450
+ if (raster.view.preview.pixels) {
37451
+ copy.view.preview.pixels = new Uint8ClampedArray(BinArray.copyToArrayBuffer(raster.view.preview.pixels));
37452
+ }
37453
+ }
37454
+ }
37455
+ if (raster.preview) {
37456
+ copy.preview = Object.assign({}, raster.preview);
37457
+ if (raster.preview.pixels) {
37458
+ copy.preview.pixels = new Uint8ClampedArray(BinArray.copyToArrayBuffer(raster.preview.pixels));
37459
+ }
37460
+ }
37461
+ if (raster.pixels) {
37462
+ copy.pixels = new Uint8ClampedArray(BinArray.copyToArrayBuffer(raster.pixels));
37463
+ }
37464
+ return copy;
37465
+ }
37466
+
37467
+ function restoreRasterSamples(buf, pixelType) {
37468
+ var arrbuf = BinArray.copyToArrayBuffer(buf);
37469
+ var Ctor = getRasterSampleArrayConstructor(pixelType);
37470
+ return new Ctor(arrbuf);
37471
+ }
37472
+
37473
+ function getRasterSampleArrayConstructor(pixelType) {
37474
+ switch (pixelType) {
37475
+ case 'uint8': return Uint8Array;
37476
+ case 'int8': return Int8Array;
37477
+ case 'uint16': return Uint16Array;
37478
+ case 'int16': return Int16Array;
37479
+ case 'uint32': return Uint32Array;
37480
+ case 'int32': return Int32Array;
37481
+ case 'float32': return Float32Array;
37482
+ case 'float64': return Float64Array;
37483
+ }
37484
+ return Uint8Array;
37485
+ }
37486
+
36015
37487
  var Unpack = /*#__PURE__*/Object.freeze({
36016
37488
  __proto__: null,
36017
37489
  restoreSessionData: restoreSessionData,
@@ -36143,11 +37615,18 @@ ${svg}
36143
37615
  function expandKmzFile(file, cache) {
36144
37616
  var files = expandZipFile(file, cache);
36145
37617
  var name = replaceFileExtension(parseLocalPath(file).filename, 'kml');
36146
- if (files[0] == 'doc.kml') {
36147
- files[0] = name;
37618
+ files = files.map(function(file) {
37619
+ if (file == 'doc.kml') {
37620
+ return name;
37621
+ }
37622
+ return file;
37623
+ });
37624
+ if ('doc.kml' in cache) {
36148
37625
  cache[name] = cache['doc.kml'];
36149
37626
  }
36150
- return files;
37627
+ return files.filter(function(file) {
37628
+ return guessInputFileType(file) == 'kml';
37629
+ });
36151
37630
  }
36152
37631
 
36153
37632
  function expandZipFile(file, cache) {
@@ -36239,6 +37718,8 @@ ${svg}
36239
37718
  content = null; // for g.c.
36240
37719
  if (fileType == 'shp' || fileType == 'dbf') {
36241
37720
  readShapefileAuxFiles(path, input, cache);
37721
+ } else if (isRasterImageInputType(fileType)) {
37722
+ readRasterImageAuxFiles(path, input, cache);
36242
37723
  }
36243
37724
  if (fileType == 'shp' && !input.dbf) {
36244
37725
  message(utils.format("[%s] .dbf file is missing - shapes imported without attribute data.", path));
@@ -36293,11 +37774,13 @@ ${svg}
36293
37774
  content = null;
36294
37775
  if (fileType == 'shp' || fileType == 'dbf') {
36295
37776
  readShapefileAuxFiles(path, input, cache);
37777
+ } else if (isRasterImageInputType(fileType)) {
37778
+ readRasterImageAuxFiles(path, input, cache);
36296
37779
  }
36297
37780
  if (fileType == 'shp' && !input.dbf) {
36298
37781
  message(utils.format("[%s] .dbf file is missing - shapes imported without attribute data.", path));
36299
37782
  }
36300
- return fileType == 'gpkg' || fileType == 'fgb' || fileType == 'parquet' ? importContentAsync(input, opts) : importContent(input, opts);
37783
+ return fileType == 'gpkg' || fileType == 'fgb' || fileType == 'parquet' || fileType == 'geotiff' || isRasterImageInputType(fileType) ? importContentAsync(input, opts) : importContent(input, opts);
36301
37784
  }
36302
37785
 
36303
37786
  // Import multiple files to a single dataset
@@ -36331,6 +37814,7 @@ ${svg}
36331
37814
  async function importFilesTogetherAsync(files, opts) {
36332
37815
  var unbuiltTopology = false;
36333
37816
  var datasets = [];
37817
+ files = removeRasterImageSidecars(files);
36334
37818
  for (var fname of files) {
36335
37819
  // import without topology or snapping
36336
37820
  var importOpts = utils.defaults({no_topology: true, snap: false, snap_interval: null, files: [fname]}, opts);
@@ -36439,6 +37923,60 @@ ${svg}
36439
37923
  }
36440
37924
  }
36441
37925
 
37926
+ function readRasterImageAuxFiles(path, obj, cache) {
37927
+ var prjPath = replaceFileExtension(path, 'prj');
37928
+ var worldPath = findWorldFile(path, cache);
37929
+ if (cli.isFile(prjPath, cache)) {
37930
+ obj.prj = {filename: prjPath, content: cli.readFile(prjPath, 'utf-8', cache)};
37931
+ }
37932
+ if (worldPath) {
37933
+ obj.world = {filename: worldPath, content: cli.readFile(worldPath, 'utf-8', cache)};
37934
+ }
37935
+ }
37936
+
37937
+ function findWorldFile(path, cache) {
37938
+ var candidates = getWorldFileCandidates(path);
37939
+ for (var i = 0; i < candidates.length; i++) {
37940
+ if (cli.isFile(candidates[i], cache)) return candidates[i];
37941
+ }
37942
+ return null;
37943
+ }
37944
+
37945
+ function getWorldFileCandidates(path) {
37946
+ var ext = getFileExtension(path).toLowerCase();
37947
+ var candidates = [replaceFileExtension(path, 'wld'), replaceFileExtension(path, 'tfw')];
37948
+ if (ext == 'png') {
37949
+ candidates.unshift(replaceFileExtension(path, 'pgw'), replaceFileExtension(path, 'pngw'));
37950
+ } else if (ext == 'jpg' || ext == 'jpeg') {
37951
+ candidates.unshift(
37952
+ replaceFileExtension(path, 'jgw'),
37953
+ replaceFileExtension(path, 'jpw'),
37954
+ replaceFileExtension(path, 'jpgw'),
37955
+ replaceFileExtension(path, 'jpegw')
37956
+ );
37957
+ }
37958
+ return utils.uniq(candidates);
37959
+ }
37960
+
37961
+ function removeRasterImageSidecars(files) {
37962
+ var imageBases = {};
37963
+ files.forEach(function(file) {
37964
+ var type = guessInputFileType(file);
37965
+ if (isRasterImageInputType(type)) {
37966
+ imageBases[getFileBase(file).toLowerCase()] = true;
37967
+ }
37968
+ });
37969
+ return files.filter(function(file) {
37970
+ var ext = getFileExtension(file).toLowerCase();
37971
+ var type = guessInputFileType(file);
37972
+ var base = getFileBase(file).toLowerCase();
37973
+ if ((type == 'prj' || isWorldFileExtension(ext)) && imageBases[base]) {
37974
+ return false;
37975
+ }
37976
+ return true;
37977
+ });
37978
+ }
37979
+
36442
37980
  var FileImport = /*#__PURE__*/Object.freeze({
36443
37981
  __proto__: null,
36444
37982
  importFile: importFile,
@@ -43263,6 +44801,10 @@ ${svg}
43263
44801
  var lyr2 = outputLayers[i];
43264
44802
  lyr.shapes = lyr2.shapes;
43265
44803
  lyr.data = lyr2.data;
44804
+ if (lyr2.raster) {
44805
+ lyr.raster = lyr2.raster;
44806
+ lyr.raster_type = lyr2.raster_type;
44807
+ }
43266
44808
  });
43267
44809
  dissolveArcs(dataset);
43268
44810
  }
@@ -43273,8 +44815,15 @@ ${svg}
43273
44815
  profileStart('clipLayers');
43274
44816
  opts = opts || {no_cleanup: true}; // TODO: update testing functions
43275
44817
  var usingPathClip = utils.some(targetLayers, layerHasPaths);
44818
+ var usingRasterClip = utils.some(targetLayers, layerHasRaster);
43276
44819
  var mergedDataset, clipLyr, nodes, result;
43277
- var clipDataset = normalizeOverlaySource(clipSrc, targetDataset, opts);
44820
+ var clipDataset;
44821
+ if (usingRasterClip) {
44822
+ result = clipRasterLayers(targetLayers, clipSrc, targetDataset, type, opts);
44823
+ profileEnd('clipLayers');
44824
+ return result;
44825
+ }
44826
+ clipDataset = normalizeOverlaySource(clipSrc, targetDataset, opts);
43278
44827
  if (!opts.no_warn) {
43279
44828
  warnIfBoundsDontOverlap(targetLayers, targetDataset, clipDataset, type);
43280
44829
  }
@@ -43308,6 +44857,30 @@ ${svg}
43308
44857
  return result;
43309
44858
  }
43310
44859
 
44860
+ function clipRasterLayers(targetLayers, clipSrc, targetDataset, type, opts) {
44861
+ var clipDataset, clipBounds, bbox;
44862
+ if (type != 'clip') {
44863
+ stop$1('Raster layers only support clipping');
44864
+ }
44865
+ if (utils.some(targetLayers, function(lyr) {return !layerHasRaster(lyr);})) {
44866
+ stop$1('Raster clipping cannot be mixed with vector target layers');
44867
+ }
44868
+ if (opts.bbox2) {
44869
+ bbox = opts.bbox2;
44870
+ } else {
44871
+ clipDataset = normalizeOverlaySource(clipSrc, targetDataset, opts);
44872
+ clipBounds = getLayerBounds(clipDataset.layers[0], clipDataset.arcs);
44873
+ if (!clipBounds || !clipBounds.hasBounds()) {
44874
+ stop$1('Missing raster clipping bounds');
44875
+ }
44876
+ bbox = clipBounds.toArray();
44877
+ }
44878
+ return targetLayers.map(function(lyr) {
44879
+ clipRasterToBBox(lyr, bbox, opts);
44880
+ return lyr;
44881
+ });
44882
+ }
44883
+
43311
44884
  function clipLayersByBBox(layers, dataset, opts) {
43312
44885
  var bbox = opts.bbox2;
43313
44886
  var clipLyr = divideDatasetByBBox(dataset, bbox);
@@ -44215,6 +45788,16 @@ ${svg}
44215
45788
  null_shape_count: 0,
44216
45789
  null_data_count: lyr.data ? countNullRecords(lyr.data.getRecords()) : n
44217
45790
  };
45791
+ if (layerHasRaster(lyr)) {
45792
+ o.geometry_type = null;
45793
+ o.raster_type = lyr.raster_type;
45794
+ o.raster_width = getRasterWidth(lyr.raster);
45795
+ o.raster_height = getRasterHeight(lyr.raster);
45796
+ o.raster_bands = getRasterBandCount(lyr.raster);
45797
+ o.raster_pixel_type = getRasterPixelType(lyr.raster);
45798
+ o.bbox = getLayerBounds(lyr, dataset.arcs).toArray();
45799
+ o.proj4 = getProjInfo(dataset);
45800
+ }
44218
45801
  if (lyr.shapes && lyr.shapes.length > 0) {
44219
45802
  o.null_shape_count = countNullShapes(lyr.shapes);
44220
45803
  o.bbox = getLayerBounds(lyr, dataset.arcs).toArray();
@@ -44244,7 +45827,7 @@ ${svg}
44244
45827
  var str = '';
44245
45828
  arr.forEach(function(info, i) {
44246
45829
  var title = 'Layer: ' + (info.layer_name || '[unnamed layer]');
44247
- var tableStr = formatAttributeTableInfo(info.attribute_data);
45830
+ var tableStr = info.raster_type ? '' : formatAttributeTableInfo(info.attribute_data);
44248
45831
  var tableWidth = measureLongestLine(tableStr);
44249
45832
  var ruleLen = Math.min(Math.max(title.length, tableWidth), MAX_RULE_LEN);
44250
45833
  str += '\n';
@@ -44259,12 +45842,17 @@ ${svg}
44259
45842
 
44260
45843
  function formatLayerInfo(data) {
44261
45844
  var str = '';
44262
- str += "Type: " + (data.geometry_type || "tabular data") + "\n";
44263
- str += utils.format("Records: %,d\n",data.feature_count);
45845
+ str += "Type: " + (data.raster_type ? "raster data" : data.geometry_type || "tabular data") + "\n";
45846
+ if (data.raster_type) {
45847
+ str += utils.format("Size: %,d x %,d x %,d\n", data.raster_width, data.raster_height, data.raster_bands);
45848
+ str += "Data: " + (data.raster_pixel_type || 'unknown') + "\n";
45849
+ } else {
45850
+ str += utils.format("Records: %,d\n",data.feature_count);
45851
+ }
44264
45852
  if (data.null_shape_count > 0) {
44265
45853
  str += utils.format("Nulls: %'d", data.null_shape_count) + "\n";
44266
45854
  }
44267
- if (data.geometry_type && data.feature_count > data.null_shape_count) {
45855
+ if (data.raster_type || data.geometry_type && data.feature_count > data.null_shape_count) {
44268
45856
  str += "Bounds: " + data.bbox.join(',') + "\n";
44269
45857
  str += "CRS: " + data.proj4 + "\n";
44270
45858
  }
@@ -55681,7 +57269,7 @@ ${svg}
55681
57269
  });
55682
57270
  }
55683
57271
 
55684
- var version = "0.7.13";
57272
+ var version = "0.7.14";
55685
57273
 
55686
57274
  // Parse command line args into commands and run them
55687
57275
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -56582,6 +58170,8 @@ ${svg}
56582
58170
  detail: copyDetail(detail),
56583
58171
  name: layer.name,
56584
58172
  geometry_type: layer.geometry_type,
58173
+ raster_type: layer.raster_type || null,
58174
+ raster: layer.raster ? copyRasterData(layer.raster) : null,
56585
58175
  shapes: layer.shapes ? cloneShapes(layer.shapes) : null,
56586
58176
  data: layer.data || null
56587
58177
  });
@@ -56875,6 +58465,8 @@ ${svg}
56875
58465
  revision: getUndoRevision(unit.target),
56876
58466
  name: unit.target.name,
56877
58467
  geometry_type: unit.target.geometry_type,
58468
+ raster_type: unit.target.raster_type || null,
58469
+ raster: unit.target.raster ? copyRasterData(unit.target.raster) : null,
56878
58470
  shapes: unit.target.shapes ? cloneShapes(unit.target.shapes) : null,
56879
58471
  data: unit.target.data || null
56880
58472
  });
@@ -57001,6 +58593,8 @@ ${svg}
57001
58593
  function restoreLayer(unit) {
57002
58594
  unit.target.name = unit.name;
57003
58595
  unit.target.geometry_type = unit.geometry_type;
58596
+ unit.target.raster_type = unit.raster_type || null;
58597
+ unit.target.raster = unit.raster ? copyRasterData(unit.raster) : null;
57004
58598
  unit.target.shapes = unit.shapes ? cloneShapes(unit.shapes) : null;
57005
58599
  unit.target.data = unit.data;
57006
58600
  }
@@ -57204,6 +58798,7 @@ ${svg}
57204
58798
  Lines,
57205
58799
  Logging,
57206
58800
  Profile,
58801
+ RasterUtils,
57207
58802
  Merging,
57208
58803
  MosaicIndex$1,
57209
58804
  OptionParsingUtils,