mapshaper 0.7.53 → 0.7.55

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 CHANGED
@@ -8447,7 +8447,7 @@
8447
8447
  var D2R$2 = Math.PI / 180;
8448
8448
  var R2D$3 = 180 / Math.PI;
8449
8449
  var HALF_PI = Math.PI / 2;
8450
- var SQRT2$1 = Math.sqrt(2);
8450
+ var SQRT2$2 = Math.sqrt(2);
8451
8451
  var SQRT3$1 = Math.sqrt(3);
8452
8452
  var ASIN_ONE_THIRD$1 = Math.asin(1 / 3);
8453
8453
  var EDGE_SCALE = Math.acos(-1 / 3) / 2;
@@ -9018,7 +9018,7 @@
9018
9018
  function narukawaFaceForward(lam, phi) {
9019
9019
  var a = lam - Math.asin(Math.sin(lam) / SQRT3$1);
9020
9020
  var theta = Math.atan(2 * SQRT3$1 / Math.PI * a);
9021
- var denominator = 2 + SQRT2$1 * Math.tan(phi);
9021
+ var denominator = 2 + SQRT2$2 * Math.tan(phi);
9022
9022
  var q = denominator > 0 ? (2 + Math.cos(lam)) / denominator : 0;
9023
9023
  var r = q * SQRT3$1 / Math.cos(theta);
9024
9024
  return [r, theta];
@@ -9038,7 +9038,7 @@
9038
9038
  lam = (lo + hi) / 2;
9039
9039
  var q = r * Math.cos(theta) / SQRT3$1;
9040
9040
  var phi = q < EPS$1 ? HALF_PI :
9041
- Math.atan(((2 + Math.cos(lam)) / q - 2) / SQRT2$1);
9041
+ Math.atan(((2 + Math.cos(lam)) / q - 2) / SQRT2$2);
9042
9042
  return [phi, lam];
9043
9043
  }
9044
9044
 
@@ -9478,7 +9478,7 @@
9478
9478
 
9479
9479
  var D2R$1 = Math.PI / 180;
9480
9480
  var R2D$2 = 180 / Math.PI;
9481
- var SQRT2 = Math.sqrt(2);
9481
+ var SQRT2$1 = Math.sqrt(2);
9482
9482
  var SQRT3 = Math.sqrt(3);
9483
9483
  var ASIN_ONE_THIRD = Math.asin(1 / 3);
9484
9484
  var MARKLEY_LATITUDE = Math.acos(1 / 3) * 0.5 * R2D$2;
@@ -10062,7 +10062,7 @@
10062
10062
 
10063
10063
  function leeRaw(lam, phi) {
10064
10064
  var s = stereographicRaw(lam, phi);
10065
- var z = [s[0] * SQRT2, s[1] * SQRT2];
10065
+ var z = [s[0] * SQRT2$1, s[1] * SQRT2$1];
10066
10066
  var i, j;
10067
10067
  // Pick the sector whose rotation maximizes the real part of rot * z.
10068
10068
  var sector = 0;
@@ -18826,6 +18826,602 @@
18826
18826
  });
18827
18827
  }
18828
18828
 
