mapshaper 0.6.32 → 0.6.34

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.32";
3
+ var VERSION = "0.6.34";
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,
@@ -18192,6 +18213,7 @@
18192
18213
  dy: 'measure',
18193
18214
  fill: 'color',
18194
18215
  'fill-pattern': 'pattern',
18216
+ 'fill-effect': null, // todo: validate effect names
18195
18217
  'font-family': null,
18196
18218
  'font-size': null,
18197
18219
  'font-style': null,
@@ -18240,7 +18262,7 @@
18240
18262
  var commonProperties = 'css,class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity,vector-effect'.split(',');
18241
18263
 
18242
18264
  var propertiesBySymbolType = {
18243
- polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
18265
+ polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern', 'fill-effect')),
18244
18266
  polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit')),
18245
18267
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
18246
18268
  label: utils.arrayToIndex(commonProperties.concat(
@@ -19125,6 +19147,9 @@
19125
19147
  if (obj.tag == 'path' && obj.properties['fill-pattern']) {
19126
19148
  convertFillPattern(obj.properties, defs);
19127
19149
  }
19150
+ if (obj.tag == 'path' && obj.properties['fill-effect'] == 'sphere') {
19151
+ convertSphereEffect(obj.properties, defs);
19152
+ }
19128
19153
  if (obj.tag == 'image') {
19129
19154
  if (/\.svg/.test(obj.properties.href || '')) {
19130
19155
  convertSvgImage(obj, defs);
@@ -19135,6 +19160,25 @@
19135
19160
  }
19136
19161
  }
19137
19162
 
19163
+ function convertSphereEffect(obj, defs) {
19164
+ var id = 'mapshaper_sphere_effect';
19165
+ var href = `url(#${ id })`;
19166
+ var svg =
19167
+ `<radialGradient id="${id}" cx="0.5" cy="0.5" r=".56" fx="0.4" fy="0.35">
19168
+ <stop offset=".35" stop-opacity="0"/>
19169
+ <stop offset=".65" stop-opacity="0.1" />
19170
+ <stop offset=".85" stop-opacity="0.45" />
19171
+ <stop offset=".95" stop-opacity="1" />
19172
+ </radialGradient>` ;
19173
+ if (!utils.find(defs, function(o) { return o.id == id; })) {
19174
+ defs.push({svg, id, href});
19175
+ }
19176
+ obj.fill = href;
19177
+ if ('opacity' in obj === false && 'fill-opacity' in obj === false) {
19178
+ obj['fill-opacity'] = 0.35;
19179
+ }
19180
+ }
19181
+
19138
19182
  function convertSvgImage(obj, defs) {
19139
19183
  // Same-origin policy prevents embedding images in the web UI
19140
19184
  var href = obj.properties.href;
@@ -23896,7 +23940,9 @@ ${svg}
23896
23940
  .option('polygon', {
23897
23941
  describe: 'create a polygon to match the outline of the graticule',
23898
23942
  type: 'flag'
23899
- });
23943
+ })
23944
+ .option('name', nameOpt);
23945
+
23900
23946
 
23901
23947
  parser.command('grid')
23902
23948
  .describe('create a grid of square or hexagonal polygons')
@@ -24426,6 +24472,9 @@ ${svg}
24426
24472
  .option('fill-pattern', {
24427
24473
  describe: 'pattern fill, ex: "hatches 2px grey 2px blue"'
24428
24474
  })
24475
+ .option('fill-effect', {
24476
+ describe: 'use "sphere" on a circle for a 3d globe effect'
24477
+ })
24429
24478
  .option('fill-opacity', {
24430
24479
  describe: 'fill opacity'
24431
24480
  })
@@ -33401,7 +33450,7 @@ ${svg}
33401
33450
  var clipArcTouches = 0;
33402
33451
  var clipArcUses = 0;
33403
33452
  var usedClipArcs = [];
33404
- var dividePath = getPathFinder(nodes, useRoute, routeIsActive);
33453
+ var findPath = getPathFinder(nodes, useRoute, routeIsActive);
33405
33454
  var dissolvePolygon = getPolygonDissolver(nodes);
33406
33455
 
33407
33456
  // The following cleanup step is a performance bottleneck (it often takes longer than
@@ -33434,12 +33483,16 @@ ${svg}
33434
33483
  return null;
33435
33484
  });
33436
33485
 
