mapshaper 0.6.31 → 0.6.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.31";
3
+ var VERSION = "0.6.33";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -11665,8 +11665,16 @@
11665
11665
  }
11666
11666
  cands.forEach(function(cand) {
11667
11667
  var p = getTestPoint(cand.ids);
11668
- var isEnclosed = b.containsPoint(p[0], p[1]) && (index ?
11669
- index.pointInPolygon(p[0], p[1]) : geom.testPointInRing(p[0], p[1], pathIds, arcs));
11668
+ var isEnclosed = b.containsPoint(p[0], p[1]) &&
11669
+ // added a bounds-in-bounds test to handle a case where the test point
11670
+ // fell along the shared boundary of two rings, but the rings did no overlap
11671
+ // (this gave a false positive for the enclosure test)
11672
+ // (for speed, the midpoint of an arc is used as the test point; this
11673
+ // works well in the typical case where rings to not share an edge.
11674
+ // Finding an internal test point would be better, we just need a fast
11675
+ // function to find internal points)
11676
+ b.contains(cand.bounds) &&
11677
+ (index ? index.pointInPolygon(p[0], p[1]) : geom.testPointInRing(p[0], p[1], pathIds, arcs));
11670
11678
  if (isEnclosed) {
11671
11679
  paths.push(cand.ids);
11672
11680
  }
@@ -11674,6 +11682,7 @@
11674
11682
  return paths.length > 0 ? paths : null;
11675
11683
  };
11676
11684
 
11685
+ // return array of indexed paths within a given shape
11677
11686
  this.findPathsInsideShape = function(shape) {
11678
11687
  var paths = []; // list of enclosed paths
11679
11688
  shape.forEach(function(ids) {
@@ -12075,7 +12084,6 @@
12075
12084
  yy = coords.yy,
12076
12085
  ids = nodes.getConnectedArcs(fromArcId),
12077
12086
  toArcId = fromArcId; // initialize to fromArcId -- an error condition
12078
-
12079
12087
  if (filter) {
12080
12088
  ids = ids.filter(filter);
12081
12089
  }
@@ -12104,14 +12112,12 @@
12104
12112
  continue;
12105
12113
  }
12106
12114
  icand = arcs.indexOfVertex(candId, -2);
12107
-
12108
12115
  if (toArcId == fromArcId) {
12109
12116
  // first valid candidate
12110
12117
  ito = icand;
12111
12118
  toArcId = candId;
12112
12119
  continue;
12113
12120
  }
12114
-
12115
12121
  code = chooseRighthandPath(fromX, fromY, nodeX, nodeY, xx[ito], yy[ito], xx[icand], yy[icand]);
12116
12122
  if (code == 2) {
12117
12123
  ito = icand;
@@ -12119,6 +12125,7 @@
12119
12125
  }
12120
12126
  }
12121
12127
 
12128
+
12122
12129
  if (toArcId == fromArcId) {
12123
12130
  // This shouldn't occur, assuming that other arcs are present
12124
12131
  error("Pathfinder error");
@@ -12183,6 +12190,9 @@
12183
12190
  chooseRighthandVector: chooseRighthandVector
12184
12191
  });
12185
12192
 
12193
+ var FWD_USED = 0x8;
12194
+ var REV_USED = 0x80;
12195
+
12186
12196
  function setBits(bits, arcBits, mask) {
12187
12197
  return (bits & ~mask) | (arcBits & mask);
12188
12198
  }
@@ -12210,6 +12220,16 @@
12210
12220
  return bits & 7;
12211
12221
  }
12212
12222
 
12223
+ function markPathsAsUsed(paths, routesArr) {
12224
+ forEachArcId(paths, function(arcId) {
12225
+ if (arcId < 0) {
12226
+ routesArr[~arcId] |= REV_USED;
12227
+ } else {
12228
+ routesArr[arcId] |= FWD_USED;
12229
+ }
12230
+ });
12231
+ }
12232
+
12213
12233
  // Open arc pathways in a single shape or array of shapes
12214
12234
  //