18829
+ class TinyQueue {
18830
+ constructor(data = [], compare = defaultCompare) {
18831
+ this.data = data;
18832
+ this.length = this.data.length;
18833
+ this.compare = compare;
18834
+
18835
+ if (this.length > 0) {
18836
+ for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
18837
+ }
18838
+ }
18839
+
18840
+ push(item) {
18841
+ this.data.push(item);
18842
+ this.length++;
18843
+ this._up(this.length - 1);
18844
+ }
18845
+
18846
+ pop() {
18847
+ if (this.length === 0) return undefined;
18848
+
18849
+ const top = this.data[0];
18850
+ const bottom = this.data.pop();
18851
+ this.length--;
18852
+
18853
+ if (this.length > 0) {
18854
+ this.data[0] = bottom;
18855
+ this._down(0);
18856
+ }
18857
+
18858
+ return top;
18859
+ }
18860
+
18861
+ peek() {
18862
+ return this.data[0];
18863
+ }
18864
+
18865
+ _up(pos) {
18866
+ const {data, compare} = this;
18867
+ const item = data[pos];
18868
+
18869
+ while (pos > 0) {
18870
+ const parent = (pos - 1) >> 1;
18871
+ const current = data[parent];
18872
+ if (compare(item, current) >= 0) break;
18873
+ data[pos] = current;
18874
+ pos = parent;
18875
+ }
18876
+
18877
+ data[pos] = item;
18878
+ }
18879
+
18880
+ _down(pos) {
18881
+ const {data, compare} = this;
18882
+ const halfLength = this.length >> 1;
18883
+ const item = data[pos];
18884
+
18885
+ while (pos < halfLength) {
18886
+ let left = (pos << 1) + 1;
18887
+ let best = data[left];
18888
+ const right = left + 1;
18889
+
18890
+ if (right < this.length && compare(data[right], best) < 0) {
18891
+ left = right;
18892
+ best = data[right];
18893
+ }
18894
+ if (compare(best, item) >= 0) break;
18895
+
18896
+ data[pos] = best;
18897
+ pos = left;
18898
+ }
18899
+
18900
+ data[pos] = item;
18901
+ }
18902
+ }
18903
+
18904
+ function defaultCompare(a, b) {
18905
+ return a < b ? -1 : a > b ? 1 : 0;
18906
+ }
18907
+
18908
+ /*
18909
+ Copyright (c) 2016-2026, Vladimir Agafonkin
18910
+
18911
+ Permission to use, copy, modify, and/or distribute this software for any
18912
+ purpose with or without fee is hereby granted, provided that the above
18913
+ copyright notice and this permission notice appear in all copies.
18914
+
18915
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
18916
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18917
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18918
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18919
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18920
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18921
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18922
+
18923
+ Based on mapbox/polylabel 2.1.0. Adapted for mapshaper to operate on
18924
+ topological polygon shapes, use a bounded single-cell search and support
18925
+ alternative centroid-aware objectives.
18926
+ */
18927
+
18928
+
18929
+ var BLOCK_SIZE = 32;
18930
+ var DEFAULT_PRECISION = 0.01;
18931
+ var DEFAULT_PRECISION_FLOOR = 1e-4;
18932
+ // Hard ceiling on cell creation. Besides bounding runtime, this limits the
18933
+ // priority queue to O(DEFAULT_PROBE_LIMIT) entries on pathological rings.
18934
+ var DEFAULT_PROBE_LIMIT = 100000;
18935
+ var DEFAULT_TOLERANCE = 0.1;
18936
+ var DEFAULT_WEIGHT = 0.6;
18937
+ var MAX_WEIGHT_PENALTY = 0.25;
18938
+ var SQRT2 = Math.SQRT2;
18939
+
18940
+ // Find an inner point using one of four polylabel-based policies.
18941
+ // Returns null for collapsed polygons, so callers can fall back to the legacy
18942
+ // anchor-point finder.
18943
+ function findPolylabelPoint(shp, arcs, opts) {
18944
+ var polygon = buildPolygon(shp, arcs);
18945
+ if (!polygon) return null;
18946
+
18947
+ opts = opts || {};
18948
+ var precision = getPrecision(polygon, opts);
18949
+ if (!(precision > 0)) return null;
18950
+
18951
+ var limit = opts.probe_limit > 0 ? opts.probe_limit : DEFAULT_PROBE_LIMIT;
18952
+ var pole = findPole(polygon, precision, limit);
18953
+ if (!isUsablePoint(pole)) return null;
18954
+
18955
+ var method = opts.method || 'centroid2';
18956
+ if (method == 'pole') return pole;
18957
+
18958
+ var centroid = getCentroidCell(polygon);
18959
+ if (!(centroid.d > 0)) return pole;
18960
+
18961
+ var tolerance = opts.tolerance;
18962
+ if (!(tolerance >= 0 && tolerance <= 1)) tolerance = DEFAULT_TOLERANCE;
18963
+ var minClearance = pole.distance * (1 - tolerance);
18964
+
18965
+ if (method == 'centroid') {
18966
+ return centroid.d >= minClearance ? toResult(centroid, pole.probes) : pole;
18967
+ }
18968
+ if (method == 'centroid2') {
18969
+ if (centroid.d >= minClearance) return toResult(centroid, pole.probes);
18970
+ return findClosestAcceptablePoint(
18971
+ polygon, centroid.x, centroid.y, pole, minClearance, precision, limit
18972
+ );
18973
+ }
18974
+ if (method == 'weighted') {
18975
+ var weight = opts.weight;
18976
+ if (!(weight >= 0)) weight = DEFAULT_WEIGHT;
18977
+ return findWeightedPoint(polygon, centroid, pole, weight, precision, limit);
18978
+ }
18979
+ return null;
18980
+ }
18981
+
18982
+ function isUsablePoint(p) {
18983
+ return p && isFinite(p.x) && isFinite(p.y) && p.distance > 0;
18984
+ }
18985
+
18986
+ function buildPolygon(shp, arcs) {
18987
+ var outerIds = shp && geom.getMaxPath(shp, arcs);
18988
+ if (!outerIds) return null;
18989
+
18990
+ var outer = pathToRing(outerIds, arcs);
18991
+ var outerArea = geom.getPlanarPathArea(outerIds, arcs);
18992
+ if (!outer || outerArea === 0) return null;
18993
+
18994
+ var outerBounds = arcs.getSimpleShapeBounds(outerIds);
18995
+ var rings = [outer];
18996
+ var area = Math.abs(outerArea);
18997
+ var perimeter = getRingPerimeter$1(outer);
18998
+
18999
+ // Mapshaper stores multipart polygons as a flat list of rings. Polylabel
19000
+ // expects one outer ring followed by its holes, so omit other outer rings.
19001
+ (shp || []).forEach(function(ids) {
19002
+ if (ids === outerIds) return;
19003
+ var ringArea = geom.getPlanarPathArea(ids, arcs);
19004
+ if (ringArea === 0 || ringArea * outerArea >= 0) return;
19005
+ var bounds = arcs.getSimpleShapeBounds(ids);
19006
+ if (!outerBounds.intersects(bounds)) return;
19007
+ var ring = pathToRing(ids, arcs);
19008
+ if (!ring || !geom.testPointInRing(ring[0][0], ring[0][1], outerIds, arcs)) {
19009
+ return;
19010
+ }
19011
+ rings.push(ring);
19012
+ area -= Math.abs(ringArea);
19013
+ perimeter += getRingPerimeter$1(ring);
19014
+ });
19015
+
19016
+ if (!(area > 0) || !(perimeter > 0)) return null;
19017
+ return createPolygonContext(rings, outerBounds, area, perimeter);
19018
+ }
19019
+
19020
+ function pathToRing(ids, arcs) {
19021
+ var iter = arcs.getShapeIter(ids);
19022
+ var points = [];
19023
+ var prevX, prevY;
19024
+ while (iter.hasNext()) {
19025
+ if (points.length === 0 || iter.x != prevX || iter.y != prevY) {
19026
+ points.push([iter.x, iter.y]);
19027
+ prevX = iter.x;
19028
+ prevY = iter.y;
19029
+ }
19030
+ }
19031
+ if (points.length < 3) return null;
19032
+ var a = points[0];
19033
+ var b = points[points.length - 1];
19034
+ if (a[0] != b[0] || a[1] != b[1]) points.push([a[0], a[1]]);
19035
+ return points.length > 3 ? points : null;
19036
+ }
19037
+
19038
+ function getRingPerimeter$1(ring) {
19039
+ var sum = 0;
19040
+ for (var i = 1; i < ring.length; i++) {
19041
+ sum += geom.distance2D(
19042
+ ring[i - 1][0], ring[i - 1][1], ring[i][0], ring[i][1]
19043
+ );
19044
+ }
19045
+ return sum;
19046
+ }
19047
+
19048
+ function createPolygonContext(rings, bounds, area, perimeter) {
19049
+ var pointCount = rings.reduce(function(sum, ring) {
19050
+ return sum + ring.length;
19051
+ }, 0);
19052
+ var coords = new Float64Array(pointCount * 2);
19053
+ var ringEnds = new Uint32Array(rings.length);
19054
+ var k = 0;
19055
+ rings.forEach(function(ring, i) {
19056
+ ring.forEach(function(p) {
19057
+ coords[k++] = p[0];
19058
+ coords[k++] = p[1];
19059
+ });
19060
+ ringEnds[i] = k;
19061
+ });
19062
+ return {
19063
+ area: area,
19064
+ perimeter: perimeter,
19065
+ bounds: bounds,
19066
+ coords: coords,
19067
+ ringEnds: ringEnds,
19068
+ blocks: buildBlocks(coords, ringEnds)
19069
+ };
19070
+ }
19071
+
19072
+ function getPrecision(polygon, opts) {
19073
+ if (opts.precision > 0) return opts.precision;
19074
+ var extent = Math.max(polygon.bounds.width(), polygon.bounds.height());
19075
+ var relative = opts.precision_fraction > 0 ?
19076
+ opts.precision_fraction : DEFAULT_PRECISION;
19077
+ var floor = opts.precision_floor >= 0 ?
19078
+ opts.precision_floor : DEFAULT_PRECISION_FLOOR;
19079
+ // 2A/P is rotation-invariant and approximates a polygon's local thickness.
19080
+ return Math.max(relative * 2 * polygon.area / polygon.perimeter, floor * extent);
19081
+ }
19082
+
19083
+ function findPole(polygon, precision, limit) {
19084
+ var queue = new TinyQueue([], compareMax);
19085
+ var centroid = getCentroidCell(polygon);
19086
+ var best = centroid.d > 0 ? centroid :
19087
+ new Cell(polygon.coords[0], polygon.coords[1], 0, polygon);
19088
+ var bounds = polygon.bounds;
19089
+ var bboxCell = new Cell(bounds.centerX(), bounds.centerY(), 0, polygon);
19090
+ if (bboxCell.d > best.d) best = bboxCell;
19091
+
19092
+ var probes = 2;
19093
+ var exhausted = false;
19094
+
19095
+ function addCell(x, y, h, seed) {
19096
+ // If the cell center is already too close to an edge for this cell to
19097
+ // improve the result, the distance scan can stop at that edge. A bailed
19098
+ // scan is never scored or queued because its returned distance is only a
19099
+ // threshold, not the center's actual signed distance.
19100
+ var threshold = best.d - Math.max(0, h * SQRT2 - precision);
19101
+ var cell = new Cell(x, y, h, polygon, threshold, seed);
19102
+ probes++;
19103
+ if (cell.bailed) return;
19104
+ if (cell.d > best.d) best = cell;
19105
+ if (cell.max > best.d + precision) queue.push(cell);
19106
+ }
19107
+
19108
+ var h = Math.max(bounds.width(), bounds.height()) / 2;
19109
+ addCell(bounds.centerX(), bounds.centerY(), h, null);
19110
+ while (queue.length > 0) {
19111
+ if (probes + 4 > limit) {
19112
+ exhausted = true;
19113
+ break;
19114
+ }
19115
+ var cell = queue.pop();
19116
+ if (cell.max - best.d <= precision) continue;
19117
+ h = cell.h / 2;
19118
+ addCell(cell.x - h, cell.y - h, h, cell);
19119
+ addCell(cell.x + h, cell.y - h, h, cell);
19120
+ addCell(cell.x - h, cell.y + h, h, cell);
19121
+ addCell(cell.x + h, cell.y + h, h, cell);
19122
+ }
19123
+ return toResult(best, probes, exhausted);
19124
+ }
19125
+
19126
+ // Among points whose clearance is near the maximum, find the point closest to
19127
+ // the centroid. This is a constrained objective, not a general centroid bias.
19128
+ function findClosestAcceptablePoint(
19129
+ polygon, centroidX, centroidY, pole, minClearance, precision, limit
19130
+ ) {
19131
+ var queue = new TinyQueue([], compareOffset);
19132
+ var bounds = polygon.bounds;
19133
+ var h = Math.max(bounds.width(), bounds.height()) / 2;
19134
+ var root = new Cell(bounds.centerX(), bounds.centerY(), h, polygon);
19135
+ setOffsetBounds(root, centroidX, centroidY);
19136
+ queue.push(root);
19137
+
19138
+ var best = {
19139
+ x: pole.x,
19140
+ y: pole.y,
19141
+ d: pole.distance,
19142
+ offset: geom.distance2D(pole.x, pole.y, centroidX, centroidY)
19143
+ };
19144
+ var probes = pole.probes + 1;
19145
+ var exhausted = false;
19146
+
19147
+ while (queue.length > 0) {
19148
+ if (probes + 4 > limit) {
19149
+ exhausted = true;
19150
+ break;
19151
+ }
19152
+ var cell = queue.pop();
19153
+ if (cell.minOffset >= best.offset || cell.max < minClearance) continue;
19154
+ if (cell.d >= minClearance && cell.offset < best.offset) {
19155
+ best = cell;
19156
+ }
19157
+ if (cell.h <= precision / 2) continue;
19158
+ h = cell.h / 2;
19159
+ [
19160
+ [cell.x - h, cell.y - h],
19161
+ [cell.x + h, cell.y - h],
19162
+ [cell.x - h, cell.y + h],
19163
+ [cell.x + h, cell.y + h]
19164
+ ].forEach(function(xy) {
19165
+ var child = new Cell(xy[0], xy[1], h, polygon);
19166
+ setOffsetBounds(child, centroidX, centroidY);
19167
+ if (child.minOffset < best.offset && child.max >= minClearance) {
19168
+ queue.push(child);
19169
+ }
19170
+ });
19171
+ probes += 4;
19172
+ }
19173
+ return toResult(best, probes, exhausted);
19174
+ }
19175
+
19176
+ function setOffsetBounds(cell, x, y) {
19177
+ cell.offset = geom.distance2D(cell.x, cell.y, x, y);
19178
+ cell.minOffset = Math.max(0, cell.offset - cell.h * SQRT2);
19179
+ }
19180
+
19181
+ function findWeightedPoint(polygon, centroid, pole, weight, precision, limit) {
19182
+ if (weight === 0) return pole;
19183
+ var bounds = polygon.bounds;
19184
+ var refDist = Math.max(bounds.width(), bounds.height()) / 2;
19185
+ var queue = new TinyQueue([], compareScore);
19186
+ var best = new Cell(pole.x, pole.y, 0, polygon);
19187
+ setWeightedScore(best, centroid, refDist, weight);
19188
+
19189
+ var h = Math.max(bounds.width(), bounds.height()) / 2;
19190
+ var root = new Cell(bounds.centerX(), bounds.centerY(), h, polygon);
19191
+ setWeightedScore(root, centroid, refDist, weight);
19192
+ queue.push(root);
19193
+ var probes = pole.probes + 1;
19194
+ var exhausted = false;
19195
+
19196
+ while (queue.length > 0) {
19197
+ if (probes + 4 > limit) {
19198
+ exhausted = true;
19199
+ break;
19200
+ }
19201
+ var cell = queue.pop();
19202
+ if (cell.score > best.score) best = cell;
19203
+ if (cell.maxScore - best.score <= precision) continue;
19204
+ h = cell.h / 2;
19205
+ [
19206
+ [cell.x - h, cell.y - h],
19207
+ [cell.x + h, cell.y - h],
19208
+ [cell.x - h, cell.y + h],
19209
+ [cell.x + h, cell.y + h]
19210
+ ].forEach(function(xy) {
19211
+ var child = new Cell(xy[0], xy[1], h, polygon);
19212
+ setWeightedScore(child, centroid, refDist, weight);
19213
+ if (child.maxScore > best.score + precision) queue.push(child);
19214
+ if (child.score > best.score) best = child;
19215
+ });
19216
+ probes += 4;
19217
+ }
19218
+ return toResult(best, probes, exhausted);
19219
+ }
19220
+
19221
+ function setWeightedScore(cell, centroid, refDist, weight) {
19222
+ var offset = geom.distance2D(cell.x, cell.y, centroid.x, centroid.y);
19223
+ var minOffset = Math.max(0, offset - cell.h * SQRT2);
19224
+ var pointWeight = getCentroidWeight(offset, refDist, weight);
19225
+ var maxWeight = getCentroidWeight(minOffset, refDist, weight);
19226
+ cell.score = cell.d * pointWeight;
19227
+ cell.maxScore = cell.max * maxWeight;
19228
+ }
19229
+
19230
+ function getCentroidWeight(offset, refDist, weight) {
19231
+ return refDist > 0 ?
19232
+ 1 - Math.min(weight * offset / refDist, MAX_WEIGHT_PENALTY) : 1;
19233
+ }
19234
+
19235
+ function getCentroidCell(polygon) {
19236
+ var coords = polygon.coords;
19237
+ var end = polygon.ringEnds[0];
19238
+ var area = 0;
19239
+ var x = 0;
19240
+ var y = 0;
19241
+ for (var i = 0, j = end - 2; i < end; j = i, i += 2) {
19242
+ var ax = coords[i];
19243
+ var ay = coords[i + 1];
19244
+ var bx = coords[j];
19245
+ var by = coords[j + 1];
19246
+ var f = ax * by - bx * ay;
19247
+ x += (ax + bx) * f;
19248
+ y += (ay + by) * f;
19249
+ area += f * 3;
19250
+ }
19251
+ if (area === 0) return new Cell(coords[0], coords[1], 0, polygon);
19252
+ return new Cell(x / area, y / area, 0, polygon);
19253
+ }
19254
+
19255
+ function Cell(x, y, h, polygon, maxD, seed) {
19256
+ this.x = x;
19257
+ this.y = y;
19258
+ this.h = h;
19259
+ this.nsx1 = 0;
19260
+ this.nsy1 = 0;
19261
+ this.nsx2 = 0;
19262
+ this.nsy2 = 0;
19263
+ this.bailed = false;
19264
+ this.d = pointToPolygonDistance(
19265
+ this, polygon, maxD === undefined ? -Infinity : maxD, seed || null
19266
+ );
19267
+ this.max = this.d + h * SQRT2;
19268
+ }
19269
+
19270
+ function pointToPolygonDistance(cell, polygon, maxD, seed) {
19271
+ var coords = polygon.coords;
19272
+ var ringEnds = polygon.ringEnds;
19273
+ var blocks = polygon.blocks;
19274
+ var x = cell.x;
19275
+ var y = cell.y;
19276
+ var inside = false;
19277
+ var minDistSq = Infinity;
19278
+ var thresholdSq = maxD > 0 ? maxD * maxD : -1;
19279
+ var stride = BLOCK_SIZE * 2;
19280
+ var blockIndex = 0;
19281
+ var ringStart = 0;
19282
+
19283
+ if (seed) {
19284
+ cell.nsx1 = seed.nsx1;
19285
+ cell.nsy1 = seed.nsy1;
19286
+ cell.nsx2 = seed.nsx2;
19287
+ cell.nsy2 = seed.nsy2;
19288
+ minDistSq = getSegmentDistanceSq(
19289
+ x, y, seed.nsx1, seed.nsy1, seed.nsx2, seed.nsy2
19290
+ );
19291
+ if (minDistSq <= thresholdSq) {
19292
+ cell.bailed = true;
19293
+ return maxD;
19294
+ }
19295
+ }
19296
+
19297
+ for (var r = 0; r < ringEnds.length; r++) {
19298
+ var ringEnd = ringEnds[r];
19299
+ var bx = coords[ringEnd - 2];
19300
+ var by = coords[ringEnd - 1];
19301
+ for (var start = ringStart; start < ringEnd;
19302
+ start += stride, blockIndex += 4) {
19303
+ var end = Math.min(start + stride, ringEnd);
19304
+ var xmin = blocks[blockIndex];
19305
+ var ymin = blocks[blockIndex + 1];
19306
+ var xmax = blocks[blockIndex + 2];
19307
+ var ymax = blocks[blockIndex + 3];
19308
+ var dx = x < xmin ? xmin - x : x > xmax ? x - xmax : 0;
19309
+ var dy = y < ymin ? ymin - y : y > ymax ? y - ymax : 0;
19310
+ var skipDist = dx * dx + dy * dy >= minDistSq;
19311
+ var skipCross = y < ymin || y >= ymax || x > xmax;
19312
+ if (skipDist && skipCross) {
19313
+ bx = coords[end - 2];
19314
+ by = coords[end - 1];
19315
+ continue;
19316
+ }
19317
+ for (var i = start; i < end; i += 2) {
19318
+ var ax = coords[i];
19319
+ var ay = coords[i + 1];
19320
+ if (!skipCross && (ay > y !== by > y) &&
19321
+ x < (bx - ax) * (y - ay) / (by - ay) + ax) {
19322
+ inside = !inside;
19323
+ }
19324
+ if (!skipDist) {
19325
+ var distSq = getSegmentDistanceSq(x, y, ax, ay, bx, by);
19326
+ if (distSq < minDistSq) {
19327
+ minDistSq = distSq;
19328
+ cell.nsx1 = ax;
19329
+ cell.nsy1 = ay;
19330
+ cell.nsx2 = bx;
19331
+ cell.nsy2 = by;
19332
+ if (minDistSq <= thresholdSq) {
19333
+ cell.bailed = true;
19334
+ return maxD;
19335
+ }
19336
+ }
19337
+ }
19338
+ bx = ax;
19339
+ by = ay;
19340
+ }
19341
+ }
19342
+ ringStart = ringEnd;
19343
+ }
19344
+ return minDistSq === 0 ? 0 : (inside ? 1 : -1) * Math.sqrt(minDistSq);
19345
+ }
19346
+
19347
+ function buildBlocks(coords, ringEnds) {
19348
+ var stride = BLOCK_SIZE * 2;
19349
+ var blockCount = 0;
19350
+ var ringStart = 0;
19351
+ for (var r = 0; r < ringEnds.length; r++) {
19352
+ blockCount += Math.ceil((ringEnds[r] - ringStart) / stride);
19353
+ ringStart = ringEnds[r];
19354
+ }
19355
+ var blocks = new Float64Array(blockCount * 4);
19356
+ var k = 0;
19357
+ ringStart = 0;
19358
+ for (r = 0; r < ringEnds.length; r++) {
19359
+ var ringEnd = ringEnds[r];
19360
+ for (var start = ringStart; start < ringEnd; start += stride) {
19361
+ var end = Math.min(start + stride, ringEnd);
19362
+ var prev = start === ringStart ? ringEnd - 2 : start - 2;
19363
+ var xmin = coords[prev];
19364
+ var ymin = coords[prev + 1];
19365
+ var xmax = xmin;
19366
+ var ymax = ymin;
19367
+ for (var i = start; i < end; i += 2) {
19368
+ var x = coords[i];
19369
+ var y = coords[i + 1];
19370
+ if (x < xmin) xmin = x;
19371
+ if (x > xmax) xmax = x;
19372
+ if (y < ymin) ymin = y;
19373
+ if (y > ymax) ymax = y;
19374
+ }
19375
+ blocks[k++] = xmin;
19376
+ blocks[k++] = ymin;
19377
+ blocks[k++] = xmax;
19378
+ blocks[k++] = ymax;
19379
+ }
19380
+ ringStart = ringEnd;
19381
+ }
19382
+ return blocks;
19383
+ }
19384
+
19385
+ function getSegmentDistanceSq(px, py, x, y, bx, by) {
19386
+ var dx = bx - x;
19387
+ var dy = by - y;
19388
+ if (dx !== 0 || dy !== 0) {
19389
+ var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
19390
+ if (t > 1) {
19391
+ x = bx;
19392
+ y = by;
19393
+ } else if (t > 0) {
19394
+ x += dx * t;
19395
+ y += dy * t;
19396
+ }
19397
+ }
19398
+ dx = px - x;
19399
+ dy = py - y;
19400
+ return dx * dx + dy * dy;
19401
+ }
19402
+
19403
+ function toResult(cell, probes, exhausted) {
19404
+ return {
19405
+ x: cell.x,
19406
+ y: cell.y,
19407
+ distance: cell.d,
19408
+ probes: probes || 0,
19409
+ exhausted: !!exhausted
19410
+ };
19411
+ }
19412
+
19413
+ function compareMax(a, b) {
19414
+ return b.max - a.max;
19415
+ }
19416
+
19417
+ function compareOffset(a, b) {
19418
+ return a.minOffset - b.minOffset;
19419
+ }
19420
+
19421
+ function compareScore(a, b) {
19422
+ return b.maxScore - a.maxScore;
19423
+ }
19424
+
18829
19425
  function simplifyArcsFast(arcs, dist) {
18830
19426
  var xx = [],
18831
19427
  yy = [],
@@ -18897,8 +19493,20 @@
18897
19493
  simplifyPolygonFast: simplifyPolygonFast
18898
19494
  });