33486
+ markPathsAsUsed(clippedShapes, routeFlags); // to help us find unused paths later
33487
+
33488
+
33437
33489
  // add clip/erase polygons that are fully contained in a target polygon
33438
33490
  // need to index only non-intersecting clip shapes
33439
33491
  // (Intersecting shapes have one or more arcs that have been scanned)
33440
33492
 
33441
33493
  // first, find shapes that do not intersect the target layer
33442
33494
  // (these could be inside or outside the target polygons)
33495
+
33443
33496
  var undividedClipShapes = findUndividedClipShapes(clipShapes);
33444
33497
 
33445
33498
  closeArcRoutes(clipShapes, arcs, routeFlags, true, true); // not needed?
@@ -33468,7 +33521,7 @@ ${svg}
33468
33521
  for (var i=0, n=ids.length; i<n; i++) {
33469
33522
  clipArcTouches = 0;
33470
33523
  clipArcUses = 0;
33471
- path = dividePath(ids[i]);
33524
+ path = findPath(ids[i]);
33472
33525
  if (path) {
33473
33526
  // if ring doesn't touch/intersect a clip/erase polygon, check if it is contained
33474
33527
  // if (clipArcTouches === 0) {
@@ -33487,6 +33540,7 @@ ${svg}
33487
33540
  }
33488
33541
  });
33489
33542
 
33543
+
33490
33544
  // Clear pathways of current target shape to hidden/closed
33491
33545
  closeArcRoutes(shape, arcs, routeFlags, true, true, true);
33492
33546
  // Also clear pathways of any clip arcs that were used
@@ -33557,8 +33611,9 @@ ${svg}
33557
33611
  return usable;
33558
33612
  }
33559
33613
 
33560
- // Filter a collection of shapes to exclude paths that contain clip/erase arcs
33561
- // and paths that are hidden (e.g. internal boundaries)
33614
+
33615
+ // Filter a collection of shapes to exclude paths that incorporate parts of
33616
+ // clip/erase polygons and paths that are hidden (e.g. internal boundaries)
33562
33617
  function findUndividedClipShapes(clipShapes) {
33563
33618
  return clipShapes.map(function(shape) {
33564
33619
  var usableParts = [];
@@ -33582,12 +33637,12 @@ ${svg}
33582
33637
  });
33583
33638
  }
33584
33639
 
33585
- // Test if arc is unused in both directions
33586
- // (not testing open/closed or visible/hidden)
33640
+
33587
33641
  function arcIsUnused(id, flags) {
33588
33642
  var abs = absArcId(id),
33589
33643
  flag = flags[abs];
33590
- return (flag & 0x44) === 0;
33644
+ return (flag & 0x88) === 0;
33645
+ // return id < 0 ? (flag & 0x80) === 0 : (flag & 0x8) === 0;
33591
33646
  }
33592
33647
 
