mapshaper 0.7.47 → 0.7.49
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/mapshaper.js +1941 -601
- package/package.json +1 -1
- package/www/mapshaper-gui.js +281 -64
- package/www/mapshaper.js +1941 -601
- package/www/page.css +11 -0
package/www/mapshaper.js
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
return obj === Object(obj); // via underscore
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function clamp$
|
|
81
|
+
function clamp$3(val, min, max) {
|
|
82
82
|
return val < min ? min : (val > max ? max : val);
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -721,7 +721,7 @@
|
|
|
721
721
|
function findValueByRank(arr, rank) {
|
|
722
722
|
if (!arr.length || rank < 1 || rank > arr.length) error$1("[findValueByRank()] invalid input");
|
|
723
723
|
|
|
724
|
-
rank = clamp$
|
|
724
|
+
rank = clamp$3(rank | 0, 1, arr.length);
|
|
725
725
|
var k = rank - 1, // conv. rank to array index
|
|
726
726
|
n = arr.length,
|
|
727
727
|
l = 0,
|
|
@@ -1095,7 +1095,7 @@
|
|
|
1095
1095
|
// self-import and the resulting Rollup circular-dependency warning.
|
|
1096
1096
|
var utils = {
|
|
1097
1097
|
addThousandsSep, addslashes, arrayToIndex,
|
|
1098
|
-
clamp: clamp$
|
|
1098
|
+
clamp: clamp$3, cleanNumericString, contains, copyElements, countValues, createBuffer,
|
|
1099
1099
|
defaults, difference,
|
|
1100
1100
|
endsWith, every, expandoBuffer, extend: extend$1, extendBuffer,
|
|
1101
1101
|
find: find$1, findMedian, findQuantile, findRankByValue, findStringPrefix,
|
|
@@ -4740,6 +4740,89 @@
|
|
|
4740
4740
|
pathIsRectangle: pathIsRectangle
|
|
4741
4741
|
});
|
|
4742
4742
|
|
|
4743
|
+
// Low-level access to raster grid samples, shared by the code that reads or
|
|
4744
|
+
// rewrites pixels (-proj, -blur, clipping and preview rendering). These used
|
|
4745
|
+
// to be reimplemented per module, which let the nodata and coverage rules
|
|
4746
|
+
// drift apart.
|
|
4747
|
+
//
|
|
4748
|
+
// Samples are band-interleaved: pixel i occupies
|
|
4749
|
+
// samples[i * bands] through samples[i * bands + bands - 1].
|
|
4750
|
+
|
|
4751
|
+
// A pixel is nodata only when every one of its color-carrying bands matches
|
|
4752
|
+
// the nodata value. An alpha band is ignored, so a transparent pixel whose
|
|
4753
|
+
// color channels hold real values is still treated as data.
|
|
4754
|
+
function samplesAreNodataAtOffset(samples, offset, bands, nodata) {
|
|
4755
|
+
var n = Math.min(bands, 3);
|
|
4756
|
+
if (nodata === null || nodata === undefined) return false;
|
|
4757
|
+
for (var i = 0; i < n; i++) {
|
|
4758
|
+
if (samples[offset + i] != nodata) return false;
|
|
4759
|
+
}
|
|
4760
|
+
return true;
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
function rasterPixelIsNodata(grid, pixelId) {
|
|
4764
|
+
return samplesAreNodataAtOffset(grid.samples, pixelId * grid.bands, grid.bands, grid.nodata);
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
// The coverage mask is set by reprojection to record which output pixels
|
|
4768
|
+
// received source content, independently of the nodata fill value.
|
|
4769
|
+
function rasterPixelIsCovered(grid, pixelId) {
|
|
4770
|
+
return !grid.coverage || grid.coverage[pixelId] > 0;
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
function rasterPixelIsValid(grid, pixelId) {
|
|
4774
|
+
return rasterPixelIsCovered(grid, pixelId) && !rasterPixelIsNodata(grid, pixelId);
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4777
|
+
// False when every pixel is known to be valid, so callers can skip
|
|
4778
|
+
// per-pixel validity testing entirely.
|
|
4779
|
+
function rasterGridHasInvalidPixels(grid) {
|
|
4780
|
+
return !!grid.coverage || grid.nodata !== null && grid.nodata !== undefined;
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4783
|
+
// One byte per pixel, so that code sampling a grid repeatedly can test
|
|
4784
|
+
// validity with a single lookup instead of re-reading every band. Returns
|
|
4785
|
+
// the coverage mask itself when there is no nodata value to fold in, and
|
|
4786
|
+
// null when every pixel is valid.
|
|
4787
|
+
function getRasterValidityMask(grid) {
|
|
4788
|
+
var n = grid.width * grid.height;
|
|
4789
|
+
var mask, i;
|
|
4790
|
+
if (grid.nodata === null || grid.nodata === undefined) return grid.coverage || null;
|
|
4791
|
+
mask = new Uint8Array(n);
|
|
4792
|
+
for (i = 0; i < n; i++) {
|
|
4793
|
+
mask[i] = rasterPixelIsValid(grid, i) ? 1 : 0;
|
|
4794
|
+
}
|
|
4795
|
+
return mask;
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
function rasterSamplesAreFloat(samples) {
|
|
4799
|
+
return samples instanceof Float32Array || samples instanceof Float64Array;
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// Value range of a sample array, used to clamp computed values before
|
|
4803
|
+
// writing them back into an integer array.
|
|
4804
|
+
function getSampleArrayRange(samples) {
|
|
4805
|
+
if (samples instanceof Uint8Array || samples instanceof Uint8ClampedArray) return {min: 0, max: 255};
|
|
4806
|
+
if (samples instanceof Int8Array) return {min: -128, max: 127};
|
|
4807
|
+
if (samples instanceof Uint16Array) return {min: 0, max: 65535};
|
|
4808
|
+
if (samples instanceof Int16Array) return {min: -32768, max: 32767};
|
|
4809
|
+
if (samples instanceof Uint32Array) return {min: 0, max: 4294967295};
|
|
4810
|
+
if (samples instanceof Int32Array) return {min: -2147483648, max: 2147483647};
|
|
4811
|
+
return {min: -Infinity, max: Infinity};
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
var RasterGrid = /*#__PURE__*/Object.freeze({
|
|
4815
|
+
__proto__: null,
|
|
4816
|
+
getRasterValidityMask: getRasterValidityMask,
|
|
4817
|
+
getSampleArrayRange: getSampleArrayRange,
|
|
4818
|
+
rasterGridHasInvalidPixels: rasterGridHasInvalidPixels,
|
|
4819
|
+
rasterPixelIsCovered: rasterPixelIsCovered,
|
|
4820
|
+
rasterPixelIsNodata: rasterPixelIsNodata,
|
|
4821
|
+
rasterPixelIsValid: rasterPixelIsValid,
|
|
4822
|
+
rasterSamplesAreFloat: rasterSamplesAreFloat,
|
|
4823
|
+
samplesAreNodataAtOffset: samplesAreNodataAtOffset
|
|
4824
|
+
});
|
|
4825
|
+
|
|
4743
4826
|
function runningInBrowser() {
|
|
4744
4827
|
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
4745
4828
|
}
|
|
@@ -4821,6 +4904,9 @@
|
|
|
4821
4904
|
function copyRasterGrid(grid) {
|
|
4822
4905
|
var copy = utils.extend({}, grid);
|
|
4823
4906
|
if (grid.samples) copy.samples = copyTypedArray(grid.samples);
|
|
4907
|
+
// The coverage mask is mutated in place by the reprojection code, so an
|
|
4908
|
+
// undo snapshot that shared it would track later edits.
|
|
4909
|
+
if (grid.coverage) copy.coverage = copyTypedArray(grid.coverage);
|
|
4824
4910
|
if (grid.sampleBands) copy.sampleBands = grid.sampleBands.concat();
|
|
4825
4911
|
if (grid.bbox) copy.bbox = grid.bbox.concat();
|
|
4826
4912
|
if (grid.transform) copy.transform = copyObjectOrArray(grid.transform);
|
|
@@ -4928,6 +5014,69 @@
|
|
|
4928
5014
|
return stats;
|
|
4929
5015
|
}
|
|
4930
5016
|
|
|
5017
|
+
// Returns the band values and display color of the pixel containing a map
|
|
5018
|
+
// coordinate, for readouts like the GUI's right-click menu, or null if the point
|
|
5019
|
+
// falls outside the raster. Rotated grids are not supported, because pixels can
|
|
5020
|
+
// only be located through the axis-aligned bbox.
|
|
5021
|
+
// x, y: a point in the coordinate system of the raster's bbox
|
|
5022
|
+
function getRasterPixelAtMapXY(raster, x, y) {
|
|
5023
|
+
var grid = getRasterGrid(raster);
|
|
5024
|
+
var col, row, pixelId, offset, values, valid, isColor, band;
|
|
5025
|
+
if (!grid || !grid.samples || !grid.bbox || rasterGridIsRotated(grid)) return null;
|
|
5026
|
+
col = Math.floor(mapXToRasterPixel(grid, x));
|
|
5027
|
+
row = Math.floor(mapYToRasterPixel(grid, y));
|
|
5028
|
+
if (col < 0 || col >= grid.width || row < 0 || row >= grid.height) return null;
|
|
5029
|
+
pixelId = row * grid.width + col;
|
|
5030
|
+
offset = pixelId * grid.bands;
|
|
5031
|
+
values = [];
|
|
5032
|
+
for (band = 0; band < grid.bands; band++) {
|
|
5033
|
+
values.push(grid.samples[offset + band]);
|
|
5034
|
+
}
|
|
5035
|
+
// The renderer reads interleaved bands in display order, so three or more
|
|
5036
|
+
// bands are shown as R, G, B and (if present) alpha.
|
|
5037
|
+
isColor = grid.bands >= 3;
|
|
5038
|
+
valid = rasterPixelIsValid(grid, pixelId);
|
|
5039
|
+
return {
|
|
5040
|
+
col: col,
|
|
5041
|
+
row: row,
|
|
5042
|
+
values: values,
|
|
5043
|
+
isColor: isColor,
|
|
5044
|
+
isFloat: rasterSamplesAreFloat(grid.samples),
|
|
5045
|
+
valid: valid,
|
|
5046
|
+
// Only color images get a color, so that inspecting a measurement raster
|
|
5047
|
+
// does not trigger a scaling-stats scan of the whole grid.
|
|
5048
|
+
// [r, g, b, a] in the 0-255 range, for formatColor() or a canvas.
|
|
5049
|
+
color: isColor && valid ? getRasterPixelDisplayColor(raster, grid, pixelId) : null
|
|
5050
|
+
};
|
|
5051
|
+
}
|
|
5052
|
+
|
|
5053
|
+
// Returns [r, g, b, a] in the 0-255 range, using the same recipe and scaling as
|
|
5054
|
+
// the preview renderer, so that a readout agrees with how the pixel is drawn.
|
|
5055
|
+
function getRasterPixelDisplayColor(raster, grid, pixelId) {
|
|
5056
|
+
var view = getRasterView(raster);
|
|
5057
|
+
var recipe = getRasterViewRecipe(grid, view && view.recipe);
|
|
5058
|
+
var stats = getRasterViewScalingStats(raster, recipe);
|
|
5059
|
+
var sourceRange = recipe.scaling == 'none' ? getPixelTypeRange(grid.pixelType) : null;
|
|
5060
|
+
var displayRange = getDisplayRange(recipe.scaleRange);
|
|
5061
|
+
var offset = pixelId * grid.bands;
|
|
5062
|
+
var color = [0, 1, 2].map(function(band) {
|
|
5063
|
+
return scaleSample(grid.samples[offset + band], stats && stats[band], sourceRange, displayRange);
|
|
5064
|
+
});
|
|
5065
|
+
color.push(grid.bands >= 4 ?
|
|
5066
|
+
scaleSample(grid.samples[offset + 3], stats && stats[3], sourceRange, [0, 255]) : 255);
|
|
5067
|
+
return color;
|
|
5068
|
+
}
|
|
5069
|
+
|
|
5070
|
+
// Float samples print with binary noise at full precision (a Float32 holding
|
|
5071
|
+
// 1234.5678 prints as 1234.5677490234375), so they are rounded to the number of
|
|
5072
|
+
// digits that the type can actually represent.
|
|
5073
|
+
function formatRasterSampleValue(val, isFloat) {
|
|
5074
|
+
if (val === null || val === undefined) return '';
|
|
5075
|
+
if (!isFinite(val)) return String(val); // NaN, Infinity
|
|
5076
|
+
if (!isFloat) return String(val);
|
|
5077
|
+
return String(+val.toPrecision(7));
|
|
5078
|
+
}
|
|
5079
|
+
|
|
4931
5080
|
function renderRasterGridPreview(grid, recipe, width, height, statsArg, sourceBbox, resamplingMethod) {
|
|
4932
5081
|
var samples = grid.samples;
|
|
4933
5082
|
var bands = grid.bands;
|
|
@@ -5170,10 +5319,22 @@
|
|
|
5170
5319
|
return (sy * grid.width + sx) * bands;
|
|
5171
5320
|
}
|
|
5172
5321
|
|
|
5322
|
+
// A grid is north-up when its affine transform has no rotation/skew terms.
|
|
5323
|
+
// Cropping and reprojection both index pixels through the axis-aligned bbox,
|
|
5324
|
+
// so they can only handle north-up grids.
|
|
5325
|
+
function rasterGridIsRotated(grid) {
|
|
5326
|
+
var t = grid && grid.transform;
|
|
5327
|
+
return !!t && (t[1] !== 0 || t[3] !== 0);
|
|
5328
|
+
}
|
|
5329
|
+
|
|
5173
5330
|
function clipRasterToBBox(lyr, bbox, opts) {
|
|
5174
5331
|
var raster = lyr.raster;
|
|
5175
5332
|
var grid = getRasterGrid(raster);
|
|
5176
|
-
var clipBbox
|
|
5333
|
+
var clipBbox;
|
|
5334
|
+
if (rasterGridIsRotated(grid)) {
|
|
5335
|
+
stop$1('Clipping a rotated or skewed raster is not supported');
|
|
5336
|
+
}
|
|
5337
|
+
clipBbox = intersectBboxes(grid.bbox, bbox);
|
|
5177
5338
|
if (!clipBbox) {
|
|
5178
5339
|
warn('Raster clipping rectangle does not intersect the raster layer');
|
|
5179
5340
|
return false;
|
|
@@ -5234,11 +5395,30 @@
|
|
|
5234
5395
|
width: crop.width,
|
|
5235
5396
|
height: crop.height,
|
|
5236
5397
|
samples: samples,
|
|
5398
|
+
coverage: cropRasterCoverage(grid, crop),
|
|
5237
5399
|
bbox: bbox,
|
|
5238
5400
|
transform: updateTransformForBBox(grid.transform, bbox, crop.width, crop.height)
|
|
5239
5401
|
});
|
|
5240
5402
|
}
|
|
5241
5403
|
|
|
5404
|
+
// The coverage mask is one byte per pixel (not per sample), and must be
|
|
5405
|
+
// cropped alongside the samples -- consumers index it with the cropped
|
|
5406
|
+
// grid's dimensions.
|
|
5407
|
+
function cropRasterCoverage(grid, crop) {
|
|
5408
|
+
var coverage = grid.coverage;
|
|
5409
|
+
var cropped, src, dest, x, y;
|
|
5410
|
+
if (!coverage) return coverage;
|
|
5411
|
+
cropped = new Uint8Array(crop.width * crop.height);
|
|
5412
|
+
for (y = 0; y < crop.height; y++) {
|
|
5413
|
+
src = (crop.y + y) * grid.width + crop.x;
|
|
5414
|
+
dest = y * crop.width;
|
|
5415
|
+
for (x = 0; x < crop.width; x++) {
|
|
5416
|
+
cropped[dest + x] = coverage[src + x];
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
return cropped;
|
|
5420
|
+
}
|
|
5421
|
+
|
|
5242
5422
|
function getRasterLayerBounds(lyr) {
|
|
5243
5423
|
var bbox = lyr.raster && getRasterBBox(lyr.raster);
|
|
5244
5424
|
return bbox && bbox.length == 4 ? new Bounds(bbox) : null;
|
|
@@ -5499,11 +5679,7 @@
|
|
|
5499
5679
|
}
|
|
5500
5680
|
|
|
5501
5681
|
function allSamplesAreNoData(data, offset, bands, noData) {
|
|
5502
|
-
|
|
5503
|
-
for (var i = 0; i < n; i++) {
|
|
5504
|
-
if (data[offset + i] != noData) return false;
|
|
5505
|
-
}
|
|
5506
|
-
return true;
|
|
5682
|
+
return samplesAreNodataAtOffset(data, offset, bands, noData);
|
|
5507
5683
|
}
|
|
5508
5684
|
|
|
5509
5685
|
function copyObjectOrArray(obj) {
|
|
@@ -5520,12 +5696,14 @@
|
|
|
5520
5696
|
copyTypedArray: copyTypedArray,
|
|
5521
5697
|
createRasterPreview: createRasterPreview,
|
|
5522
5698
|
cropRasterGrid: cropRasterGrid,
|
|
5699
|
+
formatRasterSampleValue: formatRasterSampleValue,
|
|
5523
5700
|
getRasterBBox: getRasterBBox,
|
|
5524
5701
|
getRasterBandCount: getRasterBandCount,
|
|
5525
5702
|
getRasterCrop: getRasterCrop,
|
|
5526
5703
|
getRasterGrid: getRasterGrid,
|
|
5527
5704
|
getRasterHeight: getRasterHeight,
|
|
5528
5705
|
getRasterLayerBounds: getRasterLayerBounds,
|
|
5706
|
+
getRasterPixelAtMapXY: getRasterPixelAtMapXY,
|
|
5529
5707
|
getRasterPixelType: getRasterPixelType,
|
|
5530
5708
|
getRasterPreview: getRasterPreview,
|
|
5531
5709
|
getRasterScalingStats: getRasterScalingStats,
|
|
@@ -5536,6 +5714,7 @@
|
|
|
5536
5714
|
getRasterViewScalingStats: getRasterViewScalingStats,
|
|
5537
5715
|
getRasterWidth: getRasterWidth,
|
|
5538
5716
|
intersectBboxes: intersectBboxes,
|
|
5717
|
+
rasterGridIsRotated: rasterGridIsRotated,
|
|
5539
5718
|
renderRasterExportPreview: renderRasterExportPreview,
|
|
5540
5719
|
renderRasterPreview: renderRasterPreview,
|
|
5541
5720
|
renderRasterViewportPreview: renderRasterViewportPreview,
|
|
@@ -5712,6 +5891,14 @@
|
|
|
5712
5891
|
stop$1(layerTypeMessage(lyr, "Expected a polygon or polyline layer", msg));
|
|
5713
5892
|
}
|
|
5714
5893
|
|
|
5894
|
+
// Guard for vector commands that would otherwise replace a raster layer with
|
|
5895
|
+
// a geometry-less result, silently discarding its pixels.
|
|
5896
|
+
function requireNotRasterLayer(lyr, msg) {
|
|
5897
|
+
if (layerHasRaster(lyr)) {
|
|
5898
|
+
stop$1(msg || 'This command does not support raster layers');
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5715
5902
|
// Used by info command and gui layer menu
|
|
5716
5903
|
function getLayerSourceFile(lyr, dataset) {
|
|
5717
5904
|
var inputs = dataset.info && dataset.info.input_files;
|
|
@@ -5913,6 +6100,7 @@
|
|
|
5913
6100
|
layerTypeMessage: layerTypeMessage,
|
|
5914
6101
|
requireDataField: requireDataField,
|
|
5915
6102
|
requireDataFields: requireDataFields,
|
|
6103
|
+
requireNotRasterLayer: requireNotRasterLayer,
|
|
5916
6104
|
requirePathLayer: requirePathLayer,
|
|
5917
6105
|
requirePointLayer: requirePointLayer,
|
|
5918
6106
|
requirePolygonLayer: requirePolygonLayer,
|
|
@@ -6257,6 +6445,124 @@
|
|
|
6257
6445
|
probablyDecimalDegreeBounds: probablyDecimalDegreeBounds
|
|
6258
6446
|
});
|
|
6259
6447
|
|
|
6448
|
+
/*
|
|
6449
|
+
* Small geometric helpers shared by the polyhedral projection modules
|
|
6450
|
+
* (mapshaper-polyhedral-projection, mapshaper-lee-tetrahedral,
|
|
6451
|
+
* mapshaper-narukawa2022). These projections all unfold a polyhedral net into
|
|
6452
|
+
* affine layout copies and clip the copies to a rectangular frame, so they need
|
|
6453
|
+
* the same 2x3 affine arithmetic and the same half-plane polygon clipping.
|
|
6454
|
+
*/
|
|
6455
|
+
|
|
6456
|
+
var EPS$4 = 1e-12;
|
|
6457
|
+
|
|
6458
|
+
// A 2x3 affine matrix, stored as [a, b, tx, c, d, ty].
|
|
6459
|
+
function applyMatrix(m, p) {
|
|
6460
|
+
return [
|
|
6461
|
+
m[0] * p[0] + m[1] * p[1] + m[2],
|
|
6462
|
+
m[3] * p[0] + m[4] * p[1] + m[5]
|
|
6463
|
+
];
|
|
6464
|
+
}
|
|
6465
|
+
|
|
6466
|
+
function invertMatrix(m) {
|
|
6467
|
+
var det = m[0] * m[4] - m[1] * m[3];
|
|
6468
|
+
return [
|
|
6469
|
+
m[4] / det,
|
|
6470
|
+
-m[1] / det,
|
|
6471
|
+
(m[1] * m[5] - m[4] * m[2]) / det,
|
|
6472
|
+
-m[3] / det,
|
|
6473
|
+
m[0] / det,
|
|
6474
|
+
(m[3] * m[2] - m[0] * m[5]) / det
|
|
6475
|
+
];
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6478
|
+
// Sutherland-Hodgman clip of a [x, y] ring against one axis-aligned half-plane.
|
|
6479
|
+
function clipPolygonAxis(polygon, axis, value, keepGreater) {
|
|
6480
|
+
var output = [];
|
|
6481
|
+
if (!polygon.length) return output;
|
|
6482
|
+
var a = polygon[polygon.length - 1];
|
|
6483
|
+
var aInside = isInside(a[axis], value, keepGreater);
|
|
6484
|
+
polygon.forEach(function(b) {
|
|
6485
|
+
var bInside = isInside(b[axis], value, keepGreater);
|
|
6486
|
+
if (aInside != bInside) {
|
|
6487
|
+
var t = (value - a[axis]) / (b[axis] - a[axis]);
|
|
6488
|
+
var p = [
|
|
6489
|
+
a[0] + (b[0] - a[0]) * t,
|
|
6490
|
+
a[1] + (b[1] - a[1]) * t
|
|
6491
|
+
];
|
|
6492
|
+
p[axis] = value;
|
|
6493
|
+
output.push(p);
|
|
6494
|
+
}
|
|
6495
|
+
if (bInside) output.push(b.concat());
|
|
6496
|
+
a = b;
|
|
6497
|
+
aInside = bInside;
|
|
6498
|
+
});
|
|
6499
|
+
return output;
|
|
6500
|
+
}
|
|
6501
|
+
|
|
6502
|
+
// As clipPolygonAxis, but for raster mesh vertices, which carry the source
|
|
6503
|
+
// grid position (sx, sy) and geographic position (lon, lat) alongside the
|
|
6504
|
+
// projected position. The axis argument is a property name ('x' or 'y').
|
|
6505
|
+
function clipRasterPolygonAxis(polygon, axis, value, keepGreater) {
|
|
6506
|
+
var output = [];
|
|
6507
|
+
if (!polygon.length) return output;
|
|
6508
|
+
var a = polygon[polygon.length - 1];
|
|
6509
|
+
var aInside = isInside(a[axis], value, keepGreater);
|
|
6510
|
+
polygon.forEach(function(b) {
|
|
6511
|
+
var bInside = isInside(b[axis], value, keepGreater);
|
|
6512
|
+
if (aInside != bInside) {
|
|
6513
|
+
var t = (value - a[axis]) / (b[axis] - a[axis]);
|
|
6514
|
+
var vertex = {
|
|
6515
|
+
x: a.x + (b.x - a.x) * t,
|
|
6516
|
+
y: a.y + (b.y - a.y) * t,
|
|
6517
|
+
sx: a.sx + (b.sx - a.sx) * t,
|
|
6518
|
+
sy: a.sy + (b.sy - a.sy) * t,
|
|
6519
|
+
lon: interpolateLongitude(a.lon, b.lon, t),
|
|
6520
|
+
lat: a.lat + (b.lat - a.lat) * t
|
|
6521
|
+
};
|
|
6522
|
+
vertex[axis] = value;
|
|
6523
|
+
output.push(vertex);
|
|
6524
|
+
}
|
|
6525
|
+
if (bInside) output.push(copyRasterVertex(b, b.x, b.y));
|
|
6526
|
+
a = b;
|
|
6527
|
+
aInside = bInside;
|
|
6528
|
+
});
|
|
6529
|
+
return output;
|
|
6530
|
+
}
|
|
6531
|
+
|
|
6532
|
+
function copyRasterVertex(vertex, x, y) {
|
|
6533
|
+
return {
|
|
6534
|
+
x: x,
|
|
6535
|
+
y: y,
|
|
6536
|
+
sx: vertex.sx,
|
|
6537
|
+
sy: vertex.sy,
|
|
6538
|
+
lon: vertex.lon,
|
|
6539
|
+
lat: vertex.lat
|
|
6540
|
+
};
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
// Interpolate along the shorter of the two arcs, so a cell straddling the
|
|
6544
|
+
// antimeridian does not interpolate the long way around the globe.
|
|
6545
|
+
function interpolateLongitude(a, b, t) {
|
|
6546
|
+
var delta = b - a;
|
|
6547
|
+
if (delta > 180) delta -= 360;
|
|
6548
|
+
if (delta < -180) delta += 360;
|
|
6549
|
+
return normalizeLongitude$1(a + delta * t);
|
|
6550
|
+
}
|
|
6551
|
+
|
|
6552
|
+
function normalizeLongitude$1(lon) {
|
|
6553
|
+
while (lon > 180) lon -= 360;
|
|
6554
|
+
while (lon < -180) lon += 360;
|
|
6555
|
+
return lon;
|
|
6556
|
+
}
|
|
6557
|
+
|
|
6558
|
+
function clamp$2(value, min, max) {
|
|
6559
|
+
return Math.max(min, Math.min(max, value));
|
|
6560
|
+
}
|
|
6561
|
+
|
|
6562
|
+
function isInside(coord, value, keepGreater) {
|
|
6563
|
+
return keepGreater ? coord >= value - EPS$4 : coord <= value + EPS$4;
|
|
6564
|
+
}
|
|
6565
|
+
|
|
6260
6566
|
/*
|
|
6261
6567
|
* Polyhedral unfolding code adapted from d3-geo-polygon.
|
|
6262
6568
|
*
|
|
@@ -6264,6 +6570,7 @@
|
|
|
6264
6570
|
* ISC License: https://github.com/d3/d3-geo-polygon/blob/main/LICENSE
|
|
6265
6571
|
*/
|
|
6266
6572
|
|
|
6573
|
+
|
|
6267
6574
|
var D2R$7 = Math.PI / 180;
|
|
6268
6575
|
var R2D$6 = 180 / Math.PI;
|
|
6269
6576
|
var EPS$3 = 1e-12;
|
|
@@ -6345,6 +6652,7 @@
|
|
|
6345
6652
|
outline: outline,
|
|
6346
6653
|
forward: forward,
|
|
6347
6654
|
forwardFace: forwardFace,
|
|
6655
|
+
invertFace: invertFace,
|
|
6348
6656
|
findFace: findFace,
|
|
6349
6657
|
findTransitionRegion: findTransitionRegion,
|
|
6350
6658
|
getTopology: getTopology
|
|
@@ -6377,13 +6685,44 @@
|
|
|
6377
6685
|
|
|
6378
6686
|
function projectFacePoint(face, lam, phi) {
|
|
6379
6687
|
var p = face.project(lam, phi);
|
|
6380
|
-
p = applyMatrix
|
|
6688
|
+
p = applyMatrix(face.transform, p);
|
|
6381
6689
|
p = transformOutputPoint(p);
|
|
6382
6690
|
p[0] -= centerX;
|
|
6383
6691
|
p[1] -= centerY;
|
|
6384
6692
|
return p;
|
|
6385
6693
|
}
|
|
6386
6694
|
|
|
6695
|
+
function invertFace(x, y, faceId) {
|
|
6696
|
+
var face = faces[faceId];
|
|
6697
|
+
var p, rotated;
|
|
6698
|
+
if (!face || !face.project.invert) return null;
|
|
6699
|
+
p = inverseTransformOutputPoint([x + centerX, y + centerY]);
|
|
6700
|
+
p = applyMatrix(invertMatrix(face.transform), p);
|
|
6701
|
+
rotated = face.project.invert(p[0], p[1]);
|
|
6702
|
+
if (!rotated) return null;
|
|
6703
|
+
return rotateRadians(
|
|
6704
|
+
rotated[0],
|
|
6705
|
+
rotated[1],
|
|
6706
|
+
config.rotation[0] * D2R$7,
|
|
6707
|
+
config.rotation[1] * D2R$7,
|
|
6708
|
+
config.rotation[2] * D2R$7,
|
|
6709
|
+
true
|
|
6710
|
+
);
|
|
6711
|
+
}
|
|
6712
|
+
|
|
6713
|
+
function inverseTransformOutputPoint(p) {
|
|
6714
|
+
var x = p[0];
|
|
6715
|
+
var y = p[1];
|
|
6716
|
+
if (planarAngle) {
|
|
6717
|
+
var cos = Math.cos(planarAngle);
|
|
6718
|
+
var sin = Math.sin(planarAngle);
|
|
6719
|
+
var x2 = x * cos + y * sin;
|
|
6720
|
+
y = -x * sin + y * cos;
|
|
6721
|
+
x = x2;
|
|
6722
|
+
}
|
|
6723
|
+
return [x / scale, -y / scale];
|
|
6724
|
+
}
|
|
6725
|
+
|
|
6387
6726
|
function findFace(lam, phi) {
|
|
6388
6727
|
var rotated = rotateRadians(
|
|
6389
6728
|
lam,
|
|
@@ -6451,9 +6790,9 @@
|
|
|
6451
6790
|
config.rotation[2] * D2R$7,
|
|
6452
6791
|
true
|
|
6453
6792
|
);
|
|
6454
|
-
return [normalizeLongitude$
|
|
6793
|
+
return [normalizeLongitude$1(q[0] * R2D$6 + lon0), q[1] * R2D$6];
|
|
6455
6794
|
});
|
|
6456
|
-
var paths = splitPathAtAntimeridian$
|
|
6795
|
+
var paths = splitPathAtAntimeridian$3(
|
|
6457
6796
|
interpolateGreatCircle(endpoints[0], endpoints[1], 0.05)
|
|
6458
6797
|
);
|
|
6459
6798
|
paths.forEach(function(path) {
|
|
@@ -6481,15 +6820,15 @@
|
|
|
6481
6820
|
}),
|
|
6482
6821
|
seams: seams,
|
|
6483
6822
|
findRegion: function(lon, lat) {
|
|
6484
|
-
var lam = normalizeLongitude$
|
|
6823
|
+
var lam = normalizeLongitude$1(lon - lon0) * D2R$7;
|
|
6485
6824
|
return findFace(lam, lat * D2R$7);
|
|
6486
6825
|
},
|
|
6487
6826
|
findTransitionRegion: function(lon, lat) {
|
|
6488
|
-
var lam = normalizeLongitude$
|
|
6827
|
+
var lam = normalizeLongitude$1(lon - lon0) * D2R$7;
|
|
6489
6828
|
return findTransitionRegion(lam, lat * D2R$7);
|
|
6490
6829
|
},
|
|
6491
6830
|
projectRegion: function(lon, lat, regionId) {
|
|
6492
|
-
var lam = normalizeLongitude$
|
|
6831
|
+
var lam = normalizeLongitude$1(lon - lon0) * D2R$7;
|
|
6493
6832
|
return forwardFace(lam, lat * D2R$7, regionId);
|
|
6494
6833
|
},
|
|
6495
6834
|
regionsAreAttached: function(a, b) {
|
|
@@ -6597,8 +6936,8 @@
|
|
|
6597
6936
|
var adjacent = findAdjacentFace(faces, face.id, a, b);
|
|
6598
6937
|
if (adjacent >= 0 && attachedPairs.has(pairKey(face.id, adjacent))) continue;
|
|
6599
6938
|
edges.push([
|
|
6600
|
-
applyMatrix
|
|
6601
|
-
applyMatrix
|
|
6939
|
+
applyMatrix(face.transform, face.project(a[0] * D2R$7, a[1] * D2R$7)),
|
|
6940
|
+
applyMatrix(face.transform, face.project(b[0] * D2R$7, b[1] * D2R$7))
|
|
6602
6941
|
]);
|
|
6603
6942
|
}
|
|
6604
6943
|
});
|
|
@@ -6715,13 +7054,6 @@
|
|
|
6715
7054
|
];
|
|
6716
7055
|
}
|
|
6717
7056
|
|
|
6718
|
-
function applyMatrix$1(m, p) {
|
|
6719
|
-
return [
|
|
6720
|
-
m[0] * p[0] + m[1] * p[1] + m[2],
|
|
6721
|
-
m[3] * p[0] + m[4] * p[1] + m[5]
|
|
6722
|
-
];
|
|
6723
|
-
}
|
|
6724
|
-
|
|
6725
7057
|
function rotateRadians(lam, phi, deltaLam, deltaPhi, deltaGamma, invert) {
|
|
6726
7058
|
if (invert) {
|
|
6727
7059
|
var inv = rotatePhiGamma$1(lam, phi, deltaPhi, deltaGamma, true);
|
|
@@ -6747,21 +7079,21 @@
|
|
|
6747
7079
|
return [
|
|
6748
7080
|
Math.atan2(y * cosDeltaGamma + z * sinDeltaGamma,
|
|
6749
7081
|
x * cosDeltaPhi + k * sinDeltaPhi),
|
|
6750
|
-
Math.asin(clamp$
|
|
7082
|
+
Math.asin(clamp$2(k * cosDeltaPhi - x * sinDeltaPhi, -1, 1))
|
|
6751
7083
|
];
|
|
6752
7084
|
}
|
|
6753
7085
|
k = z * cosDeltaPhi + x * sinDeltaPhi;
|
|
6754
7086
|
return [
|
|
6755
7087
|
Math.atan2(y * cosDeltaGamma - k * sinDeltaGamma,
|
|
6756
7088
|
x * cosDeltaPhi - z * sinDeltaPhi),
|
|
6757
|
-
Math.asin(clamp$
|
|
7089
|
+
Math.asin(clamp$2(k * cosDeltaGamma + y * sinDeltaGamma, -1, 1))
|
|
6758
7090
|
];
|
|
6759
7091
|
}
|
|
6760
7092
|
|
|
6761
7093
|
function interpolateGreatCircle(a, b, interval) {
|
|
6762
7094
|
var av = degreesToVector$2(a[0], a[1]);
|
|
6763
7095
|
var bv = degreesToVector$2(b[0], b[1]);
|
|
6764
|
-
var angle = Math.acos(clamp$
|
|
7096
|
+
var angle = Math.acos(clamp$2(dot$2(av, bv), -1, 1));
|
|
6765
7097
|
var n = Math.max(1, Math.ceil(angle * R2D$6 / interval));
|
|
6766
7098
|
var sinAngle = Math.sin(angle);
|
|
6767
7099
|
var points = [];
|
|
@@ -6784,7 +7116,7 @@
|
|
|
6784
7116
|
return points;
|
|
6785
7117
|
}
|
|
6786
7118
|
|
|
6787
|
-
function splitPathAtAntimeridian$
|
|
7119
|
+
function splitPathAtAntimeridian$3(path) {
|
|
6788
7120
|
var paths = [];
|
|
6789
7121
|
var part = [path[0]];
|
|
6790
7122
|
for (var i = 1; i < path.length; i++) {
|
|
@@ -6820,7 +7152,7 @@
|
|
|
6820
7152
|
}
|
|
6821
7153
|
|
|
6822
7154
|
function angularDistance(a, b) {
|
|
6823
|
-
return Math.acos(clamp$
|
|
7155
|
+
return Math.acos(clamp$2(dot$2(degreesToVector$2(a[0], a[1]),
|
|
6824
7156
|
degreesToVector$2(b[0], b[1])), -1, 1));
|
|
6825
7157
|
}
|
|
6826
7158
|
|
|
@@ -6836,7 +7168,7 @@
|
|
|
6836
7168
|
function vectorToDegrees$2(p) {
|
|
6837
7169
|
return [
|
|
6838
7170
|
Math.atan2(p[1], p[0]) * R2D$6,
|
|
6839
|
-
Math.asin(clamp$
|
|
7171
|
+
Math.asin(clamp$2(p[2], -1, 1)) * R2D$6
|
|
6840
7172
|
];
|
|
6841
7173
|
}
|
|
6842
7174
|
|
|
@@ -6891,16 +7223,6 @@
|
|
|
6891
7223
|
return lam;
|
|
6892
7224
|
}
|
|
6893
7225
|
|
|
6894
|
-
function normalizeLongitude$3(lon) {
|
|
6895
|
-
while (lon > 180) lon -= 360;
|
|
6896
|
-
while (lon < -180) lon += 360;
|
|
6897
|
-
return lon;
|
|
6898
|
-
}
|
|
6899
|
-
|
|
6900
|
-
function clamp$3(val, min, max) {
|
|
6901
|
-
return Math.max(min, Math.min(max, val));
|
|
6902
|
-
}
|
|
6903
|
-
|
|
6904
7226
|
/*
|
|
6905
7227
|
* Buckminster Fuller's Airocean arrangement of the icosahedron.
|
|
6906
7228
|
*
|
|
@@ -8106,6 +8428,7 @@
|
|
|
8106
8428
|
* equations, not Imago's power-law approximation.
|
|
8107
8429
|
*/
|
|
8108
8430
|
|
|
8431
|
+
|
|
8109
8432
|
var D2R$2 = Math.PI / 180;
|
|
8110
8433
|
var R2D$2 = 180 / Math.PI;
|
|
8111
8434
|
var HALF_PI = Math.PI / 2;
|
|
@@ -8254,7 +8577,7 @@
|
|
|
8254
8577
|
boundary: sector.sphericalBoundary.map(function(p) {
|
|
8255
8578
|
var q = fromCanonical(p[0], p[1], orientation);
|
|
8256
8579
|
return [
|
|
8257
|
-
normalizeLongitude$
|
|
8580
|
+
normalizeLongitude$1(q[0] * R2D$2 + lon0),
|
|
8258
8581
|
q[1] * R2D$2
|
|
8259
8582
|
];
|
|
8260
8583
|
})
|
|
@@ -8334,7 +8657,7 @@
|
|
|
8334
8657
|
return applyOobTransform(p, facet, oobKey);
|
|
8335
8658
|
});
|
|
8336
8659
|
[0, 1].forEach(function(folded) {
|
|
8337
|
-
var foldedPolygon = clipPolygonAxis
|
|
8660
|
+
var foldedPolygon = clipPolygonAxis(oobPolygon, 0, 0, folded == 0);
|
|
8338
8661
|
if (foldedPolygon.length < 3) return;
|
|
8339
8662
|
foldedPolygon = foldedPolygon.map(function(p) {
|
|
8340
8663
|
return applyFoldTransform(p, folded);
|
|
@@ -8463,26 +8786,26 @@
|
|
|
8463
8786
|
var polygon = clipRasterOobPolygon(vertices, piece.oobKey);
|
|
8464
8787
|
polygon = polygon.map(function(vertex) {
|
|
8465
8788
|
var p = applyOobTransform([vertex.x, vertex.y], facet, piece.oobKey);
|
|
8466
|
-
return copyRasterVertex
|
|
8789
|
+
return copyRasterVertex(vertex, p[0], p[1]);
|
|
8467
8790
|
});
|
|
8468
|
-
polygon = clipRasterPolygonAxis
|
|
8791
|
+
polygon = clipRasterPolygonAxis(
|
|
8469
8792
|
polygon, 'x', 0, piece.folded == 0);
|
|
8470
8793
|
polygon = polygon.map(function(vertex) {
|
|
8471
8794
|
var p = applyFoldTransform([vertex.x, vertex.y], piece.folded);
|
|
8472
|
-
return copyRasterVertex
|
|
8795
|
+
return copyRasterVertex(vertex, p[0], p[1]);
|
|
8473
8796
|
});
|
|
8474
8797
|
polygon = polygon.map(function(vertex) {
|
|
8475
|
-
return copyRasterVertex
|
|
8798
|
+
return copyRasterVertex(
|
|
8476
8799
|
vertex,
|
|
8477
8800
|
vertex.x + LAYOUT_SHIFT +
|
|
8478
8801
|
piece.wrap * 2 * BLOCK_HEIGHT - BLOCK_HEIGHT,
|
|
8479
8802
|
vertex.y + 1.5
|
|
8480
8803
|
);
|
|
8481
8804
|
});
|
|
8482
|
-
polygon = clipRasterPolygonAxis
|
|
8483
|
-
polygon = clipRasterPolygonAxis
|
|
8484
|
-
polygon = clipRasterPolygonAxis
|
|
8485
|
-
polygon = clipRasterPolygonAxis
|
|
8805
|
+
polygon = clipRasterPolygonAxis(polygon, 'x', XMIN, true);
|
|
8806
|
+
polygon = clipRasterPolygonAxis(polygon, 'x', XMAX, false);
|
|
8807
|
+
polygon = clipRasterPolygonAxis(polygon, 'y', YMIN, true);
|
|
8808
|
+
polygon = clipRasterPolygonAxis(polygon, 'y', YMAX, false);
|
|
8486
8809
|
polygon.forEach(function(vertex) {
|
|
8487
8810
|
vertex.x *= EDGE_SCALE;
|
|
8488
8811
|
vertex.y *= EDGE_SCALE;
|
|
@@ -8492,62 +8815,22 @@
|
|
|
8492
8815
|
|
|
8493
8816
|
function clipRasterOobPolygon(polygon, oobKey) {
|
|
8494
8817
|
if (oobKey == 'xpos') {
|
|
8495
|
-
return clipRasterPolygonAxis
|
|
8818
|
+
return clipRasterPolygonAxis(polygon, 'x', 3, true);
|
|
8496
8819
|
}
|
|
8497
8820
|
if (oobKey == 'xneg') {
|
|
8498
|
-
return clipRasterPolygonAxis
|
|
8821
|
+
return clipRasterPolygonAxis(polygon, 'x', -3, false);
|
|
8499
8822
|
}
|
|
8500
8823
|
if (oobKey == 'ypos' || oobKey == 'yneg') {
|
|
8501
|
-
polygon = clipRasterPolygonAxis
|
|
8502
|
-
polygon = clipRasterPolygonAxis
|
|
8503
|
-
return clipRasterPolygonAxis
|
|
8824
|
+
polygon = clipRasterPolygonAxis(polygon, 'x', -3, true);
|
|
8825
|
+
polygon = clipRasterPolygonAxis(polygon, 'x', 3, false);
|
|
8826
|
+
return clipRasterPolygonAxis(
|
|
8504
8827
|
polygon, 'y', oobKey == 'ypos' ? SQRT3$1 : -SQRT3$1,
|
|
8505
8828
|
oobKey == 'ypos');
|
|
8506
8829
|
}
|
|
8507
|
-
polygon = clipRasterPolygonAxis
|
|
8508
|
-
polygon = clipRasterPolygonAxis
|
|
8509
|
-
polygon = clipRasterPolygonAxis
|
|
8510
|
-
return clipRasterPolygonAxis
|
|
8511
|
-
}
|
|
8512
|
-
|
|
8513
|
-
function clipRasterPolygonAxis$1(polygon, axis, value, keepGreater) {
|
|
8514
|
-
var output = [];
|
|
8515
|
-
if (polygon.length === 0) return output;
|
|
8516
|
-
var a = polygon[polygon.length - 1];
|
|
8517
|
-
var aInside = keepGreater ?
|
|
8518
|
-
a[axis] >= value - EPS$1 : a[axis] <= value + EPS$1;
|
|
8519
|
-
polygon.forEach(function(b) {
|
|
8520
|
-
var bInside = keepGreater ?
|
|
8521
|
-
b[axis] >= value - EPS$1 : b[axis] <= value + EPS$1;
|
|
8522
|
-
if (aInside != bInside) {
|
|
8523
|
-
var t = (value - a[axis]) / (b[axis] - a[axis]);
|
|
8524
|
-
var vertex = {
|
|
8525
|
-
x: a.x + (b.x - a.x) * t,
|
|
8526
|
-
y: a.y + (b.y - a.y) * t,
|
|
8527
|
-
sx: a.sx + (b.sx - a.sx) * t,
|
|
8528
|
-
sy: a.sy + (b.sy - a.sy) * t,
|
|
8529
|
-
lon: a.lon + (b.lon - a.lon) * t,
|
|
8530
|
-
lat: a.lat + (b.lat - a.lat) * t
|
|
8531
|
-
};
|
|
8532
|
-
vertex[axis] = value;
|
|
8533
|
-
output.push(vertex);
|
|
8534
|
-
}
|
|
8535
|
-
if (bInside) output.push(copyRasterVertex$1(b, b.x, b.y));
|
|
8536
|
-
a = b;
|
|
8537
|
-
aInside = bInside;
|
|
8538
|
-
});
|
|
8539
|
-
return output;
|
|
8540
|
-
}
|
|
8541
|
-
|
|
8542
|
-
function copyRasterVertex$1(vertex, x, y) {
|
|
8543
|
-
return {
|
|
8544
|
-
x: x,
|
|
8545
|
-
y: y,
|
|
8546
|
-
sx: vertex.sx,
|
|
8547
|
-
sy: vertex.sy,
|
|
8548
|
-
lon: vertex.lon,
|
|
8549
|
-
lat: vertex.lat
|
|
8550
|
-
};
|
|
8830
|
+
polygon = clipRasterPolygonAxis(polygon, 'x', -3, true);
|
|
8831
|
+
polygon = clipRasterPolygonAxis(polygon, 'x', 3, false);
|
|
8832
|
+
polygon = clipRasterPolygonAxis(polygon, 'y', -SQRT3$1, true);
|
|
8833
|
+
return clipRasterPolygonAxis(polygon, 'y', SQRT3$1, false);
|
|
8551
8834
|
}
|
|
8552
8835
|
|
|
8553
8836
|
function applyOobTransform(p, facet, oobKey) {
|
|
@@ -8568,51 +8851,28 @@
|
|
|
8568
8851
|
}
|
|
8569
8852
|
|
|
8570
8853
|
function clipOobPolygon(polygon, oobKey) {
|
|
8571
|
-
if (oobKey == 'xpos') return clipPolygonAxis
|
|
8572
|
-
if (oobKey == 'xneg') return clipPolygonAxis
|
|
8854
|
+
if (oobKey == 'xpos') return clipPolygonAxis(polygon, 0, 3, true);
|
|
8855
|
+
if (oobKey == 'xneg') return clipPolygonAxis(polygon, 0, -3, false);
|
|
8573
8856
|
if (oobKey == 'ypos') {
|
|
8574
|
-
return clipPolygonAxis
|
|
8575
|
-
clipPolygonAxis
|
|
8576
|
-
clipPolygonAxis
|
|
8857
|
+
return clipPolygonAxis(
|
|
8858
|
+
clipPolygonAxis(
|
|
8859
|
+
clipPolygonAxis(polygon, 0, -3, true), 0, 3, false),
|
|
8577
8860
|
1, SQRT3$1, true);
|
|
8578
8861
|
}
|
|
8579
8862
|
if (oobKey == 'yneg') {
|
|
8580
|
-
return clipPolygonAxis
|
|
8581
|
-
clipPolygonAxis
|
|
8582
|
-
clipPolygonAxis
|
|
8863
|
+
return clipPolygonAxis(
|
|
8864
|
+
clipPolygonAxis(
|
|
8865
|
+
clipPolygonAxis(polygon, 0, -3, true), 0, 3, false),
|
|
8583
8866
|
1, -SQRT3$1, false);
|
|
8584
8867
|
}
|
|
8585
8868
|
return clipRectangle(polygon, -3, -SQRT3$1, 3, SQRT3$1);
|
|
8586
8869
|
}
|
|
8587
8870
|
|
|
8588
8871
|
function clipRectangle(polygon, xmin, ymin, xmax, ymax) {
|
|
8589
|
-
polygon = clipPolygonAxis
|
|
8590
|
-
polygon = clipPolygonAxis
|
|
8591
|
-
polygon = clipPolygonAxis
|
|
8592
|
-
return clipPolygonAxis
|
|
8593
|
-
}
|
|
8594
|
-
|
|
8595
|
-
function clipPolygonAxis$1(polygon, axis, value, keepGreater) {
|
|
8596
|
-
var output = [];
|
|
8597
|
-
if (polygon.length === 0) return output;
|
|
8598
|
-
var a = polygon[polygon.length - 1];
|
|
8599
|
-
var aInside = keepGreater ? a[axis] >= value - EPS$1 : a[axis] <= value + EPS$1;
|
|
8600
|
-
polygon.forEach(function(b) {
|
|
8601
|
-
var bInside = keepGreater ? b[axis] >= value - EPS$1 : b[axis] <= value + EPS$1;
|
|
8602
|
-
if (aInside != bInside) {
|
|
8603
|
-
var t = (value - a[axis]) / (b[axis] - a[axis]);
|
|
8604
|
-
var p = [
|
|
8605
|
-
a[0] + (b[0] - a[0]) * t,
|
|
8606
|
-
a[1] + (b[1] - a[1]) * t
|
|
8607
|
-
];
|
|
8608
|
-
p[axis] = value;
|
|
8609
|
-
output.push(p);
|
|
8610
|
-
}
|
|
8611
|
-
if (bInside) output.push(b.concat());
|
|
8612
|
-
a = b;
|
|
8613
|
-
aInside = bInside;
|
|
8614
|
-
});
|
|
8615
|
-
return output;
|
|
8872
|
+
polygon = clipPolygonAxis(polygon, 0, xmin, true);
|
|
8873
|
+
polygon = clipPolygonAxis(polygon, 0, xmax, false);
|
|
8874
|
+
polygon = clipPolygonAxis(polygon, 1, ymin, true);
|
|
8875
|
+
return clipPolygonAxis(polygon, 1, ymax, false);
|
|
8616
8876
|
}
|
|
8617
8877
|
|
|
8618
8878
|
function interpolateSphericalRadians(a, b, interval) {
|
|
@@ -8905,11 +9165,11 @@
|
|
|
8905
9165
|
var y = edge[0][1] + (edge[1][1] - edge[0][1]) * t;
|
|
8906
9166
|
var p = inverse(x * EDGE_SCALE, y * EDGE_SCALE);
|
|
8907
9167
|
path.push([
|
|
8908
|
-
normalizeLongitude$
|
|
9168
|
+
normalizeLongitude$1(p[0] * R2D$2 + lon0),
|
|
8909
9169
|
p[1] * R2D$2
|
|
8910
9170
|
]);
|
|
8911
9171
|
}
|
|
8912
|
-
return memo.concat(splitPathAtAntimeridian$
|
|
9172
|
+
return memo.concat(splitPathAtAntimeridian$2(path));
|
|
8913
9173
|
}, []);
|
|
8914
9174
|
return paths.map(function(part) {
|
|
8915
9175
|
part.mask_width = 4e-5;
|
|
@@ -8917,7 +9177,7 @@
|
|
|
8917
9177
|
});
|
|
8918
9178
|
}
|
|
8919
9179
|
|
|
8920
|
-
function splitPathAtAntimeridian$
|
|
9180
|
+
function splitPathAtAntimeridian$2(path) {
|
|
8921
9181
|
var paths = [];
|
|
8922
9182
|
var part = [path[0]];
|
|
8923
9183
|
for (var i = 1; i < path.length; i++) {
|
|
@@ -8996,20 +9256,194 @@
|
|
|
8996
9256
|
return lam;
|
|
8997
9257
|
}
|
|
8998
9258
|
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9259
|
+
var Narukawa2022 = /*#__PURE__*/Object.freeze({
|
|
9260
|
+
__proto__: null,
|
|
9261
|
+
getNarukawa2022Engine: getNarukawa2022Engine,
|
|
9262
|
+
registerNarukawa2022Projection: registerNarukawa2022Projection
|
|
9263
|
+
});
|
|
9264
|
+
|
|
9265
|
+
// Lightweight hierarchical profiler.
|
|
9266
|
+
// Intended for ad-hoc performance work on hot pipelines (e.g. addIntersectionCuts).
|
|
9267
|
+
//
|
|
9268
|
+
// Usage:
|
|
9269
|
+
// import { profileStart, profileEnd, profileWrap, ... } from './utils/mapshaper-profile';
|
|
9270
|
+
// profileStart('phase'); doWork(); profileEnd('phase');
|
|
9271
|
+
// var result = profileWrap('phase', () => doWork());
|
|
9272
|
+
//
|
|
9273
|
+
// When disabled (the default) every call short-circuits in ~one comparison; safe
|
|
9274
|
+
// to leave in hot code paths. Enable from the CLI with the `-profile` command,
|
|
9275
|
+
// from JS with enableProfiling(), or by setting the MAPSHAPER_PROFILE env var.
|
|
9276
|
+
//
|
|
9277
|
+
// The profiler tracks a stack of currently-open phases so calls can nest. Each
|
|
9278
|
+
// unique stack path accumulates ms-elapsed and a call count. profileReport()
|
|
9279
|
+
// returns a flat array; formatProfileReport() pretty-prints a tree.
|
|
9280
|
+
|
|
9281
|
+
var ENABLED = false;
|
|
9282
|
+
var ROOT = makeNode('<root>');
|
|
9283
|
+
var STACK = [ROOT];
|
|
9284
|
+
var WALL_START = 0;
|
|
9285
|
+
|
|
9286
|
+
function makeNode(label) {
|
|
9287
|
+
return {
|
|
9288
|
+
label: label,
|
|
9289
|
+
totalMs: 0,
|
|
9290
|
+
selfMs: 0,
|
|
9291
|
+
calls: 0,
|
|
9292
|
+
childMsAccum: 0,
|
|
9293
|
+
children: Object.create(null)
|
|
9294
|
+
};
|
|
9003
9295
|
}
|
|
9004
9296
|
|
|
9005
|
-
function
|
|
9006
|
-
|
|
9297
|
+
function nowMs() {
|
|
9298
|
+
if (typeof process !== 'undefined' && process.hrtime && process.hrtime.bigint) {
|
|
9299
|
+
// bigint -> number division: precision down to 1 microsecond is fine for our needs
|
|
9300
|
+
return Number(process.hrtime.bigint()) / 1e6;
|
|
9301
|
+
}
|
|
9302
|
+
if (typeof performance !== 'undefined' && performance.now) {
|
|
9303
|
+
return performance.now();
|
|
9304
|
+
}
|
|
9305
|
+
return Date.now();
|
|
9007
9306
|
}
|
|
9008
9307
|
|
|
9009
|
-
|
|
9308
|
+
function enableProfiling() {
|
|
9309
|
+
ENABLED = true;
|
|
9310
|
+
WALL_START = nowMs();
|
|
9311
|
+
}
|
|
9312
|
+
|
|
9313
|
+
function disableProfiling() {
|
|
9314
|
+
ENABLED = false;
|
|
9315
|
+
}
|
|
9316
|
+
|
|
9317
|
+
function profileEnabled() {
|
|
9318
|
+
return ENABLED;
|
|
9319
|
+
}
|
|
9320
|
+
|
|
9321
|
+
function profileReset() {
|
|
9322
|
+
ROOT = makeNode('<root>');
|
|
9323
|
+
STACK = [ROOT];
|
|
9324
|
+
WALL_START = ENABLED ? nowMs() : 0;
|
|
9325
|
+
}
|
|
9326
|
+
|
|
9327
|
+
// Open a new phase. Cheap (~one branch) when disabled.
|
|
9328
|
+
function profileStart(label) {
|
|
9329
|
+
if (!ENABLED) return;
|
|
9330
|
+
var parent = STACK[STACK.length - 1];
|
|
9331
|
+
var node = parent.children[label];
|
|
9332
|
+
if (!node) {
|
|
9333
|
+
node = makeNode(label);
|
|
9334
|
+
parent.children[label] = node;
|
|
9335
|
+
}
|
|
9336
|
+
node._t0 = nowMs();
|
|
9337
|
+
STACK.push(node);
|
|
9338
|
+
}
|
|
9339
|
+
|
|
9340
|
+
function profileEnd(label) {
|
|
9341
|
+
if (!ENABLED) return;
|
|
9342
|
+
var node = STACK[STACK.length - 1];
|
|
9343
|
+
if (label && node.label !== label) {
|
|
9344
|
+
// Mismatched labels usually mean an early return forgot to call profileEnd().
|
|
9345
|
+
// Walk up the stack until we find a match, closing the intervening frames.
|
|
9346
|
+
while (STACK.length > 1 && STACK[STACK.length - 1].label !== label) {
|
|
9347
|
+
profileEnd(STACK[STACK.length - 1].label);
|
|
9348
|
+
}
|
|
9349
|
+
node = STACK[STACK.length - 1];
|
|
9350
|
+
if (node.label !== label) return; // give up rather than throw
|
|
9351
|
+
}
|
|
9352
|
+
var elapsed = nowMs() - node._t0;
|
|
9353
|
+
node._t0 = 0;
|
|
9354
|
+
node.totalMs += elapsed;
|
|
9355
|
+
node.calls += 1;
|
|
9356
|
+
STACK.pop();
|
|
9357
|
+
var parent = STACK[STACK.length - 1];
|
|
9358
|
+
parent.childMsAccum += elapsed;
|
|
9359
|
+
}
|
|
9360
|
+
|
|
9361
|
+
// Wrap a function call. Re-throws if the callback throws but still closes the
|
|
9362
|
+
// phase so the stack stays consistent.
|
|
9363
|
+
function profileWrap(label, fn) {
|
|
9364
|
+
if (!ENABLED) return fn();
|
|
9365
|
+
profileStart(label);
|
|
9366
|
+
try {
|
|
9367
|
+
return fn();
|
|
9368
|
+
} finally {
|
|
9369
|
+
profileEnd(label);
|
|
9370
|
+
}
|
|
9371
|
+
}
|
|
9372
|
+
|
|
9373
|
+
// Build a flat tree report: array of {depth, label, totalMs, selfMs, calls}.
|
|
9374
|
+
function profileReport() {
|
|
9375
|
+
var rows = [];
|
|
9376
|
+
function visit(node, depth) {
|
|
9377
|
+
if (depth > 0) {
|
|
9378
|
+
rows.push({
|
|
9379
|
+
depth: depth - 1,
|
|
9380
|
+
label: node.label,
|
|
9381
|
+
totalMs: node.totalMs,
|
|
9382
|
+
selfMs: Math.max(0, node.totalMs - node.childMsAccum),
|
|
9383
|
+
calls: node.calls
|
|
9384
|
+
});
|
|
9385
|
+
}
|
|
9386
|
+
var keys = Object.keys(node.children);
|
|
9387
|
+
keys.sort(function(a, b) {
|
|
9388
|
+
return node.children[b].totalMs - node.children[a].totalMs;
|
|
9389
|
+
});
|
|
9390
|
+
for (var i = 0; i < keys.length; i++) {
|
|
9391
|
+
visit(node.children[keys[i]], depth + 1);
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
visit(ROOT, 0);
|
|
9395
|
+
return rows;
|
|
9396
|
+
}
|
|
9397
|
+
|
|
9398
|
+
function profileWallElapsedMs() {
|
|
9399
|
+
return ENABLED && WALL_START ? nowMs() - WALL_START : 0;
|
|
9400
|
+
}
|
|
9401
|
+
|
|
9402
|
+
// Pretty-print the current report as a column-aligned tree.
|
|
9403
|
+
function formatProfileReport(opts) {
|
|
9404
|
+
var rows = profileReport();
|
|
9405
|
+
if (rows.length === 0) return '(profile is empty)';
|
|
9406
|
+
opts = opts || {};
|
|
9407
|
+
var indent = ' ';
|
|
9408
|
+
var lines = [];
|
|
9409
|
+
// header
|
|
9410
|
+
lines.push(['phase', 'total ms', 'self ms', 'calls'].join('\t'));
|
|
9411
|
+
for (var i = 0; i < rows.length; i++) {
|
|
9412
|
+
var r = rows[i];
|
|
9413
|
+
var label = '';
|
|
9414
|
+
for (var j = 0; j < r.depth; j++) label += indent;
|
|
9415
|
+
label += r.label;
|
|
9416
|
+
lines.push([
|
|
9417
|
+
label,
|
|
9418
|
+
r.totalMs.toFixed(2),
|
|
9419
|
+
r.selfMs.toFixed(2),
|
|
9420
|
+
String(r.calls)
|
|
9421
|
+
].join('\t'));
|
|
9422
|
+
}
|
|
9423
|
+
if (opts.includeWall) {
|
|
9424
|
+
lines.push('');
|
|
9425
|
+
lines.push('wall elapsed ms: ' + profileWallElapsedMs().toFixed(2));
|
|
9426
|
+
}
|
|
9427
|
+
return lines.join('\n');
|
|
9428
|
+
}
|
|
9429
|
+
|
|
9430
|
+
// Honour an env var so users (and CI harnesses) can opt in without code changes.
|
|
9431
|
+
if (typeof process !== 'undefined' && process.env && process.env.MAPSHAPER_PROFILE) {
|
|
9432
|
+
enableProfiling();
|
|
9433
|
+
}
|
|
9434
|
+
|
|
9435
|
+
var Profile = /*#__PURE__*/Object.freeze({
|
|
9010
9436
|
__proto__: null,
|
|
9011
|
-
|
|
9012
|
-
|
|
9437
|
+
disableProfiling: disableProfiling,
|
|
9438
|
+
enableProfiling: enableProfiling,
|
|
9439
|
+
formatProfileReport: formatProfileReport,
|
|
9440
|
+
profileEnabled: profileEnabled,
|
|
9441
|
+
profileEnd: profileEnd,
|
|
9442
|
+
profileReport: profileReport,
|
|
9443
|
+
profileReset: profileReset,
|
|
9444
|
+
profileStart: profileStart,
|
|
9445
|
+
profileWallElapsedMs: profileWallElapsedMs,
|
|
9446
|
+
profileWrap: profileWrap
|
|
9013
9447
|
});
|
|
9014
9448
|
|
|
9015
9449
|
/*
|
|
@@ -9033,11 +9467,41 @@
|
|
|
9033
9467
|
var SQRT3 = Math.sqrt(3);
|
|
9034
9468
|
var ASIN_ONE_THIRD = Math.asin(1 / 3);
|
|
9035
9469
|
var MARKLEY_LATITUDE = Math.acos(1 / 3) * 0.5 * R2D$1;
|
|
9470
|
+
var LAYOUT_PERIOD = 8;
|
|
9471
|
+
var MARKLEY_LAYOUT_PHASE = -2;
|
|
9472
|
+
var CALM_LAYOUT_PHASE = -1.5;
|
|
9036
9473
|
var RECT_XMIN = -7;
|
|
9037
9474
|
var RECT_XMAX = 1;
|
|
9038
9475
|
var RECT_YMIN = -2 * SQRT3;
|
|
9039
9476
|
var RECT_YMAX = 0;
|
|
9040
9477
|
var EPS = 1e-12;
|
|
9478
|
+
var MIN_PIECE_AREA = 1e-6;
|
|
9479
|
+
|
|
9480
|
+
// Frame-cut tracing parameters (see createFrameCutPaths).
|
|
9481
|
+
//
|
|
9482
|
+
// FRAME_INSET is how far inside the frame the traced sampling lines run, and it
|
|
9483
|
+
// is bounded on both sides. Measured over 18003 samples with a 6000-sample
|
|
9484
|
+
// edge, the safe window is about [4e-8, 1.3e-7] and this value is its centre:
|
|
9485
|
+
//
|
|
9486
|
+
// * Too small and invertLeeRaw becomes ill-conditioned, because the frame edge
|
|
9487
|
+
// coincides with a Lee face boundary. The share of samples that fail to
|
|
9488
|
+
// invert climbs from 0.2% inside the window to 4% at 3e-8 and 14% at 1e-8,
|
|
9489
|
+
// and *which* samples fail depends on last-bit floating-point behaviour, so
|
|
9490
|
+
// different JS engines traced different seams from the same input.
|
|
9491
|
+
// * Too large and the strip of sphere between the traced seam and the true
|
|
9492
|
+
// frame edge survives the cut, leaving a hairline connection that shows up as
|
|
9493
|
+
// a long chord running along the frame edge. This appears by 1.6e-7.
|
|
9494
|
+
//
|
|
9495
|
+
// The 0.2% of samples that fail inside the window are the four facet vertices,
|
|
9496
|
+
// which genuinely have no inverse.
|
|
9497
|
+
var FRAME_INSET = 8e-8;
|
|
9498
|
+
// Guards the lower bound above. Inside the window the failure rate is 0.2%, so
|
|
9499
|
+
// this only trips on a regression, and the resulting map would be mis-cut.
|
|
9500
|
+
var MAX_FAILED_SAMPLE_RATIO = 0.02;
|
|
9501
|
+
// Samples per frame edge. This sets the resolution of the cut along the frame
|
|
9502
|
+
// (about 8 km on an Earth-sized map).
|
|
9503
|
+
var FRAME_SAMPLES = 6000;
|
|
9504
|
+
|
|
9041
9505
|
var engines = {};
|
|
9042
9506
|
|
|
9043
9507
|
var TETRAHEDRON_VERTICES = [
|
|
@@ -9081,7 +9545,10 @@
|
|
|
9081
9545
|
function getLeeTetrahedralEngine(id) {
|
|
9082
9546
|
id = id || 'markley';
|
|
9083
9547
|
if (!engines[id]) {
|
|
9084
|
-
engines[id] = createLeeTetrahedralEngine(
|
|
9548
|
+
engines[id] = createLeeTetrahedralEngine(
|
|
9549
|
+
getProjectionRotation(id),
|
|
9550
|
+
id == 'markley' ? MARKLEY_LAYOUT_PHASE : CALM_LAYOUT_PHASE
|
|
9551
|
+
);
|
|
9085
9552
|
}
|
|
9086
9553
|
return engines[id];
|
|
9087
9554
|
}
|
|
@@ -9097,23 +9564,24 @@
|
|
|
9097
9564
|
};
|
|
9098
9565
|
P.__projection_topology = engine.getTopology(P.lam0 * R2D$1);
|
|
9099
9566
|
P.__projected_outline = engine.outline;
|
|
9567
|
+
P.__graticule_precision = 1;
|
|
9100
9568
|
}
|
|
9101
9569
|
|
|
9102
9570
|
function getProjectionRotation(id) {
|
|
9103
9571
|
if (id == 'calm') {
|
|
9104
|
-
//
|
|
9105
|
-
//
|
|
9572
|
+
// Kunimune's published CALM aspect (77°N, 143°E, central meridian
|
|
9573
|
+
// 163°W), transformed into this implementation's tetrahedral symmetry.
|
|
9106
9574
|
return [
|
|
9107
|
-
|
|
9108
|
-
-
|
|
9109
|
-
|
|
9575
|
+
64.7261399569101,
|
|
9576
|
+
-39.84470062116125,
|
|
9577
|
+
-119.02303523591051
|
|
9110
9578
|
];
|
|
9111
9579
|
}
|
|
9112
9580
|
// Markley's singularities are at ±35.26439°, in the oceans.
|
|
9113
9581
|
return [115, MARKLEY_LATITUDE - 90, 180];
|
|
9114
9582
|
}
|
|
9115
9583
|
|
|
9116
|
-
function createLeeTetrahedralEngine(rotation) {
|
|
9584
|
+
function createLeeTetrahedralEngine(rotation, layoutPhase) {
|
|
9117
9585
|
var base = createPolyhedralProjection({
|
|
9118
9586
|
faces: TETRAHEDRON_FACES,
|
|
9119
9587
|
parents: [-1, 0, 0, 0],
|
|
@@ -9124,10 +9592,11 @@
|
|
|
9124
9592
|
var normalization = getBaseNormalization(base.outline);
|
|
9125
9593
|
var baseTopology = base.getTopology(0);
|
|
9126
9594
|
var sourceRegions = createSourceRegions(rotation);
|
|
9127
|
-
var
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
})
|
|
9595
|
+
var rasterPieces = createLayoutPieces(
|
|
9596
|
+
baseTopology.regions, normalization, layoutPhase);
|
|
9597
|
+
var rasterPieceIndex = indexPiecesByFaceAndCopy(rasterPieces);
|
|
9598
|
+
var rasterPieceIds = rasterPieces.map(function(piece) { return piece.id; });
|
|
9599
|
+
var seamCache = new Map();
|
|
9131
9600
|
var outline = [[
|
|
9132
9601
|
centerOutputPoint([RECT_XMIN, RECT_YMIN]),
|
|
9133
9602
|
centerOutputPoint([RECT_XMAX, RECT_YMIN]),
|
|
@@ -9138,6 +9607,7 @@
|
|
|
9138
9607
|
|
|
9139
9608
|
return {
|
|
9140
9609
|
forward: forward,
|
|
9610
|
+
inverse: inverse,
|
|
9141
9611
|
outline: outline,
|
|
9142
9612
|
getTopology: getTopology
|
|
9143
9613
|
};
|
|
@@ -9145,24 +9615,48 @@
|
|
|
9145
9615
|
function forward(lam, phi) {
|
|
9146
9616
|
var p = normalizeBasePoint(base.forward(lam, phi), normalization);
|
|
9147
9617
|
var copy = getLayoutCopy(p);
|
|
9148
|
-
|
|
9618
|
+
var q = applyMatrix(copy.matrix, p);
|
|
9619
|
+
q[0] = wrapLayoutX(q[0] + layoutPhase);
|
|
9620
|
+
q[0] = clamp$2(q[0], RECT_XMIN, RECT_XMAX);
|
|
9621
|
+
q[1] = clamp$2(q[1], RECT_YMIN, RECT_YMAX);
|
|
9622
|
+
return centerOutputPoint(q);
|
|
9623
|
+
}
|
|
9624
|
+
|
|
9625
|
+
// Internal inverse used to trace the geographic cut corresponding to the
|
|
9626
|
+
// rectangular frame. The public projection remains forward-only.
|
|
9627
|
+
//
|
|
9628
|
+
// Layout pieces can overlap along their shared edges, so a candidate is only
|
|
9629
|
+
// accepted once it round-trips through forward().
|
|
9630
|
+
function inverse(x, y) {
|
|
9631
|
+
var q = uncenterOutputPoint([x, y]);
|
|
9632
|
+
for (var i = 0; i < rasterPieces.length; i++) {
|
|
9633
|
+
var piece = rasterPieces[i];
|
|
9634
|
+
var bounds = piece.bounds;
|
|
9635
|
+
if (x < bounds[0] || x > bounds[2] || y < bounds[1] || y > bounds[3]) {
|
|
9636
|
+
continue;
|
|
9637
|
+
}
|
|
9638
|
+
if (!pointInRing$2([x, y], piece.boundary)) continue;
|
|
9639
|
+
var p = applyMatrix(piece.inverse_matrix, q);
|
|
9640
|
+
if (getLayoutCopy(p).id != piece.copy) continue;
|
|
9641
|
+
p = denormalizeBasePoint(p, normalization);
|
|
9642
|
+
var spherical = base.invertFace(p[0], p[1], piece.face);
|
|
9643
|
+
if (!spherical) continue;
|
|
9644
|
+
var check = forward(spherical[0], spherical[1]);
|
|
9645
|
+
if (Math.hypot(check[0] - x, check[1] - y) < 1e-6) {
|
|
9646
|
+
return spherical;
|
|
9647
|
+
}
|
|
9648
|
+
}
|
|
9649
|
+
return null;
|
|
9149
9650
|
}
|
|
9150
9651
|
|
|
9151
9652
|
function getTopology(lon0) {
|
|
9152
|
-
var
|
|
9153
|
-
|
|
9154
|
-
regions:
|
|
9155
|
-
seams: topology.seams.map(function(seam) {
|
|
9156
|
-
return {
|
|
9157
|
-
type: 'cut',
|
|
9158
|
-
faces: seam.faces,
|
|
9159
|
-
paths: seam.paths
|
|
9160
|
-
};
|
|
9161
|
-
}),
|
|
9653
|
+
var result = {
|
|
9654
|
+
interrupted: true,
|
|
9655
|
+
regions: rasterPieces.map(copyProjectedPiece),
|
|
9162
9656
|
findRegion: findRegion,
|
|
9163
9657
|
findTransitionRegion: findRegion,
|
|
9164
9658
|
projectRegion: projectRegion,
|
|
9165
|
-
raster_regions:
|
|
9659
|
+
raster_regions: rasterPieces.map(copyRasterPiece),
|
|
9166
9660
|
raster_source_regions: sourceRegions.map(function(region) {
|
|
9167
9661
|
return {
|
|
9168
9662
|
id: region.id,
|
|
@@ -9178,14 +9672,47 @@
|
|
|
9178
9672
|
raster_cell_halo: false,
|
|
9179
9673
|
fill_raster_mask: true,
|
|
9180
9674
|
fill_raster_coverage: true,
|
|
9181
|
-
findRasterRegion:
|
|
9182
|
-
projectRasterRegion:
|
|
9675
|
+
findRasterRegion: findRasterRegion,
|
|
9676
|
+
projectRasterRegion: projectRasterRegion,
|
|
9183
9677
|
projectRasterSourceRegion: projectRasterSourceRegion,
|
|
9184
9678
|
clipRasterRegionPolygon: clipRasterRegionPolygon,
|
|
9679
|
+
frame_bounds: [
|
|
9680
|
+
outline[0][0][0],
|
|
9681
|
+
outline[0][0][1],
|
|
9682
|
+
outline[0][2][0],
|
|
9683
|
+
outline[0][2][1]
|
|
9684
|
+
],
|
|
9185
9685
|
outline: outline.map(function(ring) {
|
|
9186
9686
|
return ring.map(function(p) { return p.concat(); });
|
|
9187
9687
|
})
|
|
9188
9688
|
};
|
|
9689
|
+
// Rasters use the region mesh but never need geographic seam paths.
|
|
9690
|
+
// Tracing the rectangular cut is deferred until vector clipping requests it.
|
|
9691
|
+
Object.defineProperty(result, 'seams', {
|
|
9692
|
+
enumerable: true,
|
|
9693
|
+
get: function() {
|
|
9694
|
+
if (!seamCache.has(lon0)) {
|
|
9695
|
+
var topology = base.getTopology(lon0);
|
|
9696
|
+
var seams = topology.seams.map(function(seam) {
|
|
9697
|
+
return {
|
|
9698
|
+
type: 'attached',
|
|
9699
|
+
faces: seam.faces,
|
|
9700
|
+
paths: seam.paths
|
|
9701
|
+
};
|
|
9702
|
+
});
|
|
9703
|
+
var frameCut = createFrameCutPaths(inverse, lon0);
|
|
9704
|
+
seams.push({
|
|
9705
|
+
type: 'cut',
|
|
9706
|
+
paths: frameCut.paths,
|
|
9707
|
+
// How much of the frame the trace resolved; see FRAME_INSET.
|
|
9708
|
+
trace_stats: frameCut.stats
|
|
9709
|
+
});
|
|
9710
|
+
seamCache.set(lon0, seams);
|
|
9711
|
+
}
|
|
9712
|
+
return seamCache.get(lon0);
|
|
9713
|
+
}
|
|
9714
|
+
});
|
|
9715
|
+
return result;
|
|
9189
9716
|
|
|
9190
9717
|
function findRegion(lon, lat) {
|
|
9191
9718
|
var lam = normalizeLongitude$1(lon - lon0) * D2R$1;
|
|
@@ -9193,11 +9720,32 @@
|
|
|
9193
9720
|
var face = base.findFace(lam, phi);
|
|
9194
9721
|
var p = normalizeBasePoint(base.forwardFace(lam, phi, face), normalization);
|
|
9195
9722
|
var copy = getLayoutCopy(p);
|
|
9196
|
-
|
|
9723
|
+
var q = applyMatrix(copy.matrix, p);
|
|
9724
|
+
q[0] = wrapLayoutX(q[0] + layoutPhase);
|
|
9725
|
+
q[0] = clamp$2(q[0], RECT_XMIN, RECT_XMAX);
|
|
9726
|
+
q[1] = clamp$2(q[1], RECT_YMIN, RECT_YMAX);
|
|
9727
|
+
q = centerOutputPoint(q);
|
|
9728
|
+
var ids = rasterPieceIndex.get(getPieceKey(face, copy.id)) || [];
|
|
9729
|
+
var region = findPieceContainingPoint(rasterPieces, ids, q);
|
|
9730
|
+
if (region === undefined) {
|
|
9731
|
+
region = findPieceContainingPoint(rasterPieces, rasterPieceIds, q);
|
|
9732
|
+
}
|
|
9733
|
+
return region;
|
|
9197
9734
|
}
|
|
9198
9735
|
|
|
9199
9736
|
function projectRegion(lon, lat, regionId) {
|
|
9200
|
-
|
|
9737
|
+
return projectPiece(lon, lat, rasterPieces[regionId]);
|
|
9738
|
+
}
|
|
9739
|
+
|
|
9740
|
+
function findRasterRegion(lon, lat) {
|
|
9741
|
+
return findRegion(lon, lat);
|
|
9742
|
+
}
|
|
9743
|
+
|
|
9744
|
+
function projectRasterRegion(lon, lat, regionId) {
|
|
9745
|
+
return projectPiece(lon, lat, rasterPieces[regionId]);
|
|
9746
|
+
}
|
|
9747
|
+
|
|
9748
|
+
function projectPiece(lon, lat, piece) {
|
|
9201
9749
|
var lam = normalizeLongitude$1(lon - lon0) * D2R$1;
|
|
9202
9750
|
var p = normalizeBasePoint(
|
|
9203
9751
|
base.forwardFace(lam, lat * D2R$1, piece.face),
|
|
@@ -9215,7 +9763,7 @@
|
|
|
9215
9763
|
}
|
|
9216
9764
|
|
|
9217
9765
|
function clipRasterRegionPolygon(vertices, regionId) {
|
|
9218
|
-
var piece =
|
|
9766
|
+
var piece = rasterPieces[regionId];
|
|
9219
9767
|
var polygon = vertices.map(function(vertex) {
|
|
9220
9768
|
var p = applyMatrix(piece.matrix, [vertex.x, vertex.y]);
|
|
9221
9769
|
return copyRasterVertex(vertex, p[0], p[1]);
|
|
@@ -9246,39 +9794,66 @@
|
|
|
9246
9794
|
});
|
|
9247
9795
|
}
|
|
9248
9796
|
|
|
9249
|
-
function createLayoutPieces(regions, normalization) {
|
|
9797
|
+
function createLayoutPieces(regions, normalization, layoutPhase) {
|
|
9250
9798
|
var pieces = [];
|
|
9251
9799
|
regions.forEach(function(region) {
|
|
9252
9800
|
var boundary = region.projected_boundary.map(function(p) {
|
|
9253
9801
|
return normalizeBasePoint(p, normalization);
|
|
9254
9802
|
});
|
|
9255
9803
|
LAYOUT_COPIES.forEach(function(copy) {
|
|
9256
|
-
var polygon = boundary.
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9804
|
+
var polygon = clipLayoutCopyDomain(boundary, copy.id);
|
|
9805
|
+
if (polygon.length < 3 ||
|
|
9806
|
+
Math.abs(getRingArea$2(polygon)) < MIN_PIECE_AREA) return;
|
|
9807
|
+
[-LAYOUT_PERIOD, 0, LAYOUT_PERIOD].forEach(function(wrap) {
|
|
9808
|
+
var matrix = shiftMatrixX(copy.matrix, layoutPhase + wrap);
|
|
9809
|
+
var projected = polygon.map(function(p) {
|
|
9810
|
+
return applyMatrix(matrix, p);
|
|
9811
|
+
});
|
|
9812
|
+
projected = clipPolygonAxis(projected, 0, RECT_XMIN, true);
|
|
9813
|
+
projected = clipPolygonAxis(projected, 0, RECT_XMAX, false);
|
|
9814
|
+
projected = clipPolygonAxis(projected, 1, RECT_YMIN, true);
|
|
9815
|
+
projected = clipPolygonAxis(projected, 1, RECT_YMAX, false);
|
|
9816
|
+
if (projected.length < 3 ||
|
|
9817
|
+
Math.abs(getRingArea$2(projected)) < MIN_PIECE_AREA) return;
|
|
9818
|
+
var boundary = projected.map(centerOutputPoint);
|
|
9819
|
+
pieces.push({
|
|
9820
|
+
id: pieces.length,
|
|
9821
|
+
face: region.id,
|
|
9822
|
+
source_region: region.id,
|
|
9823
|
+
copy: copy.id,
|
|
9824
|
+
matrix: matrix,
|
|
9825
|
+
inverse_matrix: invertMatrix(matrix),
|
|
9826
|
+
bounds: getRingBounds(boundary),
|
|
9827
|
+
boundary: boundary
|
|
9828
|
+
});
|
|
9272
9829
|
});
|
|
9273
9830
|
});
|
|
9274
9831
|
});
|
|
9275
9832
|
return pieces;
|
|
9276
9833
|
}
|
|
9277
9834
|
|
|
9835
|
+
function clipLayoutCopyDomain(polygon, copyId) {
|
|
9836
|
+
if (copyId == 0) { // green
|
|
9837
|
+
polygon = clipPolygonAxis(polygon, 1, 0, true);
|
|
9838
|
+
polygon = clipPolygonAxis(polygon, 0, 3, false);
|
|
9839
|
+
} else if (copyId == 2) { // blue
|
|
9840
|
+
polygon = clipPolygonAxis(polygon, 1, 0, true);
|
|
9841
|
+
polygon = clipPolygonAxis(polygon, 0, 3, true);
|
|
9842
|
+
} else if (copyId == 1) { // red
|
|
9843
|
+
polygon = clipPolygonAxis(polygon, 1, 0, false);
|
|
9844
|
+
polygon = clipPolygonAxis(polygon, 0, 1, false);
|
|
9845
|
+
} else { // orange
|
|
9846
|
+
polygon = clipPolygonAxis(polygon, 1, 0, false);
|
|
9847
|
+
polygon = clipPolygonAxis(polygon, 0, 1, true);
|
|
9848
|
+
}
|
|
9849
|
+
return polygon;
|
|
9850
|
+
}
|
|
9851
|
+
|
|
9278
9852
|
function copyProjectedPiece(piece) {
|
|
9279
9853
|
return {
|
|
9280
9854
|
id: piece.id,
|
|
9281
9855
|
source_region: piece.source_region,
|
|
9856
|
+
copy: piece.copy,
|
|
9282
9857
|
projected_boundary: closeRing$1(piece.boundary)
|
|
9283
9858
|
};
|
|
9284
9859
|
}
|
|
@@ -9291,9 +9866,87 @@
|
|
|
9291
9866
|
};
|
|
9292
9867
|
}
|
|
9293
9868
|
|
|
9869
|
+
// Trace the geographic curves that the rectangular frame corresponds to. The
|
|
9870
|
+
// repeated tetrahedral net is continuous inside the frame, so its only
|
|
9871
|
+
// geographic cuts are the three independent sides of the periodic rectangle.
|
|
9872
|
+
//
|
|
9873
|
+
// Sampling is uniform in frame coordinates rather than adaptive. The traced
|
|
9874
|
+
// curve becomes the centreline of the seam mask, so the polygon edges left
|
|
9875
|
+
// behind by the cut inherit this spacing where they run along the frame; the
|
|
9876
|
+
// even resolution is what lets splitPolygonFrameChords recognize a run along a
|
|
9877
|
+
// frame side. Sampling by geographic curvature instead concentrates vertices
|
|
9878
|
+
// where the map curves and leaves the frame edges coarse, which reintroduces
|
|
9879
|
+
// the long chords along the frame that the cut is meant to remove.
|
|
9880
|
+
function createFrameCutPaths(inverse, lon0) {
|
|
9881
|
+
profileStart('lee.traceFrameCuts');
|
|
9882
|
+
var e = FRAME_INSET;
|
|
9883
|
+
var xmin = centerOutputPoint([RECT_XMIN, 0])[0];
|
|
9884
|
+
var xmax = centerOutputPoint([RECT_XMAX, 0])[0];
|
|
9885
|
+
var ymin = centerOutputPoint([0, RECT_YMIN])[1];
|
|
9886
|
+
var ymax = centerOutputPoint([0, RECT_YMAX])[1];
|
|
9887
|
+
var edges = [
|
|
9888
|
+
[[xmin + e, ymin], [xmin + e, ymax]],
|
|
9889
|
+
[[xmin, ymax - e], [xmax, ymax - e]],
|
|
9890
|
+
[[xmin, ymin + e], [xmax, ymin + e]]
|
|
9891
|
+
];
|
|
9892
|
+
var stats = {sample_count: 0, failed_count: 0};
|
|
9893
|
+
var paths = edges.reduce(function(memo, edge) {
|
|
9894
|
+
var path = [];
|
|
9895
|
+
for (var i = 0; i <= FRAME_SAMPLES; i++) {
|
|
9896
|
+
var t = i / FRAME_SAMPLES;
|
|
9897
|
+
var p = inverse(
|
|
9898
|
+
edge[0][0] + (edge[1][0] - edge[0][0]) * t,
|
|
9899
|
+
edge[0][1] + (edge[1][1] - edge[0][1]) * t
|
|
9900
|
+
);
|
|
9901
|
+
stats.sample_count++;
|
|
9902
|
+
// Samples without an inverse occur only at the facet vertices, which
|
|
9903
|
+
// both neighbours converge on, so the chord across the gap is short.
|
|
9904
|
+
if (!p) {
|
|
9905
|
+
stats.failed_count++;
|
|
9906
|
+
continue;
|
|
9907
|
+
}
|
|
9908
|
+
path.push([normalizeLongitude$1(p[0] * R2D$1 + lon0), p[1] * R2D$1]);
|
|
9909
|
+
}
|
|
9910
|
+
return memo.concat(splitPathAtAntimeridian$1(path));
|
|
9911
|
+
}, []).map(function(path) {
|
|
9912
|
+
path.mask_width = 4e-5;
|
|
9913
|
+
return path;
|
|
9914
|
+
});
|
|
9915
|
+
profileEnd('lee.traceFrameCuts');
|
|
9916
|
+
if (stats.failed_count > stats.sample_count * MAX_FAILED_SAMPLE_RATIO) {
|
|
9917
|
+
error('Unable to trace the projection frame: ' + stats.failed_count +
|
|
9918
|
+
' of ' + stats.sample_count +
|
|
9919
|
+
' frame samples had no inverse (see FRAME_INSET).');
|
|
9920
|
+
}
|
|
9921
|
+
return {paths: paths, stats: stats};
|
|
9922
|
+
}
|
|
9923
|
+
|
|
9924
|
+
function splitPathAtAntimeridian$1(path) {
|
|
9925
|
+
if (path.length < 2) return [];
|
|
9926
|
+
var paths = [];
|
|
9927
|
+
var part = [path[0]];
|
|
9928
|
+
for (var i = 1; i < path.length; i++) {
|
|
9929
|
+
var a = path[i - 1];
|
|
9930
|
+
var b = path[i];
|
|
9931
|
+
if (Math.abs(a[0] - b[0]) > 180) {
|
|
9932
|
+
var adjusted = b[0] + (b[0] < a[0] ? 360 : -360);
|
|
9933
|
+
var edge = a[0] < 0 ? -180 : 180;
|
|
9934
|
+
var t = (edge - a[0]) / (adjusted - a[0]);
|
|
9935
|
+
var lat = a[1] + (b[1] - a[1]) * t;
|
|
9936
|
+
part.push([edge, lat]);
|
|
9937
|
+
paths.push(part);
|
|
9938
|
+
part = [[-edge, lat], b];
|
|
9939
|
+
} else {
|
|
9940
|
+
part.push(b);
|
|
9941
|
+
}
|
|
9942
|
+
}
|
|
9943
|
+
if (part.length > 1) paths.push(part);
|
|
9944
|
+
return paths;
|
|
9945
|
+
}
|
|
9946
|
+
|
|
9294
9947
|
function closeRing$1(ring) {
|
|
9295
9948
|
var copy = ring.map(function(p) { return p.concat(); });
|
|
9296
|
-
if (copy.length && !pointsEqual$
|
|
9949
|
+
if (copy.length && !pointsEqual$2(copy[0], copy[copy.length - 1])) {
|
|
9297
9950
|
copy.push(copy[0].concat());
|
|
9298
9951
|
}
|
|
9299
9952
|
return copy;
|
|
@@ -9313,6 +9966,13 @@
|
|
|
9313
9966
|
];
|
|
9314
9967
|
}
|
|
9315
9968
|
|
|
9969
|
+
function uncenterOutputPoint(p) {
|
|
9970
|
+
return [
|
|
9971
|
+
p[0] + (RECT_XMIN + RECT_XMAX) / 2,
|
|
9972
|
+
p[1] + (RECT_YMIN + RECT_YMAX) / 2
|
|
9973
|
+
];
|
|
9974
|
+
}
|
|
9975
|
+
|
|
9316
9976
|
function getBaseNormalization(outline) {
|
|
9317
9977
|
var xmin = Infinity;
|
|
9318
9978
|
var ymin = Infinity;
|
|
@@ -9340,72 +10000,142 @@
|
|
|
9340
10000
|
];
|
|
9341
10001
|
}
|
|
9342
10002
|
|
|
10003
|
+
function denormalizeBasePoint(p, normalization) {
|
|
10004
|
+
return [
|
|
10005
|
+
p[0] / normalization.scale + normalization.cx,
|
|
10006
|
+
-p[1] / normalization.scale + normalization.cy
|
|
10007
|
+
];
|
|
10008
|
+
}
|
|
10009
|
+
|
|
9343
10010
|
function createLeeFaceProjector(face) {
|
|
9344
10011
|
var c = face.centroid;
|
|
9345
10012
|
var rotation = Math.abs(c[1]) == 90 ?
|
|
9346
10013
|
[0, -c[1], -30] :
|
|
9347
10014
|
[-c[0], -c[1], 30];
|
|
9348
|
-
|
|
10015
|
+
function project(lam, phi) {
|
|
9349
10016
|
var p = rotateSphericalRadians(lam, phi, rotation);
|
|
9350
10017
|
var q = leeRaw(p[0], p[1]);
|
|
9351
10018
|
return [q[0], -q[1]];
|
|
10019
|
+
}
|
|
10020
|
+
project.invert = function(x, y) {
|
|
10021
|
+
var p = invertLeeRaw(x, -y);
|
|
10022
|
+
return p && rotateSphericalRadians(p[0], p[1], rotation, true);
|
|
9352
10023
|
};
|
|
10024
|
+
return project;
|
|
9353
10025
|
}
|
|
9354
10026
|
|
|
10027
|
+
// leeRaw runs tens of times per Newton step in invertLeeRaw, so its constant
|
|
10028
|
+
// tables and the three cube roots of unity are built once here rather than on
|
|
10029
|
+
// every call, and the Horner loops below accumulate into scalars instead of
|
|
10030
|
+
// allocating a complex pair per term. The arithmetic is unchanged.
|
|
10031
|
+
var LEE_W1 = 1.4021821053254548;
|
|
10032
|
+
var LEE_COEFFICIENTS = [
|
|
10033
|
+
1.15470053837925, 0.192450089729875, 0.0481125224324687,
|
|
10034
|
+
0.010309826235529, 3.34114739114366e-4, -0.00150351632601465,
|
|
10035
|
+
-0.0012304417796231, -675190201960282e-18,
|
|
10036
|
+
-284084537293856e-18, -821205120500051e-19,
|
|
10037
|
+
-159257630018706e-20, 1.91691805888369e-5,
|
|
10038
|
+
1.73095888028726e-5, 1.03865580818367e-5,
|
|
10039
|
+
4.70614523937179e-6, 1.4413500104181e-6,
|
|
10040
|
+
1.92757960170179e-8, -3.82869799649063e-7,
|
|
10041
|
+
-3.57526015225576e-7, -2.2175964844211e-7
|
|
10042
|
+
];
|
|
10043
|
+
var LEE_H0 = [1, 1 / 8, 3 / 56, 1 / 32, 35 / 1664, 63 / 4096, 231 / 19456];
|
|
10044
|
+
var LEE_ROTATIONS = [0, 1, 2].map(function(i) {
|
|
10045
|
+
return complexPower([-0.5, SQRT3 / 2], [i, 0]);
|
|
10046
|
+
});
|
|
10047
|
+
|
|
9355
10048
|
function leeRaw(lam, phi) {
|
|
9356
|
-
var
|
|
9357
|
-
var z =
|
|
9358
|
-
var
|
|
9359
|
-
|
|
9360
|
-
});
|
|
10049
|
+
var s = stereographicRaw(lam, phi);
|
|
10050
|
+
var z = [s[0] * SQRT2, s[1] * SQRT2];
|
|
10051
|
+
var i, j;
|
|
10052
|
+
// Pick the sector whose rotation maximizes the real part of rot * z.
|
|
9361
10053
|
var sector = 0;
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
10054
|
+
var best = z[0] * LEE_ROTATIONS[0][0] - z[1] * LEE_ROTATIONS[0][1];
|
|
10055
|
+
for (i = 1; i < LEE_ROTATIONS.length; i++) {
|
|
10056
|
+
var re = z[0] * LEE_ROTATIONS[i][0] - z[1] * LEE_ROTATIONS[i][1];
|
|
10057
|
+
if (re > best) {
|
|
10058
|
+
best = re;
|
|
10059
|
+
sector = i;
|
|
10060
|
+
}
|
|
9365
10061
|
}
|
|
9366
|
-
var rot =
|
|
10062
|
+
var rot = LEE_ROTATIONS[sector];
|
|
9367
10063
|
var n = complexNorm(z);
|
|
9368
10064
|
var h = [0, 0];
|
|
9369
10065
|
var k = [0, 0];
|
|
9370
10066
|
|
|
9371
10067
|
if (n > 0.3) {
|
|
9372
10068
|
var y = complexSubtract([1, 0], complexMultiply(rot, z));
|
|
9373
|
-
var
|
|
9374
|
-
var
|
|
9375
|
-
|
|
9376
|
-
0
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
-3.57526015225576e-7, -2.2175964844211e-7
|
|
9384
|
-
];
|
|
9385
|
-
var g = [0, 0];
|
|
9386
|
-
for (i = coefficients.length - 1; i >= 0; i--) {
|
|
9387
|
-
g = complexAdd([coefficients[i], 0], complexMultiply(g, y));
|
|
9388
|
-
}
|
|
9389
|
-
k = complexSubtract([w1, 0], complexMultiply(complexPower(y, 0.5), g));
|
|
10069
|
+
var g0 = 0;
|
|
10070
|
+
var g1 = 0;
|
|
10071
|
+
for (i = LEE_COEFFICIENTS.length - 1; i >= 0; i--) {
|
|
10072
|
+
j = g1 * y[0] + g0 * y[1];
|
|
10073
|
+
g0 = LEE_COEFFICIENTS[i] + (g0 * y[0] - g1 * y[1]);
|
|
10074
|
+
g1 = j;
|
|
10075
|
+
}
|
|
10076
|
+
k = complexSubtract(
|
|
10077
|
+
[LEE_W1, 0],
|
|
10078
|
+
complexMultiply(complexPower(y, 0.5), [g0, g1]));
|
|
9390
10079
|
k = complexMultiply(complexMultiply(k, rot), rot);
|
|
9391
10080
|
}
|
|
9392
10081
|
|
|
9393
10082
|
if (n < 0.5) {
|
|
9394
|
-
var h0 = [1, 1 / 8, 3 / 56, 1 / 32, 35 / 1664, 63 / 4096, 231 / 19456];
|
|
9395
10083
|
var z3 = complexPower(z, [3, 0]);
|
|
9396
|
-
|
|
9397
|
-
|
|
10084
|
+
var h0 = 0;
|
|
10085
|
+
var h1 = 0;
|
|
10086
|
+
for (i = LEE_H0.length - 1; i >= 0; i--) {
|
|
10087
|
+
j = h1 * z3[0] + h0 * z3[1];
|
|
10088
|
+
h0 = LEE_H0[i] + (h0 * z3[0] - h1 * z3[1]);
|
|
10089
|
+
h1 = j;
|
|
9398
10090
|
}
|
|
9399
|
-
h = complexMultiply(
|
|
10091
|
+
h = complexMultiply([h0, h1], z);
|
|
9400
10092
|
}
|
|
9401
10093
|
|
|
9402
10094
|
if (n < 0.3) return h;
|
|
9403
10095
|
if (n > 0.5) return k;
|
|
9404
10096
|
var t = (n - 0.3) / 0.2;
|
|
9405
|
-
return
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
10097
|
+
return [k[0] * t + h[0] * (1 - t), k[1] * t + h[1] * (1 - t)];
|
|
10098
|
+
}
|
|
10099
|
+
|
|
10100
|
+
function invertLeeRaw(x, y) {
|
|
10101
|
+
var lam = x;
|
|
10102
|
+
var phi = y * 0.5;
|
|
10103
|
+
var da = 0;
|
|
10104
|
+
var db = 0;
|
|
10105
|
+
var err2 = Infinity;
|
|
10106
|
+
var eps = 1e-12;
|
|
10107
|
+
for (var i = 0; i < 40; i++) {
|
|
10108
|
+
var q = leeRaw(lam, phi);
|
|
10109
|
+
var tx = q[0] - x;
|
|
10110
|
+
var ty = q[1] - y;
|
|
10111
|
+
if (Math.abs(tx) < eps && Math.abs(ty) < eps) break;
|
|
10112
|
+
var error = tx * tx + ty * ty;
|
|
10113
|
+
if (error > err2) {
|
|
10114
|
+
lam -= da /= 2;
|
|
10115
|
+
phi -= db /= 2;
|
|
10116
|
+
continue;
|
|
10117
|
+
}
|
|
10118
|
+
err2 = error;
|
|
10119
|
+
var ea = (lam > 0 ? -1 : 1) * eps;
|
|
10120
|
+
var eb = (phi > 0 ? -1 : 1) * eps;
|
|
10121
|
+
var qa = leeRaw(lam + ea, phi);
|
|
10122
|
+
var qb = leeRaw(lam, phi + eb);
|
|
10123
|
+
var dxa = (qa[0] - q[0]) / ea;
|
|
10124
|
+
var dya = (qa[1] - q[1]) / ea;
|
|
10125
|
+
var dxb = (qb[0] - q[0]) / eb;
|
|
10126
|
+
var dyb = (qb[1] - q[1]) / eb;
|
|
10127
|
+
var det = dyb * dxa - dya * dxb;
|
|
10128
|
+
if (Math.abs(det) < 1e-14) break;
|
|
10129
|
+
var scale = (Math.abs(det) < 0.5 ? 0.5 : 1) / det;
|
|
10130
|
+
da = (ty * dxb - tx * dyb) * scale;
|
|
10131
|
+
db = (tx * dya - ty * dxa) * scale;
|
|
10132
|
+
lam += da;
|
|
10133
|
+
phi += db;
|
|
10134
|
+
if (Math.abs(da) < eps && Math.abs(db) < eps) break;
|
|
10135
|
+
}
|
|
10136
|
+
var check = leeRaw(lam, phi);
|
|
10137
|
+
return Math.hypot(check[0] - x, check[1] - y) < 1e-8 ?
|
|
10138
|
+
[lam, phi] : null;
|
|
9409
10139
|
}
|
|
9410
10140
|
|
|
9411
10141
|
function stereographicRaw(lam, phi) {
|
|
@@ -9414,10 +10144,6 @@
|
|
|
9414
10144
|
return [k * cosPhi * Math.sin(lam), k * Math.sin(phi)];
|
|
9415
10145
|
}
|
|
9416
10146
|
|
|
9417
|
-
function complexAdd(a, b) {
|
|
9418
|
-
return [a[0] + b[0], a[1] + b[1]];
|
|
9419
|
-
}
|
|
9420
|
-
|
|
9421
10147
|
function complexSubtract(a, b) {
|
|
9422
10148
|
return [a[0] - b[0], a[1] - b[1]];
|
|
9423
10149
|
}
|
|
@@ -9445,79 +10171,27 @@
|
|
|
9445
10171
|
return [magnitude * Math.cos(angle), magnitude * Math.sin(angle)];
|
|
9446
10172
|
}
|
|
9447
10173
|
|
|
9448
|
-
function
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
];
|
|
10174
|
+
function shiftMatrixX(matrix, offset) {
|
|
10175
|
+
var shifted = matrix.concat();
|
|
10176
|
+
shifted[2] += offset;
|
|
10177
|
+
return shifted;
|
|
9453
10178
|
}
|
|
9454
10179
|
|
|
9455
|
-
function
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
var aInside = keepGreater ? a[axis] >= value - EPS : a[axis] <= value + EPS;
|
|
9460
|
-
polygon.forEach(function(b) {
|
|
9461
|
-
var bInside = keepGreater ? b[axis] >= value - EPS : b[axis] <= value + EPS;
|
|
9462
|
-
if (aInside != bInside) {
|
|
9463
|
-
var t = (value - a[axis]) / (b[axis] - a[axis]);
|
|
9464
|
-
var p = [
|
|
9465
|
-
a[0] + (b[0] - a[0]) * t,
|
|
9466
|
-
a[1] + (b[1] - a[1]) * t
|
|
9467
|
-
];
|
|
9468
|
-
p[axis] = value;
|
|
9469
|
-
output.push(p);
|
|
9470
|
-
}
|
|
9471
|
-
if (bInside) output.push(b.concat());
|
|
9472
|
-
a = b;
|
|
9473
|
-
aInside = bInside;
|
|
9474
|
-
});
|
|
9475
|
-
return output;
|
|
10180
|
+
function wrapLayoutX(x) {
|
|
10181
|
+
while (x < RECT_XMIN) x += LAYOUT_PERIOD;
|
|
10182
|
+
while (x > RECT_XMAX) x -= LAYOUT_PERIOD;
|
|
10183
|
+
return x;
|
|
9476
10184
|
}
|
|
9477
10185
|
|
|
9478
|
-
function
|
|
9479
|
-
var
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
if (aInside != bInside) {
|
|
9486
|
-
var t = (value - a[axis]) / (b[axis] - a[axis]);
|
|
9487
|
-
var vertex = {
|
|
9488
|
-
x: a.x + (b.x - a.x) * t,
|
|
9489
|
-
y: a.y + (b.y - a.y) * t,
|
|
9490
|
-
sx: a.sx + (b.sx - a.sx) * t,
|
|
9491
|
-
sy: a.sy + (b.sy - a.sy) * t,
|
|
9492
|
-
lon: interpolateLongitude(a.lon, b.lon, t),
|
|
9493
|
-
lat: a.lat + (b.lat - a.lat) * t
|
|
9494
|
-
};
|
|
9495
|
-
vertex[axis] = value;
|
|
9496
|
-
output.push(vertex);
|
|
9497
|
-
}
|
|
9498
|
-
if (bInside) output.push(copyRasterVertex(b, b.x, b.y));
|
|
9499
|
-
a = b;
|
|
9500
|
-
aInside = bInside;
|
|
10186
|
+
function getRingBounds(ring) {
|
|
10187
|
+
var bounds = [Infinity, Infinity, -Infinity, -Infinity];
|
|
10188
|
+
ring.forEach(function(p) {
|
|
10189
|
+
if (p[0] < bounds[0]) bounds[0] = p[0];
|
|
10190
|
+
if (p[1] < bounds[1]) bounds[1] = p[1];
|
|
10191
|
+
if (p[0] > bounds[2]) bounds[2] = p[0];
|
|
10192
|
+
if (p[1] > bounds[3]) bounds[3] = p[1];
|
|
9501
10193
|
});
|
|
9502
|
-
return
|
|
9503
|
-
}
|
|
9504
|
-
|
|
9505
|
-
function copyRasterVertex(vertex, x, y) {
|
|
9506
|
-
return {
|
|
9507
|
-
x: x,
|
|
9508
|
-
y: y,
|
|
9509
|
-
sx: vertex.sx,
|
|
9510
|
-
sy: vertex.sy,
|
|
9511
|
-
lon: vertex.lon,
|
|
9512
|
-
lat: vertex.lat
|
|
9513
|
-
};
|
|
9514
|
-
}
|
|
9515
|
-
|
|
9516
|
-
function interpolateLongitude(a, b, t) {
|
|
9517
|
-
var delta = b - a;
|
|
9518
|
-
if (delta > 180) delta -= 360;
|
|
9519
|
-
if (delta < -180) delta += 360;
|
|
9520
|
-
return normalizeLongitude$1(a + delta * t);
|
|
10194
|
+
return bounds;
|
|
9521
10195
|
}
|
|
9522
10196
|
|
|
9523
10197
|
function getRingArea$2(ring) {
|
|
@@ -9528,20 +10202,51 @@
|
|
|
9528
10202
|
return area / 2;
|
|
9529
10203
|
}
|
|
9530
10204
|
|
|
10205
|
+
function indexPiecesByFaceAndCopy(pieces) {
|
|
10206
|
+
var index = new Map();
|
|
10207
|
+
pieces.forEach(function(piece) {
|
|
10208
|
+
var key = getPieceKey(piece.face, piece.copy);
|
|
10209
|
+
if (!index.has(key)) index.set(key, []);
|
|
10210
|
+
index.get(key).push(piece.id);
|
|
10211
|
+
});
|
|
10212
|
+
return index;
|
|
10213
|
+
}
|
|
10214
|
+
|
|
10215
|
+
function findPieceContainingPoint(pieces, ids, point) {
|
|
10216
|
+
for (var i = 0; i < ids.length; i++) {
|
|
10217
|
+
if (pointInRing$2(point, pieces[ids[i]].boundary)) return ids[i];
|
|
10218
|
+
}
|
|
10219
|
+
}
|
|
10220
|
+
|
|
10221
|
+
function pointInRing$2(point, ring) {
|
|
10222
|
+
var inside = false;
|
|
10223
|
+
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
10224
|
+
var a = ring[j];
|
|
10225
|
+
var b = ring[i];
|
|
10226
|
+
var cross = (point[0] - a[0]) * (b[1] - a[1]) -
|
|
10227
|
+
(point[1] - a[1]) * (b[0] - a[0]);
|
|
10228
|
+
if (Math.abs(cross) < 1e-8 &&
|
|
10229
|
+
point[0] >= Math.min(a[0], b[0]) - 1e-8 &&
|
|
10230
|
+
point[0] <= Math.max(a[0], b[0]) + 1e-8 &&
|
|
10231
|
+
point[1] >= Math.min(a[1], b[1]) - 1e-8 &&
|
|
10232
|
+
point[1] <= Math.max(a[1], b[1]) + 1e-8) return true;
|
|
10233
|
+
if ((a[1] > point[1]) != (b[1] > point[1]) &&
|
|
10234
|
+
point[0] < (b[0] - a[0]) * (point[1] - a[1]) /
|
|
10235
|
+
(b[1] - a[1]) + a[0]) {
|
|
10236
|
+
inside = !inside;
|
|
10237
|
+
}
|
|
10238
|
+
}
|
|
10239
|
+
return inside;
|
|
10240
|
+
}
|
|
10241
|
+
|
|
9531
10242
|
function getPieceKey(face, copy) {
|
|
9532
10243
|
return face + ':' + copy;
|
|
9533
10244
|
}
|
|
9534
10245
|
|
|
9535
|
-
function pointsEqual$
|
|
10246
|
+
function pointsEqual$2(a, b) {
|
|
9536
10247
|
return Math.abs(a[0] - b[0]) < EPS && Math.abs(a[1] - b[1]) < EPS;
|
|
9537
10248
|
}
|
|
9538
10249
|
|
|
9539
|
-
function normalizeLongitude$1(lon) {
|
|
9540
|
-
lon = (lon + 180) % 360;
|
|
9541
|
-
if (lon < 0) lon += 360;
|
|
9542
|
-
return lon - 180;
|
|
9543
|
-
}
|
|
9544
|
-
|
|
9545
10250
|
var mproj$1 = require$1('mproj');
|
|
9546
10251
|
registerDymaxionProjections(mproj$1);
|
|
9547
10252
|
registerButterflyProjections(mproj$1);
|
|
@@ -11859,190 +12564,6 @@
|
|
|
11859
12564
|
};
|
|
11860
12565
|
}
|
|
11861
12566
|
|
|
11862
|
-
// Lightweight hierarchical profiler.
|
|
11863
|
-
// Intended for ad-hoc performance work on hot pipelines (e.g. addIntersectionCuts).
|
|
11864
|
-
//
|
|
11865
|
-
// Usage:
|
|
11866
|
-
// import { profileStart, profileEnd, profileWrap, ... } from './utils/mapshaper-profile';
|
|
11867
|
-
// profileStart('phase'); doWork(); profileEnd('phase');
|
|
11868
|
-
// var result = profileWrap('phase', () => doWork());
|
|
11869
|
-
//
|
|
11870
|
-
// When disabled (the default) every call short-circuits in ~one comparison; safe
|
|
11871
|
-
// to leave in hot code paths. Enable from the CLI with the `-profile` command,
|
|
11872
|
-
// from JS with enableProfiling(), or by setting the MAPSHAPER_PROFILE env var.
|
|
11873
|
-
//
|
|
11874
|
-
// The profiler tracks a stack of currently-open phases so calls can nest. Each
|
|
11875
|
-
// unique stack path accumulates ms-elapsed and a call count. profileReport()
|
|
11876
|
-
// returns a flat array; formatProfileReport() pretty-prints a tree.
|
|
11877
|
-
|
|
11878
|
-
var ENABLED = false;
|
|
11879
|
-
var ROOT = makeNode('<root>');
|
|
11880
|
-
var STACK = [ROOT];
|
|
11881
|
-
var WALL_START = 0;
|
|
11882
|
-
|
|
11883
|
-
function makeNode(label) {
|
|
11884
|
-
return {
|
|
11885
|
-
label: label,
|
|
11886
|
-
totalMs: 0,
|
|
11887
|
-
selfMs: 0,
|
|
11888
|
-
calls: 0,
|
|
11889
|
-
childMsAccum: 0,
|
|
11890
|
-
children: Object.create(null)
|
|
11891
|
-
};
|
|
11892
|
-
}
|
|
11893
|
-
|
|
11894
|
-
function nowMs() {
|
|
11895
|
-
if (typeof process !== 'undefined' && process.hrtime && process.hrtime.bigint) {
|
|
11896
|
-
// bigint -> number division: precision down to 1 microsecond is fine for our needs
|
|
11897
|
-
return Number(process.hrtime.bigint()) / 1e6;
|
|
11898
|
-
}
|
|
11899
|
-
if (typeof performance !== 'undefined' && performance.now) {
|
|
11900
|
-
return performance.now();
|
|
11901
|
-
}
|
|
11902
|
-
return Date.now();
|
|
11903
|
-
}
|
|
11904
|
-
|
|
11905
|
-
function enableProfiling() {
|
|
11906
|
-
ENABLED = true;
|
|
11907
|
-
WALL_START = nowMs();
|
|
11908
|
-
}
|
|
11909
|
-
|
|
11910
|
-
function disableProfiling() {
|
|
11911
|
-
ENABLED = false;
|
|
11912
|
-
}
|
|
11913
|
-
|
|
11914
|
-
function profileEnabled() {
|
|
11915
|
-
return ENABLED;
|
|
11916
|
-
}
|
|
11917
|
-
|
|
11918
|
-
function profileReset() {
|
|
11919
|
-
ROOT = makeNode('<root>');
|
|
11920
|
-
STACK = [ROOT];
|
|
11921
|
-
WALL_START = ENABLED ? nowMs() : 0;
|
|
11922
|
-
}
|
|
11923
|
-
|
|
11924
|
-
// Open a new phase. Cheap (~one branch) when disabled.
|
|
11925
|
-
function profileStart(label) {
|
|
11926
|
-
if (!ENABLED) return;
|
|
11927
|
-
var parent = STACK[STACK.length - 1];
|
|
11928
|
-
var node = parent.children[label];
|
|
11929
|
-
if (!node) {
|
|
11930
|
-
node = makeNode(label);
|
|
11931
|
-
parent.children[label] = node;
|
|
11932
|
-
}
|
|
11933
|
-
node._t0 = nowMs();
|
|
11934
|
-
STACK.push(node);
|
|
11935
|
-
}
|
|
11936
|
-
|
|
11937
|
-
function profileEnd(label) {
|
|
11938
|
-
if (!ENABLED) return;
|
|
11939
|
-
var node = STACK[STACK.length - 1];
|
|
11940
|
-
if (label && node.label !== label) {
|
|
11941
|
-
// Mismatched labels usually mean an early return forgot to call profileEnd().
|
|
11942
|
-
// Walk up the stack until we find a match, closing the intervening frames.
|
|
11943
|
-
while (STACK.length > 1 && STACK[STACK.length - 1].label !== label) {
|
|
11944
|
-
profileEnd(STACK[STACK.length - 1].label);
|
|
11945
|
-
}
|
|
11946
|
-
node = STACK[STACK.length - 1];
|
|
11947
|
-
if (node.label !== label) return; // give up rather than throw
|
|
11948
|
-
}
|
|
11949
|
-
var elapsed = nowMs() - node._t0;
|
|
11950
|
-
node._t0 = 0;
|
|
11951
|
-
node.totalMs += elapsed;
|
|
11952
|
-
node.calls += 1;
|
|
11953
|
-
STACK.pop();
|
|
11954
|
-
var parent = STACK[STACK.length - 1];
|
|
11955
|
-
parent.childMsAccum += elapsed;
|
|
11956
|
-
}
|
|
11957
|
-
|
|
11958
|
-
// Wrap a function call. Re-throws if the callback throws but still closes the
|
|
11959
|
-
// phase so the stack stays consistent.
|
|
11960
|
-
function profileWrap(label, fn) {
|
|
11961
|
-
if (!ENABLED) return fn();
|
|
11962
|
-
profileStart(label);
|
|
11963
|
-
try {
|
|
11964
|
-
return fn();
|
|
11965
|
-
} finally {
|
|
11966
|
-
profileEnd(label);
|
|
11967
|
-
}
|
|
11968
|
-
}
|
|
11969
|
-
|
|
11970
|
-
// Build a flat tree report: array of {depth, label, totalMs, selfMs, calls}.
|
|
11971
|
-
function profileReport() {
|
|
11972
|
-
var rows = [];
|
|
11973
|
-
function visit(node, depth) {
|
|
11974
|
-
if (depth > 0) {
|
|
11975
|
-
rows.push({
|
|
11976
|
-
depth: depth - 1,
|
|
11977
|
-
label: node.label,
|
|
11978
|
-
totalMs: node.totalMs,
|
|
11979
|
-
selfMs: Math.max(0, node.totalMs - node.childMsAccum),
|
|
11980
|
-
calls: node.calls
|
|
11981
|
-
});
|
|
11982
|
-
}
|
|
11983
|
-
var keys = Object.keys(node.children);
|
|
11984
|
-
keys.sort(function(a, b) {
|
|
11985
|
-
return node.children[b].totalMs - node.children[a].totalMs;
|
|
11986
|
-
});
|
|
11987
|
-
for (var i = 0; i < keys.length; i++) {
|
|
11988
|
-
visit(node.children[keys[i]], depth + 1);
|
|
11989
|
-
}
|
|
11990
|
-
}
|
|
11991
|
-
visit(ROOT, 0);
|
|
11992
|
-
return rows;
|
|
11993
|
-
}
|
|
11994
|
-
|
|
11995
|
-
function profileWallElapsedMs() {
|
|
11996
|
-
return ENABLED && WALL_START ? nowMs() - WALL_START : 0;
|
|
11997
|
-
}
|
|
11998
|
-
|
|
11999
|
-
// Pretty-print the current report as a column-aligned tree.
|
|
12000
|
-
function formatProfileReport(opts) {
|
|
12001
|
-
var rows = profileReport();
|
|
12002
|
-
if (rows.length === 0) return '(profile is empty)';
|
|
12003
|
-
opts = opts || {};
|
|
12004
|
-
var indent = ' ';
|
|
12005
|
-
var lines = [];
|
|
12006
|
-
// header
|
|
12007
|
-
lines.push(['phase', 'total ms', 'self ms', 'calls'].join('\t'));
|
|
12008
|
-
for (var i = 0; i < rows.length; i++) {
|
|
12009
|
-
var r = rows[i];
|
|
12010
|
-
var label = '';
|
|
12011
|
-
for (var j = 0; j < r.depth; j++) label += indent;
|
|
12012
|
-
label += r.label;
|
|
12013
|
-
lines.push([
|
|
12014
|
-
label,
|
|
12015
|
-
r.totalMs.toFixed(2),
|
|
12016
|
-
r.selfMs.toFixed(2),
|
|
12017
|
-
String(r.calls)
|
|
12018
|
-
].join('\t'));
|
|
12019
|
-
}
|
|
12020
|
-
if (opts.includeWall) {
|
|
12021
|
-
lines.push('');
|
|
12022
|
-
lines.push('wall elapsed ms: ' + profileWallElapsedMs().toFixed(2));
|
|
12023
|
-
}
|
|
12024
|
-
return lines.join('\n');
|
|
12025
|
-
}
|
|
12026
|
-
|
|
12027
|
-
// Honour an env var so users (and CI harnesses) can opt in without code changes.
|
|
12028
|
-
if (typeof process !== 'undefined' && process.env && process.env.MAPSHAPER_PROFILE) {
|
|
12029
|
-
enableProfiling();
|
|
12030
|
-
}
|
|
12031
|
-
|
|
12032
|
-
var Profile = /*#__PURE__*/Object.freeze({
|
|
12033
|
-
__proto__: null,
|
|
12034
|
-
disableProfiling: disableProfiling,
|
|
12035
|
-
enableProfiling: enableProfiling,
|
|
12036
|
-
formatProfileReport: formatProfileReport,
|
|
12037
|
-
profileEnabled: profileEnabled,
|
|
12038
|
-
profileEnd: profileEnd,
|
|
12039
|
-
profileReport: profileReport,
|
|
12040
|
-
profileReset: profileReset,
|
|
12041
|
-
profileStart: profileStart,
|
|
12042
|
-
profileWallElapsedMs: profileWallElapsedMs,
|
|
12043
|
-
profileWrap: profileWrap
|
|
12044
|
-
});
|
|
12045
|
-
|
|
12046
12567
|
// Dissolve arcs that can be merged without affecting topology of layers
|
|
12047
12568
|
// remove arcs that are not referenced by any layer; remap arc ids
|
|
12048
12569
|
// in layers. (dataset.arcs is replaced).
|
|
@@ -17759,6 +18280,15 @@
|
|
|
17759
18280
|
};
|
|
17760
18281
|
}
|
|
17761
18282
|
|
|
18283
|
+
var ColorUtils = /*#__PURE__*/Object.freeze({
|
|
18284
|
+
__proto__: null,
|
|
18285
|
+
formatColor: formatColor,
|
|
18286
|
+
parseColor: parseColor,
|
|
18287
|
+
parseHexColor: parseHexColor,
|
|
18288
|
+
parseRGBA: parseRGBA,
|
|
18289
|
+
validateColor: validateColor
|
|
18290
|
+
});
|
|
18291
|
+
|
|
17762
18292
|
function blend(a, b) {
|
|
17763
18293
|
var colors, weights, args;
|
|
17764
18294
|
if (Array.isArray(a)) {
|
|
@@ -21869,9 +22399,12 @@
|
|
|
21869
22399
|
// Assumes that input layers are members of the same dataset (and therefore
|
|
21870
22400
|
// share the same ArcCollection, if layers have paths).
|
|
21871
22401
|
cmd.mergeLayers = function(layersArg, opts) {
|
|
21872
|
-
var layers =
|
|
21873
|
-
var merged = {};
|
|
22402
|
+
var layers, merged = {};
|
|
21874
22403
|
opts = opts || {};
|
|
22404
|
+
layersArg.forEach(function(lyr) {
|
|
22405
|
+
requireNotRasterLayer(lyr, 'Raster layers cannot be merged');
|
|
22406
|
+
});
|
|
22407
|
+
layers = layersArg.filter(getFeatureCount); // ignore empty layers
|
|
21875
22408
|
if (!layers.length && layersArg.length > 1) {
|
|
21876
22409
|
layers = layersArg; // merge all-empty layer sets
|
|
21877
22410
|
}
|
|
@@ -28277,6 +28810,9 @@ ${svg}
|
|
|
28277
28810
|
if (raster.grid.samples) {
|
|
28278
28811
|
copy.grid.samples = typedArrayToBuffer(raster.grid.samples);
|
|
28279
28812
|
}
|
|
28813
|
+
if (raster.grid.coverage) {
|
|
28814
|
+
copy.grid.coverage = typedArrayToBuffer(raster.grid.coverage);
|
|
28815
|
+
}
|
|
28280
28816
|
}
|
|
28281
28817
|
if (raster.view) {
|
|
28282
28818
|
copy.view = Object.assign({}, raster.view);
|
|
@@ -33522,8 +34058,9 @@ ${svg}
|
|
|
33522
34058
|
error('precision= option should be a positive number');
|
|
33523
34059
|
}
|
|
33524
34060
|
|
|
33525
|
-
if (o.
|
|
33526
|
-
|
|
34061
|
+
if (o.interpretation && o.interpretation != 'image' &&
|
|
34062
|
+
o.interpretation != 'categorical' && o.interpretation != 'continuous') {
|
|
34063
|
+
error('Unsupported raster-type:', o.interpretation);
|
|
33527
34064
|
}
|
|
33528
34065
|
|
|
33529
34066
|
if (o.encoding) {
|
|
@@ -34836,8 +35373,15 @@ ${svg}
|
|
|
34836
35373
|
.option('percentile-range', {
|
|
34837
35374
|
describe: '[raster] input percentile range for percentile scaling, e.g. 2,98'
|
|
34838
35375
|
})
|
|
35376
|
+
// The user-facing spelling is raster-type=; the parsed option is named
|
|
35377
|
+
// interpretation to keep it distinct from layer.raster_type, which is a
|
|
35378
|
+
// storage tag, not a semantic one.
|
|
35379
|
+
.option('interpretation', {
|
|
35380
|
+
label: 'raster-type=',
|
|
35381
|
+
describe: '[raster] image, categorical or continuous (default is image)'
|
|
35382
|
+
})
|
|
34839
35383
|
.option('raster-type', {
|
|
34840
|
-
|
|
35384
|
+
alias_to: 'interpretation'
|
|
34841
35385
|
})
|
|
34842
35386
|
.option('rendition', {
|
|
34843
35387
|
describe: '[GeoTIFF] import a GeoTIFF rendition: full,overview-1,etc.'
|
|
@@ -35284,9 +35828,11 @@ ${svg}
|
|
|
35284
35828
|
.option('no-replace', noReplaceOpt);
|
|
35285
35829
|
|
|
35286
35830
|
parser.command('blur')
|
|
35287
|
-
|
|
35831
|
+
.describe('apply a Gaussian-like blur to a projected raster layer')
|
|
35288
35832
|
.option('radius', {
|
|
35289
|
-
|
|
35833
|
+
DEFAULT: true,
|
|
35834
|
+
type: 'distance',
|
|
35835
|
+
describe: 'blur amount as pixels or a distance (e.g. 5px, 20m)'
|
|
35290
35836
|
})
|
|
35291
35837
|
.option('target', targetOpt);
|
|
35292
35838
|
|
|
@@ -35507,6 +36053,35 @@ ${svg}
|
|
|
35507
36053
|
type: 'number'
|
|
35508
36054
|
});
|
|
35509
36055
|
|
|
36056
|
+
parser.command('contours')
|
|
36057
|
+
.describe('convert a raster layer to contour lines')
|
|
36058
|
+
.option('interval', {
|
|
36059
|
+
describe: 'spacing between contour levels (default is a round interval)',
|
|
36060
|
+
type: 'number'
|
|
36061
|
+
})
|
|
36062
|
+
.option('levels', {
|
|
36063
|
+
describe: 'explicit contour levels, instead of interval= (e.g. 0,100,500)',
|
|
36064
|
+
type: 'numbers'
|
|
36065
|
+
})
|
|
36066
|
+
.option('base', {
|
|
36067
|
+
describe: 'value to align interval= to (default is 0)',
|
|
36068
|
+
type: 'number'
|
|
36069
|
+
})
|
|
36070
|
+
.option('band', {
|
|
36071
|
+
describe: '[raster] band to read values from (default is 0)',
|
|
36072
|
+
type: 'number'
|
|
36073
|
+
})
|
|
36074
|
+
.option('field', {
|
|
36075
|
+
describe: 'name of field to hold contour values (default is "value")'
|
|
36076
|
+
})
|
|
36077
|
+
.option('no-smoothing', {
|
|
36078
|
+
describe: 'skip smoothing away the one-pixel contour staircase',
|
|
36079
|
+
type: 'flag'
|
|
36080
|
+
})
|
|
36081
|
+
.option('name', nameOpt)
|
|
36082
|
+
.option('target', targetOpt)
|
|
36083
|
+
.option('no-replace', noReplaceOpt);
|
|
36084
|
+
|
|
35510
36085
|
parser.command('dashlines')
|
|
35511
36086
|
.describe('split lines into sections, with or without a gap')
|
|
35512
36087
|
.oldAlias('split-lines')
|
|
@@ -40908,7 +41483,7 @@ ${svg}
|
|
|
40908
41483
|
}
|
|
40909
41484
|
|
|
40910
41485
|
function getRasterInterpretation$1(opts) {
|
|
40911
|
-
return opts.
|
|
41486
|
+
return opts.interpretation || 'image';
|
|
40912
41487
|
}
|
|
40913
41488
|
|
|
40914
41489
|
async function selectGeoTIFFImportImage(tiff, sourceImage, opts) {
|
|
@@ -41336,7 +41911,7 @@ ${svg}
|
|
|
41336
41911
|
}
|
|
41337
41912
|
|
|
41338
41913
|
function getRasterInterpretation(opts) {
|
|
41339
|
-
return opts.
|
|
41914
|
+
return opts.interpretation || 'image';
|
|
41340
41915
|
}
|
|
41341
41916
|
|
|
41342
41917
|
async function decodeImage(input, imageType) {
|
|
@@ -41966,10 +42541,10 @@ ${svg}
|
|
|
41966
42541
|
var coords = path.coords;
|
|
41967
42542
|
if (coords.length < 2) return;
|
|
41968
42543
|
if (forcePolylinePaths) {
|
|
41969
|
-
lines.push(path.closed || pointsEqual(coords[0], coords[coords.length - 1]) ? closeRing(coords) : coords);
|
|
42544
|
+
lines.push(path.closed || pointsEqual$1(coords[0], coords[coords.length - 1]) ? closeRing(coords) : coords);
|
|
41970
42545
|
return;
|
|
41971
42546
|
}
|
|
41972
|
-
if (path.closed || pointsEqual(coords[0], coords[coords.length - 1])) {
|
|
42547
|
+
if (path.closed || pointsEqual$1(coords[0], coords[coords.length - 1])) {
|
|
41973
42548
|
if (coords.length >= 3) {
|
|
41974
42549
|
rings.push(closeRing(coords));
|
|
41975
42550
|
}
|
|
@@ -42192,7 +42767,7 @@ ${svg}
|
|
|
42192
42767
|
for (var i = 0; i < subpaths.length; i++) {
|
|
42193
42768
|
var coords = subpaths[i].coords;
|
|
42194
42769
|
if (coords.length < 2) continue;
|
|
42195
|
-
if (!subpaths[i].closed && !pointsEqual(coords[0], coords[coords.length - 1])) {
|
|
42770
|
+
if (!subpaths[i].closed && !pointsEqual$1(coords[0], coords[coords.length - 1])) {
|
|
42196
42771
|
return true;
|
|
42197
42772
|
}
|
|
42198
42773
|
}
|
|
@@ -42277,7 +42852,7 @@ ${svg}
|
|
|
42277
42852
|
|
|
42278
42853
|
function closeRing(coords) {
|
|
42279
42854
|
var ring = coords.map(function(p) { return [p[0], p[1]]; });
|
|
42280
|
-
if (!pointsEqual(ring[0], ring[ring.length - 1])) {
|
|
42855
|
+
if (!pointsEqual$1(ring[0], ring[ring.length - 1])) {
|
|
42281
42856
|
ring.push([ring[0][0], ring[0][1]]);
|
|
42282
42857
|
}
|
|
42283
42858
|
return ring;
|
|
@@ -42332,7 +42907,7 @@ ${svg}
|
|
|
42332
42907
|
return /^[a-z]$/i.test(token);
|
|
42333
42908
|
}
|
|
42334
42909
|
|
|
42335
|
-
function pointsEqual(a, b) {
|
|
42910
|
+
function pointsEqual$1(a, b) {
|
|
42336
42911
|
return a && b && a[0] == b[0] && a[1] == b[1];
|
|
42337
42912
|
}
|
|
42338
42913
|
|
|
@@ -42681,6 +43256,9 @@ ${svg}
|
|
|
42681
43256
|
if (raster.grid.samples) {
|
|
42682
43257
|
copy.grid.samples = restoreRasterSamples(raster.grid.samples, raster.grid.pixelType);
|
|
42683
43258
|
}
|
|
43259
|
+
if (raster.grid.coverage) {
|
|
43260
|
+
copy.grid.coverage = new Uint8Array(BinArray.copyToArrayBuffer(raster.grid.coverage));
|
|
43261
|
+
}
|
|
42684
43262
|
}
|
|
42685
43263
|
if (raster.view) {
|
|
42686
43264
|
copy.view = Object.assign({}, raster.view);
|
|
@@ -55898,6 +56476,9 @@ ${svg}
|
|
|
55898
56476
|
|
|
55899
56477
|
function isInterruptedProjection(P) {
|
|
55900
56478
|
if (P && P.__projection_topology) {
|
|
56479
|
+
if (P.__projection_topology.interrupted !== undefined) {
|
|
56480
|
+
return P.__projection_topology.interrupted;
|
|
56481
|
+
}
|
|
55901
56482
|
return P.__projection_topology.seams.some(function(o) {
|
|
55902
56483
|
return o.type == 'cut';
|
|
55903
56484
|
});
|
|
@@ -56756,6 +57337,16 @@ ${svg}
|
|
|
56756
57337
|
derivation.type == 'categorical';
|
|
56757
57338
|
}
|
|
56758
57339
|
|
|
57340
|
+
// Whether uncovered output pixels should get the source's numeric nodata
|
|
57341
|
+
// value instead of a fill color. This is a separate question from the
|
|
57342
|
+
// resampling method: elevation data (raster-type=continuous) wants numeric
|
|
57343
|
+
// nodata AND bilinear resampling, a combination that a single
|
|
57344
|
+
// image/categorical switch could not express.
|
|
57345
|
+
function rasterUsesNumericNodataFill(raster) {
|
|
57346
|
+
return !!raster && raster.interpretation == 'continuous' ||
|
|
57347
|
+
rasterAppearsCategorical(raster);
|
|
57348
|
+
}
|
|
57349
|
+
|
|
56759
57350
|
function getProjectedRasterGridBBox(raster, srcCRS, destCRS, optsArg) {
|
|
56760
57351
|
var opts = optsArg || {};
|
|
56761
57352
|
var grid = getRasterGrid(raster);
|
|
@@ -57434,9 +58025,8 @@ ${svg}
|
|
|
57434
58025
|
}
|
|
57435
58026
|
|
|
57436
58027
|
function validateRasterGridForProjection(grid) {
|
|
57437
|
-
var t = grid && grid.transform;
|
|
57438
58028
|
if (!grid || !grid.samples || !grid.bbox) stop$1('Raster layer is missing required projection data');
|
|
57439
|
-
if (
|
|
58029
|
+
if (rasterGridIsRotated(grid)) {
|
|
57440
58030
|
stop$1('Raster reprojection does not support rotated or skewed rasters');
|
|
57441
58031
|
}
|
|
57442
58032
|
}
|
|
@@ -57568,18 +58158,35 @@ ${svg}
|
|
|
57568
58158
|
var color = getNoDataColor(raster, opts);
|
|
57569
58159
|
var noData = grid.nodata;
|
|
57570
58160
|
if (color) {
|
|
58161
|
+
warnIfPaintingOverContinuousData(samples, grid, raster, opts);
|
|
57571
58162
|
fillProjectedRasterColor(samples, bands, color);
|
|
57572
58163
|
return;
|
|
57573
58164
|
}
|
|
57574
|
-
if (noData === null || noData === undefined || !isFinite(noData))
|
|
58165
|
+
if (noData === null || noData === undefined || !isFinite(noData)) {
|
|
58166
|
+
if (raster && raster.interpretation == 'continuous') {
|
|
58167
|
+
warnOnce('This raster has no nodata value, so uncovered pixels will be 0');
|
|
58168
|
+
}
|
|
58169
|
+
return;
|
|
58170
|
+
}
|
|
57575
58171
|
samples.fill(noData);
|
|
57576
58172
|
}
|
|
57577
58173
|
|
|
58174
|
+
// Painting a color into a float grid means writing a color value into what
|
|
58175
|
+
// are probably measurements. Flag it, since raster-type=continuous is
|
|
58176
|
+
// usually what the user wanted.
|
|
58177
|
+
function warnIfPaintingOverContinuousData(samples, grid, raster, opts) {
|
|
58178
|
+
if (opts.nodata_color || opts.nodataColor) return; // explicit user choice
|
|
58179
|
+
if (!rasterSamplesAreFloat(samples)) return;
|
|
58180
|
+
if (grid.nodata === null || grid.nodata === undefined) return;
|
|
58181
|
+
warnOnce('Filling uncovered pixels of a floating-point raster with a color. ' +
|
|
58182
|
+
'Use -i raster-type=continuous to preserve its nodata value instead.');
|
|
58183
|
+
}
|
|
58184
|
+
|
|
57578
58185
|
function getNoDataColor(raster, opts) {
|
|
57579
58186
|
var arg = opts.nodata_color || opts.nodataColor;
|
|
57580
58187
|
var color;
|
|
57581
58188
|
if (arg == null || arg === '') {
|
|
57582
|
-
return
|
|
58189
|
+
return rasterUsesNumericNodataFill(raster) ? null : {r: 255, g: 255, b: 255, a: 1};
|
|
57583
58190
|
}
|
|
57584
58191
|
if (String(arg).toLowerCase() == 'transparent') {
|
|
57585
58192
|
return {r: 0, g: 0, b: 0, a: 0};
|
|
@@ -57604,7 +58211,8 @@ ${svg}
|
|
|
57604
58211
|
}
|
|
57605
58212
|
}
|
|
57606
58213
|
|
|
57607
|
-
function rasterizeProjectedMesh(srcGrid, destGrid, mesh,
|
|
58214
|
+
function rasterizeProjectedMesh(srcGrid, destGrid, mesh, sampleMethodArg) {
|
|
58215
|
+
var sampleMethod = getRasterSamplerContext(srcGrid, destGrid, sampleMethodArg);
|
|
57608
58216
|
var regionData = mesh.projectedRegions ?
|
|
57609
58217
|
createProjectedRegionMask(
|
|
57610
58218
|
destGrid, mesh.projectedRegions, mesh.fillRegionMask) :
|
|
@@ -57801,9 +58409,24 @@ ${svg}
|
|
|
57801
58409
|
];
|
|
57802
58410
|
}
|
|
57803
58411
|
|
|
57804
|
-
|
|
57805
|
-
|
|
57806
|
-
|
|
58412
|
+
// Per-projection sampling context, built once so the inner rasterizing loop
|
|
58413
|
+
// does not repeat type or metadata tests for every output pixel.
|
|
58414
|
+
function getRasterSamplerContext(srcGrid, destGrid, method) {
|
|
58415
|
+
return {
|
|
58416
|
+
bilinear: method == 'bilinear',
|
|
58417
|
+
// Integer sample arrays truncate on assignment, so interpolated values
|
|
58418
|
+
// have to be rounded explicitly. Float arrays must NOT be rounded, or
|
|
58419
|
+
// elevation data loses its sub-unit precision.
|
|
58420
|
+
round: !rasterSamplesAreFloat(destGrid.samples),
|
|
58421
|
+
// Resolved once for the whole source grid; null means every pixel is
|
|
58422
|
+
// valid and the sampler can skip validity testing altogether.
|
|
58423
|
+
validity: getRasterValidityMask(srcGrid)
|
|
58424
|
+
};
|
|
58425
|
+
}
|
|
58426
|
+
|
|
58427
|
+
function copyRasterSample(srcGrid, destGrid, sx, sy, dx, dy, sampler, wrapX) {
|
|
58428
|
+
if (sampler.bilinear) {
|
|
58429
|
+
copyBilinearRasterSample(srcGrid, destGrid, sx, sy, dx, dy, sampler, wrapX);
|
|
57807
58430
|
} else {
|
|
57808
58431
|
copyNearestRasterSample(srcGrid, destGrid, sx, sy, dx, dy, wrapX);
|
|
57809
58432
|
}
|
|
@@ -57816,7 +58439,9 @@ ${svg}
|
|
|
57816
58439
|
var srcY = clamp$1(Math.floor(sy), 0, srcGrid.height - 1);
|
|
57817
58440
|
var src = (srcY * srcGrid.width + srcX) * srcGrid.bands;
|
|
57818
58441
|
var dest = (dy * destGrid.width + dx) * destGrid.bands;
|
|
57819
|
-
|
|
58442
|
+
// Nearest sampling copies a nodata sample through unchanged, rather than
|
|
58443
|
+
// skipping it, so nodata areas survive a round trip.
|
|
58444
|
+
if (!rasterPixelIsCovered(srcGrid, srcY * srcGrid.width + srcX)) return;
|
|
57820
58445
|
for (var band = 0; band < srcGrid.bands; band++) {
|
|
57821
58446
|
destGrid.samples[dest + band] = srcGrid.samples[src + band];
|
|
57822
58447
|
}
|
|
@@ -57824,7 +58449,7 @@ ${svg}
|
|
|
57824
58449
|
if (destGrid.coverage) destGrid.coverage[dy * destGrid.width + dx] = 1;
|
|
57825
58450
|
}
|
|
57826
58451
|
|
|
57827
|
-
function copyBilinearRasterSample(srcGrid, destGrid, sx, sy, dx, dy, wrapX) {
|
|
58452
|
+
function copyBilinearRasterSample(srcGrid, destGrid, sx, sy, dx, dy, sampler, wrapX) {
|
|
57828
58453
|
if (wrapX) sx = modulo(sx, srcGrid.width);
|
|
57829
58454
|
var srcX = wrapX ?
|
|
57830
58455
|
modulo(Math.floor(sx - 0.5), srcGrid.width) :
|
|
@@ -57840,79 +58465,81 @@ ${svg}
|
|
|
57840
58465
|
var src01 = (srcY2 * srcGrid.width + srcX) * srcGrid.bands;
|
|
57841
58466
|
var src11 = (srcY2 * srcGrid.width + srcX2) * srcGrid.bands;
|
|
57842
58467
|
var dest = (dy * destGrid.width + dx) * destGrid.bands;
|
|
57843
|
-
if (!
|
|
57844
|
-
copyBilinearRasterSampleFast(srcGrid, destGrid, src00, src10, src01, src11, dest, tx, ty);
|
|
58468
|
+
if (!sampler.validity) {
|
|
58469
|
+
copyBilinearRasterSampleFast(srcGrid, destGrid, src00, src10, src01, src11, dest, tx, ty, sampler.round);
|
|
57845
58470
|
if (destGrid.coverage) destGrid.coverage[dy * destGrid.width + dx] = 1;
|
|
57846
58471
|
return;
|
|
57847
58472
|
}
|
|
57848
|
-
if (
|
|
58473
|
+
if (copyBilinearRasterSampleChecked(srcGrid, destGrid, sampler.validity, srcX, srcY, srcX2, srcY2, src00, src10, src01, src11, dest, tx, ty, sampler.round)) {
|
|
57849
58474
|
if (destGrid.coverage) destGrid.coverage[dy * destGrid.width + dx] = 1;
|
|
57850
58475
|
}
|
|
57851
58476
|
}
|
|
57852
58477
|
|
|
57853
|
-
function copyBilinearRasterSampleFast(srcGrid, destGrid, src00, src10, src01, src11, dest, tx, ty) {
|
|
58478
|
+
function copyBilinearRasterSampleFast(srcGrid, destGrid, src00, src10, src01, src11, dest, tx, ty, round) {
|
|
57854
58479
|
if (srcGrid.bands == 3) {
|
|
57855
|
-
copyBilinearRgbSample(srcGrid.samples, destGrid.samples, src00, src10, src01, src11, dest, tx, ty);
|
|
58480
|
+
copyBilinearRgbSample(srcGrid.samples, destGrid.samples, src00, src10, src01, src11, dest, tx, ty, round);
|
|
57856
58481
|
} else if (srcGrid.bands == 4) {
|
|
57857
|
-
copyBilinearRgbaSample(srcGrid.samples, destGrid.samples, src00, src10, src01, src11, dest, tx, ty);
|
|
58482
|
+
copyBilinearRgbaSample(srcGrid.samples, destGrid.samples, src00, src10, src01, src11, dest, tx, ty, round);
|
|
57858
58483
|
} else {
|
|
57859
|
-
copyBilinearGenericSample(srcGrid.samples, destGrid.samples, srcGrid.bands, src00, src10, src01, src11, dest, tx, ty);
|
|
58484
|
+
copyBilinearGenericSample(srcGrid.samples, destGrid.samples, srcGrid.bands, src00, src10, src01, src11, dest, tx, ty, round);
|
|
57860
58485
|
}
|
|
57861
58486
|
if (destGrid.bands > srcGrid.bands) destGrid.samples[dest + 3] = 255;
|
|
57862
58487
|
}
|
|
57863
58488
|
|
|
57864
|
-
function copyBilinearRgbSample(src, destArr, src00, src10, src01, src11, dest, tx, ty) {
|
|
57865
|
-
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 0, tx, ty);
|
|
57866
|
-
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 1, tx, ty);
|
|
57867
|
-
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 2, tx, ty);
|
|
58489
|
+
function copyBilinearRgbSample(src, destArr, src00, src10, src01, src11, dest, tx, ty, round) {
|
|
58490
|
+
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 0, tx, ty, round);
|
|
58491
|
+
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 1, tx, ty, round);
|
|
58492
|
+
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 2, tx, ty, round);
|
|
57868
58493
|
}
|
|
57869
58494
|
|
|
57870
|
-
function copyBilinearRgbaSample(src, destArr, src00, src10, src01, src11, dest, tx, ty) {
|
|
57871
|
-
copyBilinearRgbSample(src, destArr, src00, src10, src01, src11, dest, tx, ty);
|
|
57872
|
-
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 3, tx, ty);
|
|
58495
|
+
function copyBilinearRgbaSample(src, destArr, src00, src10, src01, src11, dest, tx, ty, round) {
|
|
58496
|
+
copyBilinearRgbSample(src, destArr, src00, src10, src01, src11, dest, tx, ty, round);
|
|
58497
|
+
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, 3, tx, ty, round);
|
|
57873
58498
|
}
|
|
57874
58499
|
|
|
57875
|
-
function copyBilinearGenericSample(src, destArr, bands, src00, src10, src01, src11, dest, tx, ty) {
|
|
58500
|
+
function copyBilinearGenericSample(src, destArr, bands, src00, src10, src01, src11, dest, tx, ty, round) {
|
|
57876
58501
|
for (var band = 0; band < bands; band++) {
|
|
57877
|
-
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, band, tx, ty);
|
|
58502
|
+
copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, band, tx, ty, round);
|
|
57878
58503
|
}
|
|
57879
58504
|
}
|
|
57880
58505
|
|
|
57881
|
-
function copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, band, tx, ty) {
|
|
58506
|
+
function copyBilinearBand(src, destArr, src00, src10, src01, src11, dest, band, tx, ty, round) {
|
|
57882
58507
|
var a = src[src00 + band] * (1 - tx) + src[src10 + band] * tx;
|
|
57883
58508
|
var b = src[src01 + band] * (1 - tx) + src[src11 + band] * tx;
|
|
57884
|
-
|
|
57885
|
-
|
|
57886
|
-
|
|
57887
|
-
|
|
57888
|
-
|
|
57889
|
-
|
|
57890
|
-
|
|
57891
|
-
|
|
57892
|
-
|
|
57893
|
-
|
|
57894
|
-
var
|
|
57895
|
-
var
|
|
58509
|
+
var val = a * (1 - ty) + b * ty;
|
|
58510
|
+
destArr[dest + band] = round ? Math.round(val) : val;
|
|
58511
|
+
}
|
|
58512
|
+
|
|
58513
|
+
// Bilinear sampling that skips invalid source pixels and renormalizes the
|
|
58514
|
+
// remaining weights, so nodata is never blended into a real value. Validity
|
|
58515
|
+
// is resolved once per output pixel rather than once per band.
|
|
58516
|
+
function copyBilinearRasterSampleChecked(srcGrid, destGrid, validity, srcX, srcY, srcX2, srcY2, src00, src10, src01, src11, dest, tx, ty, round) {
|
|
58517
|
+
var w00 = (1 - tx) * (1 - ty);
|
|
58518
|
+
var w10 = tx * (1 - ty);
|
|
58519
|
+
var w01 = (1 - tx) * ty;
|
|
58520
|
+
var w11 = tx * ty;
|
|
58521
|
+
var width = srcGrid.width;
|
|
58522
|
+
var samples = srcGrid.samples;
|
|
58523
|
+
var ok00 = w00 > 0 && validity[srcY * width + srcX] > 0;
|
|
58524
|
+
var ok10 = w10 > 0 && validity[srcY * width + srcX2] > 0;
|
|
58525
|
+
var ok01 = w01 > 0 && validity[srcY2 * width + srcX] > 0;
|
|
58526
|
+
var ok11 = w11 > 0 && validity[srcY2 * width + srcX2] > 0;
|
|
58527
|
+
var total = (ok00 ? w00 : 0) + (ok10 ? w10 : 0) + (ok01 ? w01 : 0) + (ok11 ? w11 : 0);
|
|
58528
|
+
var val;
|
|
58529
|
+
if (total <= 0) return false;
|
|
57896
58530
|
for (var band = 0; band < srcGrid.bands; band++) {
|
|
57897
58531
|
val = 0;
|
|
57898
|
-
|
|
57899
|
-
|
|
57900
|
-
|
|
57901
|
-
|
|
57902
|
-
|
|
57903
|
-
|
|
57904
|
-
}
|
|
57905
|
-
if (total <= 0) return false;
|
|
57906
|
-
destGrid.samples[dest + band] = Math.round(val / total);
|
|
58532
|
+
if (ok00) val += samples[src00 + band] * w00;
|
|
58533
|
+
if (ok10) val += samples[src10 + band] * w10;
|
|
58534
|
+
if (ok01) val += samples[src01 + band] * w01;
|
|
58535
|
+
if (ok11) val += samples[src11 + band] * w11;
|
|
58536
|
+
val /= total;
|
|
58537
|
+
destGrid.samples[dest + band] = round ? Math.round(val) : val;
|
|
57907
58538
|
}
|
|
57908
58539
|
if (destGrid.bands > srcGrid.bands) destGrid.samples[dest + 3] = 255;
|
|
57909
58540
|
return true;
|
|
57910
58541
|
}
|
|
57911
58542
|
|
|
57912
|
-
function rasterSourcePixelIsCovered(grid, x, y) {
|
|
57913
|
-
return !grid.coverage || grid.coverage[y * grid.width + x] > 0;
|
|
57914
|
-
}
|
|
57915
|
-
|
|
57916
58543
|
function timeStart(timing, name) {
|
|
57917
58544
|
if (!timing) return;
|
|
57918
58545
|
timing[name + 'Start'] = getTimer();
|
|
@@ -57956,6 +58583,147 @@ ${svg}
|
|
|
57956
58583
|
projectRasterGridForward: projectRasterGridForward
|
|
57957
58584
|
});
|
|
57958
58585
|
|
|
58586
|
+
/*
|
|
58587
|
+
* Post-projection repair for projections whose layout wraps at a rectangular
|
|
58588
|
+
* frame (currently the Lee tetrahedral pair, the only ones that publish
|
|
58589
|
+
* frame_bounds).
|
|
58590
|
+
*
|
|
58591
|
+
* Why this is needed: such a projection is cut along a curved seam traced in
|
|
58592
|
+
* geographic space, and the seam mask splits any path that crosses it. But
|
|
58593
|
+
* without -densify, a path segment is a straight chord in lon/lat, and a chord
|
|
58594
|
+
* between two vertices either side of the seam can pass on the wrong side of
|
|
58595
|
+
* the curve entirely. The projected path then leaves one edge of the frame and
|
|
58596
|
+
* re-enters at the opposite edge, so the ring closes with a long run along a
|
|
58597
|
+
* frame side, joining two halves that belong on opposite sides of the map.
|
|
58598
|
+
* Densified input curves with the seam and is cut normally, which is why the
|
|
58599
|
+
* artifact only appears without that option.
|
|
58600
|
+
*
|
|
58601
|
+
* The repair looks for a ring that meets the same frame side in two or more
|
|
58602
|
+
* separate runs and splits it there. A ring that legitimately touches a frame
|
|
58603
|
+
* side does so once.
|
|
58604
|
+
*
|
|
58605
|
+
* Note that the jump threshold in getFrameSide is coupled to the resolution of
|
|
58606
|
+
* the seam trace: it recognizes a spurious connection as a single long segment
|
|
58607
|
+
* lying on a frame side. Coarsening the trace breaks the connection into a
|
|
58608
|
+
* chain of shorter segments that fall below the threshold and are missed, so
|
|
58609
|
+
* the two need to be changed together.
|
|
58610
|
+
*/
|
|
58611
|
+
|
|
58612
|
+
|
|
58613
|
+
function splitPolygonFrameChords(dataset, bounds) {
|
|
58614
|
+
var editor = new DatasetEditor(dataset);
|
|
58615
|
+
dataset.layers.forEach(function(lyr) {
|
|
58616
|
+
if (lyr.geometry_type == 'polygon') {
|
|
58617
|
+
editor.editLayer(lyr, function(path) {
|
|
58618
|
+
return splitRingAtFrameChords(path, bounds);
|
|
58619
|
+
});
|
|
58620
|
+
} else {
|
|
58621
|
+
editor.editLayer(lyr, function(geometry) {
|
|
58622
|
+
return lyr.geometry_type == 'point' ? geometry : [geometry];
|
|
58623
|
+
});
|
|
58624
|
+
}
|
|
58625
|
+
});
|
|
58626
|
+
editor.done();
|
|
58627
|
+
}
|
|
58628
|
+
|
|
58629
|
+
function splitRingAtFrameChords(path, bounds, depth) {
|
|
58630
|
+
depth = depth || 0;
|
|
58631
|
+
var closed = pointsEqual(path[0], path[path.length - 1]);
|
|
58632
|
+
var n = path.length - (closed ? 1 : 0);
|
|
58633
|
+
if (n < 4) return [path];
|
|
58634
|
+
var width = bounds[2] - bounds[0];
|
|
58635
|
+
var height = bounds[3] - bounds[1];
|
|
58636
|
+
var tolerance = Math.max(width, height) * 1e-3;
|
|
58637
|
+
var edgesBySide = [[], [], [], []];
|
|
58638
|
+
for (var i = 0; i < n; i++) {
|
|
58639
|
+
var side = getFrameSide(
|
|
58640
|
+
path[i], path[(i + 1) % n], bounds, tolerance);
|
|
58641
|
+
if (side >= 0) edgesBySide[side].push({index: i, side: side});
|
|
58642
|
+
}
|
|
58643
|
+
for (var frameSide = 0; frameSide < edgesBySide.length; frameSide++) {
|
|
58644
|
+
var runs = groupEdgeRuns(edgesBySide[frameSide], n);
|
|
58645
|
+
if (runs.length > 1) {
|
|
58646
|
+
var parts = splitRingAtRuns(path, n, runs, frameSide, bounds);
|
|
58647
|
+
if (depth >= 3) return parts;
|
|
58648
|
+
return parts.reduce(function(memo, part) {
|
|
58649
|
+
return memo.concat(splitRingAtFrameChords(part, bounds, depth + 1));
|
|
58650
|
+
}, []);
|
|
58651
|
+
}
|
|
58652
|
+
}
|
|
58653
|
+
return [path];
|
|
58654
|
+
}
|
|
58655
|
+
|
|
58656
|
+
function splitRingAtRuns(path, n, runs, side, bounds) {
|
|
58657
|
+
return runs.map(function(run, i) {
|
|
58658
|
+
var next = runs[(i + 1) % runs.length];
|
|
58659
|
+
var start = (run.end + 1) % n;
|
|
58660
|
+
var end = next.start;
|
|
58661
|
+
var part = [];
|
|
58662
|
+
var j = start;
|
|
58663
|
+
while (true) {
|
|
58664
|
+
part.push(path[j].concat());
|
|
58665
|
+
if (j == end) break;
|
|
58666
|
+
j = (j + 1) % n;
|
|
58667
|
+
}
|
|
58668
|
+
if (side == 0 || side == 2) {
|
|
58669
|
+
var y = side == 0 ? bounds[1] : bounds[3];
|
|
58670
|
+
part[0][1] = y;
|
|
58671
|
+
part[part.length - 1][1] = y;
|
|
58672
|
+
} else {
|
|
58673
|
+
var x = side == 1 ? bounds[2] : bounds[0];
|
|
58674
|
+
part[0][0] = x;
|
|
58675
|
+
part[part.length - 1][0] = x;
|
|
58676
|
+
}
|
|
58677
|
+
part.push(part[0].concat());
|
|
58678
|
+
return part;
|
|
58679
|
+
}).filter(function(part) {
|
|
58680
|
+
return part.length > 3;
|
|
58681
|
+
});
|
|
58682
|
+
}
|
|
58683
|
+
|
|
58684
|
+
function getFrameSide(a, b, bounds, tolerance) {
|
|
58685
|
+
var dx = Math.abs(a[0] - b[0]);
|
|
58686
|
+
var dy = Math.abs(a[1] - b[1]);
|
|
58687
|
+
var width = bounds[2] - bounds[0];
|
|
58688
|
+
var height = bounds[3] - bounds[1];
|
|
58689
|
+
if (dx > width * 0.2) {
|
|
58690
|
+
if (Math.abs(a[1] - bounds[1]) < tolerance &&
|
|
58691
|
+
Math.abs(b[1] - bounds[1]) < tolerance) return 0;
|
|
58692
|
+
if (Math.abs(a[1] - bounds[3]) < tolerance &&
|
|
58693
|
+
Math.abs(b[1] - bounds[3]) < tolerance) return 2;
|
|
58694
|
+
}
|
|
58695
|
+
if (dy > height * 0.2) {
|
|
58696
|
+
if (Math.abs(a[0] - bounds[2]) < tolerance &&
|
|
58697
|
+
Math.abs(b[0] - bounds[2]) < tolerance) return 1;
|
|
58698
|
+
if (Math.abs(a[0] - bounds[0]) < tolerance &&
|
|
58699
|
+
Math.abs(b[0] - bounds[0]) < tolerance) return 3;
|
|
58700
|
+
}
|
|
58701
|
+
return -1;
|
|
58702
|
+
}
|
|
58703
|
+
|
|
58704
|
+
function groupEdgeRuns(edges, n) {
|
|
58705
|
+
var runs = [];
|
|
58706
|
+
edges.forEach(function(edge) {
|
|
58707
|
+
var run = runs[runs.length - 1];
|
|
58708
|
+
if (run && edge.side == run.side && edge.index == run.end + 1) {
|
|
58709
|
+
run.end = edge.index;
|
|
58710
|
+
} else {
|
|
58711
|
+
runs.push({start: edge.index, end: edge.index, side: edge.side});
|
|
58712
|
+
}
|
|
58713
|
+
});
|
|
58714
|
+
if (runs.length > 1 && runs[0].start == 0 &&
|
|
58715
|
+
runs[runs.length - 1].end == n - 1 &&
|
|
58716
|
+
runs[0].side == runs[runs.length - 1].side) {
|
|
58717
|
+
runs[0].start = runs[runs.length - 1].start;
|
|
58718
|
+
runs.pop();
|
|
58719
|
+
}
|
|
58720
|
+
return runs;
|
|
58721
|
+
}
|
|
58722
|
+
|
|
58723
|
+
function pointsEqual(a, b) {
|
|
58724
|
+
return a && b && a[0] == b[0] && a[1] == b[1];
|
|
58725
|
+
}
|
|
58726
|
+
|
|
57959
58727
|
cmd.proj = function(dataset, catalog, opts, targetLayers) {
|
|
57960
58728
|
var srcInfo, destInfo, destStr;
|
|
57961
58729
|
var implicitlyProjectedNames = getImplicitlyTargetedLayerNames(dataset, targetLayers, layerHasGeometry);
|
|
@@ -58123,6 +58891,12 @@ ${svg}
|
|
|
58123
58891
|
}
|
|
58124
58892
|
}
|
|
58125
58893
|
|
|
58894
|
+
var frameBounds = dest.__projection_topology &&
|
|
58895
|
+
dest.__projection_topology.frame_bounds;
|
|
58896
|
+
if (clipped && dataset.arcs && frameBounds) {
|
|
58897
|
+
splitPolygonFrameChords(dataset, scaleProjectionFrameBounds(frameBounds, dest));
|
|
58898
|
+
}
|
|
58899
|
+
|
|
58126
58900
|
if (clipped) {
|
|
58127
58901
|
// TODO: could more selective in cleaning clipped layers
|
|
58128
58902
|
// (probably only needed when clipped area crosses the antimeridian or includes a pole)
|
|
@@ -58138,6 +58912,18 @@ ${svg}
|
|
|
58138
58912
|
dataset.info.crs = dest;
|
|
58139
58913
|
}
|
|
58140
58914
|
|
|
58915
|
+
function scaleProjectionFrameBounds(bounds, crs) {
|
|
58916
|
+
var scale = crs.a * crs.fr_meter;
|
|
58917
|
+
var dx = crs.x0 * crs.fr_meter;
|
|
58918
|
+
var dy = crs.y0 * crs.fr_meter;
|
|
58919
|
+
return [
|
|
58920
|
+
bounds[0] * scale + dx,
|
|
58921
|
+
bounds[1] * scale + dy,
|
|
58922
|
+
bounds[2] * scale + dx,
|
|
58923
|
+
bounds[3] * scale + dy
|
|
58924
|
+
];
|
|
58925
|
+
}
|
|
58926
|
+
|
|
58141
58927
|
function projectRasterLayer(lyr, src, dest, opts) {
|
|
58142
58928
|
var raster = lyr.raster;
|
|
58143
58929
|
raster.grid = projectRasterGridForward(raster, src, dest, opts || {});
|
|
@@ -61579,6 +62365,529 @@ ${svg}
|
|
|
61579
62365
|
// message('[comment]', opts.message);
|
|
61580
62366
|
}; // no-op, so -comment doesn't trigger a parsing error
|
|
61581
62367
|
|
|
62368
|
+
// Trace contour lines (isolines) from a raster layer's working samples using
|
|
62369
|
+
// marching squares.
|
|
62370
|
+
//
|
|
62371
|
+
// Samples are treated as point measurements at pixel centers, so a W x H grid
|
|
62372
|
+
// contours over a lattice of W x H values and (W-1) x (H-1) cells. Contour
|
|
62373
|
+
// vertices therefore span from the first pixel center to the last, half a
|
|
62374
|
+
// pixel inside the grid bbox. This matches gdal_contour.
|
|
62375
|
+
|
|
62376
|
+
|
|
62377
|
+
var MAX_CONTOUR_LEVELS = 2000;
|
|
62378
|
+
var TARGET_LEVEL_COUNT = 15;
|
|
62379
|
+
|
|
62380
|
+
// Marching squares traces a staircase with treads one pixel wide, so smoothing
|
|
62381
|
+
// at one pixel is what removes the stepping. Measured against an analytic cone
|
|
62382
|
+
// quantized to whole meters (as elevation models usually are), one pixel gives
|
|
62383
|
+
// the lowest deviation from the true contour: 0.16 px, against 0.29 px
|
|
62384
|
+
// unsmoothed and 0.18 px at 1.5 px. On unquantized float data, where there is
|
|
62385
|
+
// no staircase to remove, the cost is under 0.1 px.
|
|
62386
|
+
var SMOOTHING_PIXELS = 1;
|
|
62387
|
+
|
|
62388
|
+
// A degree of longitude vanishes at the poles; keep the implied pixel width
|
|
62389
|
+
// from collapsing to zero there.
|
|
62390
|
+
var MIN_LATITUDE_SCALE = 0.05;
|
|
62391
|
+
|
|
62392
|
+
// Cell corners, and the edges between them:
|
|
62393
|
+
//
|
|
62394
|
+
// edge 0
|
|
62395
|
+
// c0 --------- c1
|
|
62396
|
+
// | |
|
|
62397
|
+
// | edge 3 | edge 1
|
|
62398
|
+
// | |
|
|
62399
|
+
// c3 --------- c2
|
|
62400
|
+
// edge 2
|
|
62401
|
+
//
|
|
62402
|
+
// Corner bits: 1 = c0, 2 = c1, 4 = c2, 8 = c3. A corner is set when its value
|
|
62403
|
+
// is >= the contour level.
|
|
62404
|
+
//
|
|
62405
|
+
// Each entry is the pair of edges a contour segment crosses, directed so that
|
|
62406
|
+
// the above-level side is always on the same hand. Consistent orientation is
|
|
62407
|
+
// what lets segments be stitched together by edge id alone: a crossing is the
|
|
62408
|
+
// exit of one cell and the entry of its neighbor.
|
|
62409
|
+
var CONTOUR_CASES = [
|
|
62410
|
+
null, // 0: none above
|
|
62411
|
+
[3, 0], // 1: c0
|
|
62412
|
+
[0, 1], // 2: c1
|
|
62413
|
+
[3, 1], // 3: c0 c1
|
|
62414
|
+
[1, 2], // 4: c2
|
|
62415
|
+
null, // 5: c0 c2 -- saddle
|
|
62416
|
+
[0, 2], // 6: c1 c2
|
|
62417
|
+
[3, 2], // 7: c0 c1 c2
|
|
62418
|
+
[2, 3], // 8: c3
|
|
62419
|
+
[2, 0], // 9: c0 c3
|
|
62420
|
+
null, // 10: c1 c3 -- saddle
|
|
62421
|
+
[2, 1], // 11: c0 c1 c3
|
|
62422
|
+
[1, 3], // 12: c2 c3
|
|
62423
|
+
[1, 0], // 13: c0 c2 c3
|
|
62424
|
+
[0, 3], // 14: c1 c2 c3
|
|
62425
|
+
null // 15: all above
|
|
62426
|
+
];
|
|
62427
|
+
|
|
62428
|
+
// A saddle cell has crossings on all four edges, which can be paired two ways.
|
|
62429
|
+
// The cell-center average breaks the tie: when the center is above the level,
|
|
62430
|
+
// the two above-level corners are joined through the middle of the cell and
|
|
62431
|
+
// the below-level corners are cut off separately.
|
|
62432
|
+
var SADDLE_C0_C2_JOINED = [[1, 0], [3, 2]];
|
|
62433
|
+
var SADDLE_C0_C2_SPLIT = [[3, 0], [1, 2]];
|
|
62434
|
+
var SADDLE_C1_C3_JOINED = [[0, 3], [2, 1]];
|
|
62435
|
+
var SADDLE_C1_C3_SPLIT = [[0, 1], [2, 3]];
|
|
62436
|
+
|
|
62437
|
+
function getRasterContourLines(raster, opts) {
|
|
62438
|
+
var grid = getRasterGrid(raster);
|
|
62439
|
+
var band, levels;
|
|
62440
|
+
validateRasterGridForContours(grid);
|
|
62441
|
+
band = getContourBand(grid, opts);
|
|
62442
|
+
levels = getContourLevels(grid, band, opts);
|
|
62443
|
+
return {
|
|
62444
|
+
levels: levels,
|
|
62445
|
+
lines: traceRasterContours(grid, band, levels)
|
|
62446
|
+
};
|
|
62447
|
+
}
|
|
62448
|
+
|
|
62449
|
+
// Auto-selected smoothing interval for traced contours, returned the way
|
|
62450
|
+
// -smooth reads a plain distance= number: as meters when the CRS is known, and
|
|
62451
|
+
// as raw coordinate units when it is not.
|
|
62452
|
+
function getContourSmoothingDistance(grid, crs) {
|
|
62453
|
+
var bbox = grid.bbox;
|
|
62454
|
+
var dx = Math.abs(bbox[2] - bbox[0]) / grid.width;
|
|
62455
|
+
var dy = Math.abs(bbox[3] - bbox[1]) / grid.height;
|
|
62456
|
+
var lat;
|
|
62457
|
+
if (crs && crs.is_latlong) {
|
|
62458
|
+
lat = (bbox[1] + bbox[3]) / 2;
|
|
62459
|
+
dx = degreesToMeters(dx) * Math.max(Math.cos(lat * D2R$8), MIN_LATITUDE_SCALE);
|
|
62460
|
+
dy = degreesToMeters(dy);
|
|
62461
|
+
} else if (crs && crs.to_meter > 0) {
|
|
62462
|
+
dx *= crs.to_meter;
|
|
62463
|
+
dy *= crs.to_meter;
|
|
62464
|
+
}
|
|
62465
|
+
// Geometric mean, so a grid with non-square pixels gets an interval between
|
|
62466
|
+
// its two resolutions rather than one axis winning.
|
|
62467
|
+
return Math.sqrt(dx * dy) * SMOOTHING_PIXELS;
|
|
62468
|
+
}
|
|
62469
|
+
|
|
62470
|
+
function validateRasterGridForContours(grid) {
|
|
62471
|
+
if (!grid || !grid.samples || !grid.bbox) {
|
|
62472
|
+
stop$1('Raster layer is missing required grid data');
|
|
62473
|
+
}
|
|
62474
|
+
if (rasterGridIsRotated(grid)) {
|
|
62475
|
+
stop$1('Tracing contours from a rotated or skewed raster is not supported');
|
|
62476
|
+
}
|
|
62477
|
+
if (grid.width < 2 || grid.height < 2) {
|
|
62478
|
+
stop$1('Raster layer is too small to contour (needs to be at least 2x2 pixels)');
|
|
62479
|
+
}
|
|
62480
|
+
}
|
|
62481
|
+
|
|
62482
|
+
function getContourBand(grid, opts) {
|
|
62483
|
+
var band = opts && 'band' in opts ? opts.band : 0;
|
|
62484
|
+
if (!(band >= 0) || band !== Math.floor(band) || band >= grid.bands) {
|
|
62485
|
+
stop$1('Invalid band=' + band + '; this raster has ' + grid.bands +
|
|
62486
|
+
(grid.bands == 1 ? ' band' : ' bands'));
|
|
62487
|
+
}
|
|
62488
|
+
return band;
|
|
62489
|
+
}
|
|
62490
|
+
|
|
62491
|
+
function getContourLevels(grid, band, opts) {
|
|
62492
|
+
var levels = opts && opts.levels ?
|
|
62493
|
+
getExplicitLevels(opts.levels) :
|
|
62494
|
+
getIntervalLevels(getRasterSampleRange(grid, band), opts || {});
|
|
62495
|
+
if (levels.length > MAX_CONTOUR_LEVELS) {
|
|
62496
|
+
stop$1('Contouring would generate ' + levels.length + ' levels; the limit is ' +
|
|
62497
|
+
MAX_CONTOUR_LEVELS + '. Use a larger interval= or an explicit levels= list.');
|
|
62498
|
+
}
|
|
62499
|
+
return levels;
|
|
62500
|
+
}
|
|
62501
|
+
|
|
62502
|
+
function getExplicitLevels(arg) {
|
|
62503
|
+
var levels = [];
|
|
62504
|
+
arg.forEach(function(val) {
|
|
62505
|
+
if (typeof val != 'number' || !isFinite(val)) {
|
|
62506
|
+
stop$1('Invalid contour level:', val);
|
|
62507
|
+
}
|
|
62508
|
+
if (levels.indexOf(val) == -1) levels.push(val);
|
|
62509
|
+
});
|
|
62510
|
+
return levels.sort(ascending);
|
|
62511
|
+
}
|
|
62512
|
+
|
|
62513
|
+
function getIntervalLevels(range, opts) {
|
|
62514
|
+
var base = 'base' in opts ? opts.base : 0;
|
|
62515
|
+
var levels = [];
|
|
62516
|
+
var interval, k, level;
|
|
62517
|
+
// Only fall back to an automatic interval when none was given: a bad
|
|
62518
|
+
// interval= should be reported, not quietly replaced.
|
|
62519
|
+
if (opts.interval === null || opts.interval === undefined) {
|
|
62520
|
+
interval = getAutoContourInterval(range);
|
|
62521
|
+
} else {
|
|
62522
|
+
interval = opts.interval;
|
|
62523
|
+
if (!(interval > 0) || !isFinite(interval)) {
|
|
62524
|
+
stop$1('Contour interval= must be a positive number');
|
|
62525
|
+
}
|
|
62526
|
+
}
|
|
62527
|
+
if (!isFinite(base)) {
|
|
62528
|
+
stop$1('Contour base= must be a number');
|
|
62529
|
+
}
|
|
62530
|
+
// A level at exactly the data minimum has no cell to cross, because every
|
|
62531
|
+
// corner counts as above it.
|
|
62532
|
+
if (!(range.max > range.min)) return levels;
|
|
62533
|
+
k = Math.ceil((range.min - base) / interval);
|
|
62534
|
+
while (levels.length <= MAX_CONTOUR_LEVELS) {
|
|
62535
|
+
level = cleanLevelValue(base + k * interval);
|
|
62536
|
+
if (level > range.max) break;
|
|
62537
|
+
if (level > range.min) levels.push(level);
|
|
62538
|
+
k++;
|
|
62539
|
+
}
|
|
62540
|
+
return levels;
|
|
62541
|
+
}
|
|
62542
|
+
|
|
62543
|
+
// Choose a round interval that divides the data range into roughly
|
|
62544
|
+
// TARGET_LEVEL_COUNT steps.
|
|
62545
|
+
function getAutoContourInterval(range) {
|
|
62546
|
+
var span = range.max - range.min;
|
|
62547
|
+
var raw, magnitude, normalized;
|
|
62548
|
+
if (!(span > 0) || !isFinite(span)) return 1;
|
|
62549
|
+
raw = span / TARGET_LEVEL_COUNT;
|
|
62550
|
+
magnitude = Math.pow(10, Math.floor(Math.log(raw) / Math.LN10));
|
|
62551
|
+
normalized = raw / magnitude;
|
|
62552
|
+
return (normalized >= 5 ? 5 : normalized >= 2 ? 2 : 1) * magnitude;
|
|
62553
|
+
}
|
|
62554
|
+
|
|
62555
|
+
// Repeated addition of a fractional interval accumulates error that shows up
|
|
62556
|
+
// in output field values, e.g. 0.30000000000000004.
|
|
62557
|
+
function cleanLevelValue(value) {
|
|
62558
|
+
return value === 0 ? 0 : Number(value.toPrecision(12));
|
|
62559
|
+
}
|
|
62560
|
+
|
|
62561
|
+
function getRasterSampleRange(grid, band) {
|
|
62562
|
+
var samples = grid.samples;
|
|
62563
|
+
var bands = grid.bands;
|
|
62564
|
+
var valid = getRasterValidityMask(grid);
|
|
62565
|
+
var n = grid.width * grid.height;
|
|
62566
|
+
var min = Infinity;
|
|
62567
|
+
var max = -Infinity;
|
|
62568
|
+
var v, i;
|
|
62569
|
+
for (i = 0; i < n; i++) {
|
|
62570
|
+
if (valid && !valid[i]) continue;
|
|
62571
|
+
v = samples[i * bands + band];
|
|
62572
|
+
if (v !== v) continue; // NaN, used as a nodata value by some float rasters
|
|
62573
|
+
if (v < min) min = v;
|
|
62574
|
+
if (v > max) max = v;
|
|
62575
|
+
}
|
|
62576
|
+
return {min: min, max: max};
|
|
62577
|
+
}
|
|
62578
|
+
|
|
62579
|
+
function traceRasterContours(grid, band, levels) {
|
|
62580
|
+
var segments = collectContourSegments(grid, band, levels);
|
|
62581
|
+
var toMapXY = getLatticeToMapTransform(grid);
|
|
62582
|
+
var hCount = (grid.width - 1) * grid.height;
|
|
62583
|
+
var lines = [];
|
|
62584
|
+
segments.forEach(function(levelSegments, i) {
|
|
62585
|
+
stitchContourSegments(levelSegments).forEach(function(path) {
|
|
62586
|
+
var coords = getPathCoords(path, grid, band, levels[i], hCount, toMapXY);
|
|
62587
|
+
if (coords.length > 1) {
|
|
62588
|
+
lines.push({value: levels[i], coords: coords});
|
|
62589
|
+
}
|
|
62590
|
+
});
|
|
62591
|
+
});
|
|
62592
|
+
return lines;
|
|
62593
|
+
}
|
|
62594
|
+
|
|
62595
|
+
// Visit each cell once and march it for every level that falls inside its
|
|
62596
|
+
// value range, rather than scanning the whole grid once per level.
|
|
62597
|
+
function collectContourSegments(grid, band, levels) {
|
|
62598
|
+
var W = grid.width;
|
|
62599
|
+
var H = grid.height;
|
|
62600
|
+
var bands = grid.bands;
|
|
62601
|
+
var samples = grid.samples;
|
|
62602
|
+
var hCount = (W - 1) * H;
|
|
62603
|
+
var valid = getRasterValidityMask(grid);
|
|
62604
|
+
var rowStride = W * bands;
|
|
62605
|
+
var out = levels.map(function() { return {from: [], to: []}; });
|
|
62606
|
+
var cx, cy, pixelId, off, v0, v1, v2, v3, min, max, i;
|
|
62607
|
+
if (levels.length === 0) return out;
|
|
62608
|
+
for (cy = 0; cy < H - 1; cy++) {
|
|
62609
|
+
for (cx = 0; cx < W - 1; cx++) {
|
|
62610
|
+
pixelId = cy * W + cx;
|
|
62611
|
+
if (valid && (!valid[pixelId] || !valid[pixelId + 1] ||
|
|
62612
|
+
!valid[pixelId + W] || !valid[pixelId + W + 1])) continue;
|
|
62613
|
+
off = pixelId * bands + band;
|
|
62614
|
+
v0 = samples[off];
|
|
62615
|
+
v1 = samples[off + bands];
|
|
62616
|
+
v3 = samples[off + rowStride];
|
|
62617
|
+
v2 = samples[off + rowStride + bands];
|
|
62618
|
+
if (v0 !== v0 || v1 !== v1 || v2 !== v2 || v3 !== v3) continue; // NaN
|
|
62619
|
+
min = v0 < v1 ? v0 : v1;
|
|
62620
|
+
if (v2 < min) min = v2;
|
|
62621
|
+
if (v3 < min) min = v3;
|
|
62622
|
+
max = v0 > v1 ? v0 : v1;
|
|
62623
|
+
if (v2 > max) max = v2;
|
|
62624
|
+
if (v3 > max) max = v3;
|
|
62625
|
+
// A level crosses the cell when min < level <= max, which matches the
|
|
62626
|
+
// >= test that classifies each corner.
|
|
62627
|
+
for (i = firstLevelAbove(levels, min); i < levels.length && levels[i] <= max; i++) {
|
|
62628
|
+
addCellSegments(out[i], levels[i], v0, v1, v2, v3, cx, cy, W, hCount);
|
|
62629
|
+
}
|
|
62630
|
+
}
|
|
62631
|
+
}
|
|
62632
|
+
return out;
|
|
62633
|
+
}
|
|
62634
|
+
|
|
62635
|
+
function firstLevelAbove(levels, value) {
|
|
62636
|
+
var lo = 0;
|
|
62637
|
+
var hi = levels.length;
|
|
62638
|
+
var mid;
|
|
62639
|
+
while (lo < hi) {
|
|
62640
|
+
mid = (lo + hi) >> 1;
|
|
62641
|
+
if (levels[mid] > value) hi = mid;
|
|
62642
|
+
else lo = mid + 1;
|
|
62643
|
+
}
|
|
62644
|
+
return lo;
|
|
62645
|
+
}
|
|
62646
|
+
|
|
62647
|
+
function addCellSegments(dest, level, v0, v1, v2, v3, cx, cy, W, hCount) {
|
|
62648
|
+
var caseId = (v0 >= level ? 1 : 0) | (v1 >= level ? 2 : 0) |
|
|
62649
|
+
(v2 >= level ? 4 : 0) | (v3 >= level ? 8 : 0);
|
|
62650
|
+
var pairs, pair;
|
|
62651
|
+
if (caseId === 5 || caseId === 10) {
|
|
62652
|
+
pairs = getSaddlePairs(caseId, level, v0, v1, v2, v3);
|
|
62653
|
+
addSegment(dest, pairs[0], cx, cy, W, hCount);
|
|
62654
|
+
addSegment(dest, pairs[1], cx, cy, W, hCount);
|
|
62655
|
+
return;
|
|
62656
|
+
}
|
|
62657
|
+
pair = CONTOUR_CASES[caseId];
|
|
62658
|
+
if (pair) addSegment(dest, pair, cx, cy, W, hCount);
|
|
62659
|
+
}
|
|
62660
|
+
|
|
62661
|
+
function getSaddlePairs(caseId, level, v0, v1, v2, v3) {
|
|
62662
|
+
var centerAbove = (v0 + v1 + v2 + v3) / 4 >= level;
|
|
62663
|
+
if (caseId === 5) {
|
|
62664
|
+
return centerAbove ? SADDLE_C0_C2_JOINED : SADDLE_C0_C2_SPLIT;
|
|
62665
|
+
}
|
|
62666
|
+
return centerAbove ? SADDLE_C1_C3_JOINED : SADDLE_C1_C3_SPLIT;
|
|
62667
|
+
}
|
|
62668
|
+
|
|
62669
|
+
function addSegment(dest, pair, cx, cy, W, hCount) {
|
|
62670
|
+
dest.from.push(getCellEdgeId(pair[0], cx, cy, W, hCount));
|
|
62671
|
+
dest.to.push(getCellEdgeId(pair[1], cx, cy, W, hCount));
|
|
62672
|
+
}
|
|
62673
|
+
|
|
62674
|
+
// Edges are numbered so that neighboring cells agree on the id of the edge
|
|
62675
|
+
// they share: horizontal edges run left to right along a lattice row,
|
|
62676
|
+
// vertical edges run top to bottom down a lattice column.
|
|
62677
|
+
function getCellEdgeId(edge, cx, cy, W, hCount) {
|
|
62678
|
+
if (edge === 0) return cy * (W - 1) + cx; // top
|
|
62679
|
+
if (edge === 1) return hCount + cy * W + cx + 1; // right
|
|
62680
|
+
if (edge === 2) return (cy + 1) * (W - 1) + cx; // bottom
|
|
62681
|
+
return hCount + cy * W + cx; // left
|
|
62682
|
+
}
|
|
62683
|
+
|
|
62684
|
+
// Join directed segments end to end. Because every segment is oriented the
|
|
62685
|
+
// same way relative to the contour, each edge is the start of at most one
|
|
62686
|
+
// segment and the end of at most one, so following the chain is unambiguous.
|
|
62687
|
+
function stitchContourSegments(segments) {
|
|
62688
|
+
var from = segments.from;
|
|
62689
|
+
var to = segments.to;
|
|
62690
|
+
var next = new Map();
|
|
62691
|
+
var hasIncoming = new Set();
|
|
62692
|
+
var visited = new Set();
|
|
62693
|
+
var paths = [];
|
|
62694
|
+
var i, ring;
|
|
62695
|
+
for (i = 0; i < from.length; i++) {
|
|
62696
|
+
next.set(from[i], to[i]);
|
|
62697
|
+
hasIncoming.add(to[i]);
|
|
62698
|
+
}
|
|
62699
|
+
// Trace open contours first, starting from the edges nothing leads into, so
|
|
62700
|
+
// that a contour running between two grid boundaries is not entered part
|
|
62701
|
+
// way along and split in two.
|
|
62702
|
+
for (i = 0; i < from.length; i++) {
|
|
62703
|
+
if (hasIncoming.has(from[i]) || visited.has(from[i])) continue;
|
|
62704
|
+
paths.push(followContour(from[i], next, visited));
|
|
62705
|
+
}
|
|
62706
|
+
// Anything left over forms a closed ring.
|
|
62707
|
+
for (i = 0; i < from.length; i++) {
|
|
62708
|
+
if (visited.has(from[i])) continue;
|
|
62709
|
+
ring = followContour(from[i], next, visited);
|
|
62710
|
+
ring.push(ring[0]);
|
|
62711
|
+
paths.push(ring);
|
|
62712
|
+
}
|
|
62713
|
+
return paths;
|
|
62714
|
+
}
|
|
62715
|
+
|
|
62716
|
+
function followContour(startEdge, next, visited) {
|
|
62717
|
+
var path = [];
|
|
62718
|
+
var edge = startEdge;
|
|
62719
|
+
while (edge !== undefined && !visited.has(edge)) {
|
|
62720
|
+
visited.add(edge);
|
|
62721
|
+
path.push(edge);
|
|
62722
|
+
edge = next.get(edge);
|
|
62723
|
+
}
|
|
62724
|
+
return path;
|
|
62725
|
+
}
|
|
62726
|
+
|
|
62727
|
+
function getPathCoords(path, grid, band, level, hCount, toMapXY) {
|
|
62728
|
+
var coords = [];
|
|
62729
|
+
var prev = null;
|
|
62730
|
+
var point, xy;
|
|
62731
|
+
for (var i = 0; i < path.length; i++) {
|
|
62732
|
+
point = getEdgeCrossing(grid, band, level, path[i], hCount);
|
|
62733
|
+
xy = toMapXY(point[0], point[1]);
|
|
62734
|
+
// A corner value exactly equal to the level puts two crossings in the
|
|
62735
|
+
// same place; keeping both would emit zero-length segments.
|
|
62736
|
+
if (prev && xy[0] === prev[0] && xy[1] === prev[1]) continue;
|
|
62737
|
+
coords.push(xy);
|
|
62738
|
+
prev = xy;
|
|
62739
|
+
}
|
|
62740
|
+
return coords;
|
|
62741
|
+
}
|
|
62742
|
+
|
|
62743
|
+
// Returns the crossing point in lattice coordinates. The result depends only
|
|
62744
|
+
// on the edge and the level, so both cells sharing an edge compute the same
|
|
62745
|
+
// point, down to the last bit.
|
|
62746
|
+
function getEdgeCrossing(grid, band, level, edgeId, hCount) {
|
|
62747
|
+
var W = grid.width;
|
|
62748
|
+
var bands = grid.bands;
|
|
62749
|
+
var samples = grid.samples;
|
|
62750
|
+
var x, y, v0, v1;
|
|
62751
|
+
if (edgeId < hCount) {
|
|
62752
|
+
y = Math.floor(edgeId / (W - 1));
|
|
62753
|
+
x = edgeId - y * (W - 1);
|
|
62754
|
+
v0 = samples[(y * W + x) * bands + band];
|
|
62755
|
+
v1 = samples[(y * W + x + 1) * bands + band];
|
|
62756
|
+
return [x + interpolateCrossing(level, v0, v1), y];
|
|
62757
|
+
}
|
|
62758
|
+
edgeId -= hCount;
|
|
62759
|
+
y = Math.floor(edgeId / W);
|
|
62760
|
+
x = edgeId - y * W;
|
|
62761
|
+
v0 = samples[(y * W + x) * bands + band];
|
|
62762
|
+
v1 = samples[((y + 1) * W + x) * bands + band];
|
|
62763
|
+
return [x, y + interpolateCrossing(level, v0, v1)];
|
|
62764
|
+
}
|
|
62765
|
+
|
|
62766
|
+
function interpolateCrossing(level, v0, v1) {
|
|
62767
|
+
var t;
|
|
62768
|
+
if (v1 === v0) return 0.5;
|
|
62769
|
+
t = (level - v0) / (v1 - v0);
|
|
62770
|
+
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
62771
|
+
}
|
|
62772
|
+
|
|
62773
|
+
// Lattice coordinates are indexed from the center of the first pixel, not the
|
|
62774
|
+
// corner of the grid.
|
|
62775
|
+
function getLatticeToMapTransform(grid) {
|
|
62776
|
+
var bbox = grid.bbox;
|
|
62777
|
+
var dx = (bbox[2] - bbox[0]) / grid.width;
|
|
62778
|
+
var dy = (bbox[3] - bbox[1]) / grid.height;
|
|
62779
|
+
var x0 = bbox[0] + dx / 2;
|
|
62780
|
+
var y0 = bbox[3] - dy / 2;
|
|
62781
|
+
return function(col, row) {
|
|
62782
|
+
return [x0 + col * dx, y0 - row * dy];
|
|
62783
|
+
};
|
|
62784
|
+
}
|
|
62785
|
+
|
|
62786
|
+
function ascending(a, b) {
|
|
62787
|
+
return a - b;
|
|
62788
|
+
}
|
|
62789
|
+
|
|
62790
|
+
var RasterContours = /*#__PURE__*/Object.freeze({
|
|
62791
|
+
__proto__: null,
|
|
62792
|
+
getContourBand: getContourBand,
|
|
62793
|
+
getContourLevels: getContourLevels,
|
|
62794
|
+
getContourSmoothingDistance: getContourSmoothingDistance,
|
|
62795
|
+
getRasterContourLines: getRasterContourLines,
|
|
62796
|
+
getRasterSampleRange: getRasterSampleRange,
|
|
62797
|
+
traceRasterContours: traceRasterContours,
|
|
62798
|
+
validateRasterGridForContours: validateRasterGridForContours
|
|
62799
|
+
});
|
|
62800
|
+
|
|
62801
|
+
var DEFAULT_CONTOUR_FIELD = 'value';
|
|
62802
|
+
|
|
62803
|
+
cmd.contours = function(targetLyr, targetDataset, opts) {
|
|
62804
|
+
var field = opts.field || DEFAULT_CONTOUR_FIELD;
|
|
62805
|
+
var contours, dataset, outputLayers;
|
|
62806
|
+
if (!layerHasRaster(targetLyr)) {
|
|
62807
|
+
stop$1('Command requires a raster layer');
|
|
62808
|
+
}
|
|
62809
|
+
// Contours come from the layer's working samples, so they reflect any
|
|
62810
|
+
// earlier edits (e.g. -blur, -clip) rather than the original source pixels.
|
|
62811
|
+
contours = getRasterContourLines(targetLyr.raster, opts);
|
|
62812
|
+
message(getContoursMessage(contours));
|
|
62813
|
+
if (contours.lines.length === 0) {
|
|
62814
|
+
return [createEmptyContourLayer(targetLyr, opts)];
|
|
62815
|
+
}
|
|
62816
|
+
dataset = importGeoJSON(getContoursGeoJSON(contours.lines, field), {});
|
|
62817
|
+
setDatasetCrsInfo(dataset, getDatasetCrsInfo(targetDataset));
|
|
62818
|
+
// Smooth before merging, so that only the contour arcs are affected. Merging
|
|
62819
|
+
// first would put them in the same ArcCollection as any vector layer already
|
|
62820
|
+
// in the target dataset, and -smooth rewrites every arc it is given.
|
|
62821
|
+
if (!opts.no_smoothing) {
|
|
62822
|
+
smoothContourDataset(dataset, getRasterGrid(targetLyr.raster));
|
|
62823
|
+
}
|
|
62824
|
+
outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
|
|
62825
|
+
setOutputLayerName(outputLayers[0], targetLyr, 'contours', opts);
|
|
62826
|
+
return outputLayers;
|
|
62827
|
+
};
|
|
62828
|
+
|
|
62829
|
+
function smoothContourDataset(dataset, grid) {
|
|
62830
|
+
var crs = getDatasetCRS(dataset);
|
|
62831
|
+
var distance = getContourSmoothingDistance(grid, crs);
|
|
62832
|
+
if (!(distance > 0)) {
|
|
62833
|
+
message('Skipped contour smoothing: unable to determine the pixel size');
|
|
62834
|
+
return;
|
|
62835
|
+
}
|
|
62836
|
+
message('Smoothing contours with an auto-selected interval of ' +
|
|
62837
|
+
formatSmoothingDistance(distance, crs) + ' (one pixel)');
|
|
62838
|
+
cmd.smooth(dataset, {
|
|
62839
|
+
distance: distance,
|
|
62840
|
+
// The contour staircase is an artifact, so there are no real corners to
|
|
62841
|
+
// preserve and no sub-pixel detail worth prefiltering out.
|
|
62842
|
+
no_corners: true,
|
|
62843
|
+
no_prefilter: true
|
|
62844
|
+
}, dataset.layers);
|
|
62845
|
+
}
|
|
62846
|
+
|
|
62847
|
+
function formatSmoothingDistance(distance, crs) {
|
|
62848
|
+
var rounded = Number(distance.toPrecision(3));
|
|
62849
|
+
return crs ? rounded + 'm' : String(rounded);
|
|
62850
|
+
}
|
|
62851
|
+
|
|
62852
|
+
function getContoursGeoJSON(lines, field) {
|
|
62853
|
+
return {
|
|
62854
|
+
type: 'FeatureCollection',
|
|
62855
|
+
features: lines.map(function(line) {
|
|
62856
|
+
var properties = {};
|
|
62857
|
+
properties[field] = line.value;
|
|
62858
|
+
return {
|
|
62859
|
+
type: 'Feature',
|
|
62860
|
+
properties: properties,
|
|
62861
|
+
geometry: {
|
|
62862
|
+
type: 'LineString',
|
|
62863
|
+
coordinates: line.coords
|
|
62864
|
+
}
|
|
62865
|
+
};
|
|
62866
|
+
})
|
|
62867
|
+
};
|
|
62868
|
+
}
|
|
62869
|
+
|
|
62870
|
+
function createEmptyContourLayer(targetLyr, opts) {
|
|
62871
|
+
var lyr = {
|
|
62872
|
+
geometry_type: 'polyline',
|
|
62873
|
+
shapes: [],
|
|
62874
|
+
data: new DataTable([])
|
|
62875
|
+
};
|
|
62876
|
+
setOutputLayerName(lyr, targetLyr, 'contours', opts);
|
|
62877
|
+
return lyr;
|
|
62878
|
+
}
|
|
62879
|
+
|
|
62880
|
+
function getContoursMessage(contours) {
|
|
62881
|
+
var levels = contours.levels;
|
|
62882
|
+
if (levels.length === 0) {
|
|
62883
|
+
return 'No contour levels fall inside the range of this raster';
|
|
62884
|
+
}
|
|
62885
|
+
return 'Traced ' + contours.lines.length + ' contour ' +
|
|
62886
|
+
(contours.lines.length == 1 ? 'line' : 'lines') + ' at ' + levels.length +
|
|
62887
|
+
(levels.length == 1 ? ' level' : ' levels') +
|
|
62888
|
+
' (' + levels[0] + ' to ' + levels[levels.length - 1] + ')';
|
|
62889
|
+
}
|
|
62890
|
+
|
|
61582
62891
|
cmd.dashlines = function(lyr, dataset, opts) {
|
|
61583
62892
|
var crs = getDatasetCRS(dataset);
|
|
61584
62893
|
var defs = getStashedVar('defs');
|
|
@@ -63127,11 +64436,19 @@ ${svg}
|
|
|
63127
64436
|
//
|
|
63128
64437
|
cmd.dissolve = function(arg1, arg2, opts) {
|
|
63129
64438
|
if (Array.isArray(arg1)) {
|
|
64439
|
+
arg1.forEach(requireDissolvableLayer);
|
|
63130
64440
|
return dissolveLayers(arg1, arg2, opts);
|
|
63131
64441
|
}
|
|
64442
|
+
requireDissolvableLayer(arg1);
|
|
63132
64443
|
return dissolveSingleLayer(arg1, arg2, opts);
|
|
63133
64444
|
};
|
|
63134
64445
|
|
|
64446
|
+
// Without this guard a raster layer takes the tabular branch of
|
|
64447
|
+
// dissolveOneLayer() and is replaced by a geometry-less aggregate.
|
|
64448
|
+
function requireDissolvableLayer(lyr) {
|
|
64449
|
+
requireNotRasterLayer(lyr, 'Raster layers cannot be dissolved');
|
|
64450
|
+
}
|
|
64451
|
+
|
|
63135
64452
|
function dissolveLayers(layers, dataset, optsArg) {
|
|
63136
64453
|
var opts = utils.extend({}, optsArg);
|
|
63137
64454
|
if (opts.field) opts.fields = [opts.field]; // support old "field" parameter
|
|
@@ -66237,7 +67554,10 @@ ${svg}
|
|
|
66237
67554
|
var src = parseCrsString$1('wgs84');
|
|
66238
67555
|
var outline = getOutlineDataset(src, dest, {});
|
|
66239
67556
|
var graticule = importGeoJSON(createGraticule(dest, !!outline, opts));
|
|
66240
|
-
projectDataset(graticule, src, dest, {
|
|
67557
|
+
projectDataset(graticule, src, dest, {
|
|
67558
|
+
no_clip: false,
|
|
67559
|
+
densify: isInterruptedProjection(dest)
|
|
67560
|
+
});
|
|
66241
67561
|
if (outline) {
|
|
66242
67562
|
graticule = addOutlineToGraticule(graticule, outline);
|
|
66243
67563
|
}
|
|
@@ -72202,7 +73522,7 @@ ${svg}
|
|
|
72202
73522
|
}
|
|
72203
73523
|
return [
|
|
72204
73524
|
'affine', 'alpha-shapes', 'blur', 'buffer', 'calc', 'check-geometry',
|
|
72205
|
-
'classify', 'clean', 'clip', 'cluster', 'dashlines', 'data-fill',
|
|
73525
|
+
'classify', 'clean', 'clip', 'cluster', 'contours', 'dashlines', 'data-fill',
|
|
72206
73526
|
'densify', 'dissolve', 'dissolve2', 'divide', 'dots', 'each', 'erase',
|
|
72207
73527
|
'explode', 'filter',
|
|
72208
73528
|
'filter-detail', 'filter-fields', 'filter-geom', 'filter-islands',
|
|
@@ -72379,6 +73699,9 @@ ${svg}
|
|
|
72379
73699
|
// } else if (name == 'comment') {
|
|
72380
73700
|
// // no-op
|
|
72381
73701
|
|
|
73702
|
+
} else if (name == 'contours') {
|
|
73703
|
+
outputLayers = applyCommandToEachLayer(cmd.contours, targetLayers, targetDataset, opts);
|
|
73704
|
+
|
|
72382
73705
|
} else if (name == 'dashlines') {
|
|
72383
73706
|
applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
|
|
72384
73707
|
|
|
@@ -72863,7 +74186,7 @@ ${svg}
|
|
|
72863
74186
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
72864
74187
|
}
|
|
72865
74188
|
|
|
72866
|
-
var version = "0.7.
|
|
74189
|
+
var version = "0.7.49";
|
|
72867
74190
|
|
|
72868
74191
|
// Parse command line args into commands and run them
|
|
72869
74192
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -73527,10 +74850,10 @@ ${svg}
|
|
|
73527
74850
|
|
|
73528
74851
|
var BOX_BLUR_PASSES = 3;
|
|
73529
74852
|
|
|
73530
|
-
function blurRasterGrid(raster, optsArg) {
|
|
74853
|
+
function blurRasterGrid(raster, optsArg, crs) {
|
|
73531
74854
|
var opts = optsArg || {};
|
|
73532
74855
|
var grid = getRasterGrid(raster);
|
|
73533
|
-
var radius = getBlurRadius(opts);
|
|
74856
|
+
var radius = getBlurRadius(grid, opts, crs);
|
|
73534
74857
|
var sigma = radius / 2;
|
|
73535
74858
|
var boxes = getGaussianBoxWidths(sigma, BOX_BLUR_PASSES);
|
|
73536
74859
|
var samples = new grid.samples.constructor(grid.samples.length);
|
|
@@ -73554,20 +74877,48 @@ ${svg}
|
|
|
73554
74877
|
});
|
|
73555
74878
|
}
|
|
73556
74879
|
|
|
73557
|
-
|
|
74880
|
+
// The blur kernel works in pixels, but the radius may be given either in pixels
|
|
74881
|
+
// (a bare number or a px suffix) or as a real-world distance (e.g. 20m), which
|
|
74882
|
+
// is converted using the size of a pixel on the ground.
|
|
74883
|
+
function getBlurRadius(grid, opts, crs) {
|
|
73558
74884
|
var arg = opts.radius;
|
|
73559
|
-
var radius;
|
|
73560
|
-
if (arg
|
|
73561
|
-
|
|
73562
|
-
|
|
74885
|
+
var measure, radius;
|
|
74886
|
+
if (arg === null || arg === undefined || arg === '') {
|
|
74887
|
+
stop$1('Missing blur radius');
|
|
74888
|
+
}
|
|
74889
|
+
if (typeof arg == 'string' && /px$/i.test(arg.trim())) {
|
|
74890
|
+
radius = Number(arg.trim().replace(/px$/i, ''));
|
|
74891
|
+
} else {
|
|
74892
|
+
measure = parseMeasure(arg);
|
|
74893
|
+
radius = measure.units ?
|
|
74894
|
+
convertDistanceToPixels(arg, grid, crs) :
|
|
74895
|
+
measure.value;
|
|
73563
74896
|
}
|
|
73564
|
-
radius = Number(arg);
|
|
73565
74897
|
if (!(radius > 0 && isFinite(radius))) {
|
|
73566
|
-
stop$1('Expected
|
|
74898
|
+
stop$1('Expected a positive blur radius, received:', arg);
|
|
73567
74899
|
}
|
|
73568
74900
|
return radius;
|
|
73569
74901
|
}
|
|
73570
74902
|
|
|
74903
|
+
function convertDistanceToPixels(arg, grid, crs) {
|
|
74904
|
+
// convertIntervalParam() gives the distance in the coordinate units of the
|
|
74905
|
+
// dataset, which is what the grid's bbox is measured in.
|
|
74906
|
+
return convertIntervalParam(arg, crs) / getPixelSize(grid);
|
|
74907
|
+
}
|
|
74908
|
+
|
|
74909
|
+
// Blurring is isotropic in pixel space, so a distance has to map to a single
|
|
74910
|
+
// pixel count; use the geometric mean when the pixels are not square.
|
|
74911
|
+
function getPixelSize(grid) {
|
|
74912
|
+
var bbox = grid.bbox;
|
|
74913
|
+
var dx, dy;
|
|
74914
|
+
if (!bbox) {
|
|
74915
|
+
stop$1('Raster layer is missing the georeferencing needed to blur by a distance');
|
|
74916
|
+
}
|
|
74917
|
+
dx = Math.abs(bbox[2] - bbox[0]) / grid.width;
|
|
74918
|
+
dy = Math.abs(bbox[3] - bbox[1]) / grid.height;
|
|
74919
|
+
return Math.sqrt(dx * dy);
|
|
74920
|
+
}
|
|
74921
|
+
|
|
73571
74922
|
function validateBlurGrid(grid) {
|
|
73572
74923
|
if (!grid || !grid.samples || !grid.width || !grid.height || !grid.bands) {
|
|
73573
74924
|
stop$1('Expected a raster grid');
|
|
@@ -73716,8 +75067,8 @@ ${svg}
|
|
|
73716
75067
|
|
|
73717
75068
|
function copyUnpremultipliedChannelToBand(channel, alpha, samples, grid, band) {
|
|
73718
75069
|
var bands = grid.bands;
|
|
73719
|
-
var isFloat = samples
|
|
73720
|
-
var range = isFloat ? null :
|
|
75070
|
+
var isFloat = rasterSamplesAreFloat(samples);
|
|
75071
|
+
var range = isFloat ? null : getSampleArrayRange(samples);
|
|
73721
75072
|
var maxAlpha = getAlphaMaxValue(samples);
|
|
73722
75073
|
var val, a;
|
|
73723
75074
|
for (var i = 0, j = band; i < channel.length; i++, j += bands) {
|
|
@@ -73729,8 +75080,8 @@ ${svg}
|
|
|
73729
75080
|
|
|
73730
75081
|
function copyChannelToBand(channel, samples, grid, band) {
|
|
73731
75082
|
var bands = grid.bands;
|
|
73732
|
-
var isFloat = samples
|
|
73733
|
-
var range = isFloat ? null :
|
|
75083
|
+
var isFloat = rasterSamplesAreFloat(samples);
|
|
75084
|
+
var range = isFloat ? null : getSampleArrayRange(samples);
|
|
73734
75085
|
var val;
|
|
73735
75086
|
for (var i = 0, j = band; i < channel.length; i++, j += bands) {
|
|
73736
75087
|
val = channel[i];
|
|
@@ -73739,62 +75090,47 @@ ${svg}
|
|
|
73739
75090
|
}
|
|
73740
75091
|
|
|
73741
75092
|
function getRasterBlurWeights(grid) {
|
|
73742
|
-
if (!grid
|
|
75093
|
+
if (!rasterGridHasInvalidPixels(grid)) return null;
|
|
73743
75094
|
var weights = new Float32Array(grid.width * grid.height);
|
|
73744
75095
|
for (var i = 0; i < weights.length; i++) {
|
|
73745
|
-
weights[i] =
|
|
75096
|
+
weights[i] = rasterPixelIsValid(grid, i) ? 1 : 0;
|
|
73746
75097
|
}
|
|
73747
75098
|
return weights;
|
|
73748
75099
|
}
|
|
73749
75100
|
|
|
73750
|
-
function rasterBlurPixelIsValid(grid, pixelId) {
|
|
73751
|
-
var off, n;
|
|
73752
|
-
if (grid.coverage && grid.coverage[pixelId] === 0) return false;
|
|
73753
|
-
if (grid.nodata == null) return true;
|
|
73754
|
-
off = pixelId * grid.bands;
|
|
73755
|
-
n = Math.min(grid.bands, 3);
|
|
73756
|
-
for (var i = 0; i < n; i++) {
|
|
73757
|
-
if (grid.samples[off + i] != grid.nodata) return true;
|
|
73758
|
-
}
|
|
73759
|
-
return false;
|
|
73760
|
-
}
|
|
73761
|
-
|
|
73762
75101
|
function getAlphaMaxValue(samples) {
|
|
73763
|
-
if (samples
|
|
73764
|
-
return
|
|
73765
|
-
}
|
|
73766
|
-
|
|
73767
|
-
function getTypedArrayRange(arr) {
|
|
73768
|
-
if (arr instanceof Uint8Array || arr instanceof Uint8ClampedArray) return {min: 0, max: 255};
|
|
73769
|
-
if (arr instanceof Int8Array) return {min: -128, max: 127};
|
|
73770
|
-
if (arr instanceof Uint16Array) return {min: 0, max: 65535};
|
|
73771
|
-
if (arr instanceof Int16Array) return {min: -32768, max: 32767};
|
|
73772
|
-
if (arr instanceof Uint32Array) return {min: 0, max: 4294967295};
|
|
73773
|
-
if (arr instanceof Int32Array) return {min: -2147483648, max: 2147483647};
|
|
73774
|
-
return {min: -Infinity, max: Infinity};
|
|
75102
|
+
if (rasterSamplesAreFloat(samples)) return 1;
|
|
75103
|
+
return getSampleArrayRange(samples).max;
|
|
73775
75104
|
}
|
|
73776
75105
|
|
|
73777
75106
|
function clamp(val, min, max) {
|
|
73778
75107
|
return val < min ? min : val > max ? max : val;
|
|
73779
75108
|
}
|
|
73780
75109
|
|
|
75110
|
+
var RasterBlur = /*#__PURE__*/Object.freeze({
|
|
75111
|
+
__proto__: null,
|
|
75112
|
+
blurRasterGrid: blurRasterGrid,
|
|
75113
|
+
getBlurRadius: getBlurRadius
|
|
75114
|
+
});
|
|
75115
|
+
|
|
73781
75116
|
cmd.blur = blurRasterLayers;
|
|
73782
75117
|
|
|
73783
75118
|
function blurRasterLayers(layers, dataset, optsArg) {
|
|
73784
75119
|
var opts = optsArg || {};
|
|
75120
|
+
var crs = getDatasetCRS(dataset);
|
|
73785
75121
|
requireProjectedDataset(dataset);
|
|
73786
75122
|
layers.forEach(function(lyr) {
|
|
73787
75123
|
if (!layerHasRaster(lyr)) {
|
|
73788
75124
|
stop$1('Command requires a raster layer');
|
|
73789
75125
|
}
|
|
73790
|
-
blurRasterLayer(lyr, opts);
|
|
75126
|
+
blurRasterLayer(lyr, opts, crs);
|
|
73791
75127
|
});
|
|
73792
75128
|
}
|
|
73793
75129
|
|
|
73794
|
-
function blurRasterLayer(lyr, opts) {
|
|
75130
|
+
function blurRasterLayer(lyr, opts, crs) {
|
|
73795
75131
|
var raster = lyr.raster;
|
|
73796
75132
|
noteLayerWillChange(lyr, {operation: 'blurRasterLayer', unit: 'raster'});
|
|
73797
|
-
raster.grid = blurRasterGrid(raster, opts);
|
|
75133
|
+
raster.grid = blurRasterGrid(raster, opts, crs);
|
|
73798
75134
|
raster.view = raster.view || {};
|
|
73799
75135
|
delete raster.view.scalingStats;
|
|
73800
75136
|
if (runningInBrowser()) {
|
|
@@ -74698,6 +76034,7 @@ ${svg}
|
|
|
74698
76034
|
Catalog$1,
|
|
74699
76035
|
ClipErase,
|
|
74700
76036
|
ClipPoints,
|
|
76037
|
+
ColorUtils,
|
|
74701
76038
|
Colorizer,
|
|
74702
76039
|
CustomProjections,
|
|
74703
76040
|
Dymaxion,
|
|
@@ -74745,6 +76082,9 @@ ${svg}
|
|
|
74745
76082
|
Lines,
|
|
74746
76083
|
Logging,
|
|
74747
76084
|
Profile,
|
|
76085
|
+
RasterBlur,
|
|
76086
|
+
RasterContours,
|
|
76087
|
+
RasterGrid,
|
|
74748
76088
|
RasterReprojection,
|
|
74749
76089
|
RasterUtils,
|
|
74750
76090
|
Merging,
|