18899
19495
 
18900
- // Find a point inside a polygon and located away from the polygon edge
18901
- // Method:
19496
+ // Find an interior point for label or symbol placement. The default method
19497
+ // finds a pole of inaccessibility, then moves toward the centroid while
19498
+ // retaining at least 90% of the pole's clearance.
19499
+ function findAnchorPoint(shp, arcs, opts) {
19500
+ var method = opts && opts.method || 'centroid2';
19501
+ var p;
19502
+ if (method != 'legacy') {
19503
+ p = findPolylabelPoint(shp, arcs, opts);
19504
+ if (p) return p;
19505
+ }
19506
+ return findLegacyAnchorPoint(shp, arcs);
19507
+ }
19508
+
19509
+ // Legacy sampled-ray method:
18902
19510
  // - get the largest ring of the polygon
18903
19511
  // - get an array of x-values distributed along the horizontal extent of the ring
18904
19512
  // - for each x:
@@ -18909,8 +19517,7 @@
18909
19517
  // - return the adjusted point having the maximum weighted distance from the edge
18910
19518
  //
18911
19519
  // (distance is weighted to slightly favor points near centroid)
18912
- //
18913
- function findAnchorPoint(shp, arcs) {
19520
+ function findLegacyAnchorPoint(shp, arcs) {
18914
19521
  var maxPath = shp && geom.getMaxPath(shp, arcs),
18915
19522
  pathBounds = maxPath && arcs.getSimpleShapeBounds(maxPath),
18916
19523
  thresh, simple;
@@ -19152,7 +19759,8 @@
19152
19759
 
19153
19760
  var AnchorPoints = /*#__PURE__*/Object.freeze({
19154
19761
  __proto__: null,
19155
- findAnchorPoint: findAnchorPoint
19762
+ findAnchorPoint: findAnchorPoint,
19763
+ findLegacyAnchorPoint: findLegacyAnchorPoint
19156
19764
  });
19157
19765
 
19158
19766
  // Returns a function for calculating the percentage of a shape's perimeter by length that
@@ -19403,6 +20011,8 @@
19403
20011
  return bbox;
19404
20012
  }
19405
20013
 
20014
+ var EXPRESSION_INNER_POINT_OPTS = {method: 'centroid2', tolerance: 0.1};
20015
+
19406
20016
  // Returns a function to return a feature proxy by id
19407
20017
  // (the proxy appears as "this" or "$" in a feature expression)
19408
20018
  function initFeatureProxy(lyr, arcs, optsArg) {
@@ -19618,7 +20228,8 @@
19618
20228
  }
19619
20229
 
19620
20230
  function innerXY() {
19621
- _innerXY = _innerXY || findAnchorPoint(_ids, arcs);
20231
+ _innerXY = _innerXY ||
20232
+ findAnchorPoint(_ids, arcs, EXPRESSION_INNER_POINT_OPTS);
19622
20233
  return _innerXY;
19623
20234
  }
19624
20235
 
@@ -25791,6 +26402,10 @@
25791
26402
  return !!(d && (d.icon || d['icon-size'] || (d['icon-color'] && d.r > 0)));
25792
26403
  }