33593
33648
  function arcIsVisible(id, flags) {
@@ -33610,7 +33665,7 @@ ${svg}
33610
33665
  enclosedPaths.forEach(function(ids) {
33611
33666
  var path;
33612
33667
  for (var j=0; j<ids.length; j++) {
33613
- path = dividePath(ids[j]);
33668
+ path = findPath(ids[j]);
33614
33669
  if (path) {
33615
33670
  dissolvedPaths.push(path);
33616
33671
  }
@@ -37902,7 +37957,8 @@ ${svg}
37902
37957
  return {
37903
37958
  gnom: 60,
37904
37959
  laea: 179,
37905
- ortho: 89.9, // TODO: investigate projection errors closer to 90
37960
+ //ortho: 89.9, // projection errors betwen lat +/-35 to 55
37961
+ ortho: 89.85, // TODO: investigate
37906
37962
  stere: 142,
37907
37963
  sterea: 142,
37908
37964
  ups: 10.5 // TODO: should be 6.5 deg at north pole
@@ -38281,7 +38337,7 @@ ${svg}
38281
38337
  });
38282
38338
 
38283
38339
  cmd.graticule = function(dataset, opts) {
38284
- var name = opts.polygon ? 'polygon' : 'graticule';
38340
+ var name = opts.name || opts.polygon && 'polygon' || 'graticule';
38285
38341
  var graticule, destInfo;
38286
38342
  if (dataset && !isLatLngDataset(dataset)) {
38287
38343
  // project graticule to match dataset
@@ -38347,7 +38403,7 @@ ${svg}
38347
38403
  //
38348
38404
  function createGraticule(P, outlined, opts) {
38349
38405
  var interval = opts.interval || 10;
38350
- if (![5,10,15,30,45].includes(interval)) stop('Invalid interval:', interval);
38406
+ if (![5,10,15,20,30,45].includes(interval)) stop('Invalid interval:', interval);
38351
38407
  P.lam0 * 180 / Math.PI;
38352
38408
  var precision = interval > 10 ? 1 : 0.5; // degrees between each vertex
38353
38409
  var xstep = interval;
@@ -40935,6 +40991,7 @@ ${svg}
40935
40991
  }
40936
40992
  commandStr = runGlobalExpression(opts.expression, targets);
40937
40993
  if (commandStr) {
40994
+ message(`command: [${commandStr}]`);
40938
40995
  commands = parseCommands(commandStr);
40939
40996
  runParsedCommands(commands, job, cb);
40940
40997
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.6.32",
3
+ "version": "0.6.34",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -4038,8 +4038,11 @@
4038
4038
  }
4039
4039
 
4040
4040
  function updateMenuBtn() {
4041
- var name = model.getActiveLayer().layer.name || "[unnamed layer]";
4042
- 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;
4043
4046
  }
4044
4047
 
4045
4048
  function render() {
@@ -10172,12 +10175,12 @@
10172
10175
  return action == 'hover' && _overlayCanv.visible();
10173
10176
  }
10174
10177
 
10175
- function drawCanvasLayer(target, canv) {
10176
- if (!target) return;
10177
- if (target.style.type == 'outline') {
10178
- drawOutlineLayerToCanvas(target, canv, ext);
10178
+ function drawCanvasLayer(lyr, canv) {
10179
+ if (!lyr) return;
10180
+ if (lyr.style.type == 'outline') {
10181
+ drawOutlineLayerToCanvas(lyr, canv, ext);
10179
10182
  } else {
10180
- drawStyledLayerToCanvas(target, canv, ext);
10183
+ drawStyledLayerToCanvas(lyr, canv, ext);
10181
10184
  }
10182
10185
  }
10183
10186
 
@@ -11039,7 +11042,9 @@
11039
11042
  } else {
11040
11043
  _overlayLyr = null;
11041
11044
  }
11042
- drawLayers('hover');
11045
+ // 'hover' bypasses style creation in drawLayers2()... sometimes we need that
11046
+ // drawLayers('hover');
11047
+ drawLayers();
11043
11048
  }
11044
11049
 
11045
11050
  function getDisplayOptions() {
@@ -11054,12 +11059,14 @@
11054
11059
  return flags.simplify_method || flags.simplify || flags.proj ||
11055
11060
  flags.arc_count || flags.repair || flags.clip || flags.erase ||
11056
11061
  flags.slice || flags.affine || flags.rectangle || flags.buffer ||
11057
- flags.union || flags.mosaic || flags.snap || flags.clean || false;
11062
+ flags.union || flags.mosaic || flags.snap || flags.clean || flags.drop || false;
11058
11063
  }
11059
11064
 
11060
11065
  // Test if an update allows hover popup to stay open
11061
11066
  function popupCanStayOpen(flags) {
11062
- // 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;
11063
11070
  if (flags.points || flags.proj) return false;
11064
11071
  if (!flags.same_table) return false;
11065
11072
  return true;
package/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.6.32";
3
+ var VERSION = "0.6.34";
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,
@@ -18192,6 +18213,7 @@
18192
18213
  dy: 'measure',
18193
18214
  fill: 'color',
18194
18215
  'fill-pattern': 'pattern',
18216
+ 'fill-effect': null, // todo: validate effect names
18195
18217
  'font-family': null,
18196
18218
  'font-size': null,
18197
18219
  'font-style': null,
@@ -18240,7 +18262,7 @@
18240
18262
  var commonProperties = 'css,class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity,vector-effect'.split(',');
18241
18263
 
18242
18264
  var propertiesBySymbolType = {
18243
- polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
18265
+ polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern', 'fill-effect')),
18244
18266
  polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit')),
18245
18267
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
18246
18268
  label: utils.arrayToIndex(commonProperties.concat(
@@ -19125,6 +19147,9 @@
19125
19147
  if (obj.tag == 'path' && obj.properties['fill-pattern']) {
19126
19148
  convertFillPattern(obj.properties, defs);
19127
19149
  }
19150
+ if (obj.tag == 'path' && obj.properties['fill-effect'] == 'sphere') {
19151
+ convertSphereEffect(obj.properties, defs);
19152
+ }
19128
19153
  if (obj.tag == 'image') {
19129
19154
  if (/\.svg/.test(obj.properties.href || '')) {
19130
19155
  convertSvgImage(obj, defs);
@@ -19135,6 +19160,25 @@
19135
19160
  }
19136
19161
  }
19137
19162
 
19163
+ function convertSphereEffect(obj, defs) {
19164
+ var id = 'mapshaper_sphere_effect';
19165
+ var href = `url(#${ id })`;
19166
+ var svg =
19167
+ `<radialGradient id="${id}" cx="0.5" cy="0.5" r=".56" fx="0.4" fy="0.35">
19168
+ <stop offset=".35" stop-opacity="0"/>
19169
+ <stop offset=".65" stop-opacity="0.1" />
19170
+ <stop offset=".85" stop-opacity="0.45" />
19171
+ <stop offset=".95" stop-opacity="1" />
19172
+ </radialGradient>` ;
19173
+ if (!utils.find(defs, function(o) { return o.id == id; })) {
19174
+ defs.push({svg, id, href});
19175
+ }
19176
+ obj.fill = href;
19177
+ if ('opacity' in obj === false && 'fill-opacity' in obj === false) {
19178
+ obj['fill-opacity'] = 0.35;
19179
+ }
19180
+ }
19181
+
19138
19182
  function convertSvgImage(obj, defs) {
19139
19183
  // Same-origin policy prevents embedding images in the web UI
19140
19184
  var href = obj.properties.href;
@@ -23896,7 +23940,9 @@ ${svg}
23896
23940
  .option('polygon', {
23897
23941
  describe: 'create a polygon to match the outline of the graticule',
23898
23942
  type: 'flag'
23899
- });
23943
+ })
23944
+ .option('name', nameOpt);
23945
+
23900
23946
 
23901
23947
  parser.command('grid')
23902
23948
  .describe('create a grid of square or hexagonal polygons')
@@ -24426,6 +24472,9 @@ ${svg}
24426
24472
  .option('fill-pattern', {
24427
24473
  describe: 'pattern fill, ex: "hatches 2px grey 2px blue"'
24428
24474
  })
24475
+ .option('fill-effect', {
24476
+ describe: 'use "sphere" on a circle for a 3d globe effect'
24477
+ })
24429
24478
  .option('fill-opacity', {
24430
24479
  describe: 'fill opacity'
24431
24480
  })
@@ -33401,7 +33450,7 @@ ${svg}
33401
33450
  var clipArcTouches = 0;
33402
33451
  var clipArcUses = 0;
33403
33452
  var usedClipArcs = [];
33404
- var dividePath = getPathFinder(nodes, useRoute, routeIsActive);
33453
+ var findPath = getPathFinder(nodes, useRoute, routeIsActive);
33405
33454
  var dissolvePolygon = getPolygonDissolver(nodes);
33406
33455
 
33407
33456
  // The following cleanup step is a performance bottleneck (it often takes longer than
@@ -33434,12 +33483,16 @@ ${svg}
33434
33483
  return null;
33435
33484
  });
33436
33485
 
33486
+ markPathsAsUsed(clippedShapes, routeFlags); // to help us find unused paths later
33487
+
33488
+
33437
33489
  // add clip/erase polygons that are fully contained in a target polygon
33438
33490
  // need to index only non-intersecting clip shapes
33439
33491
  // (Intersecting shapes have one or more arcs that have been scanned)
33440
33492
 
33441
33493
  // first, find shapes that do not intersect the target layer
33442
33494
  // (these could be inside or outside the target polygons)
33495
+
33443
33496
  var undividedClipShapes = findUndividedClipShapes(clipShapes);
33444
33497
 
33445
33498
  closeArcRoutes(clipShapes, arcs, routeFlags, true, true); // not needed?
@@ -33468,7 +33521,7 @@ ${svg}
33468
33521
  for (var i=0, n=ids.length; i<n; i++) {
33469
33522
  clipArcTouches = 0;
33470
33523
  clipArcUses = 0;
33471
- path = dividePath(ids[i]);
33524
+ path = findPath(ids[i]);
33472
33525
  if (path) {
33473
33526
  // if ring doesn't touch/intersect a clip/erase polygon, check if it is contained
33474
33527
  // if (clipArcTouches === 0) {
@@ -33487,6 +33540,7 @@ ${svg}
33487
33540
  }
33488
33541
  });
33489
33542
 
33543
+
33490
33544
  // Clear pathways of current target shape to hidden/closed
33491
33545
  closeArcRoutes(shape, arcs, routeFlags, true, true, true);
33492
33546
  // Also clear pathways of any clip arcs that were used
@@ -33557,8 +33611,9 @@ ${svg}
33557
33611
  return usable;
33558
33612
  }
33559
33613
 
33560
- // Filter a collection of shapes to exclude paths that contain clip/erase arcs
33561
- // and paths that are hidden (e.g. internal boundaries)
33614
+
33615
+ // Filter a collection of shapes to exclude paths that incorporate parts of
33616
+ // clip/erase polygons and paths that are hidden (e.g. internal boundaries)
33562
33617
  function findUndividedClipShapes(clipShapes) {
33563
33618
  return clipShapes.map(function(shape) {
33564
33619
  var usableParts = [];
@@ -33582,12 +33637,12 @@ ${svg}
33582
33637
  });
33583
33638
  }
33584
33639
 
33585
- // Test if arc is unused in both directions
33586
- // (not testing open/closed or visible/hidden)
33640
+
33587
33641
  function arcIsUnused(id, flags) {
33588
33642
  var abs = absArcId(id),
33589
33643
  flag = flags[abs];
33590
- return (flag & 0x44) === 0;
33644
+ return (flag & 0x88) === 0;
33645
+ // return id < 0 ? (flag & 0x80) === 0 : (flag & 0x8) === 0;
33591
33646
  }
33592
33647
 
33593
33648
  function arcIsVisible(id, flags) {
@@ -33610,7 +33665,7 @@ ${svg}
33610
33665
  enclosedPaths.forEach(function(ids) {
33611
33666
  var path;
33612
33667
  for (var j=0; j<ids.length; j++) {
33613
- path = dividePath(ids[j]);
33668
+ path = findPath(ids[j]);
33614
33669
  if (path) {
33615
33670
  dissolvedPaths.push(path);
33616
33671
  }
@@ -37902,7 +37957,8 @@ ${svg}
37902
37957
  return {
37903
37958
  gnom: 60,
37904
37959
  laea: 179,
37905
- ortho: 89.9, // TODO: investigate projection errors closer to 90
37960
+ //ortho: 89.9, // projection errors betwen lat +/-35 to 55
37961
+ ortho: 89.85, // TODO: investigate
37906
37962
  stere: 142,
37907
37963
  sterea: 142,
37908
37964
  ups: 10.5 // TODO: should be 6.5 deg at north pole
@@ -38281,7 +38337,7 @@ ${svg}
38281
38337
  });
38282
38338
 
38283
38339
  cmd.graticule = function(dataset, opts) {
38284
- var name = opts.polygon ? 'polygon' : 'graticule';
38340
+ var name = opts.name || opts.polygon && 'polygon' || 'graticule';
38285
38341
  var graticule, destInfo;
38286
38342
  if (dataset && !isLatLngDataset(dataset)) {
38287
38343
  // project graticule to match dataset
@@ -38347,7 +38403,7 @@ ${svg}
38347
38403
  //
38348
38404
  function createGraticule(P, outlined, opts) {
38349
38405
  var interval = opts.interval || 10;
38350
- if (![5,10,15,30,45].includes(interval)) stop('Invalid interval:', interval);
38406
+ if (![5,10,15,20,30,45].includes(interval)) stop('Invalid interval:', interval);
38351
38407
  P.lam0 * 180 / Math.PI;
38352
38408
  var precision = interval > 10 ? 1 : 0.5; // degrees between each vertex
38353
38409
  var xstep = interval;
@@ -40935,6 +40991,7 @@ ${svg}
40935
40991
  }
40936
40992
  commandStr = runGlobalExpression(opts.expression, targets);
40937
40993
  if (commandStr) {
40994
+ message(`command: [${commandStr}]`);
40938
40995
  commands = parseCommands(commandStr);
40939
40996
  runParsedCommands(commands, job, cb);
40940
40997
  } else {