mapshaper 0.7.53 → 0.7.54
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 +1121 -104
- package/package.json +1 -1
- package/www/mapshaper.js +1121 -104
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$
|
|
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$
|
|
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$
|
|
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
|
|
18901
|
-
//
|
|
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 ||
|
|
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
|
|
|
@@ -33920,6 +34534,8 @@ ${svg}
|
|
|
33920
34534
|
var featureIndex = 0;
|
|
33921
34535
|
for (var i = 0; i < state.count; i++) {
|
|
33922
34536
|
var feat = exportFeatureAtIndex(state, i);
|
|
34537
|
+
// GeoPackage's GeoJSON insert path does not store true NULL geometries
|
|
34538
|
+
// (null becomes a NaN point). Skip them, matching prior behavior.
|
|
33923
34539
|
if (!feat || !feat.geometry) continue;
|
|
33924
34540
|
var normalized = normalizeFeature(feat, fields);
|
|
33925
34541
|
try {
|
|
@@ -36017,7 +36633,8 @@ ${svg}
|
|
|
36017
36633
|
datasets = [mergeDatasetsForExport(datasets)];
|
|
36018
36634
|
}
|
|
36019
36635
|
datasets.forEach(sortExportLayers);
|
|
36020
|
-
|
|
36636
|
+
// GeoPackage bypasses exportFileContent(), where precision is normally applied.
|
|
36637
|
+
files = await exportGeoPackage(applyExportPrecision(datasets[0], opts), opts);
|
|
36021
36638
|
} else if (format == 'geoparquet') {
|
|
36022
36639
|
var layerCount = datasets.reduce(function(sum, d) {
|
|
36023
36640
|
return sum + d.layers.length;
|
|
@@ -36026,7 +36643,8 @@ ${svg}
|
|
|
36026
36643
|
files = [];
|
|
36027
36644
|
for (var i = 0; i < datasets.length; i++) {
|
|
36028
36645
|
sortExportLayers(datasets[i]);
|
|
36029
|
-
|
|
36646
|
+
// GeoParquet bypasses exportFileContent(), where precision is normally applied.
|
|
36647
|
+
files = files.concat(await exportGeoParquet(applyExportPrecision(datasets[i], opts), opts, singleFileName));
|
|
36030
36648
|
}
|
|
36031
36649
|
} else {
|
|
36032
36650
|
files = datasets.reduce(function(memo, dataset) {
|
|
@@ -36080,6 +36698,15 @@ ${svg}
|
|
|
36080
36698
|
});
|
|
36081
36699
|
}
|
|
36082
36700
|
|
|
36701
|
+
// Apply -o precision= the same way exportFileContent() does for formats that
|
|
36702
|
+
// take their own export path (GeoPackage, GeoParquet).
|
|
36703
|
+
function applyExportPrecision(dataset, opts) {
|
|
36704
|
+
if (!opts.precision) return dataset;
|
|
36705
|
+
dataset = copyDatasetForExport(dataset);
|
|
36706
|
+
setCoordinatePrecision(dataset, opts.precision, !!opts.fix_geometry);
|
|
36707
|
+
return dataset;
|
|
36708
|
+
}
|
|
36709
|
+
|
|
36083
36710
|
// Return an array of objects with 'filename' and 'content' members.
|
|
36084
36711
|
//
|
|
36085
36712
|
function exportFileContent(dataset, opts) {
|
|
@@ -39192,7 +39819,7 @@ ${svg}
|
|
|
39192
39819
|
});
|
|
39193
39820
|
|
|
39194
39821
|
parser.command('contours')
|
|
39195
|
-
.describe('convert a raster layer to contour lines')
|
|
39822
|
+
.describe('convert a raster layer to contour lines or closed bands')
|
|
39196
39823
|
.option('interval', {
|
|
39197
39824
|
describe: 'spacing between contour levels (default is a round interval)',
|
|
39198
39825
|
type: 'number'
|
|
@@ -39212,6 +39839,10 @@ ${svg}
|
|
|
39212
39839
|
.option('field', {
|
|
39213
39840
|
describe: 'name of field to hold contour values (default is "value")'
|
|
39214
39841
|
})
|
|
39842
|
+
.option('closed', {
|
|
39843
|
+
describe: 'create closed contour bands with lower and upper fields',
|
|
39844
|
+
type: 'flag'
|
|
39845
|
+
})
|
|
39215
39846
|
.option('no-smoothing', {
|
|
39216
39847
|
describe: 'skip smoothing away the one-pixel contour staircase',
|
|
39217
39848
|
type: 'flag'
|
|
@@ -39855,6 +40486,17 @@ ${svg}
|
|
|
39855
40486
|
describe: 'create an interior point for each polygon\'s largest ring',
|
|
39856
40487
|
type: 'flag'
|
|
39857
40488
|
})
|
|
40489
|
+
.option('inner-method', {
|
|
40490
|
+
// describe: 'centroid2, pole, centroid, weighted or legacy (default is centroid2)'
|
|
40491
|
+
})
|
|
40492
|
+
.option('inner-tolerance', {
|
|
40493
|
+
// describe: 'clearance loss allowed by centroid methods (default is 10%)',
|
|
40494
|
+
type: 'percent'
|
|
40495
|
+
})
|
|
40496
|
+
.option('inner-weight', {
|
|
40497
|
+
// describe: 'centroid weight used by inner-method=weighted (default is 0.6)',
|
|
40498
|
+
type: 'number'
|
|
40499
|
+
})
|
|
39858
40500
|
.option('centroid', {
|
|
39859
40501
|
describe: 'create a centroid point for each polygon\'s largest ring',
|
|
39860
40502
|
type: 'flag'
|
|
@@ -56663,6 +57305,10 @@ ${svg}
|
|
|
56663
57305
|
getArcClassifier: getArcClassifier
|
|
56664
57306
|
});
|
|
56665
57307
|
|
|
57308
|
+
// Buffer internals need a reliable interior probe, not a visually centered
|
|
57309
|
+
// label point. The bounded pole search avoids centroid2's second pass.
|
|
57310
|
+
var BUFFER_INNER_POINT_OPTS = {method: 'pole'};
|
|
57311
|
+
|
|
56666
57312
|
function makePolygonBuffer(lyr, dataset, opts) {
|
|
56667
57313
|
var spherical = isLatLngCRS(getDatasetCRS(dataset));
|
|
56668
57314
|
if (spherical && sourceHasCollapsingBandEdge(lyr, dataset)) {
|
|
@@ -57059,7 +57705,7 @@ ${svg}
|
|
|
57059
57705
|
}
|
|
57060
57706
|
// hole: keep only if wide enough to hold a disk of radius keepRadius
|
|
57061
57707
|
var holeShape = [reversePath(path.ids.concat())];
|
|
57062
|
-
var anchor = findAnchorPoint(holeShape, arcs);
|
|
57708
|
+
var anchor = findAnchorPoint(holeShape, arcs, BUFFER_INNER_POINT_OPTS);
|
|
57063
57709
|
var radius = anchor ?
|
|
57064
57710
|
getPointToShapeDistance(anchor.x, anchor.y, holeShape, arcs) : 0;
|
|
57065
57711
|
if (radius >= keepRadius) {
|
|
@@ -58530,7 +59176,7 @@ ${svg}
|
|
|
58530
59176
|
function getTileAnchorPoint(tileId, mosaicIndex, ownerCtx) {
|
|
58531
59177
|
if (tileId in ownerCtx.anchorCache) return ownerCtx.anchorCache[tileId];
|
|
58532
59178
|
var tile = mosaicIndex.mosaic[tileId];
|
|
58533
|
-
var p = findAnchorPoint(tile, ownerCtx.mosaicArcs) ||
|
|
59179
|
+
var p = findAnchorPoint(tile, ownerCtx.mosaicArcs, BUFFER_INNER_POINT_OPTS) ||
|
|
58534
59180
|
getPathCentroid(tile[0], ownerCtx.mosaicArcs) || null;
|
|
58535
59181
|
ownerCtx.anchorCache[tileId] = p;
|
|
58536
59182
|
return p;
|
|
@@ -66082,6 +66728,312 @@ ${svg}
|
|
|
66082
66728
|
// message('[comment]', opts.message);
|
|
66083
66729
|
}; // no-op, so -comment doesn't trigger a parsing error
|
|
66084
66730
|
|
|
66731
|
+
// Close the existing directed contour paths against a fixed raster/nodata
|
|
66732
|
+
// boundary, polygonize the resulting network and dissolve its tiles by range.
|
|
66733
|
+
// This deliberately uses the isoline tracer's conservative validity rule: a
|
|
66734
|
+
// lattice cell is excluded when any of its four sample corners is invalid.
|
|
66735
|
+
function buildClosedContourDataset(contourDataset, grid, contours) {
|
|
66736
|
+
var domain = getContourCellDomain(grid, contours.band);
|
|
66737
|
+
var breaks = getClosedContourBreaks(contours.levels, contours.range);
|
|
66738
|
+
var boundaryDataset, dataset, contourLyr, nodes, arcInfo, mosaic;
|
|
66739
|
+
var tileLyr, outputLyr;
|
|
66740
|
+
if (breaks.length === 0 || domain.activeCount === 0) {
|
|
66741
|
+
return createEmptyClosedContourDataset(contourDataset.info);
|
|
66742
|
+
}
|
|
66743
|
+
boundaryDataset = importGeoJSON(getBoundaryGeoJSON(traceContourDomainRings(domain)), {});
|
|
66744
|
+
boundaryDataset.info = contourDataset.info;
|
|
66745
|
+
dataset = mergeDatasets([contourDataset, boundaryDataset]);
|
|
66746
|
+
contourLyr = dataset.layers[0];
|
|
66747
|
+
nodes = addIntersectionCuts(dataset, {no_snap: true});
|
|
66748
|
+
arcInfo = indexContourArcs(contourLyr);
|
|
66749
|
+
mosaic = buildPolygonMosaic(nodes).mosaic;
|
|
66750
|
+
tileLyr = createBandTileLayer(mosaic, dataset.arcs, arcInfo, domain, grid,
|
|
66751
|
+
contours.band, breaks);
|
|
66752
|
+
dataset.layers = [tileLyr];
|
|
66753
|
+
if (tileLyr.shapes.length === 0) {
|
|
66754
|
+
cleanArcReferences(dataset);
|
|
66755
|
+
return dataset;
|
|
66756
|
+
}
|
|
66757
|
+
outputLyr = dissolvePolygonLayer2(tileLyr, dataset, {
|
|
66758
|
+
fields: ['lower', 'upper'],
|
|
66759
|
+
quiet: true
|
|
66760
|
+
});
|
|
66761
|
+
dataset.layers = [outputLyr];
|
|
66762
|
+
cleanArcReferences(dataset);
|
|
66763
|
+
return dataset;
|
|
66764
|
+
}
|
|
66765
|
+
|
|
66766
|
+
function getClosedContourBreaks(levels, range) {
|
|
66767
|
+
if (!range || !isFinite(range.min) || !isFinite(range.max)) return [];
|
|
66768
|
+
if (range.min === range.max) return [range.min, range.max];
|
|
66769
|
+
return [range.min].concat(levels.filter(function(level) {
|
|
66770
|
+
return level > range.min && level < range.max;
|
|
66771
|
+
}), [range.max]);
|
|
66772
|
+
}
|
|
66773
|
+
|
|
66774
|
+
function getContourCellDomain(grid, band) {
|
|
66775
|
+
var W = grid.width;
|
|
66776
|
+
var H = grid.height;
|
|
66777
|
+
var cellW = W - 1;
|
|
66778
|
+
var cellH = H - 1;
|
|
66779
|
+
var samples = grid.samples;
|
|
66780
|
+
var bands = grid.bands;
|
|
66781
|
+
var valid = getRasterValidityMask(grid);
|
|
66782
|
+
var rowStride = W * bands;
|
|
66783
|
+
var active = new Uint8Array(Math.max(0, cellW * cellH));
|
|
66784
|
+
var activeCount = 0;
|
|
66785
|
+
var cx, cy, pixelId, off;
|
|
66786
|
+
for (cy = 0; cy < cellH; cy++) {
|
|
66787
|
+
for (cx = 0; cx < cellW; cx++) {
|
|
66788
|
+
pixelId = cy * W + cx;
|
|
66789
|
+
if (valid && (!valid[pixelId] || !valid[pixelId + 1] ||
|
|
66790
|
+
!valid[pixelId + W] || !valid[pixelId + W + 1])) continue;
|
|
66791
|
+
off = pixelId * bands + band;
|
|
66792
|
+
if (samples[off] !== samples[off] ||
|
|
66793
|
+
samples[off + bands] !== samples[off + bands] ||
|
|
66794
|
+
samples[off + rowStride] !== samples[off + rowStride] ||
|
|
66795
|
+
samples[off + rowStride + bands] !==
|
|
66796
|
+
samples[off + rowStride + bands]) continue;
|
|
66797
|
+
active[cy * cellW + cx] = 1;
|
|
66798
|
+
activeCount++;
|
|
66799
|
+
}
|
|
66800
|
+
}
|
|
66801
|
+
return {
|
|
66802
|
+
active: active,
|
|
66803
|
+
activeCount: activeCount,
|
|
66804
|
+
width: cellW,
|
|
66805
|
+
height: cellH,
|
|
66806
|
+
gridWidth: W,
|
|
66807
|
+
gridHeight: H,
|
|
66808
|
+
bbox: grid.bbox
|
|
66809
|
+
};
|
|
66810
|
+
}
|
|
66811
|
+
|
|
66812
|
+
function pointIsInContourDomain(x, y, domain) {
|
|
66813
|
+
var bbox = domain.bbox;
|
|
66814
|
+
var dx, dy, cx, cy;
|
|
66815
|
+
if (x < bbox[0] || x > bbox[2] || y < bbox[1] || y > bbox[3] ||
|
|
66816
|
+
domain.width < 1 || domain.height < 1) return false;
|
|
66817
|
+
dx = (bbox[2] - bbox[0]) / domain.gridWidth;
|
|
66818
|
+
dy = (bbox[3] - bbox[1]) / domain.gridHeight;
|
|
66819
|
+
cx = Math.floor((x - bbox[0]) / dx - 0.5);
|
|
66820
|
+
cy = Math.floor((bbox[3] - y) / dy - 0.5);
|
|
66821
|
+
if (cx < 0) cx = 0;
|
|
66822
|
+
if (cy < 0) cy = 0;
|
|
66823
|
+
if (cx >= domain.width) cx = domain.width - 1;
|
|
66824
|
+
if (cy >= domain.height) cy = domain.height - 1;
|
|
66825
|
+
return domain.active[cy * domain.width + cx] === 1;
|
|
66826
|
+
}
|
|
66827
|
+
|
|
66828
|
+
// Trace the union boundary of active contour cells. Segments are directed with
|
|
66829
|
+
// the active domain on their right, matching Mapshaper's clockwise outer-ring
|
|
66830
|
+
// convention. At a point-touch between diagonal components, taking the
|
|
66831
|
+
// rightmost continuation keeps the two rings separate.
|
|
66832
|
+
function traceContourDomainRings(domain) {
|
|
66833
|
+
var W = domain.width;
|
|
66834
|
+
var H = domain.height;
|
|
66835
|
+
var segments = [];
|
|
66836
|
+
var outgoing = new Map();
|
|
66837
|
+
var rings = [];
|
|
66838
|
+
var cx, cy, id, segment;
|
|
66839
|
+
function isActive(x, y) {
|
|
66840
|
+
return x >= 0 && x < W && y >= 0 && y < H &&
|
|
66841
|
+
domain.active[y * W + x] === 1;
|
|
66842
|
+
}
|
|
66843
|
+
function nodeId(x, y) {
|
|
66844
|
+
return y * (W + 1) + x;
|
|
66845
|
+
}
|
|
66846
|
+
function addSegment(x1, y1, x2, y2, dir) {
|
|
66847
|
+
var seg = {
|
|
66848
|
+
from: nodeId(x1, y1),
|
|
66849
|
+
to: nodeId(x2, y2),
|
|
66850
|
+
dir: dir,
|
|
66851
|
+
visited: false
|
|
66852
|
+
};
|
|
66853
|
+
segments.push(seg);
|
|
66854
|
+
if (!outgoing.has(seg.from)) outgoing.set(seg.from, []);
|
|
66855
|
+
outgoing.get(seg.from).push(seg);
|
|
66856
|
+
}
|
|
66857
|
+
for (cy = 0; cy < H; cy++) {
|
|
66858
|
+
for (cx = 0; cx < W; cx++) {
|
|
66859
|
+
if (!isActive(cx, cy)) continue;
|
|
66860
|
+
if (!isActive(cx, cy - 1)) addSegment(cx, cy, cx + 1, cy, 0);
|
|
66861
|
+
if (!isActive(cx + 1, cy)) addSegment(cx + 1, cy, cx + 1, cy + 1, 1);
|
|
66862
|
+
if (!isActive(cx, cy + 1)) addSegment(cx + 1, cy + 1, cx, cy + 1, 2);
|
|
66863
|
+
if (!isActive(cx - 1, cy)) addSegment(cx, cy + 1, cx, cy, 3);
|
|
66864
|
+
}
|
|
66865
|
+
}
|
|
66866
|
+
for (id = 0; id < segments.length; id++) {
|
|
66867
|
+
segment = segments[id];
|
|
66868
|
+
if (!segment.visited) rings.push(followBoundary(segment, outgoing, domain));
|
|
66869
|
+
}
|
|
66870
|
+
return rings;
|
|
66871
|
+
}
|
|
66872
|
+
|
|
66873
|
+
function followBoundary(first, outgoing, domain) {
|
|
66874
|
+
var ring = [getDomainNodeCoords(first.from, domain)];
|
|
66875
|
+
var current = first;
|
|
66876
|
+
var candidates, next;
|
|
66877
|
+
while (current && !current.visited) {
|
|
66878
|
+
current.visited = true;
|
|
66879
|
+
ring.push(getDomainNodeCoords(current.to, domain));
|
|
66880
|
+
candidates = outgoing.get(current.to) || [];
|
|
66881
|
+
next = chooseBoundaryContinuation(current, candidates);
|
|
66882
|
+
current = next;
|
|
66883
|
+
}
|
|
66884
|
+
return ring;
|
|
66885
|
+
}
|
|
66886
|
+
|
|
66887
|
+
function chooseBoundaryContinuation(current, candidates) {
|
|
66888
|
+
var priorities = [1, 0, 3, 2];
|
|
66889
|
+
var i, j, candidate, turn;
|
|
66890
|
+
for (i = 0; i < priorities.length; i++) {
|
|
66891
|
+
for (j = 0; j < candidates.length; j++) {
|
|
66892
|
+
candidate = candidates[j];
|
|
66893
|
+
if (candidate.visited) continue;
|
|
66894
|
+
turn = (candidate.dir - current.dir + 4) % 4;
|
|
66895
|
+
if (turn === priorities[i]) return candidate;
|
|
66896
|
+
}
|
|
66897
|
+
}
|
|
66898
|
+
return null;
|
|
66899
|
+
}
|
|
66900
|
+
|
|
66901
|
+
function getDomainNodeCoords(id, domain) {
|
|
66902
|
+
var W = domain.width;
|
|
66903
|
+
var xId = id % (W + 1);
|
|
66904
|
+
var yId = Math.floor(id / (W + 1));
|
|
66905
|
+
var bbox = domain.bbox;
|
|
66906
|
+
var dx = (bbox[2] - bbox[0]) / domain.gridWidth;
|
|
66907
|
+
var dy = (bbox[3] - bbox[1]) / domain.gridHeight;
|
|
66908
|
+
var x = xId === 0 ? bbox[0] :
|
|
66909
|
+
xId === W ? bbox[2] : bbox[0] + (xId + 0.5) * dx;
|
|
66910
|
+
var y = yId === 0 ? bbox[3] :
|
|
66911
|
+
yId === domain.height ? bbox[1] : bbox[3] - (yId + 0.5) * dy;
|
|
66912
|
+
return [x, y];
|
|
66913
|
+
}
|
|
66914
|
+
|
|
66915
|
+
function getBoundaryGeoJSON(rings) {
|
|
66916
|
+
return {
|
|
66917
|
+
type: 'FeatureCollection',
|
|
66918
|
+
features: rings.map(function(coords) {
|
|
66919
|
+
return {
|
|
66920
|
+
type: 'Feature',
|
|
66921
|
+
properties: {},
|
|
66922
|
+
geometry: {type: 'LineString', coordinates: coords}
|
|
66923
|
+
};
|
|
66924
|
+
})
|
|
66925
|
+
};
|
|
66926
|
+
}
|
|
66927
|
+
|
|
66928
|
+
function indexContourArcs(lyr) {
|
|
66929
|
+
var records = lyr.data ? lyr.data.getRecords() : [];
|
|
66930
|
+
var index = new Map();
|
|
66931
|
+
lyr.shapes.forEach(function(shape, shapeId) {
|
|
66932
|
+
var level = records[shapeId] && records[shapeId].value;
|
|
66933
|
+
if (!shape || typeof level != 'number') return;
|
|
66934
|
+
shape.forEach(function(path) {
|
|
66935
|
+
path.forEach(function(arcId) {
|
|
66936
|
+
var absId = arcId < 0 ? ~arcId : arcId;
|
|
66937
|
+
index.set(absId, {
|
|
66938
|
+
level: level,
|
|
66939
|
+
direction: arcId < 0 ? -1 : 1
|
|
66940
|
+
});
|
|
66941
|
+
});
|
|
66942
|
+
});
|
|
66943
|
+
});
|
|
66944
|
+
return index;
|
|
66945
|
+
}
|
|
66946
|
+
|
|
66947
|
+
function createBandTileLayer(mosaic, arcs, arcInfo, domain, grid, band, breaks) {
|
|
66948
|
+
var shapes = [];
|
|
66949
|
+
var records = [];
|
|
66950
|
+
mosaic.forEach(function(tile) {
|
|
66951
|
+
var anchor = findAnchorPoint(tile, arcs, {});
|
|
66952
|
+
var bounds;
|
|
66953
|
+
if (!anchor || !pointIsInContourDomain(anchor.x, anchor.y, domain)) return;
|
|
66954
|
+
bounds = getTileBandBounds(tile, arcInfo, breaks, grid, band, anchor);
|
|
66955
|
+
if (!bounds) return;
|
|
66956
|
+
shapes.push(tile);
|
|
66957
|
+
records.push(bounds);
|
|
66958
|
+
});
|
|
66959
|
+
return {
|
|
66960
|
+
geometry_type: 'polygon',
|
|
66961
|
+
shapes: shapes,
|
|
66962
|
+
data: new DataTable(records)
|
|
66963
|
+
};
|
|
66964
|
+
}
|
|
66965
|
+
|
|
66966
|
+
function getTileBandBounds(tile, arcInfo, breaks, grid, band, anchor) {
|
|
66967
|
+
var lower = breaks[0];
|
|
66968
|
+
var upper = breaks[breaks.length - 1];
|
|
66969
|
+
var sawLower = false;
|
|
66970
|
+
var sawUpper = false;
|
|
66971
|
+
tile.forEach(function(path) {
|
|
66972
|
+
path.forEach(function(arcId) {
|
|
66973
|
+
var absId = arcId < 0 ? ~arcId : arcId;
|
|
66974
|
+
var info = arcInfo.get(absId);
|
|
66975
|
+
var direction;
|
|
66976
|
+
if (!info) return;
|
|
66977
|
+
direction = arcId < 0 ? -1 : 1;
|
|
66978
|
+
// Contour paths are directed with the above-level side on their left.
|
|
66979
|
+
// Polygon rings keep their interior on the right, so following a contour
|
|
66980
|
+
// arc in its original direction means this tile is below that level.
|
|
66981
|
+
if (direction === info.direction) {
|
|
66982
|
+
if (!sawUpper || info.level < upper) upper = info.level;
|
|
66983
|
+
sawUpper = true;
|
|
66984
|
+
} else {
|
|
66985
|
+
if (!sawLower || info.level > lower) lower = info.level;
|
|
66986
|
+
sawLower = true;
|
|
66987
|
+
}
|
|
66988
|
+
});
|
|
66989
|
+
});
|
|
66990
|
+
if (areAdjacentBreaks(lower, upper, breaks)) {
|
|
66991
|
+
return {lower: lower, upper: upper};
|
|
66992
|
+
}
|
|
66993
|
+
return getSampleBandBounds(grid, band, anchor.x, anchor.y, breaks);
|
|
66994
|
+
}
|
|
66995
|
+
|
|
66996
|
+
function areAdjacentBreaks(lower, upper, breaks) {
|
|
66997
|
+
var i = breaks.indexOf(lower);
|
|
66998
|
+
return i > -1 && i + 1 < breaks.length && breaks[i + 1] === upper;
|
|
66999
|
+
}
|
|
67000
|
+
|
|
67001
|
+
function getSampleBandBounds(grid, band, x, y, breaks) {
|
|
67002
|
+
var bbox = grid.bbox;
|
|
67003
|
+
var col = Math.floor((x - bbox[0]) / (bbox[2] - bbox[0]) * grid.width);
|
|
67004
|
+
var row = Math.floor((bbox[3] - y) / (bbox[3] - bbox[1]) * grid.height);
|
|
67005
|
+
var value, i;
|
|
67006
|
+
if (col < 0) col = 0;
|
|
67007
|
+
if (row < 0) row = 0;
|
|
67008
|
+
if (col >= grid.width) col = grid.width - 1;
|
|
67009
|
+
if (row >= grid.height) row = grid.height - 1;
|
|
67010
|
+
value = grid.samples[(row * grid.width + col) * grid.bands + band];
|
|
67011
|
+
if (value !== value) return null;
|
|
67012
|
+
i = 0;
|
|
67013
|
+
while (i + 2 < breaks.length && value >= breaks[i + 1]) i++;
|
|
67014
|
+
return {lower: breaks[i], upper: breaks[i + 1]};
|
|
67015
|
+
}
|
|
67016
|
+
|
|
67017
|
+
function createEmptyClosedContourDataset(info) {
|
|
67018
|
+
return {
|
|
67019
|
+
info: info || {},
|
|
67020
|
+
layers: [{
|
|
67021
|
+
geometry_type: 'polygon',
|
|
67022
|
+
shapes: [],
|
|
67023
|
+
data: new DataTable([])
|
|
67024
|
+
}]
|
|
67025
|
+
};
|
|
67026
|
+
}
|
|
67027
|
+
|
|
67028
|
+
var RasterContourBands = /*#__PURE__*/Object.freeze({
|
|
67029
|
+
__proto__: null,
|
|
67030
|
+
buildClosedContourDataset: buildClosedContourDataset,
|
|
67031
|
+
getClosedContourBreaks: getClosedContourBreaks,
|
|
67032
|
+
getContourCellDomain: getContourCellDomain,
|
|
67033
|
+
pointIsInContourDomain: pointIsInContourDomain,
|
|
67034
|
+
traceContourDomainRings: traceContourDomainRings
|
|
67035
|
+
});
|
|
67036
|
+
|
|
66085
67037
|
// Trace contour lines (isolines) from a raster layer's working samples using
|
|
66086
67038
|
// marching squares.
|
|
66087
67039
|
//
|
|
@@ -66174,13 +67126,17 @@ ${svg}
|
|
|
66174
67126
|
|
|
66175
67127
|
function getRasterContourLines(raster, opts) {
|
|
66176
67128
|
var grid = getRasterGrid(raster);
|
|
66177
|
-
var band, levels;
|
|
67129
|
+
var band, levels, range;
|
|
66178
67130
|
validateRasterGridForContours(grid);
|
|
66179
67131
|
band = getContourBand(grid, opts);
|
|
66180
67132
|
levels = getContourLevels(grid, band, opts);
|
|
67133
|
+
range = getRasterSampleRange(grid, band);
|
|
66181
67134
|
return {
|
|
67135
|
+
band: band,
|
|
67136
|
+
range: range,
|
|
66182
67137
|
levels: levels,
|
|
66183
|
-
lines: traceRasterContours(grid, band, levels, getCornerClearance(opts)
|
|
67138
|
+
lines: traceRasterContours(grid, band, levels, getCornerClearance(opts),
|
|
67139
|
+
{extend_to_bbox: opts && opts.closed})
|
|
66184
67140
|
};
|
|
66185
67141
|
}
|
|
66186
67142
|
|
|
@@ -66319,7 +67275,7 @@ ${svg}
|
|
|
66319
67275
|
}
|
|
66320
67276
|
|
|
66321
67277
|
// clearance: see CORNER_CLEARANCE; 0 places crossings exactly.
|
|
66322
|
-
function traceRasterContours(grid, band, levels, clearance) {
|
|
67278
|
+
function traceRasterContours(grid, band, levels, clearance, opts) {
|
|
66323
67279
|
var segments = collectContourSegments(grid, band, levels);
|
|
66324
67280
|
var toMapXY = getLatticeToMapTransform(grid);
|
|
66325
67281
|
var hCount = (grid.width - 1) * grid.height;
|
|
@@ -66329,6 +67285,9 @@ ${svg}
|
|
|
66329
67285
|
stitchContourSegments(levelSegments).forEach(function(path) {
|
|
66330
67286
|
var coords = getPathCoords(path, grid, band, levels[i], hCount, toMapXY,
|
|
66331
67287
|
clearance);
|
|
67288
|
+
if (opts && opts.extend_to_bbox) {
|
|
67289
|
+
coords = extendContourPathToBBox(coords, path, grid, hCount);
|
|
67290
|
+
}
|
|
66332
67291
|
if (coords.length > 1) {
|
|
66333
67292
|
lines.push({value: levels[i], coords: coords});
|
|
66334
67293
|
}
|
|
@@ -66337,6 +67296,39 @@ ${svg}
|
|
|
66337
67296
|
return lines;
|
|
66338
67297
|
}
|
|
66339
67298
|
|
|
67299
|
+
// Closed contour bands use the raster bbox as their outer frame. Open contour
|
|
67300
|
+
// paths that reach the outer sample-center lattice are extended through the
|
|
67301
|
+
// half-pixel margin to that frame. Endpoints created by skipped nodata cells
|
|
67302
|
+
// lie on interior lattice edges and are intentionally left in place.
|
|
67303
|
+
function extendContourPathToBBox(coords, path, grid, hCount) {
|
|
67304
|
+
if (path.length < 2 || path[0] === path[path.length - 1]) return coords;
|
|
67305
|
+
var first = getOuterEdgePoint(coords[0], path[0], grid, hCount);
|
|
67306
|
+
var last = getOuterEdgePoint(coords[coords.length - 1],
|
|
67307
|
+
path[path.length - 1], grid, hCount);
|
|
67308
|
+
if (first) coords.unshift(first);
|
|
67309
|
+
if (last) coords.push(last);
|
|
67310
|
+
return coords;
|
|
67311
|
+
}
|
|
67312
|
+
|
|
67313
|
+
function getOuterEdgePoint(xy, edgeId, grid, hCount) {
|
|
67314
|
+
var W = grid.width;
|
|
67315
|
+
var H = grid.height;
|
|
67316
|
+
var bbox = grid.bbox;
|
|
67317
|
+
var x, y;
|
|
67318
|
+
if (edgeId < hCount) {
|
|
67319
|
+
y = Math.floor(edgeId / (W - 1));
|
|
67320
|
+
if (y === 0) return [xy[0], bbox[3]];
|
|
67321
|
+
if (y === H - 1) return [xy[0], bbox[1]];
|
|
67322
|
+
return null;
|
|
67323
|
+
}
|
|
67324
|
+
edgeId -= hCount;
|
|
67325
|
+
y = Math.floor(edgeId / W);
|
|
67326
|
+
x = edgeId - y * W;
|
|
67327
|
+
if (x === 0) return [bbox[0], xy[1]];
|
|
67328
|
+
if (x === W - 1) return [bbox[2], xy[1]];
|
|
67329
|
+
return null;
|
|
67330
|
+
}
|
|
67331
|
+
|
|
66340
67332
|
// Visit each cell once and march it for every level that falls inside its
|
|
66341
67333
|
// value range, rather than scanning the whole grid once per level.
|
|
66342
67334
|
function collectContourSegments(grid, band, levels) {
|
|
@@ -66557,6 +67549,21 @@ ${svg}
|
|
|
66557
67549
|
// Contours come from the layer's working samples, so they reflect any
|
|
66558
67550
|
// earlier edits (e.g. -blur, -clip) rather than the original source pixels.
|
|
66559
67551
|
contours = getRasterContourLines(targetLyr.raster, opts);
|
|
67552
|
+
if (opts.closed) {
|
|
67553
|
+
dataset = contours.lines.length > 0 ?
|
|
67554
|
+
importGeoJSON(getContoursGeoJSON(contours.lines, DEFAULT_CONTOUR_FIELD), {}) :
|
|
67555
|
+
createEmptyContourDataset();
|
|
67556
|
+
setDatasetCrsInfo(dataset, getDatasetCrsInfo(targetDataset));
|
|
67557
|
+
if (contours.lines.length > 0 && !opts.no_smoothing) {
|
|
67558
|
+
smoothContourDataset(dataset, getRasterGrid(targetLyr.raster));
|
|
67559
|
+
}
|
|
67560
|
+
dataset = buildClosedContourDataset(dataset, getRasterGrid(targetLyr.raster),
|
|
67561
|
+
contours);
|
|
67562
|
+
outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
|
|
67563
|
+
setOutputLayerName(outputLayers[0], targetLyr, 'contours', opts);
|
|
67564
|
+
message(getClosedContoursMessage(outputLayers[0], contours));
|
|
67565
|
+
return outputLayers;
|
|
67566
|
+
}
|
|
66560
67567
|
message(getContoursMessage(contours));
|
|
66561
67568
|
if (contours.lines.length === 0) {
|
|
66562
67569
|
return [createEmptyContourLayer(targetLyr, opts)];
|
|
@@ -66640,6 +67647,17 @@ ${svg}
|
|
|
66640
67647
|
return lyr;
|
|
66641
67648
|
}
|
|
66642
67649
|
|
|
67650
|
+
function createEmptyContourDataset() {
|
|
67651
|
+
return {
|
|
67652
|
+
info: {},
|
|
67653
|
+
layers: [{
|
|
67654
|
+
geometry_type: 'polyline',
|
|
67655
|
+
shapes: [],
|
|
67656
|
+
data: new DataTable([])
|
|
67657
|
+
}]
|
|
67658
|
+
};
|
|
67659
|
+
}
|
|
67660
|
+
|
|
66643
67661
|
function getContoursMessage(contours) {
|
|
66644
67662
|
var levels = contours.levels;
|
|
66645
67663
|
if (levels.length === 0) {
|
|
@@ -66651,6 +67669,16 @@ ${svg}
|
|
|
66651
67669
|
' (' + levels[0] + ' to ' + levels[levels.length - 1] + ')';
|
|
66652
67670
|
}
|
|
66653
67671
|
|
|
67672
|
+
function getClosedContoursMessage(lyr, contours) {
|
|
67673
|
+
var count = lyr.shapes.length;
|
|
67674
|
+
if (count === 0) {
|
|
67675
|
+
return 'No closed contour bands were generated';
|
|
67676
|
+
}
|
|
67677
|
+
return 'Created ' + count + ' closed contour ' +
|
|
67678
|
+
(count === 1 ? 'band' : 'bands') + ' from ' + contours.levels.length +
|
|
67679
|
+
(contours.levels.length === 1 ? ' level' : ' levels');
|
|
67680
|
+
}
|
|
67681
|
+
|
|
66654
67682
|
cmd.dashlines = function(lyr, dataset, opts) {
|
|
66655
67683
|
var crs = getDatasetCRS(dataset);
|
|
66656
67684
|
var defs = getStashedVar('defs');
|
|
@@ -72267,13 +73295,33 @@ ${svg}
|
|
|
72267
73295
|
}
|
|
72268
73296
|
|
|
72269
73297
|
function pointsFromPolygons(lyr, arcs, opts) {
|
|
72270
|
-
var
|
|
73298
|
+
var innerOpts = opts.inner ? getInnerPointOptions(opts) : null;
|
|
72271
73299
|
return lyr.shapes.map(function(shp) {
|
|
72272
|
-
var p =
|
|
73300
|
+
var p = innerOpts ?
|
|
73301
|
+
findAnchorPoint(shp, arcs, innerOpts) : geom.getShapeCentroid(shp, arcs);
|
|
72273
73302
|
return p ? [[p.x, p.y]] : null;
|
|
72274
73303
|
});
|
|
72275
73304
|
}
|
|
72276
73305
|
|
|
73306
|
+
function getInnerPointOptions(opts) {
|
|
73307
|
+
var method = opts.inner_method || 'centroid2';
|
|
73308
|
+
var methods = ['legacy', 'pole', 'centroid', 'centroid2', 'weighted'];
|
|
73309
|
+
if (methods.indexOf(method) == -1) {
|
|
73310
|
+
stop$1('Unsupported inner point method:', method);
|
|
73311
|
+
}
|
|
73312
|
+
var tolerance = opts.inner_tolerance === undefined ?
|
|
73313
|
+
0.1 : parsePercent(opts.inner_tolerance);
|
|
73314
|
+
var weight = opts.inner_weight === undefined ? 0.6 : opts.inner_weight;
|
|
73315
|
+
if (!(weight >= 0)) {
|
|
73316
|
+
stop$1('Expected inner-weight to be a non-negative number');
|
|
73317
|
+
}
|
|
73318
|
+
return {
|
|
73319
|
+
method: method,
|
|
73320
|
+
tolerance: tolerance,
|
|
73321
|
+
weight: weight
|
|
73322
|
+
};
|
|
73323
|
+
}
|
|
73324
|
+
|
|
72277
73325
|
function coordinateFromValue(val) {
|
|
72278
73326
|
var tmp;
|
|
72279
73327
|
if (utils.isFiniteNumber(val)) {
|
|
@@ -72407,10 +73455,14 @@ ${svg}
|
|
|
72407
73455
|
}
|
|
72408
73456
|
|
|
72409
73457
|
function pointsFromPolygonsForJoin(lyr, dataset) {
|
|
72410
|
-
// TODO use faster method to get inner points
|
|
72411
73458
|
return {
|
|
72412
73459
|
geometry_type: 'point',
|
|
72413
|
-
|
|
73460
|
+
// Joins only require a reliable interior point; centroid bias adds a
|
|
73461
|
+
// second search without improving the containment test.
|
|
73462
|
+
shapes: pointsFromPolygons(lyr, dataset.arcs, {
|
|
73463
|
+
inner: true,
|
|
73464
|
+
inner_method: 'pole'
|
|
73465
|
+
}),
|
|
72414
73466
|
data: lyr.data // TODO copy if needed
|
|
72415
73467
|
};
|
|
72416
73468
|
}
|
|
@@ -72455,85 +73507,6 @@ ${svg}
|
|
|
72455
73507
|
return retn;
|
|
72456
73508
|
}
|
|
72457
73509
|
|
|
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
73510
|
/*
|
|
72538
73511
|
ISC License
|
|
72539
73512
|
|
|
@@ -76178,6 +77151,16 @@ ${svg}
|
|
|
76178
77151
|
stopJob(job);
|
|
76179
77152
|
};
|
|
76180
77153
|
|
|
77154
|
+
// The icon names accepted by the icon= property of the -style command.
|
|
77155
|
+
// Kept in a module of its own so that both the renderer (svg-symbols.mjs) and
|
|
77156
|
+
// the -style command can use it without forming an import cycle.
|
|
77157
|
+
|
|
77158
|
+
var iconNames = ['circle', 'square', 'ring', 'star'];
|
|
77159
|
+
|
|
77160
|
+
function isSupportedIconName(name) {
|
|
77161
|
+
return iconNames.indexOf(name) > -1;
|
|
77162
|
+
}
|
|
77163
|
+
|
|
76181
77164
|
cmd.svgStyle = function(lyr, dataset, opts) {
|
|
76182
77165
|
var filterFn, table, fields, hasNewFields;
|
|
76183
77166
|
if (getFeatureCount(lyr) === 0) {
|
|
@@ -76214,12 +77197,16 @@ ${svg}
|
|
|
76214
77197
|
}
|
|
76215
77198
|
var strVal = opts[optName].trim();
|
|
76216
77199
|
var accessor = getSymbolPropertyAccessor(strVal, svgName, lyr);
|
|
77200
|
+
var badIcons = svgName == 'icon' ? [] : null;
|
|
76217
77201
|
table.getRecords().forEach(function(rec, i) {
|
|
76218
77202
|
if (filterFn && !filterFn(i)) {
|
|
76219
77203
|
// make sure field exists if record is excluded by filter
|
|
76220
77204
|
setUndefinedFields(rec, svgName == 'label-pos' ? labelPositionFields : [svgName]);
|
|
76221
77205
|
} else {
|
|
76222
77206
|
rec[svgName] = accessor(i);
|
|
77207
|
+
if (badIcons) {
|
|
77208
|
+
addUnsupportedIconName(badIcons, rec.icon);
|
|
77209
|
+
}
|
|
76223
77210
|
if (svgName == 'label-pos') {
|
|
76224
77211
|
if (!setLabelPositionStyle(rec, rec['label-pos'])) {
|
|
76225
77212
|
stop$1('Unexpected value for label-pos:', rec['label-pos']);
|
|
@@ -76227,6 +77214,9 @@ ${svg}
|
|
|
76227
77214
|
}
|
|
76228
77215
|
}
|
|
76229
77216
|
});
|
|
77217
|
+
if (badIcons && badIcons.length > 0) {
|
|
77218
|
+
warn(formatUnsupportedIconMessage(badIcons));
|
|
77219
|
+
}
|
|
76230
77220
|
});
|
|
76231
77221
|
if (fields.length > 0) {
|
|
76232
77222
|
if (hasNewFields) {
|
|
@@ -76258,6 +77248,32 @@ ${svg}
|
|
|
76258
77248
|
}
|
|
76259
77249
|
}
|
|
76260
77250
|
|
|
77251
|
+
// Icon names can not be validated when options are parsed, because an icon=
|
|
77252
|
+
// value may be a field name or an expression. The assigned values are checked
|
|
77253
|
+
// instead, so that computed names are covered too. Unsupported names are a
|
|
77254
|
+
// warning, not an error -- features with an unsupported name render without an
|
|
77255
|
+
// icon.
|
|
77256
|
+
var maxReportedIconNames = 4;
|
|
77257
|
+
|
|
77258
|
+
function addUnsupportedIconName(names, val) {
|
|
77259
|
+
var name;
|
|
77260
|
+
if (!val) return; // a blank value removes the icon
|
|
77261
|
+
name = String(val);
|
|
77262
|
+
if (isSupportedIconName(name) || names.indexOf(name) > -1) return;
|
|
77263
|
+
names.push(name);
|
|
77264
|
+
}
|
|
77265
|
+
|
|
77266
|
+
function formatUnsupportedIconMessage(names) {
|
|
77267
|
+
var extra = names.length - maxReportedIconNames;
|
|
77268
|
+
var listed = extra > 0 ? names.slice(0, maxReportedIconNames) : names;
|
|
77269
|
+
var str = 'Unsupported icon ' + (names.length > 1 ? 'names' : 'name') + ': ' +
|
|
77270
|
+
listed.join(', ');
|
|
77271
|
+
if (extra > 0) {
|
|
77272
|
+
str += ' (and ' + extra + ' more)';
|
|
77273
|
+
}
|
|
77274
|
+
return str + '. Expected one of: ' + iconNames.join(', ');
|
|
77275
|
+
}
|
|
77276
|
+
|
|
76261
77277
|
function setUndefinedFields(rec, fields) {
|
|
76262
77278
|
fields.forEach(function(field) {
|
|
76263
77279
|
if (field in rec === false) {
|
|
@@ -77949,7 +78965,7 @@ ${svg}
|
|
|
77949
78965
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
77950
78966
|
}
|
|
77951
78967
|
|
|
77952
|
-
var version = "0.7.
|
|
78968
|
+
var version = "0.7.54";
|
|
77953
78969
|
|
|
77954
78970
|
// Parse command line args into commands and run them
|
|
77955
78971
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -80100,6 +81116,7 @@ ${svg}
|
|
|
80100
81116
|
Logging,
|
|
80101
81117
|
Profile,
|
|
80102
81118
|
RasterBlur,
|
|
81119
|
+
RasterContourBands,
|
|
80103
81120
|
RasterContours,
|
|
80104
81121
|
RasterGrid,
|
|
80105
81122
|
RasterReprojection,
|