25793
26404
 
26405
+ // An unsupported icon name renders as nothing, without a message -- this
26406
+ // function runs once per feature per render, so warning here would repeat the
26407
+ // same message for every feature (and on every redraw in the GUI). The -style
26408
+ // command warns about unsupported names when they are assigned instead.
25794
26409
  function renderIcon(d) {
25795
26410
  var type = d.icon || 'circle';
25796
26411
  var r = getIconRadius(d, type);
@@ -25799,7 +26414,6 @@
25799
26414
  if (type == 'square') return square(getIconStyleData(d, r), 0, 0);
25800
26415
  if (type == 'ring') return ring(getIconStyleData(d, r), 0, 0);
25801
26416
  if (type == 'star') return star(getIconStyleData(d, r));
25802
- message('Unknown icon type: ' + type);
25803
26417
  return empty();
25804
26418
  }
25805
26419
 
@@ -33389,8 +34003,6 @@ ${svg}
33389
34003
  }); });
33390
34004
  }
33391
34005
 
33392
- function buildHeader(t,r=0){let n,o=new Builder,i=0;t.columns&&(i=Header.createColumnsVector(o,t.columns.map(e=>{let t;return t=o.createString(e.name),Column.startColumn(o),Column.addName(o,t),Column.addType(o,e.type),Column.endColumn(o)})));let f=o.createString("L1");r&&(Crs.startCrs(o),Crs.addCode(o,r),n=Crs.endCrs(o)),Header.startHeader(o),n&&Header.addCrs(o,n),Header.addFeaturesCount(o,BigInt(t.featuresCount)),Header.addGeometryType(o,t.geometryType),Header.addIndexNodeSize(o,0),i&&Header.addColumns(o,i),Header.addName(o,f);let u=Header.endHeader(o);return o.finishSizePrefixed(u),o.asUint8Array()}
33393
-
33394
34006
  // bytes: Uint8Array
33395
34007
  function getHeaderMeta(bytes) {
33396
34008
  if (!bytes.subarray(0, 3).every((v, i) => magicbytes[i] === v)) {
@@ -33398,7 +34010,11 @@ ${svg}
33398
34010
  }
33399
34011
  var bb = new ByteBuffer(bytes);
33400
34012
  bb.setPosition(magicbytes.length + SIZE_PREFIX_LEN);
33401
- return fromByteBuffer(bb);
34013
+ var header = Header.getRootAsHeader(bb);
34014
+ var meta = fromByteBuffer(bb);
34015
+ // The library's header-meta helper omits the FlatGeobuf dataset name.
34016
+ meta.name = header.name();
34017
+ return meta;
33402
34018
  }
33403
34019
 
33404
34020
  // bytes: Uint8Array
@@ -33472,7 +34088,7 @@ ${svg}
33472
34088
  // source: a feature cursor, {length, getFeature(i)}. Features are pulled and
33473
34089
  // encoded one at a time rather than as a collection, because the GeoJSON form
33474
34090
  // of a layer is several times larger than the FlatGeobuf it encodes into.