12215
12235
  function openArcRoutes(paths, arcColl, routesArr, fwd, rev, dissolve, orBits) {
@@ -12400,6 +12420,7 @@
12400
12420
  andBits: andBits,
12401
12421
  setRouteBits: setRouteBits,
12402
12422
  getRouteBits: getRouteBits,
12423
+ markPathsAsUsed: markPathsAsUsed,
12403
12424
  openArcRoutes: openArcRoutes,
12404
12425
  closeArcRoutes: closeArcRoutes,
12405
12426
  getPathFinder: getPathFinder,
@@ -24840,13 +24861,12 @@ ${svg}
24840
24861
 
24841
24862
  parser.command('run')
24842
24863
  .describe('create commands on-the-fly and run them')
24843
- .option('include', {
24844
- // TODO: remove this option
24845
- })
24846
- .option('commands', {
24864
+ .option('expression', {
24847
24865
  DEFAULT: true,
24848
- describe: 'command string or JS expresson to generate command(s)'
24866
+ describe: 'JS expression to generate command(s)'
24849
24867
  })
24868
+ // deprecated
24869
+ .option('commands', {alias_to: 'expression'})
24850
24870
  .option('target', targetOpt);
24851
24871
 
24852
24872
  parser.command('scalebar')
@@ -33402,7 +33422,7 @@ ${svg}
33402
33422
  var clipArcTouches = 0;
33403
33423
  var clipArcUses = 0;
33404
33424
  var usedClipArcs = [];
33405
- var dividePath = getPathFinder(nodes, useRoute, routeIsActive);
33425
+ var findPath = getPathFinder(nodes, useRoute, routeIsActive);
33406
33426
  var dissolvePolygon = getPolygonDissolver(nodes);
33407
33427
 
33408
33428
  // The following cleanup step is a performance bottleneck (it often takes longer than
@@ -33435,12 +33455,16 @@ ${svg}
33435
33455
  return null;
33436
33456
  });
33437
33457
 
33458
+ markPathsAsUsed(clippedShapes, routeFlags); // to help us find unused paths later
33459
+
33460
+
33438
33461
  // add clip/erase polygons that are fully contained in a target polygon
33439
33462
  // need to index only non-intersecting clip shapes
33440
33463
  // (Intersecting shapes have one or more arcs that have been scanned)
33441
33464
 
33442
33465
  // first, find shapes that do not intersect the target layer
33443
33466
  // (these could be inside or outside the target polygons)
33467
+
33444
33468
  var undividedClipShapes = findUndividedClipShapes(clipShapes);
33445
33469
 
33446
33470
  closeArcRoutes(clipShapes, arcs, routeFlags, true, true); // not needed?
@@ -33469,7 +33493,7 @@ ${svg}
33469
33493
  for (var i=0, n=ids.length; i<n; i++) {
33470
33494
  clipArcTouches = 0;
33471
33495
  clipArcUses = 0;
33472
- path = dividePath(ids[i]);
33496
+ path = findPath(ids[i]);
33473
33497
  if (path) {
33474
33498
  // if ring doesn't touch/intersect a clip/erase polygon, check if it is contained
33475
33499
  // if (clipArcTouches === 0) {
@@ -33488,6 +33512,7 @@ ${svg}
33488
33512
  }
33489
33513
  });
33490
33514
 
33515
+
33491
33516
  // Clear pathways of current target shape to hidden/closed
33492
33517
  closeArcRoutes(shape, arcs, routeFlags, true, true, true);
33493
33518
  // Also clear pathways of any clip arcs that were used
@@ -33558,8 +33583,9 @@ ${svg}
33558
33583
  return usable;
33559
33584
  }
33560
33585
 
33561
- // Filter a collection of shapes to exclude paths that contain clip/erase arcs
33562
- // and paths that are hidden (e.g. internal boundaries)
33586
+
33587
+ // Filter a collection of shapes to exclude paths that incorporate parts of
33588
+ // clip/erase polygons and paths that are hidden (e.g. internal boundaries)
33563
33589
  function findUndividedClipShapes(clipShapes) {
33564
33590
  return clipShapes.map(function(shape) {
33565
33591
  var usableParts = [];
@@ -33583,12 +33609,12 @@ ${svg}
33583
33609
  });
33584
33610
  }
33585
33611
 
33586
- // Test if arc is unused in both directions
33587
- // (not testing open/closed or visible/hidden)
33612
+
33588
33613
  function arcIsUnused(id, flags) {
33589
33614
  var abs = absArcId(id),
33590
33615
  flag = flags[abs];
33591
- return (flag & 0x44) === 0;
33616
+ return (flag & 0x88) === 0;
33617
+ // return id < 0 ? (flag & 0x80) === 0 : (flag & 0x8) === 0;
33592
33618
  }
33593
33619
 
