mapshaper 0.5.110 → 0.5.113

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/www/mapshaper.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function () {
2
2
 
3
- var VERSION = "0.5.110";
3
+ var VERSION = "0.5.113";
4
4
 
5
5
 
6
6
  var utils = /*#__PURE__*/Object.freeze({
@@ -2725,21 +2725,30 @@
2725
2725
  message(utils.format("Snapped %s point%s", snapCount, utils.pluralSuffix(snapCount)));
2726
2726
  }
2727
2727
 
2728
+ function snapCoordsByInterval(arcs, snapDist) {
2729
+ if (snapDist > 0 === false) return 0;
2730
+ var ids = getCoordinateIds(arcs);
2731
+ return snapCoordsInternal(ids, arcs, snapDist);
2732
+ }
2733
+
2734
+ function snapEndpointsByInterval(arcs, snapDist) {
2735
+ if (snapDist > 0 === false) return 0;
2736
+ var ids = getEndpointIds(arcs);
2737
+ return snapCoordsInternal(ids, arcs, snapDist);
2738
+ }
2739
+
2728
2740
  // Snap together points within a small threshold
2729
2741
  //
2730
- function snapCoordsByInterval(arcs, snapDist) {
2742
+ function snapCoordsInternal(ids, arcs, snapDist) {
2731
2743
  var snapCount = 0,
2732
- data = arcs.getVertexData(),
2733
- ids;
2744
+ n = ids.length,
2745
+ data = arcs.getVertexData();
2734
2746
 
2735
- if (snapDist > 0) {
2736
- // Get sorted coordinate ids
2737
- // Consider: speed up sorting -- try bucket sort as first pass.
2738
- //
2739
- ids = sortCoordinateIds(data.xx);
2740
- for (var i=0, n=ids.length; i<n; i++) {
2741
- snapCount += snapPoint(i, snapDist, ids, data.xx, data.yy);
2742
- }
2747
+ quicksortIds(data.xx, ids, 0, n-1);
2748
+
2749
+ // Consider: speed up sorting -- try bucket sort as first pass.
2750
+ for (var i=0; i<n; i++) {
2751
+ snapCount += snapPoint(i, snapDist, ids, data.xx, data.yy);
2743
2752
  }
2744
2753
  return snapCount;
2745
2754
 
@@ -2765,13 +2774,25 @@
2765
2774
  }
2766
2775
  }
2767
2776
 
2768
- function sortCoordinateIds(a) {
2769
- var n = a.length,
2777
+ function getCoordinateIds(arcs) {
2778
+ var data = arcs.getVertexData(),
2779
+ n = data.xx.length,
2770
2780
  ids = new Uint32Array(n);
2771
2781
  for (var i=0; i<n; i++) {
2772
2782
  ids[i] = i;
2773
2783
  }
2774
- quicksortIds(a, ids, 0, ids.length-1);
2784
+ return ids;
2785
+ }
2786
+
2787
+ function getEndpointIds(arcs) {
2788
+ var i = 0;
2789
+ var ids = [];
2790
+ var data = arcs.getVertexData();
2791
+ data.nn.forEach(function(n) {
2792
+ if (n > 0 === false) return;
2793
+ ids.push(i, i+n-1);
2794
+ i += n;
2795
+ });
2775
2796
  return ids;
2776
2797
  }
2777
2798
 
@@ -2871,7 +2892,9 @@
2871
2892
  getHighPrecisionSnapInterval: getHighPrecisionSnapInterval,
2872
2893
  snapCoords: snapCoords,
2873
2894
  snapCoordsByInterval: snapCoordsByInterval,
2874
- sortCoordinateIds: sortCoordinateIds
2895
+ snapEndpointsByInterval: snapEndpointsByInterval,
2896
+ getCoordinateIds: getCoordinateIds,
2897
+ getEndpointIds: getEndpointIds
2875
2898
  });
2876
2899
 
2877
2900
  // Find the intersection between two 2D segments
@@ -9500,6 +9523,140 @@
9500
9523
  };
9501
9524
  }
9502
9525
 
9526
+ // convert targets from [{layers: [...], dataset: <>}, ...] format to
9527
+ // [{layer: <>, dataset: <>}, ...] format
9528
+ function expandCommandTargets(targets) {
9529
+ return targets.reduce(function(memo, target) {
9530
+ target.layers.forEach(function(lyr) {
9531
+ memo.push({layer: lyr, dataset: target.dataset});
9532
+ });
9533
+ return memo;
9534
+ }, []);
9535
+ }
9536
+
9537
+
9538
+ function findCommandTargets(layers, pattern, type) {
9539
+ var targets = [];
9540
+ var matches = findMatchingLayers(layers, pattern, true);
9541
+ if (type) {
9542
+ matches = matches.filter(function(o) {return o.layer.geometry_type == type;});
9543
+ }
9544
+ // assign target_id to matched layers
9545
+ // (kludge so layers can be sorted in the order that they match; used by -o command)
9546
+ layers.forEach(function(o) {o.layer.target_id = -1;});
9547
+ matches.forEach(function(o, i) {o.layer.target_id = i;});
9548
+ return groupLayersByDataset(matches);
9549
+ }
9550
+
9551
+ // arr: array of {layer: <>, dataset: <>} objects
9552
+ function groupLayersByDataset(arr) {
9553
+ var datasets = [];
9554
+ var targets = [];
9555
+ arr.forEach(function(o) {
9556
+ var i = datasets.indexOf(o.dataset);
9557
+ if (i == -1) {
9558
+ datasets.push(o.dataset);
9559
+ targets.push({layers: [o.layer], dataset: o.dataset});
9560
+ } else {
9561
+ targets[i].layers.push(o.layer);
9562
+ }
9563
+ });
9564
+ return targets;
9565
+ }
9566
+
9567
+ // layers: array of {layer: <>, dataset: <>} objects
9568
+ // pattern: is a layer identifier or a comma-sep. list of identifiers.
9569
+ // An identifier is a literal name, a pattern containing "*" wildcard or
9570
+ // a 1-based index (1..n)
9571
+ function findMatchingLayers(layers, pattern, throws) {
9572
+ var matchedLayers = [];
9573
+ var unmatchedIds = [];
9574
+ var index = {};
9575
+ pattern.split(',').forEach(function(subpattern, i) {
9576
+ var test = getLayerMatch(subpattern);
9577
+ var matched = false;
9578
+ layers.forEach(function(o, layerId) {
9579
+ // if (matchedLayers.indexOf(lyr) > -1) return; // performance bottleneck with 1000s of layers
9580
+ if (layerId in index) {
9581
+ matched = true;
9582
+ } else if (test(o.layer, layerId + 1)) { // layers are 1-indexed
9583
+ matchedLayers.push(o);
9584
+ index[layerId] = true;
9585
+ matched = true;
9586
+ }
9587
+ });
9588
+ if (matched == false) {
9589
+ unmatchedIds.push(subpattern);
9590
+ }
9591
+ });
9592
+ if (throws && unmatchedIds.length) {
9593
+ stop(utils.format('Missing layer%s: %s', unmatchedIds.length == 1 ? '' : 's', unmatchedIds.join(',')));
9594
+ }
9595
+ return matchedLayers;
9596
+ }
9597
+
9598
+ function getLayerMatch(pattern) {
9599
+ var isIndex = utils.isInteger(Number(pattern));
9600
+ var nameRxp = isIndex ? null : utils.wildcardToRegExp(pattern);
9601
+ return function(lyr, i) {
9602
+ return isIndex ? String(i) == pattern : nameRxp.test(lyr.name || '');
9603
+ };
9604
+ }
9605
+
9606
+ function countTargetLayers(targets) {
9607
+ return targets.reduce(function(memo, target) {
9608
+ return memo + target.layers.length;
9609
+ }, 0);
9610
+ }
9611
+
9612
+ // get an identifier for a layer that can be used in a target= option
9613
+ // (returns name if layer has a unique name, or a numerical id)
9614
+ function getLayerTargetId(catalog, lyr) {
9615
+ var nameCount = 0,
9616
+ name = lyr.name,
9617
+ id;
9618
+ catalog.getLayers().forEach(function(o, i) {
9619
+ if (lyr.name && o.layer.name == lyr.name) nameCount++;
9620
+ if (lyr == o.layer) id = String(i + 1);
9621
+ });
9622
+ if (!id) error('Layer not found');
9623
+ return nameCount == 1 ? lyr.name : id;
9624
+ }
9625
+
9626
+ var TargetUtils = /*#__PURE__*/Object.freeze({
9627
+ __proto__: null,
9628
+ expandCommandTargets: expandCommandTargets,
9629
+ findCommandTargets: findCommandTargets,
9630
+ groupLayersByDataset: groupLayersByDataset,
9631
+ findMatchingLayers: findMatchingLayers,
9632
+ getLayerMatch: getLayerMatch,
9633
+ countTargetLayers: countTargetLayers,
9634
+ getLayerTargetId: getLayerTargetId
9635
+ });
9636
+
9637
+ function getNullLayerProxy(targets) {
9638
+ var obj = {};
9639
+ var n = countTargetLayers(targets);
9640
+ var getters = {
9641
+ name: error,
9642
+ data: error,
9643
+ type: error,
9644
+ size: error,
9645
+ empty: error,
9646
+ bbox: error
9647
+ };
9648
+ addGetters(obj, getters);
9649
+ obj.field_exists = error;
9650
+ obj.field_type = error;
9651
+ obj.field_includes = error;
9652
+ return obj;
9653
+ function error() {
9654
+ throw Error(`This expression requires a single target layer; Received ${n} layers.`);
9655
+ }
9656
+ }
9657
+
9658
+
9659
+
9503
9660
  // Returns an object representing a layer in a JS expression