33475
- function serializeWithColumns(source, columns) {
34091
+ function serializeWithColumns(source, columns, name) {
33476
34092
  if (source.length === 0) {
33477
34093
  throw new Error('Could not infer geometry type for collection of features.');
33478
34094
  }
@@ -33480,13 +34096,13 @@ ${svg}
33480
34096
  // type, but that type isn't known until every feature has been seen. Assume
33481
34097
  // the layer is homogeneous, which nearly all are, and encode again as
33482
34098
  // Unknown in the rare case that it isn't.
33483
- return encodeCollection(source, columns, null) ||
33484
- encodeCollection(source, columns, GeometryType.Unknown);
34099
+ return encodeCollection(source, columns, name, null) ||
34100
+ encodeCollection(source, columns, name, GeometryType.Unknown);
33485
34101
  }
33486
34102
 
33487
34103
  // Returns null if forcedType is null and the features turn out to have more
33488
34104
  // than one geometry type.
33489
- function encodeCollection(source, columns, forcedType) {
34105
+ function encodeCollection(source, columns, name, forcedType) {
33490
34106
  var headerMeta = null;
33491
34107
  var sink = null;
33492
34108
  // Null-geometry features encountered before the collection's geometry type is
@@ -33512,7 +34128,8 @@ ${svg}
33512
34128
  }
33513
34129
  var type = GeometryType[feature.geometry.type] || GeometryType.Unknown;
33514
34130
  if (!headerMeta) {
33515
- headerMeta = getEncodedHeaderMeta(forcedType === null ? type : forcedType, columns, source.length);
34131
+ headerMeta = getEncodedHeaderMeta(
34132
+ forcedType === null ? type : forcedType, columns, source.length, name);
33516
34133
  sink = initSink(source, headerMeta, deferred);
33517
34134
  } else if (forcedType === null && type != headerMeta.geometryType) {
33518
34135
  return null;
@@ -33525,15 +34142,16 @@ ${svg}
33525
34142
  }
33526
34143
  if (!sink) {
33527
34144
  // No feature had a geometry, so the collection has no geometry type.
33528
- headerMeta = getEncodedHeaderMeta(forcedType === null ? GeometryType.Unknown : forcedType,
33529
- columns, source.length);
34145
+ headerMeta = getEncodedHeaderMeta(
34146
+ forcedType === null ? GeometryType.Unknown : forcedType, columns, source.length, name);
33530
34147
  sink = initSink(source, headerMeta, deferred);
33531
34148
  }
33532
34149
  return sink.toBytes();
33533
34150
  }
33534
34151
 
33535
- function getEncodedHeaderMeta(geometryType, columns, featuresCount) {
34152
+ function getEncodedHeaderMeta(geometryType, columns, featuresCount, name) {
33536
34153
  return {
34154
+ name: name,
33537
34155
  geometryType: geometryType,
33538
34156
  columns: columns,
33539
34157
  envelope: null,
@@ -33549,7 +34167,7 @@ ${svg}
33549
34167
  function initSink(source, headerMeta, deferredFeatures) {
33550
34168
  var sink = new ByteSink(magicbytes.length + estimateEncodedBytes(source));
33551
34169
  sink.append(magicbytes);
33552
- sink.append(buildHeader(headerMeta));
34170
+ sink.append(buildHeaderWithCRS(headerMeta, null));
33553
34171
  for (var i = 0; i < deferredFeatures.length; i++) {
33554
34172
  sink.append(deferredFeatures[i]);
33555
34173
  }
@@ -33673,13 +34291,14 @@ ${svg}
33673
34291
  }
33674
34292
  var crsMeta = resolveOutputCRS(dataset);
33675
34293
  return dataset.layers.map(function(lyr) {
34294
+ var name = lyr.name || 'layer';
33676
34295
  var cursor = getFeatureCursor(lyr, dataset, opts, true);
33677
34296
  if (cursor.length === 0) {
33678
34297
  stop$1('FlatGeobuf export does not support empty layers');
33679
34298
  }
33680
34299
  var columns = getFlatGeobufColumns(lyr);
33681
- var content = serializeWithColumns(cursor, columns);
33682
- var filename = lyr.name + '.' + extension;
34300
+ var content = serializeWithColumns(cursor, columns, name);
34301
+ var filename = name + '.' + extension;
33683
34302
  if (crsMeta) {
33684
34303
  content = rewriteHeaderWithCRS(content, crsMeta);
33685
34304
  } else {
@@ -33920,6 +34539,8 @@ ${svg}
33920
34539
  var featureIndex = 0;
33921
34540
  for (var i = 0; i < state.count; i++) {
33922
34541
  var feat = exportFeatureAtIndex(state, i);
34542
+ // GeoPackage's GeoJSON insert path does not store true NULL geometries
34543
+ // (null becomes a NaN point). Skip them, matching prior behavior.
33923
34544
  if (!feat || !feat.geometry) continue;
33924
34545
  var normalized = normalizeFeature(feat, fields);
33925
34546
  try {
@@ -36017,7 +36638,8 @@ ${svg}
36017
36638
  datasets = [mergeDatasetsForExport(datasets)];
36018
36639
  }
36019
36640
  datasets.forEach(sortExportLayers);
36020
- files = await exportGeoPackage(datasets[0], opts);
36641
+ // GeoPackage bypasses exportFileContent(), where precision is normally applied.
36642
+ files = await exportGeoPackage(applyExportPrecision(datasets[0], opts), opts);
36021
36643
  } else if (format == 'geoparquet') {
36022
36644
  var layerCount = datasets.reduce(function(sum, d) {
36023
36645
  return sum + d.layers.length;
@@ -36026,7 +36648,8 @@ ${svg}
36026
36648
  files = [];
36027
36649
  for (var i = 0; i < datasets.length; i++) {
36028
36650
  sortExportLayers(datasets[i]);
36029
- files = files.concat(await exportGeoParquet(datasets[i], opts, singleFileName));
36651
+ // GeoParquet bypasses exportFileContent(), where precision is normally applied.
36652
+ files = files.concat(await exportGeoParquet(applyExportPrecision(datasets[i], opts), opts, singleFileName));
36030
36653
  }
36031
36654
  } else {
36032
36655
  files = datasets.reduce(function(memo, dataset) {
@@ -36080,6 +36703,15 @@ ${svg}
36080
36703
  });
36081
36704
  }
36082
36705
 
36706
+ // Apply -o precision= the same way exportFileContent() does for formats that
36707
+ // take their own export path (GeoPackage, GeoParquet).
36708
+ function applyExportPrecision(dataset, opts) {
36709
+ if (!opts.precision) return dataset;
36710
+ dataset = copyDatasetForExport(dataset);
36711
+ setCoordinatePrecision(dataset, opts.precision, !!opts.fix_geometry);
36712
+ return dataset;
36713
+ }
36714
+
36083
36715
  // Return an array of objects with 'filename' and 'content' members.
36084
36716
  //
36085
36717
  function exportFileContent(dataset, opts) {
@@ -39192,7 +39824,7 @@ ${svg}
39192
39824
  });
39193
39825
 
39194
39826
  parser.command('contours')
39195
- .describe('convert a raster layer to contour lines')
39827
+ .describe('convert a raster layer to contour lines or closed bands')
39196
39828
  .option('interval', {
39197
39829
  describe: 'spacing between contour levels (default is a round interval)',
39198
39830
  type: 'number'
@@ -39212,6 +39844,10 @@ ${svg}
39212
39844
  .option('field', {
39213
39845
  describe: 'name of field to hold contour values (default is "value")'
39214
39846
  })
39847
+ .option('closed', {
39848
+ describe: 'create closed contour bands with lower and upper fields',
39849
+ type: 'flag'
39850
+ })
39215
39851
  .option('no-smoothing', {
39216
39852
  describe: 'skip smoothing away the one-pixel contour staircase',
39217
39853
  type: 'flag'
@@ -39855,6 +40491,17 @@ ${svg}
39855
40491
  describe: 'create an interior point for each polygon\'s largest ring',
39856
40492
  type: 'flag'
39857
40493
  })
40494
+ .option('inner-method', {
40495
+ // describe: 'centroid2, pole, centroid, weighted or legacy (default is centroid2)'
40496
+ })
40497
+ .option('inner-tolerance', {
40498
+ // describe: 'clearance loss allowed by centroid methods (default is 10%)',
40499
+ type: 'percent'
40500
+ })
40501
+ .option('inner-weight', {
40502
+ // describe: 'centroid weight used by inner-method=weighted (default is 0.6)',
40503
+ type: 'number'
40504
+ })
39858
40505
  .option('centroid', {
39859
40506
  describe: 'create a centroid point for each polygon\'s largest ring',
39860
40507
  type: 'flag'
@@ -56663,6 +57310,10 @@ ${svg}
56663
57310
  getArcClassifier: getArcClassifier
56664
57311
  });
56665
57312
 
57313
+ // Buffer internals need a reliable interior probe, not a visually centered
57314
+ // label point. The bounded pole search avoids centroid2's second pass.
57315
+ var BUFFER_INNER_POINT_OPTS = {method: 'pole'};
57316
+
56666
57317
  function makePolygonBuffer(lyr, dataset, opts) {
56667
57318
  var spherical = isLatLngCRS(getDatasetCRS(dataset));
56668
57319
  if (spherical && sourceHasCollapsingBandEdge(lyr, dataset)) {
@@ -57059,7 +57710,7 @@ ${svg}
57059
57710
  }
57060
57711
  // hole: keep only if wide enough to hold a disk of radius keepRadius
57061
57712
  var holeShape = [reversePath(path.ids.concat())];
57062
- var anchor = findAnchorPoint(holeShape, arcs);
57713
+ var anchor = findAnchorPoint(holeShape, arcs, BUFFER_INNER_POINT_OPTS);
57063
57714
  var radius = anchor ?
57064
57715
  getPointToShapeDistance(anchor.x, anchor.y, holeShape, arcs) : 0;
57065
57716
  if (radius >= keepRadius) {
@@ -58530,7 +59181,7 @@ ${svg}
58530
59181
  function getTileAnchorPoint(tileId, mosaicIndex, ownerCtx) {
58531
59182
  if (tileId in ownerCtx.anchorCache) return ownerCtx.anchorCache[tileId];
58532
59183
  var tile = mosaicIndex.mosaic[tileId];
58533
- var p = findAnchorPoint(tile, ownerCtx.mosaicArcs) ||
59184
+ var p = findAnchorPoint(tile, ownerCtx.mosaicArcs, BUFFER_INNER_POINT_OPTS) ||
58534
59185
  getPathCentroid(tile[0], ownerCtx.mosaicArcs) || null;
58535
59186
  ownerCtx.anchorCache[tileId] = p;
58536
59187
  return p;
@@ -66082,6 +66733,312 @@ ${svg}
66082
66733
  // message('[comment]', opts.message);
66083
66734
  }; // no-op, so -comment doesn't trigger a parsing error
66084
66735
 
66736
+ // Close the existing directed contour paths against a fixed raster/nodata
66737
+ // boundary, polygonize the resulting network and dissolve its tiles by range.
66738
+ // This deliberately uses the isoline tracer's conservative validity rule: a
66739
+ // lattice cell is excluded when any of its four sample corners is invalid.
66740
+ function buildClosedContourDataset(contourDataset, grid, contours) {
66741
+ var domain = getContourCellDomain(grid, contours.band);
66742
+ var breaks = getClosedContourBreaks(contours.levels, contours.range);
66743
+ var boundaryDataset, dataset, contourLyr, nodes, arcInfo, mosaic;
66744
+ var tileLyr, outputLyr;
66745
+ if (breaks.length === 0 || domain.activeCount === 0) {
66746
+ return createEmptyClosedContourDataset(contourDataset.info);
66747
+ }
66748
+ boundaryDataset = importGeoJSON(getBoundaryGeoJSON(traceContourDomainRings(domain)), {});
66749
+ boundaryDataset.info = contourDataset.info;
66750
+ dataset = mergeDatasets([contourDataset, boundaryDataset]);
66751
+ contourLyr = dataset.layers[0];
66752
+ nodes = addIntersectionCuts(dataset, {no_snap: true});
66753
+ arcInfo = indexContourArcs(contourLyr);
66754
+ mosaic = buildPolygonMosaic(nodes).mosaic;
66755
+ tileLyr = createBandTileLayer(mosaic, dataset.arcs, arcInfo, domain, grid,
66756
+ contours.band, breaks);
66757
+ dataset.layers = [tileLyr];
66758
+ if (tileLyr.shapes.length === 0) {
66759
+ cleanArcReferences(dataset);
66760
+ return dataset;
66761
+ }
66762
+ outputLyr = dissolvePolygonLayer2(tileLyr, dataset, {
66763
+ fields: ['lower', 'upper'],
66764
+ quiet: true
66765
+ });
66766
+ dataset.layers = [outputLyr];
66767
+ cleanArcReferences(dataset);
66768
+ return dataset;
66769
+ }
66770
+
66771
+ function getClosedContourBreaks(levels, range) {
66772
+ if (!range || !isFinite(range.min) || !isFinite(range.max)) return [];
66773
+ if (range.min === range.max) return [range.min, range.max];
66774
+ return [range.min].concat(levels.filter(function(level) {
66775
+ return level > range.min && level < range.max;
66776
+ }), [range.max]);
66777
+ }
66778
+
66779
+ function getContourCellDomain(grid, band) {
66780
+ var W = grid.width;
66781
+ var H = grid.height;
66782
+ var cellW = W - 1;
66783
+ var cellH = H - 1;
66784
+ var samples = grid.samples;
66785
+ var bands = grid.bands;
66786
+ var valid = getRasterValidityMask(grid);
66787
+ var rowStride = W * bands;
66788
+ var active = new Uint8Array(Math.max(0, cellW * cellH));
66789
+ var activeCount = 0;
66790
+ var cx, cy, pixelId, off;
66791
+ for (cy = 0; cy < cellH; cy++) {
66792
+ for (cx = 0; cx < cellW; cx++) {
66793
+ pixelId = cy * W + cx;
66794
+ if (valid && (!valid[pixelId] || !valid[pixelId + 1] ||
66795
+ !valid[pixelId + W] || !valid[pixelId + W + 1])) continue;
66796
+ off = pixelId * bands + band;
66797
+ if (samples[off] !== samples[off] ||
66798
+ samples[off + bands] !== samples[off + bands] ||
66799
+ samples[off + rowStride] !== samples[off + rowStride] ||
66800
+ samples[off + rowStride + bands] !==
66801
+ samples[off + rowStride + bands]) continue;
66802
+ active[cy * cellW + cx] = 1;
66803
+ activeCount++;
66804
+ }
66805
+ }
66806
+ return {
66807
+ active: active,
66808
+ activeCount: activeCount,
66809
+ width: cellW,
66810
+ height: cellH,
66811
+ gridWidth: W,
66812
+ gridHeight: H,
66813
+ bbox: grid.bbox
66814
+ };
66815
+ }
66816
+
66817
+ function pointIsInContourDomain(x, y, domain) {
66818
+ var bbox = domain.bbox;
66819
+ var dx, dy, cx, cy;
66820
+ if (x < bbox[0] || x > bbox[2] || y < bbox[1] || y > bbox[3] ||
66821
+ domain.width < 1 || domain.height < 1) return false;
66822
+ dx = (bbox[2] - bbox[0]) / domain.gridWidth;
66823
+ dy = (bbox[3] - bbox[1]) / domain.gridHeight;
66824
+ cx = Math.floor((x - bbox[0]) / dx - 0.5);
66825
+ cy = Math.floor((bbox[3] - y) / dy - 0.5);
66826
+ if (cx < 0) cx = 0;
66827
+ if (cy < 0) cy = 0;
66828
+ if (cx >= domain.width) cx = domain.width - 1;
66829
+ if (cy >= domain.height) cy = domain.height - 1;
66830
+ return domain.active[cy * domain.width + cx] === 1;
66831
+ }
66832
+
66833
+ // Trace the union boundary of active contour cells. Segments are directed with
66834
+ // the active domain on their right, matching Mapshaper's clockwise outer-ring
66835
+ // convention. At a point-touch between diagonal components, taking the
66836
+ // rightmost continuation keeps the two rings separate.
66837
+ function traceContourDomainRings(domain) {
66838
+ var W = domain.width;
66839
+ var H = domain.height;
66840
+ var segments = [];
66841
+ var outgoing = new Map();
66842
+ var rings = [];
66843
+ var cx, cy, id, segment;
66844
+ function isActive(x, y) {
66845
+ return x >= 0 && x < W && y >= 0 && y < H &&
66846
+ domain.active[y * W + x] === 1;
66847
+ }
66848
+ function nodeId(x, y) {
66849
+ return y * (W + 1) + x;
66850
+ }
66851
+ function addSegment(x1, y1, x2, y2, dir) {
66852
+ var seg = {
66853
+ from: nodeId(x1, y1),
66854
+ to: nodeId(x2, y2),
66855
+ dir: dir,
66856
+ visited: false
66857
+ };
66858
+ segments.push(seg);
66859
+ if (!outgoing.has(seg.from)) outgoing.set(seg.from, []);
66860
+ outgoing.get(seg.from).push(seg);
66861
+ }
66862
+ for (cy = 0; cy < H; cy++) {
66863
+ for (cx = 0; cx < W; cx++) {
66864
+ if (!isActive(cx, cy)) continue;
66865
+ if (!isActive(cx, cy - 1)) addSegment(cx, cy, cx + 1, cy, 0);
66866
+ if (!isActive(cx + 1, cy)) addSegment(cx + 1, cy, cx + 1, cy + 1, 1);
66867
+ if (!isActive(cx, cy + 1)) addSegment(cx + 1, cy + 1, cx, cy + 1, 2);
66868
+ if (!isActive(cx - 1, cy)) addSegment(cx, cy + 1, cx, cy, 3);
66869
+ }
66870
+ }
66871
+ for (id = 0; id < segments.length; id++) {
66872
+ segment = segments[id];
66873
+ if (!segment.visited) rings.push(followBoundary(segment, outgoing, domain));
66874
+ }
66875
+ return rings;
66876
+ }
66877
+
66878
+ function followBoundary(first, outgoing, domain) {
66879
+ var ring = [getDomainNodeCoords(first.from, domain)];
66880
+ var current = first;
66881
+ var candidates, next;
66882
+ while (current && !current.visited) {
66883
+ current.visited = true;
66884
+ ring.push(getDomainNodeCoords(current.to, domain));
66885
+ candidates = outgoing.get(current.to) || [];
66886
+ next = chooseBoundaryContinuation(current, candidates);
66887
+ current = next;
66888
+ }
66889
+ return ring;
66890
+ }
66891
+
66892
+ function chooseBoundaryContinuation(current, candidates) {
66893
+ var priorities = [1, 0, 3, 2];
66894
+ var i, j, candidate, turn;
66895
+ for (i = 0; i < priorities.length; i++) {
66896
+ for (j = 0; j < candidates.length; j++) {
66897
+ candidate = candidates[j];
66898
+ if (candidate.visited) continue;
66899
+ turn = (candidate.dir - current.dir + 4) % 4;
66900
+ if (turn === priorities[i]) return candidate;
66901
+ }
66902
+ }
66903
+ return null;
66904
+ }
66905
+
66906
+ function getDomainNodeCoords(id, domain) {
66907
+ var W = domain.width;
66908
+ var xId = id % (W + 1);
66909
+ var yId = Math.floor(id / (W + 1));
66910
+ var bbox = domain.bbox;
66911
+ var dx = (bbox[2] - bbox[0]) / domain.gridWidth;
66912
+ var dy = (bbox[3] - bbox[1]) / domain.gridHeight;
66913
+ var x = xId === 0 ? bbox[0] :
66914
+ xId === W ? bbox[2] : bbox[0] + (xId + 0.5) * dx;
66915
+ var y = yId === 0 ? bbox[3] :
66916
+ yId === domain.height ? bbox[1] : bbox[3] - (yId + 0.5) * dy;
66917
+ return [x, y];
66918
+ }
66919
+
66920
+ function getBoundaryGeoJSON(rings) {
66921
+ return {
66922
+ type: 'FeatureCollection',
66923
+ features: rings.map(function(coords) {
66924
+ return {
66925
+ type: 'Feature',
66926
+ properties: {},
66927
+ geometry: {type: 'LineString', coordinates: coords}
66928
+ };
66929
+ })
66930
+ };
66931
+ }
66932
+
66933
+ function indexContourArcs(lyr) {
66934
+ var records = lyr.data ? lyr.data.getRecords() : [];
66935
+ var index = new Map();
66936
+ lyr.shapes.forEach(function(shape, shapeId) {
66937
+ var level = records[shapeId] && records[shapeId].value;
66938
+ if (!shape || typeof level != 'number') return;
66939
+ shape.forEach(function(path) {
66940
+ path.forEach(function(arcId) {
66941
+ var absId = arcId < 0 ? ~arcId : arcId;
66942
+ index.set(absId, {
66943
+ level: level,
66944
+ direction: arcId < 0 ? -1 : 1
66945
+ });
66946
+ });
66947
+ });
66948
+ });
66949
+ return index;
66950
+ }
66951
+
66952
+ function createBandTileLayer(mosaic, arcs, arcInfo, domain, grid, band, breaks) {
66953
+ var shapes = [];
66954
+ var records = [];
66955
+ mosaic.forEach(function(tile) {
66956
+ var anchor = findAnchorPoint(tile, arcs, {});
66957
+ var bounds;
66958
+ if (!anchor || !pointIsInContourDomain(anchor.x, anchor.y, domain)) return;
66959
+ bounds = getTileBandBounds(tile, arcInfo, breaks, grid, band, anchor);
66960
+ if (!bounds) return;
66961
+ shapes.push(tile);
66962
+ records.push(bounds);
66963
+ });
66964
+ return {
66965
+ geometry_type: 'polygon',
66966
+ shapes: shapes,
66967
+ data: new DataTable(records)
66968
+ };
66969
+ }
66970
+
66971
+ function getTileBandBounds(tile, arcInfo, breaks, grid, band, anchor) {
66972
+ var lower = breaks[0];
66973
+ var upper = breaks[breaks.length - 1];
66974
+ var sawLower = false;
66975
+ var sawUpper = false;
66976
+ tile.forEach(function(path) {
66977
+ path.forEach(function(arcId) {
66978
+ var absId = arcId < 0 ? ~arcId : arcId;
66979
+ var info = arcInfo.get(absId);
66980
+ var direction;
66981
+ if (!info) return;
66982
+ direction = arcId < 0 ? -1 : 1;
66983
+ // Contour paths are directed with the above-level side on their left.
66984
+ // Polygon rings keep their interior on the right, so following a contour
66985
+ // arc in its original direction means this tile is below that level.
66986
+ if (direction === info.direction) {
66987
+ if (!sawUpper || info.level < upper) upper = info.level;
66988
+ sawUpper = true;
66989
+ } else {
66990
+ if (!sawLower || info.level > lower) lower = info.level;
66991
+ sawLower = true;
66992
+ }
66993
+ });
66994
+ });
66995
+ if (areAdjacentBreaks(lower, upper, breaks)) {
66996
+ return {lower: lower, upper: upper};
66997
+ }
66998
+ return getSampleBandBounds(grid, band, anchor.x, anchor.y, breaks);
66999
+ }
67000
+
67001
+ function areAdjacentBreaks(lower, upper, breaks) {
67002
+ var i = breaks.indexOf(lower);
67003
+ return i > -1 && i + 1 < breaks.length && breaks[i + 1] === upper;
67004
+ }
67005
+
67006
+ function getSampleBandBounds(grid, band, x, y, breaks) {
67007
+ var bbox = grid.bbox;
67008
+ var col = Math.floor((x - bbox[0]) / (bbox[2] - bbox[0]) * grid.width);
67009
+ var row = Math.floor((bbox[3] - y) / (bbox[3] - bbox[1]) * grid.height);
67010
+ var value, i;
67011
+ if (col < 0) col = 0;
67012
+ if (row < 0) row = 0;
67013
+ if (col >= grid.width) col = grid.width - 1;
67014
+ if (row >= grid.height) row = grid.height - 1;
67015
+ value = grid.samples[(row * grid.width + col) * grid.bands + band];
67016
+ if (value !== value) return null;
67017
+ i = 0;
67018
+ while (i + 2 < breaks.length && value >= breaks[i + 1]) i++;
67019
+ return {lower: breaks[i], upper: breaks[i + 1]};
67020
+ }
67021
+
67022
+ function createEmptyClosedContourDataset(info) {
67023
+ return {
67024
+ info: info || {},
67025
+ layers: [{
67026
+ geometry_type: 'polygon',
67027
+ shapes: [],
67028
+ data: new DataTable([])
67029
+ }]
67030
+ };
67031
+ }
67032
+
67033
+ var RasterContourBands = /*#__PURE__*/Object.freeze({
67034
+ __proto__: null,
67035
+ buildClosedContourDataset: buildClosedContourDataset,
67036
+ getClosedContourBreaks: getClosedContourBreaks,
67037
+ getContourCellDomain: getContourCellDomain,
67038
+ pointIsInContourDomain: pointIsInContourDomain,
67039
+ traceContourDomainRings: traceContourDomainRings
67040
+ });
67041
+
66085
67042
  // Trace contour lines (isolines) from a raster layer's working samples using
66086
67043
  // marching squares.
66087
67044
  //
@@ -66174,13 +67131,17 @@ ${svg}
66174
67131
 
66175
67132
  function getRasterContourLines(raster, opts) {
66176
67133
  var grid = getRasterGrid(raster);
66177
- var band, levels;
67134
+ var band, levels, range;
66178
67135
  validateRasterGridForContours(grid);
66179
67136
  band = getContourBand(grid, opts);
66180
67137
  levels = getContourLevels(grid, band, opts);
67138
+ range = getRasterSampleRange(grid, band);
66181
67139
  return {
67140
+ band: band,
67141
+ range: range,
66182
67142
  levels: levels,
66183
- lines: traceRasterContours(grid, band, levels, getCornerClearance(opts))
67143
+ lines: traceRasterContours(grid, band, levels, getCornerClearance(opts),
67144
+ {extend_to_bbox: opts && opts.closed})
66184
67145
  };
66185
67146
  }
66186
67147
 
@@ -66319,7 +67280,7 @@ ${svg}
66319
67280
  }
66320
67281
 
66321
67282
  // clearance: see CORNER_CLEARANCE; 0 places crossings exactly.
66322
- function traceRasterContours(grid, band, levels, clearance) {
67283
+ function traceRasterContours(grid, band, levels, clearance, opts) {
66323
67284
  var segments = collectContourSegments(grid, band, levels);
66324
67285
  var toMapXY = getLatticeToMapTransform(grid);
66325
67286
  var hCount = (grid.width - 1) * grid.height;
@@ -66329,6 +67290,9 @@ ${svg}
66329
67290
  stitchContourSegments(levelSegments).forEach(function(path) {
66330
67291
  var coords = getPathCoords(path, grid, band, levels[i], hCount, toMapXY,
66331
67292
  clearance);
67293
+ if (opts && opts.extend_to_bbox) {
67294
+ coords = extendContourPathToBBox(coords, path, grid, hCount);
67295
+ }
66332
67296
  if (coords.length > 1) {
66333
67297
  lines.push({value: levels[i], coords: coords});
66334
67298
  }
@@ -66337,6 +67301,39 @@ ${svg}
66337
67301
  return lines;
66338
67302
  }
66339
67303
 
67304
+ // Closed contour bands use the raster bbox as their outer frame. Open contour
67305
+ // paths that reach the outer sample-center lattice are extended through the
67306
+ // half-pixel margin to that frame. Endpoints created by skipped nodata cells
67307
+ // lie on interior lattice edges and are intentionally left in place.
67308
+ function extendContourPathToBBox(coords, path, grid, hCount) {
67309
+ if (path.length < 2 || path[0] === path[path.length - 1]) return coords;
67310
+ var first = getOuterEdgePoint(coords[0], path[0], grid, hCount);
67311
+ var last = getOuterEdgePoint(coords[coords.length - 1],
67312
+ path[path.length - 1], grid, hCount);
67313
+ if (first) coords.unshift(first);
67314
+ if (last) coords.push(last);
67315
+ return coords;
67316
+ }
67317
+
67318
+ function getOuterEdgePoint(xy, edgeId, grid, hCount) {
67319
+ var W = grid.width;
67320
+ var H = grid.height;
67321
+ var bbox = grid.bbox;
67322
+ var x, y;
67323
+ if (edgeId < hCount) {
67324
+ y = Math.floor(edgeId / (W - 1));
67325
+ if (y === 0) return [xy[0], bbox[3]];
67326
+ if (y === H - 1) return [xy[0], bbox[1]];
67327
+ return null;
67328
+ }
67329
+ edgeId -= hCount;
67330
+ y = Math.floor(edgeId / W);
67331
+ x = edgeId - y * W;
67332
+ if (x === 0) return [bbox[0], xy[1]];
67333
+ if (x === W - 1) return [bbox[2], xy[1]];
67334
+ return null;
67335
+ }
67336
+
66340
67337
  // Visit each cell once and march it for every level that falls inside its
66341
67338
  // value range, rather than scanning the whole grid once per level.
66342
67339
  function collectContourSegments(grid, band, levels) {
@@ -66557,6 +67554,21 @@ ${svg}
66557
67554
  // Contours come from the layer's working samples, so they reflect any
66558
67555
  // earlier edits (e.g. -blur, -clip) rather than the original source pixels.
66559
67556
  contours = getRasterContourLines(targetLyr.raster, opts);
67557
+ if (opts.closed) {
67558
+ dataset = contours.lines.length > 0 ?
67559
+ importGeoJSON(getContoursGeoJSON(contours.lines, DEFAULT_CONTOUR_FIELD), {}) :
67560
+ createEmptyContourDataset();
67561
+ setDatasetCrsInfo(dataset, getDatasetCrsInfo(targetDataset));
67562
+ if (contours.lines.length > 0 && !opts.no_smoothing) {
67563
+ smoothContourDataset(dataset, getRasterGrid(targetLyr.raster));
67564
+ }
67565
+ dataset = buildClosedContourDataset(dataset, getRasterGrid(targetLyr.raster),
67566
+ contours);
67567
+ outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
67568
+ setOutputLayerName(outputLayers[0], targetLyr, 'contours', opts);
67569
+ message(getClosedContoursMessage(outputLayers[0], contours));
67570
+ return outputLayers;
67571
+ }
66560
67572
  message(getContoursMessage(contours));
66561
67573
  if (contours.lines.length === 0) {
66562
67574
  return [createEmptyContourLayer(targetLyr, opts)];
@@ -66640,6 +67652,17 @@ ${svg}
66640
67652
  return lyr;
66641
67653
  }
66642
67654
 
67655
+ function createEmptyContourDataset() {
67656
+ return {
67657
+ info: {},
67658
+ layers: [{
67659
+ geometry_type: 'polyline',
67660
+ shapes: [],
67661
+ data: new DataTable([])
67662
+ }]
67663
+ };
67664
+ }
67665
+
66643
67666
  function getContoursMessage(contours) {
66644
67667
  var levels = contours.levels;
66645
67668
  if (levels.length === 0) {
@@ -66651,6 +67674,16 @@ ${svg}
66651
67674
  ' (' + levels[0] + ' to ' + levels[levels.length - 1] + ')';
66652
67675
  }
66653
67676
 
67677
+ function getClosedContoursMessage(lyr, contours) {
67678
+ var count = lyr.shapes.length;
67679
+ if (count === 0) {
67680
+ return 'No closed contour bands were generated';
67681
+ }
67682
+ return 'Created ' + count + ' closed contour ' +
67683
+ (count === 1 ? 'band' : 'bands') + ' from ' + contours.levels.length +
67684
+ (contours.levels.length === 1 ? ' level' : ' levels');
67685
+ }
67686
+
66654
67687
  cmd.dashlines = function(lyr, dataset, opts) {
66655
67688
  var crs = getDatasetCRS(dataset);
66656
67689
  var defs = getStashedVar('defs');
@@ -72267,13 +73300,33 @@ ${svg}
72267
73300
  }
72268
73301
 
72269
73302
  function pointsFromPolygons(lyr, arcs, opts) {
72270
- var func = opts.inner ? findAnchorPoint : geom.getShapeCentroid;
73303
+ var innerOpts = opts.inner ? getInnerPointOptions(opts) : null;
72271
73304
  return lyr.shapes.map(function(shp) {
72272
- var p = func(shp, arcs);
73305
+ var p = innerOpts ?
73306
+ findAnchorPoint(shp, arcs, innerOpts) : geom.getShapeCentroid(shp, arcs);
72273
73307
  return p ? [[p.x, p.y]] : null;
72274
73308
  });
72275
73309
  }
72276
73310
 
73311
+ function getInnerPointOptions(opts) {
73312
+ var method = opts.inner_method || 'centroid2';
73313
+ var methods = ['legacy', 'pole', 'centroid', 'centroid2', 'weighted'];
73314
+ if (methods.indexOf(method) == -1) {
73315
+ stop$1('Unsupported inner point method:', method);
73316
+ }
73317
+ var tolerance = opts.inner_tolerance === undefined ?
73318
+ 0.1 : parsePercent(opts.inner_tolerance);
73319
+ var weight = opts.inner_weight === undefined ? 0.6 : opts.inner_weight;
73320
+ if (!(weight >= 0)) {
73321
+ stop$1('Expected inner-weight to be a non-negative number');
73322
+ }
73323
+ return {
73324
+ method: method,
73325
+ tolerance: tolerance,
73326
+ weight: weight
73327
+ };
73328
+ }
73329
+
72277
73330
  function coordinateFromValue(val) {
72278
73331
  var tmp;
72279
73332
  if (utils.isFiniteNumber(val)) {
@@ -72407,10 +73460,14 @@ ${svg}
72407
73460
  }
72408
73461
 
72409
73462
  function pointsFromPolygonsForJoin(lyr, dataset) {
72410
- // TODO use faster method to get inner points
72411
73463
  return {
72412
73464
  geometry_type: 'point',
72413
- shapes: pointsFromPolygons(lyr, dataset.arcs, {inner: true}),
73465
+ // Joins only require a reliable interior point; centroid bias adds a
73466
+ // second search without improving the containment test.
73467
+ shapes: pointsFromPolygons(lyr, dataset.arcs, {
73468
+ inner: true,
73469
+ inner_method: 'pole'
73470
+ }),
72414
73471
  data: lyr.data // TODO copy if needed
72415
73472
  };
72416
73473
  }
@@ -72455,85 +73512,6 @@ ${svg}
72455
73512
  return retn;
72456
73513
  }
72457
73514
 
72458
- class TinyQueue {
72459
- constructor(data = [], compare = defaultCompare) {
72460
- this.data = data;
72461
- this.length = this.data.length;
72462
- this.compare = compare;
72463
-
72464
- if (this.length > 0) {
72465
- for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
72466
- }
72467
- }
72468
-
72469
- push(item) {
72470
- this.data.push(item);
72471
- this.length++;
72472
- this._up(this.length - 1);
72473
- }
72474
-
72475
- pop() {
72476
- if (this.length === 0) return undefined;
72477
-
72478
- const top = this.data[0];
72479
- const bottom = this.data.pop();
72480
- this.length--;
72481
-
72482
- if (this.length > 0) {
72483
- this.data[0] = bottom;
72484
- this._down(0);
72485
- }
72486
-
72487
- return top;
72488
- }
72489
-
72490
- peek() {
72491
- return this.data[0];
72492
- }
72493
-
72494
- _up(pos) {
72495
- const {data, compare} = this;
72496
- const item = data[pos];
72497
-
72498
- while (pos > 0) {
72499
- const parent = (pos - 1) >> 1;
72500
- const current = data[parent];
72501
- if (compare(item, current) >= 0) break;
72502
- data[pos] = current;
72503
- pos = parent;
72504
- }
72505
-
72506
- data[pos] = item;
72507
- }
72508
-
72509
- _down(pos) {
72510
- const {data, compare} = this;
72511
- const halfLength = this.length >> 1;
72512
- const item = data[pos];
72513
-
72514
- while (pos < halfLength) {
72515
- let left = (pos << 1) + 1;
72516
- let best = data[left];
72517
- const right = left + 1;
72518
-
72519
- if (right < this.length && compare(data[right], best) < 0) {
72520
- left = right;
72521
- best = data[right];
72522
- }
72523
- if (compare(best, item) >= 0) break;
72524
-
72525
- data[pos] = best;
72526
- pos = left;
72527
- }
72528
-
72529
- data[pos] = item;
72530
- }
72531
- }
72532
-
72533
- function defaultCompare(a, b) {
72534
- return a < b ? -1 : a > b ? 1 : 0;
72535
- }
72536
-
72537
73515
  /*
72538
73516
  ISC License
72539
73517
 
@@ -76178,6 +77156,16 @@ ${svg}
76178
77156
  stopJob(job);
76179
77157
  };
76180
77158
 
77159
+ // The icon names accepted by the icon= property of the -style command.
77160
+ // Kept in a module of its own so that both the renderer (svg-symbols.mjs) and
77161
+ // the -style command can use it without forming an import cycle.
77162
+
77163
+ var iconNames = ['circle', 'square', 'ring', 'star'];
77164
+
77165
+ function isSupportedIconName(name) {
77166
+ return iconNames.indexOf(name) > -1;
77167
+ }
77168
+
76181
77169
  cmd.svgStyle = function(lyr, dataset, opts) {
76182
77170
  var filterFn, table, fields, hasNewFields;
76183
77171
  if (getFeatureCount(lyr) === 0) {
@@ -76214,12 +77202,16 @@ ${svg}
76214
77202
  }
76215
77203
  var strVal = opts[optName].trim();
76216
77204
  var accessor = getSymbolPropertyAccessor(strVal, svgName, lyr);
77205
+ var badIcons = svgName == 'icon' ? [] : null;
76217
77206
  table.getRecords().forEach(function(rec, i) {
76218
77207
  if (filterFn && !filterFn(i)) {
76219
77208
  // make sure field exists if record is excluded by filter
76220
77209
  setUndefinedFields(rec, svgName == 'label-pos' ? labelPositionFields : [svgName]);
76221
77210
  } else {
76222
77211
  rec[svgName] = accessor(i);
77212
+ if (badIcons) {
77213
+ addUnsupportedIconName(badIcons, rec.icon);
77214
+ }
76223
77215
  if (svgName == 'label-pos') {
76224
77216
  if (!setLabelPositionStyle(rec, rec['label-pos'])) {
76225
77217
  stop$1('Unexpected value for label-pos:', rec['label-pos']);
@@ -76227,6 +77219,9 @@ ${svg}
76227
77219
  }
76228
77220
  }
76229
77221
  });
77222
+ if (badIcons && badIcons.length > 0) {
77223
+ warn(formatUnsupportedIconMessage(badIcons));
77224
+ }
76230
77225
  });
76231
77226
  if (fields.length > 0) {
76232
77227
  if (hasNewFields) {
@@ -76258,6 +77253,32 @@ ${svg}
76258
77253
  }
76259
77254
  }
76260
77255
 
77256
+ // Icon names can not be validated when options are parsed, because an icon=
77257
+ // value may be a field name or an expression. The assigned values are checked
77258
+ // instead, so that computed names are covered too. Unsupported names are a
77259
+ // warning, not an error -- features with an unsupported name render without an
77260
+ // icon.
77261
+ var maxReportedIconNames = 4;
77262
+
77263
+ function addUnsupportedIconName(names, val) {
77264
+ var name;
77265
+ if (!val) return; // a blank value removes the icon
77266
+ name = String(val);
77267
+ if (isSupportedIconName(name) || names.indexOf(name) > -1) return;
77268
+ names.push(name);
77269
+ }
77270
+
77271
+ function formatUnsupportedIconMessage(names) {
77272
+ var extra = names.length - maxReportedIconNames;
77273
+ var listed = extra > 0 ? names.slice(0, maxReportedIconNames) : names;
77274
+ var str = 'Unsupported icon ' + (names.length > 1 ? 'names' : 'name') + ': ' +
77275
+ listed.join(', ');
77276
+ if (extra > 0) {
77277
+ str += ' (and ' + extra + ' more)';
77278
+ }
77279
+ return str + '. Expected one of: ' + iconNames.join(', ');
77280
+ }
77281
+
76261
77282
  function setUndefinedFields(rec, fields) {
76262
77283
  fields.forEach(function(field) {
76263
77284
  if (field in rec === false) {
@@ -77949,7 +78970,7 @@ ${svg}
77949
78970
  return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
77950
78971
  }
77951
78972
 
77952
- var version = "0.7.53";
78973
+ var version = "0.7.55";
77953
78974
 
77954
78975
  // Parse command line args into commands and run them
77955
78976
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
@@ -80100,6 +81121,7 @@ ${svg}
80100
81121
  Logging,
80101
81122
  Profile,
80102
81123
  RasterBlur,
81124
+ RasterContourBands,
80103
81125
  RasterContours,
80104
81126
  RasterGrid,
80105
81127
  RasterReprojection,