33594
33620
  function arcIsVisible(id, flags) {
@@ -33611,7 +33637,7 @@ ${svg}
33611
33637
  enclosedPaths.forEach(function(ids) {
33612
33638
  var path;
33613
33639
  for (var j=0; j<ids.length; j++) {
33614
- path = dividePath(ids[j]);
33640
+ path = findPath(ids[j]);
33615
33641
  if (path) {
33616
33642
  dissolvedPaths.push(path);
33617
33643
  }
@@ -34730,7 +34756,64 @@ ${svg}
34730
34756
  };
34731
34757
  }
34732
34758
 
34733
- cmd.define = function(opts) {
34759
+ function compileIfCommandExpression(expr, catalog, opts) {
34760
+ return compileLayerExpression(expr, catalog, opts);
34761
+ }
34762
+
34763
+
34764
+ function compileLayerExpression(expr, catalog, opts) {
34765
+ var targetId = opts.layer || opts.target || null;
34766
+ var targets = catalog.findCommandTargets(targetId);
34767
+ var isSingle = targets.length == 1 && targets[0].layers.length == 1;
34768
+ if (targets.length === 0 && targetId) {
34769
+ stop('Layer not found:', targetId);
34770
+ }
34771
+ // var vars = getAssignedVars(exp);
34772
+ var defs = getStashedVar('defs') || {};
34773
+
34774
+ var ctx;
34775
+ if (isSingle) {
34776
+ ctx = getLayerProxy(targets[0].layers[0], targets[0].dataset.arcs);
34777
+ } else {
34778
+ ctx = getNullLayerProxy(targets);
34779
+ }
34780
+ ctx.global = defs; // TODO: remove duplication with mapshaper.expressions.mjs
34781
+ var exprOpts = Object.assign({returns: true}, opts);
34782
+ var func = compileExpressionToFunction(expr, exprOpts);
34783
+
34784
+ // @geoType: optional geometry type (polygon, polyline, point, null);
34785
+ ctx.layer_exists = function(name, geoType) {
34786
+ try {
34787
+ var targets = catalog.findCommandTargets(name, geoType);
34788
+ if (targets.length > 0) return true;
34789
+ } catch(e) {}
34790
+ return false;
34791
+ };
34792
+
34793
+ ctx.file_exists = function(file) {
34794
+ return cli.isFile(file);
34795
+ };
34796
+
34797
+ return function() {
34798
+ try {
34799
+ return func.call(ctx, defs, ctx);
34800
+ } catch(e) {
34801
+ // if (opts.quiet) throw e;
34802
+ stop(e.name, "in expression [" + expr + "]:", e.message);
34803
+ }
34804
+ };
34805
+ }
34806
+
34807
+ /*
34808
+ cmd.define_v2 = function(catalog, opts) {
34809
+ if (!opts.expression) {
34810
+ stop('Missing an assignment expression');
34811
+ }
34812
+ compileLayerExpression(opts.expression, catalog, opts)();
34813
+ };
34814
+ */
34815
+
34816
+ cmd.define = function(catalog, opts) {
34734
34817
  if (!opts.expression) {
34735
34818
  stop('Missing an assignment expression');
34736
34819
  }
@@ -38418,46 +38501,6 @@ ${svg}
38418
38501
  return job.control || (job.control = {});
38419
38502
  }
38420
38503
 
38421
- function compileIfCommandExpression(expr, catalog, opts) {
38422
- var targetId = opts.layer || opts.target || null;
38423
- var targets = catalog.findCommandTargets(targetId);
38424
- var isSingle = targets.length == 1 && targets[0].layers.length == 1;
38425
- if (targets.length === 0 && targetId) {
38426
- stop('Layer not found:', targetId);
38427
- }
38428
- var ctx;
38429
- if (isSingle) {
38430
- ctx = getLayerProxy(targets[0].layers[0], targets[0].dataset.arcs);
38431
- } else {
38432
- ctx = getNullLayerProxy(targets);
38433
- }
38434
- ctx.global = getStashedVar('defs') || {}; // TODO: remove duplication with mapshaper.expressions.mjs
38435
- var exprOpts = Object.assign({returns: true}, opts);
38436
- var func = compileExpressionToFunction(expr, exprOpts);
38437
-
38438
- // @geoType: optional geometry type (polygon, polyline, point, null);
38439
- ctx.layer_exists = function(name, geoType) {
38440
- try {
38441
- var targets = catalog.findCommandTargets(name, geoType);
38442
- if (targets.length > 0) return true;
38443
- } catch(e) {}
38444
- return false;
38445
- };
38446
-
38447
- ctx.file_exists = function(file) {
38448
- return cli.isFile(file);
38449
- };
38450
-
38451
- return function() {
38452
- try {
38453
- return func.call(ctx, {}, ctx);
38454
- } catch(e) {
38455
- // if (opts.quiet) throw e;
38456
- stop(e.name, "in expression [" + expr + "]:", e.message);
38457
- }
38458
- };
38459
- }
38460
-
38461
38504
  function skipCommand(cmdName, job) {
38462
38505
  // allow all control commands to run
38463
38506
  if (jobIsStopped(job)) return true;
@@ -40914,13 +40957,10 @@ ${svg}
40914
40957
 
40915
40958
  cmd.run = function(job, targets, opts, cb) {
40916
40959
  var commandStr, commands;
40917
- if (opts.include) {
40918
- cmd.include({file: opts.include});
40919
- }
40920
- if (!opts.commands) {
40921
- stop("Missing commands parameter");
40960
+ if (!opts.expression) {
40961
+ stop("Missing expression parameter");
40922
40962
  }
40923
- commandStr = runGlobalExpression(opts.commands, targets);
40963
+ commandStr = runGlobalExpression(opts.expression, targets);
40924
40964
  if (commandStr) {
40925
40965
  commands = parseCommands(commandStr);
40926
40966
  runParsedCommands(commands, job, cb);
@@ -40937,6 +40977,8 @@ ${svg}
40937
40977
  targetData = getRunCommandData(targets[0]);
40938
40978
  Object.defineProperty(ctx, 'target', {value: targetData});
40939
40979
  }
40980
+ // Add defined functions and data to the expression context
40981
+ // (Such as functions imported via the -require command)
40940
40982
  utils.extend(ctx, getStashedVar('defs'));
40941
40983
  try {
40942
40984
  output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
@@ -43220,7 +43262,7 @@ ${svg}
43220
43262
  applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
43221
43263
 
43222
43264
  } else if (name == 'define') {
43223
- cmd.define(opts);
43265
+ cmd.define(job.catalog, opts);
43224
43266
 
43225
43267
  } else if (name == 'dissolve') {
43226
43268
  outputLayers = applyCommandToEachLayer(cmd.dissolve, targetLayers, arcs, opts);
@@ -43648,7 +43690,6 @@ ${svg}
43648
43690
 
43649
43691
  // Unified function for processing calls to runCommands() and applyCommands()
43650
43692
  function _runCommands(argv, opts, callback) {
43651
-
43652
43693
  var outputArr = opts.output || null,
43653
43694
  inputObj = opts.input,
43654
43695
  commands;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.31",
3
+ "version": "0.6.33",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/index.html CHANGED
@@ -154,9 +154,14 @@
154
154
  <div class="export-formats option-menu">
155
155
  </div>
156
156
 
157
- <div class="option-menu"><input type="text" class="advanced-options" placeholder="command line options" /><div class="tip-button">?<div class="tip-anchor">
158
- <div class="tip">Enter options from the command line interface for the -o command. Examples: "bbox" "no-quantization"
159
- "precision=0.001"</div></div></div></div>
157
+ <div class="option-menu"><input type="text" class="advanced-options" placeholder="command line options" />
158
+ <a href="https://github.com/mbloch/mapshaper/wiki/Command-Reference#-o-output" target="_mapshaper_output_docs">
159
+ <div class="tip-button">?<div class="tip-anchor">
160
+ <div class="tip">Enter options from the command line interface for
161
+ the -o command. Examples: bbox no-quantization
162
+ precision=0.001. Click to see all options.</div></div></div></a>
163
+
164
+ </div>
160
165
  <!-- <div class="cancel-btn btn dialog-btn">Cancel</div> -->
161
166
  <div class="save-btn btn dialog-btn">Export</div>
162
167
  <span id="save-preference"><input type="checkbox"/>choose directory</span>
@@ -283,10 +288,13 @@ apply to TopoJSON files.</div></div></div></div> -->
283
288
 
284
289
  </div>
285
290
 
286
- <div><input type="text" class="advanced-options" placeholder="import options" /><div class="tip-button">?<div class="tip-anchor">
291
+ <div><input type="text" class="advanced-options" placeholder="import options" />
292
+ <a href="https://github.com/mbloch/mapshaper/wiki/Command-Reference#-i-input" target="_mapshaper_import_docs">
293
+ <div class="tip-button">?<div class="tip-anchor">
287
294
  <div class="tip">Enter options from the command line
288
- interface. Examples: <span id="import-option-examples">no-topology
289
- encoding=big5</span></div></div></div></div>
295
+ interface. Examples: <span id="import-option-examples">snap no-topology
296
+ encoding=big5</span>. Click to see all options.</div></div></div></div>
297
+ </a>
290
298
 
291
299
  </div>
292
300
 
@@ -1834,6 +1834,7 @@
1834
1834
  var importCount = 0;
1835
1835
  var importTotal = 0;
1836
1836
  var overQuickView = false;
1837
+ var useQuickView = false;
1837
1838
  var queuedFiles = [];
1838
1839
  var manifestFiles = opts.files || [];
1839
1840
  var catalog;
@@ -1863,21 +1864,17 @@
1863
1864
  }
1864
1865
  });
1865
1866
 
1866
- function useQuickView() {
1867
- return initialImport && (opts.quick_view || overQuickView);
1868
- }
1869
-
1870
1867
  function initDropArea(el, isQuick) {
1871
1868
  var area = El(el)
1872
1869
  .on('dragleave', onout)
1873
- .on('dragover', onover)
1870
+ .on('dragover', function() {overQuickView = !!isQuick; onover();})
1874
1871
  .on('mouseover', onover)
1875
1872
  .on('mouseout', onout);
1876
1873
 
1877
1874
  function onover() {
1878
- overQuickView = !!isQuick;
1879
1875
  area.addClass('dragover');
1880
1876
  }
1877
+
1881
1878
  function onout() {
1882
1879
  overQuickView = false;
1883
1880
  area.removeClass('dragover');
@@ -1981,6 +1978,7 @@
1981
1978
  var names = getFileNames(files);
1982
1979
  var expanded = [];
1983
1980
  if (files.length === 0) return;
1981
+ useQuickView = importCount === 0 && (opts.quick_view || overQuickView);
1984
1982
  try {
1985
1983
  expanded = await expandFiles(files);
1986
1984
  } catch(e) {
@@ -1995,7 +1993,7 @@
1995
1993
  return;
1996
1994
  }
1997
1995
  gui.enterMode('import');
1998
- if (useQuickView()) {
1996
+ if (useQuickView) {
1999
1997
  await importQueuedFiles();
2000
1998
  } else {
2001
1999
  gui.container.addClass('queued-files');
@@ -2091,7 +2089,7 @@
2091
2089
 
2092
2090
  function readImportOpts() {
2093
2091
  var importOpts;
2094
- if (useQuickView()) {
2092
+ if (useQuickView) {
2095
2093
  importOpts = {}; // default opts using quickview
2096
2094
  } else {
2097
2095
  var freeform = El('#import-options .advanced-options').node().value;
@@ -4040,8 +4038,11 @@
4040
4038
  }
4041
4039
 
4042
4040
  function updateMenuBtn() {
4043
- var name = model.getActiveLayer().layer.name || "[unnamed layer]";
4044
- btn.classed('active', 'true').findChild('.layer-name').html(name + " &nbsp;&#9660;");
4041
+ var lyrName = model.getActiveLayer().layer.name || '';
4042
+ var menuTitle = lyrName || '[unnamed layer]';
4043
+ var pageTitle = lyrName || 'mapshaper';
4044
+ btn.classed('active', 'true').findChild('.layer-name').html(menuTitle + " &nbsp;&#9660;");
4045
+ window.document.title = pageTitle;
4045
4046
  }
4046
4047
 
4047
4048
  function render() {
@@ -10174,12 +10175,12 @@
10174
10175
  return action == 'hover' && _overlayCanv.visible();
10175
10176
  }
10176
10177
 
10177
- function drawCanvasLayer(target, canv) {
10178
- if (!target) return;
10179
- if (target.style.type == 'outline') {
10180
- drawOutlineLayerToCanvas(target, canv, ext);
10178
+ function drawCanvasLayer(lyr, canv) {
10179
+ if (!lyr) return;
10180
+ if (lyr.style.type == 'outline') {
10181
+ drawOutlineLayerToCanvas(lyr, canv, ext);
10181
10182
  } else {
10182
- drawStyledLayerToCanvas(target, canv, ext);
10183
+ drawStyledLayerToCanvas(lyr, canv, ext);
10183
10184
  }
10184
10185
  }
10185
10186
 
@@ -11041,7 +11042,9 @@
11041
11042
  } else {
11042
11043
  _overlayLyr = null;
11043
11044
  }
11044
- drawLayers('hover');
11045
+ // 'hover' bypasses style creation in drawLayers2()... sometimes we need that
11046
+ // drawLayers('hover');
11047
+ drawLayers();
11045
11048
  }
11046
11049
 
11047
11050
  function getDisplayOptions() {
@@ -11056,12 +11059,14 @@
11056
11059
  return flags.simplify_method || flags.simplify || flags.proj ||
11057
11060
  flags.arc_count || flags.repair || flags.clip || flags.erase ||
11058
11061
  flags.slice || flags.affine || flags.rectangle || flags.buffer ||
11059
- flags.union || flags.mosaic || flags.snap || flags.clean || false;
11062
+ flags.union || flags.mosaic || flags.snap || flags.clean || flags.drop || false;
11060
11063
  }
11061
11064
 
11062
11065
  // Test if an update allows hover popup to stay open
11063
11066
  function popupCanStayOpen(flags) {
11064
- // if (arcsMayHaveChanged(flags)) return false;
11067
+ // keeping popup open after -drop geometry causes problems...
11068
+ // // if (arcsMayHaveChanged(flags)) return false;
11069
+ if (arcsMayHaveChanged(flags)) return false;
11065
11070
  if (flags.points || flags.proj) return false;
11066
11071
  if (!flags.same_table) return false;
11067
11072
  return true;
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.31";
3
+ var VERSION = "0.6.33";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -11665,8 +11665,16 @@
11665
11665
  }
11666
11666
  cands.forEach(function(cand) {
11667
11667
  var p = getTestPoint(cand.ids);
11668
- var isEnclosed = b.containsPoint(p[0], p[1]) && (index ?
11669
- index.pointInPolygon(p[0], p[1]) : geom.testPointInRing(p[0], p[1], pathIds, arcs));
11668
+ var isEnclosed = b.containsPoint(p[0], p[1]) &&
11669
+ // added a bounds-in-bounds test to handle a case where the test point
11670
+ // fell along the shared boundary of two rings, but the rings did no overlap
11671
+ // (this gave a false positive for the enclosure test)
11672
+ // (for speed, the midpoint of an arc is used as the test point; this
11673
+ // works well in the typical case where rings to not share an edge.
11674
+ // Finding an internal test point would be better, we just need a fast
11675
+ // function to find internal points)
11676
+ b.contains(cand.bounds) &&
11677
+ (index ? index.pointInPolygon(p[0], p[1]) : geom.testPointInRing(p[0], p[1], pathIds, arcs));
11670
11678
  if (isEnclosed) {
11671
11679
  paths.push(cand.ids);
11672
11680
  }
@@ -11674,6 +11682,7 @@
11674
11682
  return paths.length > 0 ? paths : null;
11675
11683
  };
11676
11684
 
11685
+ // return array of indexed paths within a given shape
11677
11686
  this.findPathsInsideShape = function(shape) {
11678
11687
  var paths = []; // list of enclosed paths
11679
11688
  shape.forEach(function(ids) {
@@ -12075,7 +12084,6 @@
12075
12084
  yy = coords.yy,
12076
12085
  ids = nodes.getConnectedArcs(fromArcId),
12077
12086
  toArcId = fromArcId; // initialize to fromArcId -- an error condition
12078
-
12079
12087
  if (filter) {
12080
12088
  ids = ids.filter(filter);
12081
12089
  }
@@ -12104,14 +12112,12 @@
12104
12112
  continue;
12105
12113
  }
12106
12114
  icand = arcs.indexOfVertex(candId, -2);
12107
-
12108
12115
  if (toArcId == fromArcId) {
12109
12116
  // first valid candidate
12110
12117
  ito = icand;
12111
12118
  toArcId = candId;
12112
12119
  continue;
12113
12120
  }
12114
-
12115
12121
  code = chooseRighthandPath(fromX, fromY, nodeX, nodeY, xx[ito], yy[ito], xx[icand], yy[icand]);
12116
12122
  if (code == 2) {
12117
12123
  ito = icand;
@@ -12119,6 +12125,7 @@
12119
12125
  }
12120
12126
  }
12121
12127
 
12128
+
12122
12129
  if (toArcId == fromArcId) {
12123
12130
  // This shouldn't occur, assuming that other arcs are present
12124
12131
  error("Pathfinder error");
@@ -12183,6 +12190,9 @@
12183
12190
  chooseRighthandVector: chooseRighthandVector
12184
12191
  });
12185
12192
 
12193
+ var FWD_USED = 0x8;
12194
+ var REV_USED = 0x80;
12195
+
12186
12196
  function setBits(bits, arcBits, mask) {
12187
12197
  return (bits & ~mask) | (arcBits & mask);
12188
12198
  }
@@ -12210,6 +12220,16 @@
12210
12220
  return bits & 7;
12211
12221
  }
12212
12222
 
12223
+ function markPathsAsUsed(paths, routesArr) {
12224
+ forEachArcId(paths, function(arcId) {
12225
+ if (arcId < 0) {
12226
+ routesArr[~arcId] |= REV_USED;
12227
+ } else {
12228
+ routesArr[arcId] |= FWD_USED;
12229
+ }
12230
+ });
12231
+ }
12232
+
12213
12233
  // Open arc pathways in a single shape or array of shapes
12214
12234
  //
12215
12235
  function openArcRoutes(paths, arcColl, routesArr, fwd, rev, dissolve, orBits) {
@@ -12400,6 +12420,7 @@
12400
12420
  andBits: andBits,
12401
12421
  setRouteBits: setRouteBits,
12402
12422
  getRouteBits: getRouteBits,
12423
+ markPathsAsUsed: markPathsAsUsed,
12403
12424
  openArcRoutes: openArcRoutes,
12404
12425
  closeArcRoutes: closeArcRoutes,
12405
12426
  getPathFinder: getPathFinder,
@@ -24840,13 +24861,12 @@ ${svg}
24840
24861
 
24841
24862
  parser.command('run')
24842
24863
  .describe('create commands on-the-fly and run them')
24843
- .option('include', {
24844
- // TODO: remove this option
24845
- })
24846
- .option('commands', {
24864
+ .option('expression', {
24847
24865
  DEFAULT: true,
24848
- describe: 'command string or JS expresson to generate command(s)'
24866
+ describe: 'JS expression to generate command(s)'
24849
24867
  })
24868
+ // deprecated
24869
+ .option('commands', {alias_to: 'expression'})
24850
24870
  .option('target', targetOpt);
24851
24871
 
24852
24872
  parser.command('scalebar')
@@ -33402,7 +33422,7 @@ ${svg}
33402
33422
  var clipArcTouches = 0;
33403
33423
  var clipArcUses = 0;
33404
33424
  var usedClipArcs = [];
33405
- var dividePath = getPathFinder(nodes, useRoute, routeIsActive);
33425
+ var findPath = getPathFinder(nodes, useRoute, routeIsActive);
33406
33426
  var dissolvePolygon = getPolygonDissolver(nodes);
33407
33427
 
33408
33428
  // The following cleanup step is a performance bottleneck (it often takes longer than
@@ -33435,12 +33455,16 @@ ${svg}
33435
33455
  return null;
33436
33456
  });
33437
33457
 
33458
+ markPathsAsUsed(clippedShapes, routeFlags); // to help us find unused paths later
33459
+
33460
+
33438
33461
  // add clip/erase polygons that are fully contained in a target polygon
33439
33462
  // need to index only non-intersecting clip shapes
33440
33463
  // (Intersecting shapes have one or more arcs that have been scanned)
33441
33464
 
33442
33465
  // first, find shapes that do not intersect the target layer
33443
33466
  // (these could be inside or outside the target polygons)
33467
+
33444
33468
  var undividedClipShapes = findUndividedClipShapes(clipShapes);
33445
33469
 
33446
33470
  closeArcRoutes(clipShapes, arcs, routeFlags, true, true); // not needed?
@@ -33469,7 +33493,7 @@ ${svg}
33469
33493
  for (var i=0, n=ids.length; i<n; i++) {
33470
33494
  clipArcTouches = 0;
33471
33495
  clipArcUses = 0;
33472
- path = dividePath(ids[i]);
33496
+ path = findPath(ids[i]);
33473
33497
  if (path) {
33474
33498
  // if ring doesn't touch/intersect a clip/erase polygon, check if it is contained
33475
33499
  // if (clipArcTouches === 0) {
@@ -33488,6 +33512,7 @@ ${svg}
33488
33512
  }
33489
33513
  });
33490
33514
 
33515
+
33491
33516
  // Clear pathways of current target shape to hidden/closed
33492
33517
  closeArcRoutes(shape, arcs, routeFlags, true, true, true);
33493
33518
  // Also clear pathways of any clip arcs that were used
@@ -33558,8 +33583,9 @@ ${svg}
33558
33583
  return usable;
33559
33584
  }
33560
33585
 
33561
- // Filter a collection of shapes to exclude paths that contain clip/erase arcs
33562
- // and paths that are hidden (e.g. internal boundaries)
33586
+
33587
+ // Filter a collection of shapes to exclude paths that incorporate parts of
33588
+ // clip/erase polygons and paths that are hidden (e.g. internal boundaries)
33563
33589
  function findUndividedClipShapes(clipShapes) {
33564
33590
  return clipShapes.map(function(shape) {
33565
33591
  var usableParts = [];
@@ -33583,12 +33609,12 @@ ${svg}
33583
33609
  });
33584
33610
  }
33585
33611
 
33586
- // Test if arc is unused in both directions
33587
- // (not testing open/closed or visible/hidden)
33612
+
33588
33613
  function arcIsUnused(id, flags) {
33589
33614
  var abs = absArcId(id),
33590
33615
  flag = flags[abs];
33591
- return (flag & 0x44) === 0;
33616
+ return (flag & 0x88) === 0;
33617
+ // return id < 0 ? (flag & 0x80) === 0 : (flag & 0x8) === 0;
33592
33618
  }
33593
33619
 
33594
33620
  function arcIsVisible(id, flags) {
@@ -33611,7 +33637,7 @@ ${svg}
33611
33637
  enclosedPaths.forEach(function(ids) {
33612
33638
  var path;
33613
33639
  for (var j=0; j<ids.length; j++) {
33614
- path = dividePath(ids[j]);
33640
+ path = findPath(ids[j]);
33615
33641
  if (path) {
33616
33642
  dissolvedPaths.push(path);
33617
33643
  }
@@ -34730,7 +34756,64 @@ ${svg}
34730
34756
  };
34731
34757
  }
34732
34758
 
34733
- cmd.define = function(opts) {
34759
+ function compileIfCommandExpression(expr, catalog, opts) {
34760
+ return compileLayerExpression(expr, catalog, opts);
34761
+ }
34762
+
34763
+
34764
+ function compileLayerExpression(expr, catalog, opts) {
34765
+ var targetId = opts.layer || opts.target || null;
34766
+ var targets = catalog.findCommandTargets(targetId);
34767
+ var isSingle = targets.length == 1 && targets[0].layers.length == 1;
34768
+ if (targets.length === 0 && targetId) {
34769
+ stop('Layer not found:', targetId);
34770
+ }
34771
+ // var vars = getAssignedVars(exp);
34772
+ var defs = getStashedVar('defs') || {};
34773
+
34774
+ var ctx;
34775
+ if (isSingle) {
34776
+ ctx = getLayerProxy(targets[0].layers[0], targets[0].dataset.arcs);
34777
+ } else {
34778
+ ctx = getNullLayerProxy(targets);
34779
+ }
34780
+ ctx.global = defs; // TODO: remove duplication with mapshaper.expressions.mjs
34781
+ var exprOpts = Object.assign({returns: true}, opts);
34782
+ var func = compileExpressionToFunction(expr, exprOpts);
34783
+
34784
+ // @geoType: optional geometry type (polygon, polyline, point, null);
34785
+ ctx.layer_exists = function(name, geoType) {
34786
+ try {
34787
+ var targets = catalog.findCommandTargets(name, geoType);
34788
+ if (targets.length > 0) return true;
34789
+ } catch(e) {}
34790
+ return false;
34791
+ };
34792
+
34793
+ ctx.file_exists = function(file) {
34794
+ return cli.isFile(file);
34795
+ };
34796
+
34797
+ return function() {
34798
+ try {
34799
+ return func.call(ctx, defs, ctx);
34800
+ } catch(e) {
34801
+ // if (opts.quiet) throw e;
34802
+ stop(e.name, "in expression [" + expr + "]:", e.message);
34803
+ }
34804
+ };
34805
+ }
34806
+
34807
+ /*
34808
+ cmd.define_v2 = function(catalog, opts) {
34809
+ if (!opts.expression) {
34810
+ stop('Missing an assignment expression');
34811
+ }
34812
+ compileLayerExpression(opts.expression, catalog, opts)();
34813
+ };
34814
+ */
34815
+
34816
+ cmd.define = function(catalog, opts) {
34734
34817
  if (!opts.expression) {
34735
34818
  stop('Missing an assignment expression');
34736
34819
  }
@@ -38418,46 +38501,6 @@ ${svg}
38418
38501
  return job.control || (job.control = {});
38419
38502
  }
38420
38503
 
38421
- function compileIfCommandExpression(expr, catalog, opts) {
38422
- var targetId = opts.layer || opts.target || null;
38423
- var targets = catalog.findCommandTargets(targetId);
38424
- var isSingle = targets.length == 1 && targets[0].layers.length == 1;
38425
- if (targets.length === 0 && targetId) {
38426
- stop('Layer not found:', targetId);
38427
- }
38428
- var ctx;
38429
- if (isSingle) {
38430
- ctx = getLayerProxy(targets[0].layers[0], targets[0].dataset.arcs);
38431
- } else {
38432
- ctx = getNullLayerProxy(targets);
38433
- }
38434
- ctx.global = getStashedVar('defs') || {}; // TODO: remove duplication with mapshaper.expressions.mjs
38435
- var exprOpts = Object.assign({returns: true}, opts);
38436
- var func = compileExpressionToFunction(expr, exprOpts);
38437
-
38438
- // @geoType: optional geometry type (polygon, polyline, point, null);
38439
- ctx.layer_exists = function(name, geoType) {
38440
- try {
38441
- var targets = catalog.findCommandTargets(name, geoType);
38442
- if (targets.length > 0) return true;
38443
- } catch(e) {}
38444
- return false;
38445
- };
38446
-
38447
- ctx.file_exists = function(file) {
38448
- return cli.isFile(file);
38449
- };
38450
-
38451
- return function() {
38452
- try {
38453
- return func.call(ctx, {}, ctx);
38454
- } catch(e) {
38455
- // if (opts.quiet) throw e;
38456
- stop(e.name, "in expression [" + expr + "]:", e.message);
38457
- }
38458
- };
38459
- }
38460
-
38461
38504
  function skipCommand(cmdName, job) {
38462
38505
  // allow all control commands to run
38463
38506
  if (jobIsStopped(job)) return true;
@@ -40914,13 +40957,10 @@ ${svg}
40914
40957
 
40915
40958
  cmd.run = function(job, targets, opts, cb) {
40916
40959
  var commandStr, commands;
40917
- if (opts.include) {
40918
- cmd.include({file: opts.include});
40919
- }
40920
- if (!opts.commands) {
40921
- stop("Missing commands parameter");
40960
+ if (!opts.expression) {
40961
+ stop("Missing expression parameter");
40922
40962
  }
40923
- commandStr = runGlobalExpression(opts.commands, targets);
40963
+ commandStr = runGlobalExpression(opts.expression, targets);
40924
40964
  if (commandStr) {
40925
40965
  commands = parseCommands(commandStr);
40926
40966
  runParsedCommands(commands, job, cb);
@@ -40937,6 +40977,8 @@ ${svg}
40937
40977
  targetData = getRunCommandData(targets[0]);
40938
40978
  Object.defineProperty(ctx, 'target', {value: targetData});
40939
40979
  }
40980
+ // Add defined functions and data to the expression context
40981
+ // (Such as functions imported via the -require command)
40940
40982
  utils.extend(ctx, getStashedVar('defs'));
40941
40983
  try {
40942
40984
  output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
@@ -43220,7 +43262,7 @@ ${svg}
43220
43262
  applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
43221
43263
 
43222
43264
  } else if (name == 'define') {
43223
- cmd.define(opts);
43265
+ cmd.define(job.catalog, opts);
43224
43266
 
43225
43267
  } else if (name == 'dissolve') {
43226
43268
  outputLayers = applyCommandToEachLayer(cmd.dissolve, targetLayers, arcs, opts);
@@ -43648,7 +43690,6 @@ ${svg}
43648
43690
 
43649
43691
  // Unified function for processing calls to runCommands() and applyCommands()
43650
43692
  function _runCommands(argv, opts, callback) {
43651
-
43652
43693
  var outputArr = opts.output || null,
43653
43694
  inputObj = opts.input,
43654
43695
  commands;