mapshaper 0.6.24 → 0.6.25
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 +244 -45
- package/package.json +2 -3
- package/www/index.html +1 -0
- package/www/mapshaper-gui.js +28 -5
- package/www/mapshaper.js +244 -45
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.6.
|
|
3
|
+
var VERSION = "0.6.25";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -2449,6 +2449,17 @@
|
|
|
2449
2449
|
getPathCentroid: getPathCentroid
|
|
2450
2450
|
});
|
|
2451
2451
|
|
|
2452
|
+
function testSegmentBoundsIntersection(a, b, bb) {
|
|
2453
|
+
if (bb.containsPoint(a[0], a[1])) {
|
|
2454
|
+
return true;
|
|
2455
|
+
}
|
|
2456
|
+
return !!(
|
|
2457
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymin, bb.xmin, bb.ymax) ||
|
|
2458
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymax, bb.xmax, bb.ymax) ||
|
|
2459
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymax, bb.xmax, bb.ymin) ||
|
|
2460
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymin, bb.xmin, bb.ymin));
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2452
2463
|
// A compactness measure designed for testing electoral districts for gerrymandering.
|
|
2453
2464
|
// Returns value in [0-1] range. 1 = perfect circle, 0 = collapsed polygon
|
|
2454
2465
|
function calcPolsbyPopperCompactness(area, perimeter) {
|
|
@@ -2496,21 +2507,32 @@
|
|
|
2496
2507
|
// }, 0);
|
|
2497
2508
|
// }
|
|
2498
2509
|
|
|
2510
|
+
// test if a rectangle is completely enclosed in a planar polygon
|
|
2511
|
+
function testBoundsInPolygon(bounds, shp, arcs) {
|
|
2512
|
+
if (!shp || !testPointInPolygon(bounds.xmin, bounds.ymin, shp, arcs)) return false;
|
|
2513
|
+
var isIn = true;
|
|
2514
|
+
shp.forEach(function(ids) {
|
|
2515
|
+
forEachSegmentInPath(ids, arcs, function(a, b, xx, yy) {
|
|
2516
|
+
isIn = isIn && !testSegmentBoundsIntersection([xx[a], yy[a]], [xx[b], yy[b]], bounds);
|
|
2517
|
+
});
|
|
2518
|
+
});
|
|
2519
|
+
return isIn;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2499
2522
|
// Return true if point is inside or on boundary of a shape
|
|
2500
2523
|
//
|
|
2501
2524
|
function testPointInPolygon(x, y, shp, arcs) {
|
|
2502
2525
|
var isIn = false,
|
|
2503
2526
|
isOn = false;
|
|
2504
|
-
if (shp)
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
}
|
|
2527
|
+
if (!shp) return false;
|
|
2528
|
+
shp.forEach(function(ids) {
|
|
2529
|
+
var inRing = testPointInRing(x, y, ids, arcs);
|
|
2530
|
+
if (inRing == 1) {
|
|
2531
|
+
isIn = !isIn;
|
|
2532
|
+
} else if (inRing == -1) {
|
|
2533
|
+
isOn = true;
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2514
2536
|
return isOn || isIn;
|
|
2515
2537
|
}
|
|
2516
2538
|
|
|
@@ -2518,6 +2540,8 @@
|
|
|
2518
2540
|
return ay + (x - ax) * (by - ay) / (bx - ax);
|
|
2519
2541
|
}
|
|
2520
2542
|
|
|
2543
|
+
|
|
2544
|
+
|
|
2521
2545
|
// Test if point (x, y) is inside, outside or on the boundary of a polygon ring
|
|
2522
2546
|
// Return 0: outside; 1: inside; -1: on boundary
|
|
2523
2547
|
//
|
|
@@ -2708,6 +2732,7 @@
|
|
|
2708
2732
|
getShapeArea: getShapeArea,
|
|
2709
2733
|
getPlanarShapeArea: getPlanarShapeArea,
|
|
2710
2734
|
getSphericalShapeArea: getSphericalShapeArea,
|
|
2735
|
+
testBoundsInPolygon: testBoundsInPolygon,
|
|
2711
2736
|
testPointInPolygon: testPointInPolygon,
|
|
2712
2737
|
testPointInRing: testPointInRing,
|
|
2713
2738
|
testRayIntersection: testRayIntersection,
|
|
@@ -7251,7 +7276,7 @@
|
|
|
7251
7276
|
let size = source.length;
|
|
7252
7277
|
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
|
|
7253
7278
|
if (forEach) {
|
|
7254
|
-
forEach(value);
|
|
7279
|
+
if (forEach(value) === false) return;
|
|
7255
7280
|
while(position$1 < size) {
|
|
7256
7281
|
lastPosition = position$1;
|
|
7257
7282
|
if (forEach(checkedRead()) === false) {
|
|
@@ -8193,7 +8218,7 @@
|
|
|
8193
8218
|
let safeEnd;
|
|
8194
8219
|
let bundledStrings = null;
|
|
8195
8220
|
let writeStructSlots;
|
|
8196
|
-
const MAX_BUNDLE_SIZE =
|
|
8221
|
+
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
|
|
8197
8222
|
const hasNonLatin = /[\u0080-\uFFFF]/;
|
|
8198
8223
|
const RECORD_SYMBOL = Symbol('record-id');
|
|
8199
8224
|
class Packr extends Unpackr {
|
|
@@ -8222,7 +8247,7 @@
|
|
|
8222
8247
|
if (maxSharedStructures > 8160)
|
|
8223
8248
|
throw new Error('Maximum maxSharedStructure is 8160')
|
|
8224
8249
|
if (options.structuredClone && options.moreTypes == undefined) {
|
|
8225
|
-
|
|
8250
|
+
this.moreTypes = true;
|
|
8226
8251
|
}
|
|
8227
8252
|
let maxOwnStructures = options.maxOwnStructures;
|
|
8228
8253
|
if (maxOwnStructures == null)
|
|
@@ -8917,12 +8942,11 @@
|
|
|
8917
8942
|
if (notifySharedUpdate)
|
|
8918
8943
|
return hasSharedUpdate = true;
|
|
8919
8944
|
position = newPosition;
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
}
|
|
8925
|
-
pack(value);
|
|
8945
|
+
let startTarget = target;
|
|
8946
|
+
pack(value);
|
|
8947
|
+
if (startTarget !== target) {
|
|
8948
|
+
return { position, targetView, target }; // indicate the buffer was re-allocated
|
|
8949
|
+
}
|
|
8926
8950
|
return position;
|
|
8927
8951
|
}, this);
|
|
8928
8952
|
if (newPosition === 0) // bail and go to a msgpack object
|
|
@@ -13729,6 +13753,11 @@
|
|
|
13729
13753
|
return rect.contains(bbox);
|
|
13730
13754
|
};
|
|
13731
13755
|
|
|
13756
|
+
// TODO
|
|
13757
|
+
// ctx.intersectsRectangle = function(a, b, c, d) {}; // paths... points too?
|
|
13758
|
+
// ctx.containsPoint = function(x, y) {}; // polygon only
|
|
13759
|
+
// ctx.containedByRectangle(a, b, c, d); // paths and points... how do multipart points work?
|
|
13760
|
+
|
|
13732
13761
|
addGetters(ctx, {
|
|
13733
13762
|
// TODO: count hole/s + containing ring as one part
|
|
13734
13763
|
partCount: function() {
|
|
@@ -24602,6 +24631,21 @@ ${svg}
|
|
|
24602
24631
|
// Experimental commands
|
|
24603
24632
|
parser.section('Experimental commands (may give unexpected results)');
|
|
24604
24633
|
|
|
24634
|
+
parser.command('add-shape')
|
|
24635
|
+
.describe('')
|
|
24636
|
+
.option('geojson', {
|
|
24637
|
+
|
|
24638
|
+
})
|
|
24639
|
+
.option('coordinates', {
|
|
24640
|
+
|
|
24641
|
+
})
|
|
24642
|
+
.option('properties', {
|
|
24643
|
+
|
|
24644
|
+
})
|
|
24645
|
+
.option('name', nameOpt)
|
|
24646
|
+
.option('target', targetOpt)
|
|
24647
|
+
.option('no-replace', noReplaceOpt);
|
|
24648
|
+
|
|
24605
24649
|
parser.command('alpha-shapes')
|
|
24606
24650
|
// .describe('convert points to alpha shapes (aka concave hulls)')
|
|
24607
24651
|
.option('interval', {
|
|
@@ -28076,6 +28120,136 @@ ${svg}
|
|
|
28076
28120
|
stashVar('input_files', job.input_files);
|
|
28077
28121
|
}
|
|
28078
28122
|
|
|
28123
|
+
cmd.addShape = addShape;
|
|
28124
|
+
|
|
28125
|
+
function addShape(targetLayers, targetDataset, opts) {
|
|
28126
|
+
if (targetLayers.length > 1) {
|
|
28127
|
+
stop('Command expects a single target layer');
|
|
28128
|
+
}
|
|
28129
|
+
var targetLyr = targetLayers[0]; // may be undefined
|
|
28130
|
+
var targetType = !opts.no_replace && targetLyr && targetLyr.geometry_type || null;
|
|
28131
|
+
var dataset = importGeoJSON(toFeature(opts, targetType));
|
|
28132
|
+
var outputLyr = mergeDatasetsIntoDataset(targetDataset, [dataset])[0];
|
|
28133
|
+
if (opts.no_replace || !targetLyr) {
|
|
28134
|
+
// create new layer
|
|
28135
|
+
setOutputLayerName(outputLyr, targetLyr && targetLyr.name, null, opts);
|
|
28136
|
+
return [outputLyr];
|
|
28137
|
+
}
|
|
28138
|
+
// merge into target layer
|
|
28139
|
+
return cmd.mergeLayers([targetLyr, outputLyr], {force: true});
|
|
28140
|
+
}
|
|
28141
|
+
|
|
28142
|
+
function toFeature(opts, geomType) {
|
|
28143
|
+
if (opts.geojson) {
|
|
28144
|
+
return parseArg(opts.geojson);
|
|
28145
|
+
}
|
|
28146
|
+
|
|
28147
|
+
var geom = opts.coordinates && parseCoordsAsGeometry(opts.coordinates) || null;
|
|
28148
|
+
|
|
28149
|
+
if (!geom) {
|
|
28150
|
+
stop('Missing required shape coordinates');
|
|
28151
|
+
}
|
|
28152
|
+
|
|
28153
|
+
if (geomType == 'point' && geom.type != 'Point') {
|
|
28154
|
+
stop('Expected point coordinates, received', geom.type);
|
|
28155
|
+
}
|
|
28156
|
+
|
|
28157
|
+
if (geomType == 'polygon' && geom.type != 'Polygon') {
|
|
28158
|
+
stop('Expected polygon coordinates, received', geom.type);
|
|
28159
|
+
}
|
|
28160
|
+
|
|
28161
|
+
if (geomType == 'polyline') {
|
|
28162
|
+
if (geom.type == 'Polygon') {
|
|
28163
|
+
geom.coordinates = geom.coordinates[0];
|
|
28164
|
+
geom.type = 'LineString';
|
|
28165
|
+
} else {
|
|
28166
|
+
stop('Expected polyline coordinates, received', geom.type);
|
|
28167
|
+
}
|
|
28168
|
+
}
|
|
28169
|
+
|
|
28170
|
+
return {
|
|
28171
|
+
type: 'Feature',
|
|
28172
|
+
properties: parseProperties(opts.properties),
|
|
28173
|
+
geometry: geom
|
|
28174
|
+
};
|
|
28175
|
+
}
|
|
28176
|
+
|
|
28177
|
+
function parseArg(obj) {
|
|
28178
|
+
return typeof obj == 'string' ? JSON.parse(obj) : obj;
|
|
28179
|
+
}
|
|
28180
|
+
|
|
28181
|
+
function parseProperties(arg) {
|
|
28182
|
+
if (!arg) return null;
|
|
28183
|
+
return parseArg(arg);
|
|
28184
|
+
}
|
|
28185
|
+
|
|
28186
|
+
function isArrayOfNumbers(arr) {
|
|
28187
|
+
return arr.length >= 2 && arr.every(utils.isNumber);
|
|
28188
|
+
}
|
|
28189
|
+
|
|
28190
|
+
function isClosedPath$1(arr) {
|
|
28191
|
+
return isArrayOfPoints(arr) && arr.length > 3 && samePoint$1(arr[0], arr[arr.length - 1]);
|
|
28192
|
+
}
|
|
28193
|
+
|
|
28194
|
+
function samePoint$1(a, b) {
|
|
28195
|
+
return a[0] == b[0] && a[1] == b[1];
|
|
28196
|
+
}
|
|
28197
|
+
|
|
28198
|
+
function isArrayOfPoints(arr) {
|
|
28199
|
+
return arr.every(isPoint);
|
|
28200
|
+
}
|
|
28201
|
+
|
|
28202
|
+
function isPoint(arr) {
|
|
28203
|
+
return arr && arr.length == 2 && isArrayOfNumbers(arr);
|
|
28204
|
+
}
|
|
28205
|
+
|
|
28206
|
+
function transposeCoords(arr) {
|
|
28207
|
+
var coords = [];
|
|
28208
|
+
for (var i=0; i<arr.length; i+=2) {
|
|
28209
|
+
coords.push([arr[i], arr[i+1]]);
|
|
28210
|
+
}
|
|
28211
|
+
if (!isArrayOfPoints(coords)) {
|
|
28212
|
+
stop('Unable to parse x,y,x,y... coordinates');
|
|
28213
|
+
}
|
|
28214
|
+
return coords;
|
|
28215
|
+
}
|
|
28216
|
+
|
|
28217
|
+
function parseCoordsAsGeometry(arg) {
|
|
28218
|
+
if (typeof arg == 'string') {
|
|
28219
|
+
arg = arg.trim();
|
|
28220
|
+
if (!arg.startsWith('[') && !arg.endsWith(']')) {
|
|
28221
|
+
arg = '[' + arg + ']';
|
|
28222
|
+
}
|
|
28223
|
+
}
|
|
28224
|
+
var arr = parseArg(arg);
|
|
28225
|
+
if (isPoint(arr)) {
|
|
28226
|
+
return {
|
|
28227
|
+
type: 'Point',
|
|
28228
|
+
coordinates: arr
|
|
28229
|
+
};
|
|
28230
|
+
}
|
|
28231
|
+
|
|
28232
|
+
if (isArrayOfNumbers(arr)) {
|
|
28233
|
+
arr = transposeCoords(arr);
|
|
28234
|
+
}
|
|
28235
|
+
|
|
28236
|
+
if (isClosedPath$1(arr)) {
|
|
28237
|
+
return {
|
|
28238
|
+
type: 'Polygon',
|
|
28239
|
+
coordinates: [arr]
|
|
28240
|
+
};
|
|
28241
|
+
}
|
|
28242
|
+
|
|
28243
|
+
if (isArrayOfPoints(arr)) {
|
|
28244
|
+
return {
|
|
28245
|
+
type: 'LineString',
|
|
28246
|
+
coordinates: arr
|
|
28247
|
+
};
|
|
28248
|
+
}
|
|
28249
|
+
|
|
28250
|
+
stop('Unable to import coordinates');
|
|
28251
|
+
}
|
|
28252
|
+
|
|
28079
28253
|
const epsilon = 1.1102230246251565e-16;
|
|
28080
28254
|
const splitter = 134217729;
|
|
28081
28255
|
const resulterrbound = (3 + 8 * epsilon) * epsilon;
|
|
@@ -29264,17 +29438,6 @@ ${svg}
|
|
|
29264
29438
|
bufferIntersection: bufferIntersection
|
|
29265
29439
|
});
|
|
29266
29440
|
|
|
29267
|
-
function testSegmentBoundsIntersection(a, b, bb) {
|
|
29268
|
-
if (bb.containsPoint(a[0], a[1])) {
|
|
29269
|
-
return true;
|
|
29270
|
-
}
|
|
29271
|
-
return !!(
|
|
29272
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymin, bb.xmin, bb.ymax) ||
|
|
29273
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymax, bb.xmax, bb.ymax) ||
|
|
29274
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymax, bb.xmax, bb.ymin) ||
|
|
29275
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymin, bb.xmin, bb.ymin));
|
|
29276
|
-
}
|
|
29277
|
-
|
|
29278
29441
|
function getPolylineBufferMaker2(arcs, geod, getBearing, opts) {
|
|
29279
29442
|
var makeLeftBuffer = getPathBufferMaker2(arcs, geod, getBearing, opts);
|
|
29280
29443
|
var geomType = opts.geometry_type;
|
|
@@ -37095,7 +37258,7 @@ ${svg}
|
|
|
37095
37258
|
editor.done();
|
|
37096
37259
|
if (!opts.debug) {
|
|
37097
37260
|
buildTopology(dataset);
|
|
37098
|
-
|
|
37261
|
+
cleanProjectedPathLayers(dataset);
|
|
37099
37262
|
}
|
|
37100
37263
|
}
|
|
37101
37264
|
|
|
@@ -37606,15 +37769,41 @@ ${svg}
|
|
|
37606
37769
|
} else {
|
|
37607
37770
|
clipData = getClippingDataset(src, dest, opts);
|
|
37608
37771
|
}
|
|
37772
|
+
// clip data to projection limits (some projections), if content exceeds the limit
|
|
37773
|
+
//
|
|
37609
37774
|
if (clipData) {
|
|
37610
|
-
|
|
37611
|
-
// the clipping shape. But how to tell?
|
|
37612
|
-
clipLayersInPlace(dataset.layers, clipData, dataset, 'clip');
|
|
37613
|
-
clipped = true;
|
|
37775
|
+
clipped = clipLayersIfNeeded(dataset, clipData);
|
|
37614
37776
|
}
|
|
37615
37777
|
return cut || clipped;
|
|
37616
37778
|
}
|
|
37617
37779
|
|
|
37780
|
+
function clipLayersIfNeeded(dataset, clipData) {
|
|
37781
|
+
// Avoid clipping layers that are fully enclosed within the projectable
|
|
37782
|
+
// coordinate space (represented by a dataset containing a single
|
|
37783
|
+
// polygon layer, @clipData). This avoids performing unnecessary intersection
|
|
37784
|
+
// tests on each line segment.
|
|
37785
|
+
var layers = dataset.layers.filter(function(lyr) {
|
|
37786
|
+
return !layerIsFullyEnclosed(lyr, dataset, clipData);
|
|
37787
|
+
});
|
|
37788
|
+
if (layers.length > 0) {
|
|
37789
|
+
clipLayersInPlace(layers, clipData, dataset, 'clip');
|
|
37790
|
+
return true;
|
|
37791
|
+
}
|
|
37792
|
+
return false;
|
|
37793
|
+
}
|
|
37794
|
+
|
|
37795
|
+
// @clipData: a dataset containing a polygon layer
|
|
37796
|
+
function layerIsFullyEnclosed(lyr, dataset, clipData) {
|
|
37797
|
+
// This test uses the layer's bounding box to represent the extent of the
|
|
37798
|
+
// layer, and can produce false negatives.
|
|
37799
|
+
var dataBounds = getLayerBounds(lyr, dataset.arcs);
|
|
37800
|
+
var enclosed = false;
|
|
37801
|
+
clipData.layers[0].shapes.forEach(function(shp, i) {
|
|
37802
|
+
enclosed = enclosed || testBoundsInPolygon(dataBounds, shp, clipData.arcs);
|
|
37803
|
+
});
|
|
37804
|
+
return enclosed;
|
|
37805
|
+
}
|
|
37806
|
+
|
|
37618
37807
|
|
|
37619
37808
|
function insertPreProjectionCuts(dataset, src, dest) {
|
|
37620
37809
|
var antimeridian = getAntimeridian(dest.lam0 * 180 / Math.PI);
|
|
@@ -37809,7 +37998,6 @@ ${svg}
|
|
|
37809
37998
|
var badArcs = 0;
|
|
37810
37999
|
var badPoints = 0;
|
|
37811
38000
|
var clipped = preProjectionClip(dataset, src, dest, opts);
|
|
37812
|
-
|
|
37813
38001
|
dataset.layers.forEach(function(lyr) {
|
|
37814
38002
|
if (layerHasPoints(lyr)) {
|
|
37815
38003
|
badPoints += projectPointLayer(lyr, proj); // v2 compatible (invalid points are removed)
|
|
@@ -37826,7 +38014,7 @@ ${svg}
|
|
|
37826
38014
|
if (clipped) {
|
|
37827
38015
|
// TODO: could more selective in cleaning clipped layers
|
|
37828
38016
|
// (probably only needed when clipped area crosses the antimeridian or includes a pole)
|
|
37829
|
-
|
|
38017
|
+
cleanProjectedPathLayers(dataset);
|
|
37830
38018
|
}
|
|
37831
38019
|
|
|
37832
38020
|
if (badArcs > 0 && !opts.quiet) {
|
|
@@ -37842,7 +38030,7 @@ ${svg}
|
|
|
37842
38030
|
// * Removes line intersections
|
|
37843
38031
|
// * TODO: what if a layer contains polygons with desired overlaps? should
|
|
37844
38032
|
// we ignore overlaps between different features?
|
|
37845
|
-
function
|
|
38033
|
+
function cleanProjectedPathLayers(dataset) {
|
|
37846
38034
|
// TODO: only clean affected polygons (cleaning all polygons can be slow)
|
|
37847
38035
|
var polygonLayers = dataset.layers.filter(lyr => lyr.geometry_type == 'polygon');
|
|
37848
38036
|
// clean options: force a topology update (by default, this only happens when
|
|
@@ -37910,7 +38098,7 @@ ${svg}
|
|
|
37910
38098
|
__proto__: null,
|
|
37911
38099
|
fetchCrsInfo: fetchCrsInfo,
|
|
37912
38100
|
projectDataset: projectDataset,
|
|
37913
|
-
|
|
38101
|
+
cleanProjectedPathLayers: cleanProjectedPathLayers,
|
|
37914
38102
|
projectPointLayer: projectPointLayer,
|
|
37915
38103
|
projectArcs: projectArcs,
|
|
37916
38104
|
projectArcs2: projectArcs2
|
|
@@ -38884,12 +39072,12 @@ ${svg}
|
|
|
38884
39072
|
function polylineToMidpoints(shp, arcs, opts) {
|
|
38885
39073
|
if (!shp) return null;
|
|
38886
39074
|
var points = shp.map(function(path) {
|
|
38887
|
-
return findPathMidpoint(path, arcs);
|
|
39075
|
+
return findPathMidpoint(path, arcs, false);
|
|
38888
39076
|
});
|
|
38889
39077
|
return points;
|
|
38890
39078
|
}
|
|
38891
39079
|
|
|
38892
|
-
function findPathMidpoint(path, arcs) {
|
|
39080
|
+
function findPathMidpoint(path, arcs, useNearestVertex) {
|
|
38893
39081
|
var halfLen = calcPathLen(path, arcs, false) / 2;
|
|
38894
39082
|
var partialLen = 0;
|
|
38895
39083
|
var p;
|
|
@@ -38906,7 +39094,11 @@ ${svg}
|
|
|
38906
39094
|
var k;
|
|
38907
39095
|
if (partialLen + segLen >= halfLen) {
|
|
38908
39096
|
k = (halfLen - partialLen) / segLen;
|
|
38909
|
-
|
|
39097
|
+
if (useNearestVertex) {
|
|
39098
|
+
k = k < 0.5 ? 0 : 1;
|
|
39099
|
+
}
|
|
39100
|
+
// p = [a + k * (c - a), b + k * (d - b)];
|
|
39101
|
+
p = [(1 - k) * a + k * c, (1 - k) * b + k * d];
|
|
38910
39102
|
}
|
|
38911
39103
|
partialLen += segLen;
|
|
38912
39104
|
});
|
|
@@ -42786,7 +42978,7 @@ ${svg}
|
|
|
42786
42978
|
name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
|
|
42787
42979
|
name == 'require' || name == 'run' || name == 'define' ||
|
|
42788
42980
|
name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
|
|
42789
|
-
name == 'else' || name == 'endif' || name == 'stop';
|
|
42981
|
+
name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape';
|
|
42790
42982
|
}
|
|
42791
42983
|
|
|
42792
42984
|
async function runCommand(command, job) {
|
|
@@ -42860,7 +43052,14 @@ ${svg}
|
|
|
42860
43052
|
source = findCommandSource(convertSourceName(opts.source, targets), job.catalog, opts);
|
|
42861
43053
|
}
|
|
42862
43054
|
|
|
42863
|
-
if (name == '
|
|
43055
|
+
if (name == 'add-shape') {
|
|
43056
|
+
if (!targetDataset) {
|
|
43057
|
+
targetDataset = {info: {}, layers: []};
|
|
43058
|
+
targetLayers = targetDataset.layers;
|
|
43059
|
+
job.catalog.addDataset(targetDataset);
|
|
43060
|
+
}
|
|
43061
|
+
outputLayers = cmd.addShape(targetLayers, targetDataset, opts);
|
|
43062
|
+
} else if (name == 'affine') {
|
|
42864
43063
|
cmd.affine(targetLayers, targetDataset, opts);
|
|
42865
43064
|
|
|
42866
43065
|
} else if (name == 'alpha-shapes') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapshaper",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.25",
|
|
4
4
|
"description": "A tool for editing vector datasets for mapping and GIS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shapefile",
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"@tmcw/togeojson": "^5.6.0",
|
|
45
45
|
"@xmldom/xmldom": "^0.8.6",
|
|
46
46
|
"adm-zip": "^0.5.9",
|
|
47
|
-
"bson": "^4.7.0",
|
|
48
47
|
"commander": "7.0.0",
|
|
49
48
|
"cookies": "^0.8.0",
|
|
50
49
|
"d3-color": "3.1.0",
|
|
@@ -58,7 +57,7 @@
|
|
|
58
57
|
"idb-keyval": "^6.2.0",
|
|
59
58
|
"kdbush": "^3.0.0",
|
|
60
59
|
"mproj": "0.0.35",
|
|
61
|
-
"msgpackr": "^1.8.
|
|
60
|
+
"msgpackr": "^1.8.5",
|
|
62
61
|
"opn": "^5.3.0",
|
|
63
62
|
"rw": "~1.3.3",
|
|
64
63
|
"sync-request": "5.0.0",
|
package/www/index.html
CHANGED
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
<div>
|
|
119
119
|
<div class="select-btn btn dialog-btn">Select</div>
|
|
120
120
|
<div class="clip-btn btn dialog-btn">Clip</div>
|
|
121
|
+
<div class="erase-btn btn dialog-btn">Erase</div>
|
|
121
122
|
<div class="info-btn btn dialog-btn">Info</div>
|
|
122
123
|
<!-- <div class="zoom-btn btn dialog-btn">Zoom In</div>
|
|
123
124
|
<div class="rectangle-btn btn dialog-btn">Rectangle</div> -->
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -3050,7 +3050,9 @@
|
|
|
3050
3050
|
|
|
3051
3051
|
function saveHistory() {
|
|
3052
3052
|
history = history.filter(Boolean); // TODO: fix condition that leaves a blank line on the history
|
|
3053
|
-
|
|
3053
|
+
if (history.length > 0) {
|
|
3054
|
+
GUI.setSavedValue('console_history', history.slice(-100));
|
|
3055
|
+
}
|
|
3054
3056
|
}
|
|
3055
3057
|
|
|
3056
3058
|
function toLog(str, cname) {
|
|
@@ -3679,7 +3681,7 @@
|
|
|
3679
3681
|
.on('change', function() {
|
|
3680
3682
|
GUI.setSavedValue('choose-save-dir', this.checked);
|
|
3681
3683
|
})
|
|
3682
|
-
.attr('checked', GUI.getSavedValue('choose-save-dir') ||
|
|
3684
|
+
.attr('checked', GUI.getSavedValue('choose-save-dir') || null);
|
|
3683
3685
|
}
|
|
3684
3686
|
|
|
3685
3687
|
function turnOn() {
|
|
@@ -8053,8 +8055,14 @@
|
|
|
8053
8055
|
}
|
|
8054
8056
|
});
|
|
8055
8057
|
|
|
8058
|
+
// add new field form
|
|
8059
|
+
if (editable) {
|
|
8060
|
+
renderNewRow(tableEl, rec, table);
|
|
8061
|
+
}
|
|
8062
|
+
|
|
8063
|
+
tableEl.appendTo(el);
|
|
8056
8064
|
if (rows > 0) {
|
|
8057
|
-
tableEl.appendTo(el);
|
|
8065
|
+
// tableEl.appendTo(el);
|
|
8058
8066
|
|
|
8059
8067
|
tableEl.on('copy', function(e) {
|
|
8060
8068
|
// remove leading or trailing tabs that sometimes get copied when
|
|
@@ -8074,15 +8082,26 @@
|
|
|
8074
8082
|
el.html(utils$1.format('<div class="note">This %s is missing attribute data.</div>',
|
|
8075
8083
|
table && table.getFields().length > 0 ? 'feature': 'layer'));
|
|
8076
8084
|
}
|
|
8085
|
+
|
|
8086
|
+
if (editable) {
|
|
8087
|
+
var line = El('div').appendTo(el);
|
|
8088
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
8089
|
+
|
|
8090
|
+
}).text('add field');
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
|
|
8094
|
+
function renderNewRow(el, rec, table) {
|
|
8095
|
+
|
|
8077
8096
|
}
|
|
8078
8097
|
|
|
8079
8098
|
function renderRow(table, rec, key, type, editable) {
|
|
8080
|
-
var rowHtml = '<td class="field-name">%s</td><td><span class="value">%s</span> </td>';
|
|
8081
8099
|
var val = rec[key];
|
|
8082
8100
|
var str = formatInspectorValue(val, type);
|
|
8101
|
+
var rowHtml = `<td class="field-name">${key}</td><td><span class="value">${utils$1.htmlEscape(str)}</span> </td>`;
|
|
8083
8102
|
var cell = El('tr')
|
|
8084
8103
|
.appendTo(table)
|
|
8085
|
-
.html(
|
|
8104
|
+
.html(rowHtml)
|
|
8086
8105
|
.findChild('.value');
|
|
8087
8106
|
setFieldClass(cell, val, type);
|
|
8088
8107
|
if (editable) {
|
|
@@ -10193,6 +10212,10 @@
|
|
|
10193
10212
|
runCommand('-clip bbox=' + box.getDataCoords().join(','));
|
|
10194
10213
|
});
|
|
10195
10214
|
|
|
10215
|
+
new SimpleButton(popup.findChild('.erase-btn')).on('click', function() {
|
|
10216
|
+
runCommand('-erase bbox=' + box.getDataCoords().join(','));
|
|
10217
|
+
});
|
|
10218
|
+
|
|
10196
10219
|
gui.addMode('box_tool', turnOn, turnOff);
|
|
10197
10220
|
|
|
10198
10221
|
gui.on('interaction_mode_change', function(e) {
|
package/www/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.6.
|
|
3
|
+
var VERSION = "0.6.25";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -2449,6 +2449,17 @@
|
|
|
2449
2449
|
getPathCentroid: getPathCentroid
|
|
2450
2450
|
});
|
|
2451
2451
|
|
|
2452
|
+
function testSegmentBoundsIntersection(a, b, bb) {
|
|
2453
|
+
if (bb.containsPoint(a[0], a[1])) {
|
|
2454
|
+
return true;
|
|
2455
|
+
}
|
|
2456
|
+
return !!(
|
|
2457
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymin, bb.xmin, bb.ymax) ||
|
|
2458
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymax, bb.xmax, bb.ymax) ||
|
|
2459
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymax, bb.xmax, bb.ymin) ||
|
|
2460
|
+
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymin, bb.xmin, bb.ymin));
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2452
2463
|
// A compactness measure designed for testing electoral districts for gerrymandering.
|
|
2453
2464
|
// Returns value in [0-1] range. 1 = perfect circle, 0 = collapsed polygon
|
|
2454
2465
|
function calcPolsbyPopperCompactness(area, perimeter) {
|
|
@@ -2496,21 +2507,32 @@
|
|
|
2496
2507
|
// }, 0);
|
|
2497
2508
|
// }
|
|
2498
2509
|
|
|
2510
|
+
// test if a rectangle is completely enclosed in a planar polygon
|
|
2511
|
+
function testBoundsInPolygon(bounds, shp, arcs) {
|
|
2512
|
+
if (!shp || !testPointInPolygon(bounds.xmin, bounds.ymin, shp, arcs)) return false;
|
|
2513
|
+
var isIn = true;
|
|
2514
|
+
shp.forEach(function(ids) {
|
|
2515
|
+
forEachSegmentInPath(ids, arcs, function(a, b, xx, yy) {
|
|
2516
|
+
isIn = isIn && !testSegmentBoundsIntersection([xx[a], yy[a]], [xx[b], yy[b]], bounds);
|
|
2517
|
+
});
|
|
2518
|
+
});
|
|
2519
|
+
return isIn;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2499
2522
|
// Return true if point is inside or on boundary of a shape
|
|
2500
2523
|
//
|
|
2501
2524
|
function testPointInPolygon(x, y, shp, arcs) {
|
|
2502
2525
|
var isIn = false,
|
|
2503
2526
|
isOn = false;
|
|
2504
|
-
if (shp)
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
}
|
|
2527
|
+
if (!shp) return false;
|
|
2528
|
+
shp.forEach(function(ids) {
|
|
2529
|
+
var inRing = testPointInRing(x, y, ids, arcs);
|
|
2530
|
+
if (inRing == 1) {
|
|
2531
|
+
isIn = !isIn;
|
|
2532
|
+
} else if (inRing == -1) {
|
|
2533
|
+
isOn = true;
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2514
2536
|
return isOn || isIn;
|
|
2515
2537
|
}
|
|
2516
2538
|
|
|
@@ -2518,6 +2540,8 @@
|
|
|
2518
2540
|
return ay + (x - ax) * (by - ay) / (bx - ax);
|
|
2519
2541
|
}
|
|
2520
2542
|
|
|
2543
|
+
|
|
2544
|
+
|
|
2521
2545
|
// Test if point (x, y) is inside, outside or on the boundary of a polygon ring
|
|
2522
2546
|
// Return 0: outside; 1: inside; -1: on boundary
|
|
2523
2547
|
//
|
|
@@ -2708,6 +2732,7 @@
|
|
|
2708
2732
|
getShapeArea: getShapeArea,
|
|
2709
2733
|
getPlanarShapeArea: getPlanarShapeArea,
|
|
2710
2734
|
getSphericalShapeArea: getSphericalShapeArea,
|
|
2735
|
+
testBoundsInPolygon: testBoundsInPolygon,
|
|
2711
2736
|
testPointInPolygon: testPointInPolygon,
|
|
2712
2737
|
testPointInRing: testPointInRing,
|
|
2713
2738
|
testRayIntersection: testRayIntersection,
|
|
@@ -7251,7 +7276,7 @@
|
|
|
7251
7276
|
let size = source.length;
|
|
7252
7277
|
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size);
|
|
7253
7278
|
if (forEach) {
|
|
7254
|
-
forEach(value);
|
|
7279
|
+
if (forEach(value) === false) return;
|
|
7255
7280
|
while(position$1 < size) {
|
|
7256
7281
|
lastPosition = position$1;
|
|
7257
7282
|
if (forEach(checkedRead()) === false) {
|
|
@@ -8193,7 +8218,7 @@
|
|
|
8193
8218
|
let safeEnd;
|
|
8194
8219
|
let bundledStrings = null;
|
|
8195
8220
|
let writeStructSlots;
|
|
8196
|
-
const MAX_BUNDLE_SIZE =
|
|
8221
|
+
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
|
|
8197
8222
|
const hasNonLatin = /[\u0080-\uFFFF]/;
|
|
8198
8223
|
const RECORD_SYMBOL = Symbol('record-id');
|
|
8199
8224
|
class Packr extends Unpackr {
|
|
@@ -8222,7 +8247,7 @@
|
|
|
8222
8247
|
if (maxSharedStructures > 8160)
|
|
8223
8248
|
throw new Error('Maximum maxSharedStructure is 8160')
|
|
8224
8249
|
if (options.structuredClone && options.moreTypes == undefined) {
|
|
8225
|
-
|
|
8250
|
+
this.moreTypes = true;
|
|
8226
8251
|
}
|
|
8227
8252
|
let maxOwnStructures = options.maxOwnStructures;
|
|
8228
8253
|
if (maxOwnStructures == null)
|
|
@@ -8917,12 +8942,11 @@
|
|
|
8917
8942
|
if (notifySharedUpdate)
|
|
8918
8943
|
return hasSharedUpdate = true;
|
|
8919
8944
|
position = newPosition;
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
}
|
|
8925
|
-
pack(value);
|
|
8945
|
+
let startTarget = target;
|
|
8946
|
+
pack(value);
|
|
8947
|
+
if (startTarget !== target) {
|
|
8948
|
+
return { position, targetView, target }; // indicate the buffer was re-allocated
|
|
8949
|
+
}
|
|
8926
8950
|
return position;
|
|
8927
8951
|
}, this);
|
|
8928
8952
|
if (newPosition === 0) // bail and go to a msgpack object
|
|
@@ -13729,6 +13753,11 @@
|
|
|
13729
13753
|
return rect.contains(bbox);
|
|
13730
13754
|
};
|
|
13731
13755
|
|
|
13756
|
+
// TODO
|
|
13757
|
+
// ctx.intersectsRectangle = function(a, b, c, d) {}; // paths... points too?
|
|
13758
|
+
// ctx.containsPoint = function(x, y) {}; // polygon only
|
|
13759
|
+
// ctx.containedByRectangle(a, b, c, d); // paths and points... how do multipart points work?
|
|
13760
|
+
|
|
13732
13761
|
addGetters(ctx, {
|
|
13733
13762
|
// TODO: count hole/s + containing ring as one part
|
|
13734
13763
|
partCount: function() {
|
|
@@ -24602,6 +24631,21 @@ ${svg}
|
|
|
24602
24631
|
// Experimental commands
|
|
24603
24632
|
parser.section('Experimental commands (may give unexpected results)');
|
|
24604
24633
|
|
|
24634
|
+
parser.command('add-shape')
|
|
24635
|
+
.describe('')
|
|
24636
|
+
.option('geojson', {
|
|
24637
|
+
|
|
24638
|
+
})
|
|
24639
|
+
.option('coordinates', {
|
|
24640
|
+
|
|
24641
|
+
})
|
|
24642
|
+
.option('properties', {
|
|
24643
|
+
|
|
24644
|
+
})
|
|
24645
|
+
.option('name', nameOpt)
|
|
24646
|
+
.option('target', targetOpt)
|
|
24647
|
+
.option('no-replace', noReplaceOpt);
|
|
24648
|
+
|
|
24605
24649
|
parser.command('alpha-shapes')
|
|
24606
24650
|
// .describe('convert points to alpha shapes (aka concave hulls)')
|
|
24607
24651
|
.option('interval', {
|
|
@@ -28076,6 +28120,136 @@ ${svg}
|
|
|
28076
28120
|
stashVar('input_files', job.input_files);
|
|
28077
28121
|
}
|
|
28078
28122
|
|
|
28123
|
+
cmd.addShape = addShape;
|
|
28124
|
+
|
|
28125
|
+
function addShape(targetLayers, targetDataset, opts) {
|
|
28126
|
+
if (targetLayers.length > 1) {
|
|
28127
|
+
stop('Command expects a single target layer');
|
|
28128
|
+
}
|
|
28129
|
+
var targetLyr = targetLayers[0]; // may be undefined
|
|
28130
|
+
var targetType = !opts.no_replace && targetLyr && targetLyr.geometry_type || null;
|
|
28131
|
+
var dataset = importGeoJSON(toFeature(opts, targetType));
|
|
28132
|
+
var outputLyr = mergeDatasetsIntoDataset(targetDataset, [dataset])[0];
|
|
28133
|
+
if (opts.no_replace || !targetLyr) {
|
|
28134
|
+
// create new layer
|
|
28135
|
+
setOutputLayerName(outputLyr, targetLyr && targetLyr.name, null, opts);
|
|
28136
|
+
return [outputLyr];
|
|
28137
|
+
}
|
|
28138
|
+
// merge into target layer
|
|
28139
|
+
return cmd.mergeLayers([targetLyr, outputLyr], {force: true});
|
|
28140
|
+
}
|
|
28141
|
+
|
|
28142
|
+
function toFeature(opts, geomType) {
|
|
28143
|
+
if (opts.geojson) {
|
|
28144
|
+
return parseArg(opts.geojson);
|
|
28145
|
+
}
|
|
28146
|
+
|
|
28147
|
+
var geom = opts.coordinates && parseCoordsAsGeometry(opts.coordinates) || null;
|
|
28148
|
+
|
|
28149
|
+
if (!geom) {
|
|
28150
|
+
stop('Missing required shape coordinates');
|
|
28151
|
+
}
|
|
28152
|
+
|
|
28153
|
+
if (geomType == 'point' && geom.type != 'Point') {
|
|
28154
|
+
stop('Expected point coordinates, received', geom.type);
|
|
28155
|
+
}
|
|
28156
|
+
|
|
28157
|
+
if (geomType == 'polygon' && geom.type != 'Polygon') {
|
|
28158
|
+
stop('Expected polygon coordinates, received', geom.type);
|
|
28159
|
+
}
|
|
28160
|
+
|
|
28161
|
+
if (geomType == 'polyline') {
|
|
28162
|
+
if (geom.type == 'Polygon') {
|
|
28163
|
+
geom.coordinates = geom.coordinates[0];
|
|
28164
|
+
geom.type = 'LineString';
|
|
28165
|
+
} else {
|
|
28166
|
+
stop('Expected polyline coordinates, received', geom.type);
|
|
28167
|
+
}
|
|
28168
|
+
}
|
|
28169
|
+
|
|
28170
|
+
return {
|
|
28171
|
+
type: 'Feature',
|
|
28172
|
+
properties: parseProperties(opts.properties),
|
|
28173
|
+
geometry: geom
|
|
28174
|
+
};
|
|
28175
|
+
}
|
|
28176
|
+
|
|
28177
|
+
function parseArg(obj) {
|
|
28178
|
+
return typeof obj == 'string' ? JSON.parse(obj) : obj;
|
|
28179
|
+
}
|
|
28180
|
+
|
|
28181
|
+
function parseProperties(arg) {
|
|
28182
|
+
if (!arg) return null;
|
|
28183
|
+
return parseArg(arg);
|
|
28184
|
+
}
|
|
28185
|
+
|
|
28186
|
+
function isArrayOfNumbers(arr) {
|
|
28187
|
+
return arr.length >= 2 && arr.every(utils.isNumber);
|
|
28188
|
+
}
|
|
28189
|
+
|
|
28190
|
+
function isClosedPath$1(arr) {
|
|
28191
|
+
return isArrayOfPoints(arr) && arr.length > 3 && samePoint$1(arr[0], arr[arr.length - 1]);
|
|
28192
|
+
}
|
|
28193
|
+
|
|
28194
|
+
function samePoint$1(a, b) {
|
|
28195
|
+
return a[0] == b[0] && a[1] == b[1];
|
|
28196
|
+
}
|
|
28197
|
+
|
|
28198
|
+
function isArrayOfPoints(arr) {
|
|
28199
|
+
return arr.every(isPoint);
|
|
28200
|
+
}
|
|
28201
|
+
|
|
28202
|
+
function isPoint(arr) {
|
|
28203
|
+
return arr && arr.length == 2 && isArrayOfNumbers(arr);
|
|
28204
|
+
}
|
|
28205
|
+
|
|
28206
|
+
function transposeCoords(arr) {
|
|
28207
|
+
var coords = [];
|
|
28208
|
+
for (var i=0; i<arr.length; i+=2) {
|
|
28209
|
+
coords.push([arr[i], arr[i+1]]);
|
|
28210
|
+
}
|
|
28211
|
+
if (!isArrayOfPoints(coords)) {
|
|
28212
|
+
stop('Unable to parse x,y,x,y... coordinates');
|
|
28213
|
+
}
|
|
28214
|
+
return coords;
|
|
28215
|
+
}
|
|
28216
|
+
|
|
28217
|
+
function parseCoordsAsGeometry(arg) {
|
|
28218
|
+
if (typeof arg == 'string') {
|
|
28219
|
+
arg = arg.trim();
|
|
28220
|
+
if (!arg.startsWith('[') && !arg.endsWith(']')) {
|
|
28221
|
+
arg = '[' + arg + ']';
|
|
28222
|
+
}
|
|
28223
|
+
}
|
|
28224
|
+
var arr = parseArg(arg);
|
|
28225
|
+
if (isPoint(arr)) {
|
|
28226
|
+
return {
|
|
28227
|
+
type: 'Point',
|
|
28228
|
+
coordinates: arr
|
|
28229
|
+
};
|
|
28230
|
+
}
|
|
28231
|
+
|
|
28232
|
+
if (isArrayOfNumbers(arr)) {
|
|
28233
|
+
arr = transposeCoords(arr);
|
|
28234
|
+
}
|
|
28235
|
+
|
|
28236
|
+
if (isClosedPath$1(arr)) {
|
|
28237
|
+
return {
|
|
28238
|
+
type: 'Polygon',
|
|
28239
|
+
coordinates: [arr]
|
|
28240
|
+
};
|
|
28241
|
+
}
|
|
28242
|
+
|
|
28243
|
+
if (isArrayOfPoints(arr)) {
|
|
28244
|
+
return {
|
|
28245
|
+
type: 'LineString',
|
|
28246
|
+
coordinates: arr
|
|
28247
|
+
};
|
|
28248
|
+
}
|
|
28249
|
+
|
|
28250
|
+
stop('Unable to import coordinates');
|
|
28251
|
+
}
|
|
28252
|
+
|
|
28079
28253
|
const epsilon = 1.1102230246251565e-16;
|
|
28080
28254
|
const splitter = 134217729;
|
|
28081
28255
|
const resulterrbound = (3 + 8 * epsilon) * epsilon;
|
|
@@ -29264,17 +29438,6 @@ ${svg}
|
|
|
29264
29438
|
bufferIntersection: bufferIntersection
|
|
29265
29439
|
});
|
|
29266
29440
|
|
|
29267
|
-
function testSegmentBoundsIntersection(a, b, bb) {
|
|
29268
|
-
if (bb.containsPoint(a[0], a[1])) {
|
|
29269
|
-
return true;
|
|
29270
|
-
}
|
|
29271
|
-
return !!(
|
|
29272
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymin, bb.xmin, bb.ymax) ||
|
|
29273
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmin, bb.ymax, bb.xmax, bb.ymax) ||
|
|
29274
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymax, bb.xmax, bb.ymin) ||
|
|
29275
|
-
geom.segmentIntersection(a[0], a[1], b[0], b[1], bb.xmax, bb.ymin, bb.xmin, bb.ymin));
|
|
29276
|
-
}
|
|
29277
|
-
|
|
29278
29441
|
function getPolylineBufferMaker2(arcs, geod, getBearing, opts) {
|
|
29279
29442
|
var makeLeftBuffer = getPathBufferMaker2(arcs, geod, getBearing, opts);
|
|
29280
29443
|
var geomType = opts.geometry_type;
|
|
@@ -37095,7 +37258,7 @@ ${svg}
|
|
|
37095
37258
|
editor.done();
|
|
37096
37259
|
if (!opts.debug) {
|
|
37097
37260
|
buildTopology(dataset);
|
|
37098
|
-
|
|
37261
|
+
cleanProjectedPathLayers(dataset);
|
|
37099
37262
|
}
|
|
37100
37263
|
}
|
|
37101
37264
|
|
|
@@ -37606,15 +37769,41 @@ ${svg}
|
|
|
37606
37769
|
} else {
|
|
37607
37770
|
clipData = getClippingDataset(src, dest, opts);
|
|
37608
37771
|
}
|
|
37772
|
+
// clip data to projection limits (some projections), if content exceeds the limit
|
|
37773
|
+
//
|
|
37609
37774
|
if (clipData) {
|
|
37610
|
-
|
|
37611
|
-
// the clipping shape. But how to tell?
|
|
37612
|
-
clipLayersInPlace(dataset.layers, clipData, dataset, 'clip');
|
|
37613
|
-
clipped = true;
|
|
37775
|
+
clipped = clipLayersIfNeeded(dataset, clipData);
|
|
37614
37776
|
}
|
|
37615
37777
|
return cut || clipped;
|
|
37616
37778
|
}
|
|
37617
37779
|
|
|
37780
|
+
function clipLayersIfNeeded(dataset, clipData) {
|
|
37781
|
+
// Avoid clipping layers that are fully enclosed within the projectable
|
|
37782
|
+
// coordinate space (represented by a dataset containing a single
|
|
37783
|
+
// polygon layer, @clipData). This avoids performing unnecessary intersection
|
|
37784
|
+
// tests on each line segment.
|
|
37785
|
+
var layers = dataset.layers.filter(function(lyr) {
|
|
37786
|
+
return !layerIsFullyEnclosed(lyr, dataset, clipData);
|
|
37787
|
+
});
|
|
37788
|
+
if (layers.length > 0) {
|
|
37789
|
+
clipLayersInPlace(layers, clipData, dataset, 'clip');
|
|
37790
|
+
return true;
|
|
37791
|
+
}
|
|
37792
|
+
return false;
|
|
37793
|
+
}
|
|
37794
|
+
|
|
37795
|
+
// @clipData: a dataset containing a polygon layer
|
|
37796
|
+
function layerIsFullyEnclosed(lyr, dataset, clipData) {
|
|
37797
|
+
// This test uses the layer's bounding box to represent the extent of the
|
|
37798
|
+
// layer, and can produce false negatives.
|
|
37799
|
+
var dataBounds = getLayerBounds(lyr, dataset.arcs);
|
|
37800
|
+
var enclosed = false;
|
|
37801
|
+
clipData.layers[0].shapes.forEach(function(shp, i) {
|
|
37802
|
+
enclosed = enclosed || testBoundsInPolygon(dataBounds, shp, clipData.arcs);
|
|
37803
|
+
});
|
|
37804
|
+
return enclosed;
|
|
37805
|
+
}
|
|
37806
|
+
|
|
37618
37807
|
|
|
37619
37808
|
function insertPreProjectionCuts(dataset, src, dest) {
|
|
37620
37809
|
var antimeridian = getAntimeridian(dest.lam0 * 180 / Math.PI);
|
|
@@ -37809,7 +37998,6 @@ ${svg}
|
|
|
37809
37998
|
var badArcs = 0;
|
|
37810
37999
|
var badPoints = 0;
|
|
37811
38000
|
var clipped = preProjectionClip(dataset, src, dest, opts);
|
|
37812
|
-
|
|
37813
38001
|
dataset.layers.forEach(function(lyr) {
|
|
37814
38002
|
if (layerHasPoints(lyr)) {
|
|
37815
38003
|
badPoints += projectPointLayer(lyr, proj); // v2 compatible (invalid points are removed)
|
|
@@ -37826,7 +38014,7 @@ ${svg}
|
|
|
37826
38014
|
if (clipped) {
|
|
37827
38015
|
// TODO: could more selective in cleaning clipped layers
|
|
37828
38016
|
// (probably only needed when clipped area crosses the antimeridian or includes a pole)
|
|
37829
|
-
|
|
38017
|
+
cleanProjectedPathLayers(dataset);
|
|
37830
38018
|
}
|
|
37831
38019
|
|
|
37832
38020
|
if (badArcs > 0 && !opts.quiet) {
|
|
@@ -37842,7 +38030,7 @@ ${svg}
|
|
|
37842
38030
|
// * Removes line intersections
|
|
37843
38031
|
// * TODO: what if a layer contains polygons with desired overlaps? should
|
|
37844
38032
|
// we ignore overlaps between different features?
|
|
37845
|
-
function
|
|
38033
|
+
function cleanProjectedPathLayers(dataset) {
|
|
37846
38034
|
// TODO: only clean affected polygons (cleaning all polygons can be slow)
|
|
37847
38035
|
var polygonLayers = dataset.layers.filter(lyr => lyr.geometry_type == 'polygon');
|
|
37848
38036
|
// clean options: force a topology update (by default, this only happens when
|
|
@@ -37910,7 +38098,7 @@ ${svg}
|
|
|
37910
38098
|
__proto__: null,
|
|
37911
38099
|
fetchCrsInfo: fetchCrsInfo,
|
|
37912
38100
|
projectDataset: projectDataset,
|
|
37913
|
-
|
|
38101
|
+
cleanProjectedPathLayers: cleanProjectedPathLayers,
|
|
37914
38102
|
projectPointLayer: projectPointLayer,
|
|
37915
38103
|
projectArcs: projectArcs,
|
|
37916
38104
|
projectArcs2: projectArcs2
|
|
@@ -38884,12 +39072,12 @@ ${svg}
|
|
|
38884
39072
|
function polylineToMidpoints(shp, arcs, opts) {
|
|
38885
39073
|
if (!shp) return null;
|
|
38886
39074
|
var points = shp.map(function(path) {
|
|
38887
|
-
return findPathMidpoint(path, arcs);
|
|
39075
|
+
return findPathMidpoint(path, arcs, false);
|
|
38888
39076
|
});
|
|
38889
39077
|
return points;
|
|
38890
39078
|
}
|
|
38891
39079
|
|
|
38892
|
-
function findPathMidpoint(path, arcs) {
|
|
39080
|
+
function findPathMidpoint(path, arcs, useNearestVertex) {
|
|
38893
39081
|
var halfLen = calcPathLen(path, arcs, false) / 2;
|
|
38894
39082
|
var partialLen = 0;
|
|
38895
39083
|
var p;
|
|
@@ -38906,7 +39094,11 @@ ${svg}
|
|
|
38906
39094
|
var k;
|
|
38907
39095
|
if (partialLen + segLen >= halfLen) {
|
|
38908
39096
|
k = (halfLen - partialLen) / segLen;
|
|
38909
|
-
|
|
39097
|
+
if (useNearestVertex) {
|
|
39098
|
+
k = k < 0.5 ? 0 : 1;
|
|
39099
|
+
}
|
|
39100
|
+
// p = [a + k * (c - a), b + k * (d - b)];
|
|
39101
|
+
p = [(1 - k) * a + k * c, (1 - k) * b + k * d];
|
|
38910
39102
|
}
|
|
38911
39103
|
partialLen += segLen;
|
|
38912
39104
|
});
|
|
@@ -42786,7 +42978,7 @@ ${svg}
|
|
|
42786
42978
|
name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
|
|
42787
42979
|
name == 'require' || name == 'run' || name == 'define' ||
|
|
42788
42980
|
name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
|
|
42789
|
-
name == 'else' || name == 'endif' || name == 'stop';
|
|
42981
|
+
name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape';
|
|
42790
42982
|
}
|
|
42791
42983
|
|
|
42792
42984
|
async function runCommand(command, job) {
|
|
@@ -42860,7 +43052,14 @@ ${svg}
|
|
|
42860
43052
|
source = findCommandSource(convertSourceName(opts.source, targets), job.catalog, opts);
|
|
42861
43053
|
}
|
|
42862
43054
|
|
|
42863
|
-
if (name == '
|
|
43055
|
+
if (name == 'add-shape') {
|
|
43056
|
+
if (!targetDataset) {
|
|
43057
|
+
targetDataset = {info: {}, layers: []};
|
|
43058
|
+
targetLayers = targetDataset.layers;
|
|
43059
|
+
job.catalog.addDataset(targetDataset);
|
|
43060
|
+
}
|
|
43061
|
+
outputLayers = cmd.addShape(targetLayers, targetDataset, opts);
|
|
43062
|
+
} else if (name == 'affine') {
|
|
42864
43063
|
cmd.affine(targetLayers, targetDataset, opts);
|
|
42865
43064
|
|
|
42866
43065
|
} else if (name == 'alpha-shapes') {
|