9504
9661
  function getLayerProxy(lyr, arcs) {
9505
9662
  var obj = {};
@@ -9509,10 +9666,10 @@
9509
9666
  data: records,
9510
9667
  type: lyr.geometry_type,
9511
9668
  size: getFeatureCount(lyr),
9512
- empty: getFeatureCount(lyr) === 0
9669
+ empty: getFeatureCount(lyr) === 0,
9670
+ bbox: getBBoxGetter(obj, lyr, arcs)
9513
9671
  };
9514
9672
  addGetters(obj, getters);
9515
- addBBoxGetter(obj, lyr, arcs);
9516
9673
  obj.field_exists = function(name) {
9517
9674
  return lyr.data && lyr.data.fieldExists(name) ? true : false;
9518
9675
  };
@@ -9541,16 +9698,14 @@
9541
9698
  return ctx;
9542
9699
  }
9543
9700
 
9544
- function addBBoxGetter(obj, lyr, arcs) {
9701
+ function getBBoxGetter(obj, lyr, arcs) {
9545
9702
  var bbox;
9546
- addGetters(obj, {
9547
- bbox: function() {
9548
- if (!bbox) {
9549
- bbox = getBBox$1(lyr, arcs);
9550
- }
9551
- return bbox;
9703
+ return function() {
9704
+ if (!bbox) {
9705
+ bbox = getBBox$1(lyr, arcs);
9552
9706
  }
9553
- });
9707
+ return bbox;
9708
+ };
9554
9709
  }
9555
9710
 
9556
9711
  function getBBox$1(lyr, arcs) {
@@ -13589,6 +13744,14 @@
13589
13744
  return p.length > 2 && p[2] == 'C';
13590
13745
  }
13591
13746
 
13747
+ function stringifyPolygonCoords(coords) {
13748
+ var parts = [];
13749
+ for (var i=0; i<coords.length; i++) {
13750
+ parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
13751
+ }
13752
+ return parts.length > 0 ? parts.join(' ') : '';
13753
+ }
13754
+
13592
13755
  function stringifyLineStringCoords(coords) {
13593
13756
  if (coords.length === 0) return '';
13594
13757
  var d = 'M';
@@ -13619,6 +13782,7 @@
13619
13782
 
13620
13783
  var SvgPathUtils = /*#__PURE__*/Object.freeze({
13621
13784
  __proto__: null,
13785
+ stringifyPolygonCoords: stringifyPolygonCoords,
13622
13786
  stringifyLineStringCoords: stringifyLineStringCoords
13623
13787
  });
13624
13788
 
@@ -13886,6 +14050,7 @@
13886
14050
  'stroke-dasharray': 'dasharray',
13887
14051
  'stroke-width': 'number',
13888
14052
  'stroke-opacity': 'number',
14053
+ 'stroke-miterlimit': 'number',
13889
14054
  'fill-opacity': 'number',
13890
14055
  'text-anchor': null
13891
14056
  };
@@ -13920,12 +14085,30 @@
13920
14085
 
13921
14086
  var propertiesBySymbolType = {
13922
14087
  polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
13923
- polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin')),
14088
+ polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit')),
13924
14089
  point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
13925
14090
  label: utils.arrayToIndex(commonProperties.concat(
13926
- 'fill,r,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
14091
+ 'fill,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
13927
14092
  };
13928
14093
 
14094
+ // symType: point, polygon, polyline, label
14095
+ function applyStyleAttributes(svgObj, symType, rec, filter) {
14096
+ var fields = findPropertiesBySymbolGeom(Object.keys(rec || {}), symType);
14097
+ for (var i=0, n=fields.length; i<n; i++) {
14098
+ if (filter && !filter(fields[i])) continue;
14099
+ setAttribute(svgObj, fields[i], rec[fields[i]]);
14100
+ }
14101
+ }
14102
+
14103
+ function setAttribute(obj, k, v) {
14104
+ if (!obj.properties) obj.properties = {};
14105
+ obj.properties[k] = v;
14106
+ if (k == 'stroke-dasharray' && v) {
14107
+ // kludge for cleaner dashes... make butt the default?
14108
+ obj.properties['stroke-linecap'] = 'butt';
14109
+ }
14110
+ }
14111
+
13929
14112
  function isSupportedSvgStyleProperty(name) {
13930
14113
  return name in stylePropertyTypes;
13931
14114
  }
@@ -14081,6 +14264,7 @@
14081
14264
 
14082
14265
  var SvgProperties = /*#__PURE__*/Object.freeze({
14083
14266
  __proto__: null,
14267
+ applyStyleAttributes: applyStyleAttributes,
14084
14268
  isSupportedSvgStyleProperty: isSupportedSvgStyleProperty,
14085
14269
  findPropertiesBySymbolGeom: findPropertiesBySymbolGeom,
14086
14270
  getSymbolDataAccessor: getSymbolDataAccessor,
@@ -14094,7 +14278,69 @@
14094
14278
  isSvgColor: isSvgColor
14095
14279
  });
14096
14280
 
14097
- var symbolRenderers = {};
14281
+ function toLabelString(val) {
14282
+ if (val || val === 0 || val === false) return String(val);
14283
+ return '';
14284
+ }
14285
+
14286
+ // Kludge for applying fill and other styles to a <text> element
14287
+ // (for rendering labels in the GUI with the dot in Canvas, not SVG)
14288
+ function renderStyledLabel(rec) {
14289
+ var o = renderLabel(rec);
14290
+ applyStyleAttributes(o, 'label', rec);
14291
+ return o;
14292
+ }
14293
+
14294
+ function renderLabel(rec) {
14295
+ var line = toLabelString(rec['label-text']);
14296
+ var morelines, obj;
14297
+ // Accepting \n (two chars) as an alternative to the newline character
14298
+ // (sometimes, '\n' is not converted to newline, e.g. in a Makefile)
14299
+ // Also accepting <br>
14300
+ var newline = /\n|\\n|<br>/i;
14301
+ var dx = rec.dx || 0;
14302
+ var dy = rec.dy || 0;
14303
+ var properties = {
14304
+ // using x, y instead of dx, dy for shift, because Illustrator doesn't apply
14305
+ // dx value when importing text with text-anchor=end
14306
+ y: dy,
14307
+ x: dx
14308
+ };
14309
+ if (newline.test(line)) {
14310
+ morelines = line.split(newline);
14311
+ line = morelines.shift();
14312
+ }
14313
+ obj = {
14314
+ tag: 'text',
14315
+ value: line,
14316
+ properties: properties
14317
+ };
14318
+ if (morelines) {
14319
+ // multiline label
14320
+ obj.children = [];
14321
+ morelines.forEach(function(line) {
14322
+ var tspan = {
14323
+ tag: 'tspan',
14324
+ value: line,
14325
+ properties: {
14326
+ x: dx,
14327
+ dy: rec['line-height'] || '1.1em'
14328
+ }
14329
+ };
14330
+ obj.children.push(tspan);
14331
+ });
14332
+ }
14333
+ return obj;
14334
+ }
14335
+
14336
+ var SvgLabels = /*#__PURE__*/Object.freeze({
14337
+ __proto__: null,
14338
+ renderStyledLabel: renderStyledLabel,
14339
+ renderLabel: renderLabel
14340
+ });
14341
+
14342
+ // convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
14343
+
14098
14344
 
14099
14345
  function getTransform(xy, scale) {
14100
14346
  var str = 'translate(' + roundToTenths(xy[0]) + ' ' + roundToTenths(xy[1]) + ')';
@@ -14104,27 +14350,197 @@
14104
14350
  return str;
14105
14351
  }
14106
14352
 
14107
- var SvgCommon = /*#__PURE__*/Object.freeze({
14353
+ var symbolRenderers = {
14354
+ line: line,
14355
+ polygon: polygon,
14356
+ polyline: polyline,
14357
+ circle: circle,
14358
+ square: square,
14359
+ image: image,
14360
+ group: group,
14361
+ label: label
14362
+ };
14363
+
14364
+ // render label and/or point symbol
14365
+ function renderPoint(rec) {
14366
+ var children = [];
14367
+ // var halfSize = rec.r || 0; // radius or half of symbol size
14368
+ if (featureHasSvgSymbol(rec)) {
14369
+ children.push(renderSymbol(rec));
14370
+ }
14371
+ if (featureHasLabel(rec)) {
14372
+ children.push(renderStyledLabel(rec));
14373
+ }
14374
+ var o = children.length > 1 ? {tag: 'g', children: children} : children[0];
14375
+ if (!o) return null;
14376
+ o.properties = o.properties || {};
14377
+ return o;
14378
+ }
14379
+
14380
+ function renderSymbol(d) {
14381
+ if (d['svg-symbol']) {
14382
+ return renderComplexSymbol(d['svg-symbol']);
14383
+ }
14384
+ if (d.r > 0) {
14385
+ return circle(d);
14386
+ }
14387
+ return empty();
14388
+ }
14389
+
14390
+ function renderComplexSymbol(sym, x, y) {
14391
+ if (utils.isString(sym)) {
14392
+ sym = JSON.parse(sym);
14393
+ }
14394
+ if (sym.tag) {
14395
+ // symbol appears to already use mapshaper's svg notation... pass through
14396
+ return sym;
14397
+ }
14398
+ var renderer = symbolRenderers[sym.type];
14399
+ if (!renderer) {
14400
+ message(sym.type ? 'Unknown symbol type: ' + sym.type : 'Symbol is missing a type property');
14401
+ return empty();
14402
+ }
14403
+ var o = renderer(sym, x || 0, y || 0);
14404
+ if (sym.opacity) {
14405
+ o.properties.opacity = sym.opacity;
14406
+ }
14407
+ return o;
14408
+ }
14409
+
14410
+ function empty() {
14411
+ return {tag: 'g', properties: {}, children: []};
14412
+ }
14413
+
14414
+ function circle(d, x, y) {
14415
+ var o = {
14416
+ tag: 'circle',
14417
+ properties: {
14418
+ cx: x || 0,
14419
+ cy: y || 0
14420
+ }
14421
+ };
14422
+ applyStyleAttributes(o, 'point', d);
14423
+ return o;
14424
+ }
14425
+
14426
+ function label(d, x, y) {
14427
+ var o = renderStyledLabel(d);
14428
+ if (x || y) {
14429
+ o.properties.x = x || 0;
14430
+ o.properties.y = y || 0;
14431
+ }
14432
+ return o;
14433
+ }
14434
+
14435
+ function image(d, x, y) {
14436
+ var w = d.width || 20,
14437
+ h = d.height || 20;
14438
+ var o = {
14439
+ tag: 'image',
14440
+ properties: {
14441
+ width: w,
14442
+ height: h,
14443
+ x: (x || 0) - w / 2,
14444
+ y: (y || 0) - h / 2,
14445
+ href: d.href || ''
14446
+ }
14447
+ };
14448
+ return o;
14449
+ }
14450
+
14451
+ function square(d, x, y) {
14452
+ var r = d.r || 0;
14453
+ var o = {
14454
+ tag: 'rect',
14455
+ properties: {
14456
+ x: x - r,
14457
+ y: y - r,
14458
+ width: r * 2,
14459
+ height: r * 2
14460
+ }
14461
+ };
14462
+ applyStyleAttributes(o, 'point', d);
14463
+ return o;
14464
+ }
14465
+
14466
+ function line(d, x, y) {
14467
+ var coords, o;
14468
+ coords = [[x, y], [x + (d.dx || 0), y + (d.dy || 0)]];
14469
+ o = importLineString(coords);
14470
+ applyStyleAttributes(o, 'polyline', d);
14471
+ return o;
14472
+ }
14473
+
14474
+ // polyline coords are like GeoJSON MultiLineString coords: an array of 0 or more paths
14475
+ function polyline(d, x, y) {
14476
+ var coords = d.coordinates || [];
14477
+ var o = importMultiLineString(coords);
14478
+ applyStyleAttributes(o, 'polyline', d);
14479
+ return o;
14480
+ }
14481
+
14482
+ // polygon coords are an array of rings (and holes), like flattened MultiPolygon coords
14483
+ function polygon(d, x, y) {
14484
+ var coords = d.coordinates || [];
14485
+ var o = importPolygon(coords);
14486
+ applyStyleAttributes(o, 'polygon', d);
14487
+ return o;
14488
+ }
14489
+
14490
+ function group(d, x, y) {
14491
+ var parts = (d.parts || []).map(function(o) {
14492
+ var sym = renderComplexSymbol(o, x, y);
14493
+ if (d.chained) {
14494
+ //if (o.chained) {
14495
+ x += (o.dx || 0);
14496
+ y += (o.dy || 0);
14497
+ }
14498
+ return sym;
14499
+ });
14500
+ if (parts.length == 1) return parts[0];
14501
+ return {
14502
+ tag: 'g',
14503
+ children: parts
14504
+ };
14505
+ }
14506
+
14507
+ var SvgSymbols = /*#__PURE__*/Object.freeze({
14108
14508
  __proto__: null,
14509
+ getTransform: getTransform,
14109
14510
  symbolRenderers: symbolRenderers,
14110
- getTransform: getTransform
14511
+ renderPoint: renderPoint
14111
14512
  });
14112
14513
 
14514
+ var geojsonImporters = {
14515
+ Point: importPoint,
14516
+ Polygon: importPolygon,
14517
+ LineString: importLineString,
14518
+ MultiPoint: importMultiPoint,
14519
+ MultiLineString: importMultiLineString,
14520
+ MultiPolygon: importMultiPolygon
14521
+ };
14522
+
14113
14523
  function importGeoJSONFeatures(features, opts) {
14114
14524
  opts = opts || {};
14115
14525
  return features.map(function(obj, i) {
14116
14526
  var geom = obj.type == 'Feature' ? obj.geometry : obj; // could be null
14117
14527
  var geomType = geom && geom.type;
14528
+ var msType = GeoJSON.translateGeoJSONType(geomType);
14529
+ var d = obj.properties || {};
14118
14530
  var svgObj = null;
14119
14531
  if (geomType && geom.coordinates) {
14120
- svgObj = geojsonImporters[geomType](geom.coordinates, obj.properties, opts);
14532
+ svgObj = geojsonImporters[geomType](geom.coordinates, d);
14121
14533
  }
14122
14534
  if (!svgObj) {
14123
14535
  return {tag: 'g'}; // empty element
14124
- }
14125
- // TODO: fix error caused by null svgObj (caused by e.g. MultiPolygon with [] coordinates)
14126
- if (obj.properties) {
14127
- applyStyleAttributes(svgObj, geomType, obj.properties);
14536
+ } else if (msType == 'polyline' || msType == 'polygon') {
14537
+ applyStyleAttributes(svgObj, msType, d);
14538
+ } else if (msType == 'point' && isSimpleCircle(d)) {
14539
+ // kludge -- maintains bw compatibility/passes tests -- style attributes
14540
+ // are applied to the <g> container, 'r' property is applied to circle
14541
+ applyStyleAttributes(svgObj, msType, d, simpleCircleFilter);
14542
+ } else {
14543
+ // other point symbols: attributes are complicated, added downstream
14128
14544
  }
14129
14545
  if ('id' in obj) {
14130
14546
  if (!svgObj.properties) {
@@ -14136,34 +14552,37 @@
14136
14552
  });
14137
14553
  }
14138
14554
 
14139
- function applyStyleAttributes(svgObj, geomType, rec) {
14140
- var symbolType = GeoJSON.translateGeoJSONType(geomType);
14141
- if (symbolType == 'point' && ('label-text' in rec)) {
14142
- symbolType = 'label';
14143
- }
14144
- var fields = findPropertiesBySymbolGeom(Object.keys(rec), symbolType);
14145
- for (var i=0, n=fields.length; i<n; i++) {
14146
- setAttribute(svgObj, fields[i], rec[fields[i]]);
14555
+ function importPoint(coords, rec) {
14556
+ rec = rec || {};
14557
+ if (isSimpleCircle(rec)) {
14558
+ return {
14559
+ tag: 'circle',
14560
+ properties: {
14561
+ cx: coords[0],
14562
+ cy: coords[1],
14563
+ r: rec.r
14564
+ }
14565
+ };
14147
14566
  }
14567
+ var o = renderPoint(rec, coords);
14568
+ if (o) o.properties.transform = getTransform(coords);
14569
+ return o;
14148
14570
  }
14149
14571
 
14150
- function setAttribute(obj, k, v) {
14151
- if (k == 'r') {
14152
- // assigned by importPoint()
14153
- } else {
14154
- if (!obj.properties) obj.properties = {};
14155
- obj.properties[k] = v;
14156
- if (k == 'stroke-dasharray' && v) {
14157
- // kludge for cleaner dashes... make butt the default?
14158
- obj.properties['stroke-linecap'] = 'butt';
14159
- }
14160
- }
14572
+ function simpleCircleFilter(k) {
14573
+ return k != 'r';
14161
14574
  }
14162
14575
 
14163
- function importMultiPoint(coords, rec, layerOpts) {
14576
+ // just a dot, no label or icon
14577
+ function isSimpleCircle(rec) {
14578
+ return rec && (rec.r > 0 && !rec['svg-symbol'] && !rec['label-text']);
14579
+ }
14580
+
14581
+ function importMultiPoint(coords, rec) {
14164
14582
  var children = [], p;
14165
14583
  for (var i=0; i<coords.length; i++) {
14166
- p = importPoint(coords[i], rec, layerOpts);
14584
+ p = importPoint(coords[i], rec);
14585
+ if (!p) continue;
14167
14586
  if (p.tag == 'g' && p.children) {
14168
14587
  children = children.concat(p.children);
14169
14588
  } else {
@@ -14173,18 +14592,6 @@
14173
14592
  return children.length > 0 ? {tag: 'g', children: children} : null;
14174
14593
  }
14175
14594
 
14176
- function importMultiPath(coords, importer) {
14177
- var o;
14178
- for (var i=0; i<coords.length; i++) {
14179
- if (i === 0) {
14180
- o = importer(coords[i]);
14181
- } else {
14182
- o.properties.d += ' ' + importer(coords[i]).properties.d;
14183
- }
14184
- }
14185
- return o;
14186
- }
14187
-
14188
14595
  function importLineString(coords) {
14189
14596
  var d = stringifyLineStringCoords(coords);
14190
14597
  return {
@@ -14193,7 +14600,6 @@
14193
14600
  };
14194
14601
  }
14195
14602
 
14196
-
14197
14603
  function importMultiLineString(coords) {
14198
14604
  var d = coords.map(stringifyLineStringCoords).join(' ');
14199
14605
  return {
@@ -14202,180 +14608,38 @@
14202
14608
  };
14203
14609
  }
14204
14610
 
14205
- // Kludge for applying fill and other styles to a <text> element
14206
- // (for rendering labels in the GUI with the dot in Canvas, not SVG)
14207
- function importStyledLabel(rec, p) {
14208
- var o = importLabel(rec, p);
14209
- applyStyleAttributes(o, 'Point', rec);
14210
- return o;
14211
- }
14212
-
14213
- function toLabelString(val) {
14214
- if (val || val === 0 || val === false) return String(val);
14215
- return '';
14611
+ function importMultiPolygon(coords) {
14612
+ return importPolygon(flattenMultiPolygonCoords(coords));
14216
14613
  }
14217
14614
 
14218
- function importLabel(rec, p) {
14219
- var line = toLabelString(rec['label-text']);
14220
- var morelines, obj;
14221
- // Accepting \n (two chars) as an alternative to the newline character
14222
- // (sometimes, '\n' is not converted to newline, e.g. in a Makefile)
14223
- // Also accepting <br>
14224
- var newline = /\n|\\n|<br>/i;
14225
- var dx = rec.dx || 0;
14226
- var dy = rec.dy || 0;
14227
- var properties = {
14228
- // using x, y instead of dx, dy for shift, because Illustrator doesn't apply
14229
- // dx value when importing text with text-anchor=end
14230
- y: dy,
14231
- x: dx
14232
- };
14233
- if (p) {
14234
- properties.transform = getTransform(p);
14235
- }
14236
- if (newline.test(line)) {
14237
- morelines = line.split(newline);
14238
- line = morelines.shift();
14239
- }
14240
- obj = {
14241
- tag: 'text',
14242
- value: line,
14243
- properties: properties
14244
- };
14245
- if (morelines) {
14246
- // multiline label
14247
- obj.children = [];
14248
- morelines.forEach(function(line) {
14249
- var tspan = {
14250
- tag: 'tspan',
14251
- value: line,
14252
- properties: {
14253
- x: dx,
14254
- dy: rec['line-height'] || '1.1em'
14255
- }
14256
- };
14257
- obj.children.push(tspan);
14258
- });
14259
- }
14260
- return obj;
14261
- }
14262
-
14263
- function getEmptySymbol() {
14264
- return {tag: 'g', properties: {}, children: []};
14265
- }
14266
-
14267
-
14268
- function renderSymbol(d, x, y) {
14269
- var renderer = symbolRenderers[d.type];
14270
- if (!renderer) {
14271
- stop(d.type ? 'Unknown symbol type: ' + d.type : 'Symbol is missing a type property');
14272
- }
14273
- return renderer(d, x || 0, y || 0);
14615
+ function flattenMultiPolygonCoords(coords) {
14616
+ return coords.reduce(function(memo, poly) {
14617
+ return memo.concat(poly);
14618
+ }, []);
14274
14619
  }
14275
14620
 
14276
- // d: svg-symbol object from feature data object
14277
- function importSymbol(d, xy) {
14278
- var renderer;
14279
- if (!d) {
14280
- return getEmptySymbol();
14281
- }
14282
- if (utils.isString(d)) {
14283
- d = JSON.parse(d);
14284
- }
14285
- return {
14286
- tag: 'g',
14621
+ function importPolygon(coords) {
14622
+ if (coords.length === 0) return null;
14623
+ var o = {
14624
+ tag: 'path',
14287
14625
  properties: {
14288
- 'class': 'mapshaper-svg-symbol',
14289
- transform: xy ? getTransform(xy) : null
14290
- },
14291
- children: renderSymbol(d)
14626
+ d: stringifyPolygonCoords(coords)
14627
+ }
14292
14628
  };
14293
- }
14294
-
14295
- function importPoint(coords, rec, layerOpts) {
14296
- rec = rec || {};
14297
- if ('svg-symbol' in rec) {
14298
- return importSymbol(rec['svg-symbol'], coords);
14299
- }
14300
- return importStandardPoint(coords, rec, layerOpts || {});
14301
- }
14302
-
14303
- function importPolygon(coords) {
14304
- var d, o;
14305
- for (var i=0; i<coords.length; i++) {
14306
- d = o ? o.properties.d + ' ' : '';
14307
- o = importLineString(coords[i]);
14308
- o.properties.d = d + o.properties.d + ' Z';
14309
- }
14310
14629
  if (coords.length > 1) {
14311
14630
  o.properties['fill-rule'] = 'evenodd'; // support polygons with holes
14312
14631
  }
14313
14632
  return o;
14314
14633
  }
14315
14634
 
14316
- function importStandardPoint(coords, rec, layerOpts) {
14317
- var isLabel = 'label-text' in rec;
14318
- var symbolType = layerOpts.point_symbol || '';
14319
- var children = [];
14320
- var halfSize = rec.r || 0; // radius or half of symbol size
14321
- var p;
14322
- // if not a label, create a symbol even without a size
14323
- // (circle radius can be set via CSS)
14324
- if (halfSize > 0 || !isLabel) {
14325
- if (symbolType == 'square') {
14326
- p = {
14327
- tag: 'rect',
14328
- properties: {
14329
- x: coords[0] - halfSize,
14330
- y: coords[1] - halfSize,
14331
- width: halfSize * 2,
14332
- height: halfSize * 2
14333
- }};
14334
- } else { // default is circle
14335
- p = {
14336
- tag: 'circle',
14337
- properties: {
14338
- cx: coords[0],
14339
- cy: coords[1]
14340
- }};
14341
- if (halfSize > 0) {
14342
- p.properties.r = halfSize;
14343
- }
14344
- }
14345
- children.push(p);
14346
- }
14347
- if (isLabel) {
14348
- children.push(importLabel(rec, coords));
14349
- }
14350
- return children.length > 1 ? {tag: 'g', children: children} : children[0];
14351
- }
14352
-
14353
- var geojsonImporters = {
14354
- Point: importPoint,
14355
- Polygon: importPolygon,
14356
- LineString: importLineString,
14357
- MultiPoint: function(coords, rec, opts) {
14358
- return importMultiPoint(coords, rec, opts);
14359
- },
14360
- MultiLineString: function(coords) {
14361
- return importMultiPath(coords, importLineString);
14362
- },
14363
- MultiPolygon: function(coords) {
14364
- return importMultiPath(coords, importPolygon);
14365
- }
14366
- };
14367
-
14368
14635
  var GeojsonToSvg = /*#__PURE__*/Object.freeze({
14369
14636
  __proto__: null,
14370
14637
  importGeoJSONFeatures: importGeoJSONFeatures,
14371
- applyStyleAttributes: applyStyleAttributes,
14638
+ importPoint: importPoint,
14372
14639
  importLineString: importLineString,
14373
14640
  importMultiLineString: importMultiLineString,
14374
- importStyledLabel: importStyledLabel,
14375
- importLabel: importLabel,
14376
- renderSymbol: renderSymbol,
14377
- importSymbol: importSymbol,
14378
- importPoint: importPoint,
14641
+ importMultiPolygon: importMultiPolygon,
14642
+ flattenMultiPolygonCoords: flattenMultiPolygonCoords,
14379
14643
  importPolygon: importPolygon
14380
14644
  });
14381
14645
 
@@ -14834,6 +15098,7 @@
14834
15098
  return svg;
14835
15099
  }
14836
15100
 
15101
+ // href: A URL or a local path
14837
15102
  // TODO: download SVG files asynchronously
14838
15103
  // (currently, files are downloaded synchronously, which is obviously undesirable)
14839
15104
  //
@@ -14841,7 +15106,7 @@
14841
15106
  var content;
14842
15107
  if (href.indexOf('http') === 0) {
14843
15108
  content = fetchFileSync(href);
14844
- } else if (require('fs').existsSync(href)) { // assume href is a relative path
15109
+ } else if (require('fs').existsSync(href)) {
14845
15110
  content = require('fs').readFileSync(href, 'utf8');
14846
15111
  } else {
14847
15112
  stop("Invalid SVG location:", href);
@@ -14916,15 +15181,25 @@
14916
15181
  convertPropertiesToDefinitions(obj, defs);
14917
15182
  return stringify(obj);
14918
15183
  }).join('\n');
15184
+
14919
15185
  if (defs.length > 0) {
14920
15186
  svg = '<defs>\n' + utils.pluck(defs, 'svg').join('') + '</defs>\n' + svg;
14921
15187
  }
15188
+
14922
15189
  if (svg.includes('xlink:')) {
14923
15190
  namespace += ' xmlns:xlink="http://www.w3.org/1999/xlink"';
14924
15191
  }
15192
+
15193
+ // default line style properties
14925
15194
  var capStyle = opts.default_linecap || 'round';
15195
+ var lineProps = `stroke-linecap="${capStyle}" stroke-linejoin="round"`;
15196
+ if (svg.includes('stroke-linejoin="miter"')) {
15197
+ // the default limit in Illustrator seems to be 10 -- too large for mapping
15198
+ // (Mapbox uses 2 as the default in their styles)
15199
+ lineProps += ' stroke-miterlimit="2"';
15200
+ }
14926
15201
  var template = `<?xml version="1.0"?>
14927
- <svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s" stroke-linecap="${capStyle}" stroke-linejoin="round">${style}
15202
+ <svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s" ${lineProps}>${style}
14928
15203
  ${svg}
14929
15204
  </svg>`;
14930
15205
  svg = utils.format(template,size[0], size[1], 0, 0, size[0], size[1]);
@@ -15090,6 +15365,15 @@ ${svg}
15090
15365
  return layerObj;
15091
15366
  }
15092
15367
 
15368
+ function featureHasSvgSymbol(d) {
15369
+ return !!(d && (d['svg-symbol'] || d.r));
15370
+ }
15371
+
15372
+ function featureHasLabel(d) {
15373
+ var text = d && d['label-text'];
15374
+ return text || text === 0; // accept numerical 0 as label text
15375
+ }
15376
+
15093
15377
  function layerHasSvgSymbols(lyr) {
15094
15378
  return lyr.geometry_type == 'point' && lyr.data && lyr.data.fieldExists('svg-symbol');
15095
15379
  }
@@ -15109,6 +15393,8 @@ ${svg}
15109
15393
  validateSvgDataFields: validateSvgDataFields,
15110
15394
  exportDataAttributesForSVG: exportDataAttributesForSVG,
15111
15395
  getEmptyLayerForSVG: getEmptyLayerForSVG,
15396
+ featureHasSvgSymbol: featureHasSvgSymbol,
15397
+ featureHasLabel: featureHasLabel,
15112
15398
  layerHasSvgSymbols: layerHasSvgSymbols,
15113
15399
  layerHasLabels: layerHasLabels
15114
15400
  });
@@ -17169,116 +17455,6 @@ ${svg}
17169
17455
  formatVersionedFileName: formatVersionedFileName
17170
17456
  });
17171
17457
 
17172
- // convert targets from [{layers: [...], dataset: <>}, ...] format to
17173
- // [{layer: <>, dataset: <>}, ...] format
17174
- function expandCommandTargets(targets) {
17175
- return targets.reduce(function(memo, target) {
17176
- target.layers.forEach(function(lyr) {
17177
- memo.push({layer: lyr, dataset: target.dataset});
17178
- });
17179
- return memo;
17180
- }, []);
17181
- }
17182
-
17183
- function findCommandTargets(layers, pattern, type) {
17184
- var targets = [];
17185
- var matches = findMatchingLayers(layers, pattern, true);
17186
- if (type) {
17187
- matches = matches.filter(function(o) {return o.layer.geometry_type == type;});
17188
- }
17189
- // assign target_id to matched layers
17190
- // (kludge so layers can be sorted in the order that they match; used by -o command)
17191
- layers.forEach(function(o) {o.layer.target_id = -1;});
17192
- matches.forEach(function(o, i) {o.layer.target_id = i;});
17193
- return groupLayersByDataset(matches);
17194
- }
17195
-
17196
- // arr: array of {layer: <>, dataset: <>} objects
17197
- function groupLayersByDataset(arr) {
17198
- var datasets = [];
17199
- var targets = [];
17200
- arr.forEach(function(o) {
17201
- var i = datasets.indexOf(o.dataset);
17202
- if (i == -1) {
17203
- datasets.push(o.dataset);
17204
- targets.push({layers: [o.layer], dataset: o.dataset});
17205
- } else {
17206
- targets[i].layers.push(o.layer);
17207
- }
17208
- });
17209
- return targets;
17210
- }
17211
-
17212
- // layers: array of {layer: <>, dataset: <>} objects
17213
- // pattern: is a layer identifier or a comma-sep. list of identifiers.
17214
- // An identifier is a literal name, a pattern containing "*" wildcard or
17215
- // a 1-based index (1..n)
17216
- function findMatchingLayers(layers, pattern, throws) {
17217
- var matchedLayers = [];
17218
- var unmatchedIds = [];
17219
- var index = {};
17220
- pattern.split(',').forEach(function(subpattern, i) {
17221
- var test = getLayerMatch(subpattern);
17222
- var matched = false;
17223
- layers.forEach(function(o, layerId) {
17224
- // if (matchedLayers.indexOf(lyr) > -1) return; // performance bottleneck with 1000s of layers
17225
- if (layerId in index) {
17226
- matched = true;
17227
- } else if (test(o.layer, layerId + 1)) { // layers are 1-indexed
17228
- matchedLayers.push(o);
17229
- index[layerId] = true;
17230
- matched = true;
17231
- }
17232
- });
17233
- if (matched == false) {
17234
- unmatchedIds.push(subpattern);
17235
- }
17236
- });
17237
- if (throws && unmatchedIds.length) {
17238
- stop(utils.format('Missing layer%s: %s', unmatchedIds.length == 1 ? '' : 's', unmatchedIds.join(',')));
17239
- }
17240
- return matchedLayers;
17241
- }
17242
-
17243
- function getLayerMatch(pattern) {
17244
- var isIndex = utils.isInteger(Number(pattern));
17245
- var nameRxp = isIndex ? null : utils.wildcardToRegExp(pattern);
17246
- return function(lyr, i) {
17247
- return isIndex ? String(i) == pattern : nameRxp.test(lyr.name || '');
17248
- };
17249
- }
17250
-
17251
- function countTargetLayers(targets) {
17252
- return targets.reduce(function(memo, target) {
17253
- return memo + target.layers.length;
17254
- }, 0);
17255
- }
17256
-
17257
- // get an identifier for a layer that can be used in a target= option
17258
- // (returns name if layer has a unique name, or a numerical id)
17259
- function getLayerTargetId(catalog, lyr) {
17260
- var nameCount = 0,
17261
- name = lyr.name,
17262
- id;
17263
- catalog.getLayers().forEach(function(o, i) {
17264
- if (lyr.name && o.layer.name == lyr.name) nameCount++;
17265
- if (lyr == o.layer) id = String(i + 1);
17266
- });
17267
- if (!id) error('Layer not found');
17268
- return nameCount == 1 ? lyr.name : id;
17269
- }
17270
-
17271
- var TargetUtils = /*#__PURE__*/Object.freeze({
17272
- __proto__: null,
17273
- expandCommandTargets: expandCommandTargets,
17274
- findCommandTargets: findCommandTargets,
17275
- groupLayersByDataset: groupLayersByDataset,
17276
- findMatchingLayers: findMatchingLayers,
17277
- getLayerMatch: getLayerMatch,
17278
- countTargetLayers: countTargetLayers,
17279
- getLayerTargetId: getLayerTargetId
17280
- });
17281
-
17282
17458
  function readFirstChars(reader, n) {
17283
17459
  return bufferToString(reader.readSync(0, Math.min(n || 1000, reader.size())));
17284
17460
  }
@@ -20154,6 +20330,10 @@ ${svg}
20154
20330
  DEFAULT: true,
20155
20331
  type: 'distance'
20156
20332
  })
20333
+ .option('endpoints', {
20334
+ describe: 'only snap together the endpoints of lines',
20335
+ type: 'flag'
20336
+ })
20157
20337
  .option('precision', {
20158
20338
  describe: 'round all coordinates to a given decimal precision (e.g. 0.000001)',
20159
20339
  type: 'number'
@@ -20297,14 +20477,21 @@ ${svg}
20297
20477
  .option('stroke', {})
20298
20478
  .option('stroke-width', {})
20299
20479
  .option('fill', {
20300
- describe: 'symbol fill color'
20480
+ describe: 'symbol fill color (filled symbols only)'
20301
20481
  })
20302
- .option('polygons', {
20303
- describe: 'generate symbols as polygons instead of SVG objects',
20482
+ .option('stroke', {
20483
+ describe: 'symbol line color (linear symbols only)'
20484
+ })
20485
+ .option('stroke-width', {
20486
+ describe: 'symbol line width (linear symbols only)'
20487
+ })
20488
+ .option('geographic', {
20489
+ old_alias: 'polygons',
20490
+ describe: 'make geographic shapes instead of SVG objects',
20304
20491
  type: 'flag'
20305
20492
  })
20306
20493
  .option('pixel-scale', {
20307
- describe: 'set symbol scale in meters-per-pixel (for polygons option)',
20494
+ describe: 'set symbol scale in meters per pixel (geographic option)',
20308
20495
  type: 'number',
20309
20496
  })
20310
20497
  // .option('flipped', {
@@ -20341,6 +20528,9 @@ ${svg}
20341
20528
  .option('radii', {
20342
20529
  describe: '(ring) comma-sep. list of concentric radii, ascending order'
20343
20530
  })
20531
+ .option('arrow-style', {
20532
+ describe: '(arrow) options: stick, standard (default is standard)'
20533
+ })
20344
20534
  .option('length', {
20345
20535
  old_alias: 'arrow-length',
20346
20536
  describe: '(arrow) length of arrow in pixels'
@@ -20695,16 +20885,16 @@ ${svg}
20695
20885
  var ifOpts = {
20696
20886
  expression: {
20697
20887
  DEFAULT: true,
20698
- describe: 'JS expression targeting a single layer'
20699
- },
20700
- empty: {
20701
- describe: 'run if layer is empty',
20702
- type: 'flag'
20703
- },
20704
- 'not-empty': {
20705
- describe: 'run if layer is not empty',
20706
- type: 'flag'
20888
+ describe: 'JS expression'
20707
20889
  },
20890
+ // empty: {
20891
+ // describe: 'run if layer is empty',
20892
+ // type: 'flag'
20893
+ // },
20894
+ // 'not-empty': {
20895
+ // describe: 'run if layer is not empty',
20896
+ // type: 'flag'
20897
+ // },
20708
20898
  layer: {
20709
20899
  describe: 'name or id of layer to test (default is current target)'
20710
20900
  },
@@ -34689,13 +34879,27 @@ ${svg}
34689
34879
  return job.control || (job.control = {});
34690
34880
  }
34691
34881
 
34692
- function compileIfCommandExpression(expr, catalog, targ, opts) {
34693
- var ctx = getLayerProxy(targ.layer, targ.dataset.arcs, opts);
34882
+ function compileIfCommandExpression(expr, catalog, opts) {
34883
+ var targetId = opts.layer || opts.target || null;
34884
+ var targets = catalog.findCommandTargets(targetId);
34885
+ var isSingle = targets.length == 1 && targets[0].layers.length == 1;
34886
+ if (targets.length === 0 && targetId) {
34887
+ stop('Layer not found:', targetId);
34888
+ }
34889
+ var ctx;
34890
+ if (isSingle) {
34891
+ ctx = getLayerProxy(targets[0].layers[0], targets[0].dataset.arcs, opts);
34892
+ } else {
34893
+ ctx = getNullLayerProxy(targets);
34894
+ }
34694
34895
  var exprOpts = Object.assign({returns: true}, opts);
34695
34896
  var func = compileExpressionToFunction(expr, exprOpts);
34696
34897
 
34697
- ctx.layer_exists = function(name) {
34698
- return !!catalog.findSingleLayer(name);
34898
+ // @geoType: optional geometry type (polygon, polyline, point, null);
34899
+ ctx.layer_exists = function(name, geoType) {
34900
+ var targets = catalog.findCommandTargets(name, geoType);
34901
+ if (targets.length === 0) return false;
34902
+ return true;
34699
34903
  };
34700
34904
 
34701
34905
  ctx.file_exists = function(file) {
@@ -34754,49 +34958,28 @@ ${svg}
34754
34958
  return ['if','elif','else','endif'].includes(cmd);
34755
34959
  }
34756
34960
 
34757
- function testLayer(catalog, opts) {
34758
- var targ = getTargetLayer(catalog, opts);
34961
+ function test(catalog, opts) {
34962
+ // var targ = getTargetLayer(catalog, opts);
34759
34963
  if (opts.expression) {
34760
- return compileIfCommandExpression(opts.expression, catalog, targ, opts)();
34761
- }
34762
- if (opts.empty) {
34763
- return layerIsEmpty(targ.layer);
34764
- }
34765
- if (opts.not_empty) {
34766
- return !layerIsEmpty(targ.layer);
34964
+ return compileIfCommandExpression(opts.expression, catalog, opts)();
34767
34965
  }
34966
+ // if (opts.empty) {
34967
+ // return layerIsEmpty(targ.layer);
34968
+ // }
34969
+ // if (opts.not_empty) {
34970
+ // return !layerIsEmpty(targ.layer);
34971
+ // }
34768
34972
  return true;
34769
34973
  }
34770
34974
 
34771
34975
  function evaluateIf(job, opts) {
34772
- if (!blockWasActive(job) && testLayer(job.catalog, opts)) {
34976
+ if (!blockWasActive(job) && test(job.catalog, opts)) {
34773
34977
  enterActiveBranch(job);
34774
34978
  } else {
34775
34979
  enterInactiveBranch(job);
34776
34980
  }
34777
34981
  }
34778
34982
 
34779
- // layerId: optional layer identifier
34780
- //
34781
- function getTargetLayer(catalog, opts) {
34782
- var layerId = opts.layer || opts.target;
34783
- var targets = catalog.findCommandTargets(layerId);
34784
- if (targets.length === 0) {
34785
- if (layerId) {
34786
- stop('Layer not found:', layerId);
34787
- } else {
34788
- stop('Missing a target layer.');
34789
- }
34790
- }
34791
- if (targets.length > 1 || targets[0].layers.length > 1) {
34792
- stop('Command requires a single target layer.');
34793
- }
34794
- return {
34795
- layer: targets[0].layers[0],
34796
- dataset: targets[0].dataset
34797
- };
34798
- }
34799
-
34800
34983
  cmd.ignore = function(targetLayer, dataset, opts) {
34801
34984
  if (opts.empty && layerIsEmpty(targetLayer)) {
34802
34985
  interrupt('Layer is empty, stopping processing');
@@ -37238,72 +37421,6 @@ ${svg}
37238
37421
  }
37239
37422
  };
37240
37423
 
37241
- symbolRenderers.circle = function(d, x, y) {
37242
- var o = importPoint([x, y], d, {});
37243
- applyStyleAttributes(o, 'Point', d);
37244
- return [o];
37245
- };
37246
-
37247
- symbolRenderers.label = function(d, x, y) {
37248
- var o = importStyledLabel(d, [x, y]);
37249
- return [o];
37250
- };
37251
-
37252
- symbolRenderers.image = function(d, x, y) {
37253
- var w = d.width || 20,
37254
- h = d.height || 20;
37255
- var o = {
37256
- tag: 'image',
37257
- properties: {
37258
- width: w,
37259
- height: h,
37260
- x: x - w / 2,
37261
- y: y - h / 2,
37262
- href: d.href || ''
37263
- }
37264
- };
37265
- return [o];
37266
- };
37267
-
37268
- symbolRenderers.square = function(d, x, y) {
37269
- var o = importPoint([x, y], d, {point_symbol: 'square'});
37270
- applyStyleAttributes(o, 'Point', d);
37271
- return [o];
37272
- };
37273
-
37274
- symbolRenderers.line = function(d, x, y) {
37275
- var coords, o;
37276
- coords = [[x, y], [x + (d.dx || 0), y + (d.dy || 0)]];
37277
- o = importLineString(coords);
37278
- applyStyleAttributes(o, 'LineString', d);
37279
- return [o];
37280
- };
37281
-
37282
- symbolRenderers.polyline = function(d, x, y) {
37283
- var coords = d.coordinates || [];
37284
- var o = importMultiLineString(coords);
37285
- applyStyleAttributes(o, 'LineString', d);
37286
- return [o];
37287
- };
37288
-
37289
- symbolRenderers.polygon = function(d, x, y) {
37290
- var coords = d.coordinates || [];
37291
- var o = importPolygon(coords);
37292
- applyStyleAttributes(o, 'Polygon', d);
37293
- return [o];
37294
- };
37295
-
37296
- symbolRenderers.group = function(d, x, y) {
37297
- return (d.parts || []).reduce(function(memo, o) {
37298
- var sym = renderSymbol(o, x, y);
37299
- if (d.chained) {
37300
- x += (o.dx || 0);
37301
- y += (o.dy || 0);
37302
- }
37303
- return memo.concat(sym);
37304
- }, []);
37305
- };
37306
-
37307
37424
  cmd.scalebar = function(catalog, opts) {
37308
37425
  var frame = findFrameDataset(catalog);
37309
37426
  var obj, lyr;
@@ -37387,7 +37504,7 @@ ${svg}
37387
37504
  // so I'm using 'hanging' and 'auto', which seem to be well supported.
37388
37505
  // downside: requires a kludgy multiplier to calculate scalebar height (see above)
37389
37506
  };
37390
- var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY)[0];
37507
+ var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY);
37391
37508
  var g = {
37392
37509
  tag: 'g',
37393
37510
  children: [barObj, labelObj],
@@ -38490,7 +38607,12 @@ ${svg}
38490
38607
  interval = getHighPrecisionSnapInterval(arcBounds.toArray());
38491
38608
  }
38492
38609
  arcs.flatten(); // bake in any simplification
38493
- if (interval > 0) {
38610
+ if (interval > 0 && opts.endpoints) {
38611
+ // snaps line endpoints together
38612
+ // TODO: also snap endpoints to line segments to remove undershoots and overshoots
38613
+ snapCount = snapEndpointsByInterval(arcs, interval);
38614
+ message(utils.format("Snapped %s endpoint%s", snapCount, utils.pluralSuffix(snapCount)));
38615
+ } else if (interval > 0) {
38494
38616
  snapCount = snapCoordsByInterval(arcs, interval);
38495
38617
  message(utils.format("Snapped %s point%s", snapCount, utils.pluralSuffix(snapCount)));
38496
38618
  }
@@ -38633,6 +38755,19 @@ ${svg}
38633
38755
 
38634
38756
  var roundCoord = getRoundingFunction(0.01);
38635
38757
 
38758
+ function getSymbolFillColor(d) {
38759
+ return d.fill || 'magenta';
38760
+ }
38761
+
38762
+ function getSymbolStrokeColor(d) {
38763
+ return d.stroke || d.fill || 'magenta';
38764
+ }
38765
+
38766
+ function getSymbolRadius(d) {
38767
+ if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
38768
+ return d.radius || d.length || d.r || 5; // use a default value
38769
+ }
38770
+
38636
38771
  function forEachSymbolCoord(coords, cb) {
38637
38772
  var isPoint = coords && utils.isNumber(coords[0]);
38638
38773
  var isNested = !isPoint && coords && Array.isArray(coords[0]);
@@ -38701,57 +38836,104 @@ ${svg}
38701
38836
  points.push(p2);
38702
38837
  }
38703
38838
 
38704
- // export function getStickArrowCoords(d, totalLen) {
38705
- // var minStemRatio = getMinStemRatio(d);
38706
- // var headAngle = d['arrow-head-angle'] || 90;
38707
- // var curve = d['arrow-stem-curve'] || 0;
38708
- // var unscaledHeadWidth = d['arrow-head-width'] || 9;
38709
- // var unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle);
38710
- // var scale = getScale(totalLen, unscaledHeadLen, minStemRatio);
38711
- // var headWidth = unscaledHeadWidth * scale;
38712
- // var headLen = unscaledHeadLen * scale;
38713
- // var tip = getStickArrowTip(totalLen, curve);
38714
- // var stem = [[0, 0], tip.concat()];
38715
- // if (curve) {
38716
- // addBezierArcControlPoints(stem, curve);
38717
- // }
38718
- // if (!headLen) return [stem];
38719
- // var head = [addPoints([-headWidth / 2, -headLen], tip), tip.concat(), addPoints([headWidth / 2, -headLen], tip)];
38839
+ function getStickArrowCoords(d) {
38840
+ return getArrowCoords(d, 'stick');
38841
+ }
38720
38842
 
38721
- // rotateCoords(stem, d.rotation);
38722
- // rotateCoords(head, d.rotation);
38723
- // return [stem, head];
38724
- // }
38843
+ function getFilledArrowCoords(d) {
38844
+ return getArrowCoords(d, 'standard');
38845
+ }
38725
38846
 
38726
- // function getStickArrowTip(totalLen, curve) {
38727
- // // curve/2 intersects the arrowhead at 90deg (trigonometry)
38728
- // var theta = Math.abs(curve/2) / 180 * Math.PI;
38729
- // var dx = totalLen * Math.sin(theta) * (curve > 0 ? -1 : 1);
38730
- // var dy = totalLen * Math.cos(theta);
38731
- // return [dx, dy];
38732
- // }
38847
+ function getArrowCoords(d, style) {
38848
+ var stickArrow = style == 'stick',
38849
+ // direction = d.rotation || d.direction || 0,
38850
+ direction = d.direction || 0, // rotation is an independent parameter
38851
+ stemTaper = d['stem-taper'] || 0,
38852
+ curvature = d['stem-curve'] || 0,
38853
+ size = calcArrowSize(d, stickArrow);
38854
+ if (!size) return null;
38855
+ var stemLen = size.stemLen,
38856
+ headLen = size.headLen,
38857
+ headDx = size.headWidth / 2,
38858
+ stemDx = size.stemWidth / 2,
38859
+ baseDx = stemDx * (1 - stemTaper),
38860
+ head, stem, coords, dx, dy;
38733
38861
 
38734
- // function addPoints(a, b) {
38735
- // return [a[0] + b[0], a[1] + b[1]];
38736
- // }
38862
+ if (curvature) {
38863
+ // make curved stem
38864
+ if (direction > 0) curvature = -curvature;
38865
+ var theta = Math.abs(curvature) / 180 * Math.PI;
38866
+ var sign = curvature > 0 ? 1 : -1;
38867
+ var ax = baseDx * Math.cos(theta); // rotate arrow base
38868
+ var ay = baseDx * Math.sin(theta) * -sign;
38869
+ dx = stemLen * Math.sin(theta / 2) * sign;
38870
+ dy = stemLen * Math.cos(theta / 2);
38871
+
38872
+ if (stickArrow) {
38873
+ stem = getCurvedStemCoords(-ax, -ay, dx, dy, theta);
38874
+ } else {
38875
+ var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
38876
+ var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
38877
+ stem = leftStem.concat(rightStem.reverse());
38878
+ }
38879
+
38880
+ } else {
38881
+ // make straight stem
38882
+ dx = 0;
38883
+ dy = stemLen;
38884
+ if (stickArrow) {
38885
+ stem = [[0, 0], [0, stemLen]];
38886
+ } else {
38887
+ stem = [[-baseDx, 0], [baseDx, 0]];
38888
+ }
38889
+ }
38890
+
38891
+ if (stickArrow) {
38892
+ // make stick arrow
38893
+ head = [[-headDx + dx, stemLen - headLen], [dx, stemLen], [headDx + dx, stemLen - headLen]];
38894
+ coords = [stem, head]; // MultiLineString coords
38895
+ } else {
38896
+ // make filled arrow
38897
+ // coordinates go counter clockwise, starting from the leftmost head coordinate
38898
+ head = [[stemDx + dx, dy], [headDx + dx, dy],
38899
+ [dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
38900
+ coords = stem.concat(head);
38901
+ coords.push(stem[0].concat()); // closed path
38902
+ coords = [coords]; // Polygon coords
38903
+ }
38904
+
38905
+ if (d.anchor == 'end') {
38906
+ scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
38907
+ } else if (d.anchor == 'middle') {
38908
+ // shift midpoint away from the head a bit for a more balanced placement
38909
+ // scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
38910
+ scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
38911
+ }
38912
+
38913
+ rotateCoords(coords, direction);
38914
+ if (d.flipped) {
38915
+ flipY(coords);
38916
+ }
38917
+ return coords;
38918
+ }
38737
38919
 
38738
38920
  function calcStraightArrowCoords(stemLen, headLen, stemDx, headDx, baseDx) {
38739
38921
  return [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
38740
38922
  [-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
38741
38923
  }
38742
38924
 
38743
- function calcArrowSize(d) {
38925
+ function calcArrowSize(d, stickArrow) {
38744
38926
  // don't display arrows with negative length
38745
38927
  var totalLen = Math.max(d.radius || d.length || d.r || 0, 0),
38746
38928
  scale = 1,
38747
38929
  o = initArrowSize(d); // calc several parameters
38748
38930
 
38749
- if (totalLen > 0) {
38931
+ if (totalLen >= 0) {
38750
38932
  scale = calcScale(totalLen, o.headLen, d);
38751
38933
  o.stemWidth *= scale;
38752
38934
  o.headWidth *= scale;
38753
38935
  o.headLen *= scale;
38754
- o.stemLen = totalLen - o.headLen;
38936
+ o.stemLen = stickArrow ? totalLen : totalLen - o.headLen;
38755
38937
  }
38756
38938
 
38757
38939
  if (o.headWidth < o.stemWidth) {
@@ -38772,7 +38954,7 @@ ${svg}
38772
38954
  } else if (stemLen + headLen > totalLen) {
38773
38955
  scale = totalLen / (stemLen + headLen);
38774
38956
  }
38775
- return scale;
38957
+ return scale || 0;
38776
38958
  }
38777
38959
 
38778
38960
  function initArrowSize(d) {
@@ -38802,56 +38984,6 @@ ${svg}
38802
38984
  return 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2;
38803
38985
  }
38804
38986
 
38805
- function getFilledArrowCoords(d) {
38806
- var direction = d.rotation || d.direction || 0,
38807
- stemTaper = d['stem-taper'] || 0,
38808
- curvature = d['stem-curve'] || 0,
38809
- size = calcArrowSize(d);
38810
- if (!size) return null;
38811
- var stemLen = size.stemLen,
38812
- headLen = size.headLen,
38813
- headDx = size.headWidth / 2,
38814
- stemDx = size.stemWidth / 2,
38815
- baseDx = stemDx * (1 - stemTaper),
38816
- head, stem, coords, dx, dy;
38817
-
38818
- if (curvature) {
38819
- if (direction > 0) curvature = -curvature;
38820
- var theta = Math.abs(curvature) / 180 * Math.PI;
38821
- var sign = curvature > 0 ? 1 : -1;
38822
- var ax = baseDx * Math.cos(theta); // rotate arrow base
38823
- var ay = baseDx * Math.sin(theta) * -sign;
38824
- dx = stemLen * Math.sin(theta / 2) * sign;
38825
- dy = stemLen * Math.cos(theta / 2);
38826
- var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
38827
- var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
38828
- stem = leftStem.concat(rightStem.reverse());
38829
-
38830
- } else {
38831
- dx = 0;
38832
- dy = stemLen;
38833
- stem = [[-baseDx, 0], [baseDx, 0], [baseDx, 0]];
38834
- }
38835
-
38836
- // coordinates go counter clockwise, starting from the leftmost head coordinate
38837
- head = [[stemDx + dx, dy], [headDx + dx, dy],
38838
- [dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
38839
-
38840
- coords = stem.concat(head);
38841
- if (d.anchor == 'end') {
38842
- scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
38843
- } else if (d.anchor == 'middle') {
38844
- // shift midpoint away from the head a bit for a more balanced placement
38845
- // scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
38846
- scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
38847
- }
38848
-
38849
- rotateCoords(coords, direction);
38850
- if (d.flipped) {
38851
- flipY(coords);
38852
- }
38853
- return [coords];
38854
- }
38855
38987
 
38856
38988
  // ax, ay: point on the base
38857
38989
  // bx, by: point on the stem
@@ -38887,8 +39019,19 @@ ${svg}
38887
39019
  return coords;
38888
39020
  }
38889
39021
 
39022
+ function makeCircleSymbol(d, opts) {
39023
+ var radius = getSymbolRadius(d);
39024
+ // TODO: remove duplication with svg-symbols.js
39025
+ if (+opts.scale) radius *= +opts.scale;
39026
+ return {
39027
+ type: 'circle',
39028
+ fill: getSymbolFillColor(d),
39029
+ r: radius
39030
+ };
39031
+ }
39032
+
38890
39033
  function getPolygonCoords(d) {
38891
- var radius = d.radius || d.length || d.r,
39034
+ var radius = getSymbolRadius(d),
38892
39035
  sides = +d.sides || getSidesByType(d.type),
38893
39036
  rotated = sides % 2 == 1,
38894
39037
  coords = [],
@@ -38925,7 +39068,7 @@ ${svg}
38925
39068
  }
38926
39069
 
38927
39070
  function getStarCoords(d) {
38928
- var radius = d.radius || d.length || d.r,
39071
+ var radius = getSymbolRadius(d),
38929
39072
  points = d.points || d.sides && d.sides / 2 || 5,
38930
39073
  sides = points * 2,
38931
39074
  minorRadius = getMinorRadius(points) * radius,
@@ -38970,6 +39113,35 @@ ${svg}
38970
39113
  return 180 - centerAngle;
38971
39114
  }
38972
39115
 
39116
+ // Returns a svg-symbol object
39117
+ function makeRingSymbol(d, opts) {
39118
+ var scale = +opts.scale || 1;
39119
+ var radii = parseRings(d.radii || '2').map(function(r) { return r * scale; });
39120
+ var solidCenter = utils.isOdd(radii.length);
39121
+ var color = getSymbolFillColor(d);
39122
+ var parts = [];
39123
+ if (solidCenter) {
39124
+ parts.push({
39125
+ type: 'circle',
39126
+ fill: color,
39127
+ r: radii.shift()
39128
+ });
39129
+ }
39130
+ for (var i=0; i<radii.length; i+= 2) {
39131
+ parts.push({
39132
+ type: 'circle',
39133
+ fill: 'none', // TODO remove default black fill so this is not needed
39134
+ stroke: color,
39135
+ 'stroke-width': roundToTenths(radii[i+1] - radii[i]),
39136
+ r: roundToTenths(radii[i+1] * 0.5 + radii[i] * 0.5)
39137
+ });
39138
+ }
39139
+ return {
39140
+ type: 'group',
39141
+ parts: parts
39142
+ };
39143
+ }
39144
+
38973
39145
  // Returns GeoJSON MultiPolygon coords
38974
39146
  function getRingCoords(d) {
38975
39147
  var radii = parseRings(d.radii || '2');
@@ -39001,13 +39173,36 @@ ${svg}
39001
39173
  return utils.uniq(arr);
39002
39174
  }
39003
39175
 
39176
+ // Returns an svg-symbol data object for one symbol
39177
+ function makePathSymbol(coords, properties, geojsonType) {
39178
+ var sym;
39179
+ if (geojsonType == 'MultiPolygon' || geojsonType == 'Polygon') {
39180
+ sym = {
39181
+ type: 'polygon',
39182
+ fill: getSymbolFillColor(properties),
39183
+ coordinates: geojsonType == 'Polygon' ? coords : flattenMultiPolygonCoords(coords)
39184
+ };
39185
+ } else if (geojsonType == 'LineString' || geojsonType == 'MultiLineString') {
39186
+ sym = {
39187
+ type: 'polyline',
39188
+ stroke: getSymbolStrokeColor(properties),
39189
+ 'stroke-width': properties['stroke-width'] || 2,
39190
+ coordinates: geojsonType == 'LineString' ? [coords] : coords
39191
+ };
39192
+ } else {
39193
+ error('Unsupported type:', geojsonType);
39194
+ }
39195
+ roundCoordsForSVG(sym.coordinates);
39196
+ return sym;
39197
+ }
39198
+
39004
39199
  // TODO: refactor to remove duplication in mapshaper-svg-style.js
39005
39200
  cmd.symbols = function(inputLyr, dataset, opts) {
39006
39201
  requireSinglePointLayer(inputLyr);
39007
39202
  var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
39008
- var polygonMode = !!opts.polygons;
39203
+ var shapeMode = !!opts.geographic;
39009
39204
  var metersPerPx;
39010
- if (polygonMode) {
39205
+ if (shapeMode) {
39011
39206
  requireProjectedDataset(dataset);
39012
39207
  metersPerPx = opts.pixel_scale || getMetersPerPixel(lyr, dataset);
39013
39208
  }
@@ -39017,9 +39212,24 @@ ${svg}
39017
39212
  if (!shp) return null;
39018
39213
  var d = getSymbolData(i);
39019
39214
  var rec = records[i] || {};
39215
+
39216
+ // non-polygon symbols
39217
+ if (!shapeMode && d.type == 'circle') {
39218
+ rec['svg-symbol'] = makeCircleSymbol(d, opts);
39219
+ return;
39220
+ }
39221
+ if (!shapeMode && d.type == 'ring') {
39222
+ rec['svg-symbol'] = makeRingSymbol(d, opts);
39223
+ return;
39224
+ }
39225
+
39020
39226
  var geojsonType = 'Polygon';
39021
39227
  var coords;
39022
- if (d.type == 'arrow') {
39228
+ // these symbols get converted to polygon shapes
39229
+ if (d.type == 'arrow' && opts.arrow_style == 'stick') {
39230
+ coords = getStickArrowCoords(d);
39231
+ geojsonType = 'MultiLineString';
39232
+ } else if (d.type == 'arrow') {
39023
39233
  coords = getFilledArrowCoords(d);
39024
39234
  } else if (d.type == 'ring') {
39025
39235
  coords = getRingCoords(d);
@@ -39031,23 +39241,24 @@ ${svg}
39031
39241
  }
39032
39242
  if (!coords) return null;
39033
39243
  rotateCoords(coords, +d.rotation || 0);
39034
- if (!polygonMode) {
39244
+ if (!shapeMode) {
39035
39245
  flipY(coords);
39036
39246
  }
39037
39247
  if (+opts.scale) {
39038
39248
  scaleAndShiftCoords(coords, +opts.scale, [0, 0]);
39039
39249
  }
39040
- if (polygonMode) {
39250
+ if (shapeMode) {
39041
39251
  scaleAndShiftCoords(coords, metersPerPx, shp[0]);
39042
- if (d.tfill) rec.fill = d.fill;
39252
+ if (d.fill) rec.fill = d.fill;
39253
+ if (d.stroke) rec.stroke = d.stroke;
39043
39254
  return createGeometry(coords, geojsonType);
39044
39255
  } else {
39045
- rec['svg-symbol'] = makeSvgPolygonSymbol(coords, d, geojsonType);
39256
+ rec['svg-symbol'] = makePathSymbol(coords, d, geojsonType);
39046
39257
  }
39047
39258
  });
39048
39259
 
39049
39260
  var outputLyr, dataset2;
39050
- if (polygonMode) {
39261
+ if (shapeMode) {
39051
39262
  dataset2 = importGeometries(geometries, records);
39052
39263
  outputLyr = mergeOutputLayerIntoDataset(inputLyr, dataset, dataset2, opts);
39053
39264
  outputLyr.data = lyr.data;
@@ -39081,31 +39292,10 @@ ${svg}
39081
39292
  }
39082
39293
 
39083
39294
  function getMetersPerPixel(lyr, dataset) {
39084
-
39085
- // TODO: handle single point, no extent
39086
39295
  var bounds = getLayerBounds(lyr);
39087
- return bounds.width() / 800;
39088
- }
39089
-
39090
- // Returns an svg-symbol data object for one symbol
39091
- function makeSvgPolygonSymbol(coords, properties, geojsonType) {
39092
- if (geojsonType == 'MultiPolygon') {
39093
- coords = convertMultiPolygonCoords(coords);
39094
- } else if (geojsonType != 'Polygon') {
39095
- error('Unsupported type:', geojsonType);
39096
- }
39097
- roundCoordsForSVG(coords);
39098
- return {
39099
- type: 'polygon',
39100
- coordinates: coords,
39101
- fill: properties.fill || 'magenta'
39102
- };
39103
- }
39104
-
39105
- function convertMultiPolygonCoords(coords) {
39106
- return coords.reduce(function(memo, poly) {
39107
- return memo.concat(poly);
39108
- }, []);
39296
+ // TODO: need a better way to handle a single point with no extent
39297
+ var extent = bounds.width() || bounds.height() || 1000;
39298
+ return extent / 800;
39109
39299
  }
39110
39300
 
39111
39301
  var Symbols = /*#__PURE__*/Object.freeze({
@@ -39510,6 +39700,19 @@ ${svg}
39510
39700
  return [lyr1, lyr2];
39511
39701
  }
39512
39702
 
39703
+ function commandAcceptsMultipleTargetDatasets(name) {
39704
+ return name == 'rotate' || name == 'info' || name == 'proj' ||
39705
+ name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
39706
+ name == 'else' || name == 'endif';
39707
+ }
39708
+
39709
+ function commandAcceptsEmptyTarget(name) {
39710
+ return name == 'graticule' || name == 'i' || name == 'help' ||
39711
+ name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
39712
+ name == 'include' || name == 'print' || name == 'if' || name == 'elif' ||
39713
+ name == 'else' || name == 'endif';
39714
+ }
39715
+
39513
39716
  function runCommand(command, job, cb) {
39514
39717
  var name = command.name,
39515
39718
  opts = command.options,
@@ -39531,7 +39734,6 @@ ${svg}
39531
39734
  job.startCommand(command);
39532
39735
 
39533
39736
  try { // catch errors from synchronous functions
39534
-
39535
39737
  T$1.start();
39536
39738
 
39537
39739
  if (name == 'rename-layers') {
@@ -39546,13 +39748,11 @@ ${svg}
39546
39748
  // TODO: check that combine_layers is only used w/ GeoJSON output
39547
39749
  targets = job.catalog.findCommandTargets(opts.target || opts.combine_layers && '*');
39548
39750
 
39549
- } else if (name == 'rotate' || name == 'info' || name == 'proj' || name == 'drop' || name == 'target') {
39550
- // these commands accept multiple target datasets
39751
+ } else if (commandAcceptsMultipleTargetDatasets(name)) {
39551
39752
  targets = job.catalog.findCommandTargets(opts.target);
39552
39753
 
39553
39754
  } else {
39554
39755
  targets = job.catalog.findCommandTargets(opts.target);
39555
-
39556
39756
  // special case to allow -merge-layers and -union to combine layers from multiple datasets
39557
39757
  // TODO: support multi-dataset targets for other commands
39558
39758
  if (targets.length > 1 && (name == 'merge-layers' || name == 'union')) {
@@ -39576,9 +39776,7 @@ ${svg}
39576
39776
  stop(utils.format('Missing target: %s\nAvailable layers: %s',
39577
39777
  opts.target, getFormattedLayerList(job.catalog)));
39578
39778
  }
39579
- if (!(name == 'graticule' || name == 'i' || name == 'help' ||
39580
- name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
39581
- name == 'include' || name == 'print')) {
39779
+ if (!commandAcceptsEmptyTarget(name)) {
39582
39780
  throw new UserError("No data is available");
39583
39781
  }
39584
39782
  }
@@ -40606,7 +40804,8 @@ ${svg}
40606
40804
  // export only functions called by the GUI.
40607
40805
 
40608
40806
  var internal = {};
40609
- internal.svg = Object.assign({}, SvgCommon, SvgStringify, SvgPathUtils, GeojsonToSvg);
40807
+
40808
+ internal.svg = Object.assign({}, SvgStringify, SvgPathUtils, GeojsonToSvg, SvgLabels, SvgSymbols);
40610
40809
 
40611
40810
  // Assign functions and objects exported from modules to the 'internal' namespace
40612
40811
  // to maintain compatibility with tests and to expose (some of) them to the GUI.