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/README.md +2 -2
- package/mapshaper.js +1140 -118
- package/package.json +1 -1
- package/www/index.html +1 -2
- package/www/mapshaper-gui.js +1 -1
- package/www/mapshaper.js +1140 -118
- package/www/sponsor.html +0 -3
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
|
|
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
73303
|
+
var innerOpts = opts.inner ? getInnerPointOptions(opts) : null;
|
|
72271
73304
|
return lyr.shapes.map(function(shp) {
|
|
72272
|
-
var p =
|
|
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
|
-
|
|
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.
|
|
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,
|