mapshaper 0.5.112 → 0.5.115
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/CHANGELOG.md +10 -0
- package/mapshaper.js +810 -689
- package/package.json +1 -1
- package/www/mapshaper-gui.js +14 -7
- package/www/mapshaper.js +810 -689
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.5.
|
|
3
|
+
var VERSION = "0.5.115";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -13607,6 +13607,224 @@
|
|
|
13607
13607
|
importFurniture: importFurniture
|
|
13608
13608
|
});
|
|
13609
13609
|
|
|
13610
|
+
/*
|
|
13611
|
+
{
|
|
13612
|
+
width: size[0],
|
|
13613
|
+
height: size[1],
|
|
13614
|
+
bbox: bounds.toArray(),
|
|
13615
|
+
type: 'frame'
|
|
13616
|
+
}
|
|
13617
|
+
*/
|
|
13618
|
+
function getFrameData(dataset, exportOpts) {
|
|
13619
|
+
var frameLyr = findFrameLayerInDataset(dataset);
|
|
13620
|
+
var frameData;
|
|
13621
|
+
if (frameLyr) {
|
|
13622
|
+
frameData = Object.assign({}, getFrameLayerData(frameLyr));
|
|
13623
|
+
} else {
|
|
13624
|
+
frameData = calcFrameData(dataset, exportOpts);
|
|
13625
|
+
}
|
|
13626
|
+
frameData.crs = getDatasetCRS(dataset);
|
|
13627
|
+
return frameData;
|
|
13628
|
+
}
|
|
13629
|
+
|
|
13630
|
+
function calcFrameData(dataset, opts) {
|
|
13631
|
+
var bounds = getDatasetBounds(dataset);
|
|
13632
|
+
var bounds2 = calcOutputSizeInPixels(bounds, opts);
|
|
13633
|
+
return {
|
|
13634
|
+
bbox: bounds.toArray(),
|
|
13635
|
+
width: Math.round(bounds2.width()),
|
|
13636
|
+
height: Math.round(bounds2.height()) || 1,
|
|
13637
|
+
type: 'frame'
|
|
13638
|
+
};
|
|
13639
|
+
}
|
|
13640
|
+
|
|
13641
|
+
function getFrameLayerData(lyr) {
|
|
13642
|
+
return lyr.data && lyr.data.getReadOnlyRecordAt(0);
|
|
13643
|
+
}
|
|
13644
|
+
|
|
13645
|
+
// Used by mapshaper-frame and mapshaper-pixel-transform. TODO: refactor
|
|
13646
|
+
function getFrameSize(bounds, opts) {
|
|
13647
|
+
var aspectRatio = bounds.width() / bounds.height();
|
|
13648
|
+
var height, width;
|
|
13649
|
+
if (opts.pixels) {
|
|
13650
|
+
width = Math.sqrt(+opts.pixels * aspectRatio);
|
|
13651
|
+
} else {
|
|
13652
|
+
width = +opts.width;
|
|
13653
|
+
}
|
|
13654
|
+
height = width / aspectRatio;
|
|
13655
|
+
return [Math.round(width), Math.round(height)];
|
|
13656
|
+
}
|
|
13657
|
+
|
|
13658
|
+
|
|
13659
|
+
// @lyr dataset layer
|
|
13660
|
+
function isFrameLayer(lyr) {
|
|
13661
|
+
return getFurnitureLayerType(lyr) == 'frame';
|
|
13662
|
+
}
|
|
13663
|
+
|
|
13664
|
+
function findFrameLayerInDataset(dataset) {
|
|
13665
|
+
return utils.find(dataset.layers, function(lyr) {
|
|
13666
|
+
return isFrameLayer(lyr);
|
|
13667
|
+
});
|
|
13668
|
+
}
|
|
13669
|
+
|
|
13670
|
+
function findFrameDataset(catalog) {
|
|
13671
|
+
var target = utils.find(catalog.getLayers(), function(o) {
|
|
13672
|
+
return isFrameLayer(o.layer);
|
|
13673
|
+
});
|
|
13674
|
+
return target ? target.dataset : null;
|
|
13675
|
+
}
|
|
13676
|
+
|
|
13677
|
+
function findFrameLayer(catalog) {
|
|
13678
|
+
var target = utils.find(catalog.getLayers(), function(o) {
|
|
13679
|
+
return isFrameLayer(o.layer);
|
|
13680
|
+
});
|
|
13681
|
+
return target && target.layer || null;
|
|
13682
|
+
}
|
|
13683
|
+
|
|
13684
|
+
function getFrameLayerBounds(lyr) {
|
|
13685
|
+
return new Bounds(getFrameLayerData(lyr).bbox);
|
|
13686
|
+
}
|
|
13687
|
+
|
|
13688
|
+
// @data frame data, including crs property if available
|
|
13689
|
+
// Returns a single value: the ratio or
|
|
13690
|
+
function getMapFrameMetersPerPixel(data) {
|
|
13691
|
+
var bounds = new Bounds(data.bbox);
|
|
13692
|
+
var k, toMeters, metersPerPixel;
|
|
13693
|
+
if (data.crs) {
|
|
13694
|
+
// TODO: handle CRS without inverse projections
|
|
13695
|
+
// scale factor is the ratio of coordinate distance to true distance at a point
|
|
13696
|
+
k = getScaleFactorAtXY(bounds.centerX(), bounds.centerY(), data.crs);
|
|
13697
|
+
toMeters = data.crs.to_meter;
|
|
13698
|
+
} else {
|
|
13699
|
+
// Assuming coordinates are meters and k is 1 (not safe)
|
|
13700
|
+
// A warning should be displayed when relevant furniture element is created
|
|
13701
|
+
k = 1;
|
|
13702
|
+
toMeters = 1;
|
|
13703
|
+
}
|
|
13704
|
+
metersPerPixel = bounds.width() / k * toMeters / data.width;
|
|
13705
|
+
return metersPerPixel;
|
|
13706
|
+
}
|
|
13707
|
+
|
|
13708
|
+
|
|
13709
|
+
// bounds: Bounds object containing bounds of content in geographic coordinates
|
|
13710
|
+
// returns Bounds object containing bounds of pixel output
|
|
13711
|
+
// side effect: bounds param is modified to match the output frame
|
|
13712
|
+
function calcOutputSizeInPixels(bounds, opts) {
|
|
13713
|
+
var padX = 0,
|
|
13714
|
+
padY = 0,
|
|
13715
|
+
offX = 0,
|
|
13716
|
+
offY = 0,
|
|
13717
|
+
width = bounds.width(),
|
|
13718
|
+
height = bounds.height(),
|
|
13719
|
+
margins = parseMarginOption(opts.margin),
|
|
13720
|
+
marginX = margins[0] + margins[2],
|
|
13721
|
+
marginY = margins[1] + margins[3],
|
|
13722
|
+
// TODO: add option to tweak alignment of content when both width and height are given
|
|
13723
|
+
wx = 0.5, // how padding is distributed horizontally (0: left aligned, 0.5: centered, 1: right aligned)
|
|
13724
|
+
wy = 0.5, // vertical padding distribution
|
|
13725
|
+
widthPx, heightPx, size, kx, ky;
|
|
13726
|
+
|
|
13727
|
+
if (opts.fit_bbox) {
|
|
13728
|
+
// scale + shift content to fit within a bbox
|
|
13729
|
+
offX = opts.fit_bbox[0];
|
|
13730
|
+
offY = opts.fit_bbox[1];
|
|
13731
|
+
widthPx = opts.fit_bbox[2] - offX;
|
|
13732
|
+
heightPx = opts.fit_bbox[3] - offY;
|
|
13733
|
+
if (width / height > widthPx / heightPx) {
|
|
13734
|
+
// data is wider than fit box...
|
|
13735
|
+
// scale the data to fit widthwise
|
|
13736
|
+
heightPx = 0;
|
|
13737
|
+
} else {
|
|
13738
|
+
widthPx = 0; // fit the data to the height
|
|
13739
|
+
}
|
|
13740
|
+
marginX = marginY = 0; // TODO: support margins
|
|
13741
|
+
|
|
13742
|
+
} else if (opts.svg_scale > 0) {
|
|
13743
|
+
// alternative to using a fixed width (e.g. when generating multiple files
|
|
13744
|
+
// at a consistent geographic scale)
|
|
13745
|
+
widthPx = width / opts.svg_scale + marginX;
|
|
13746
|
+
heightPx = 0;
|
|
13747
|
+
} else if (+opts.pixels) {
|
|
13748
|
+
size = getFrameSize(bounds, opts);
|
|
13749
|
+
widthPx = size[0];
|
|
13750
|
+
heightPx = size[1];
|
|
13751
|
+
} else {
|
|
13752
|
+
heightPx = opts.height || 0;
|
|
13753
|
+
widthPx = opts.width || (heightPx > 0 ? 0 : 800); // 800 is default width
|
|
13754
|
+
}
|
|
13755
|
+
|
|
13756
|
+
if (heightPx > 0) {
|
|
13757
|
+
// vertical meters per pixel to fit height param
|
|
13758
|
+
ky = (height || width || 1) / (heightPx - marginY);
|
|
13759
|
+
}
|
|
13760
|
+
if (widthPx > 0) {
|
|
13761
|
+
// horizontal meters per pixel to fit width param
|
|
13762
|
+
kx = (width || height || 1) / (widthPx - marginX);
|
|
13763
|
+
}
|
|
13764
|
+
|
|
13765
|
+
if (!widthPx) { // heightPx and ky are defined, set width to match
|
|
13766
|
+
kx = ky;
|
|
13767
|
+
widthPx = width > 0 ? marginX + width / kx : heightPx; // export square graphic if content has 0 width (reconsider this?)
|
|
13768
|
+
} else if (!heightPx) { // widthPx and kx are set, set height to match
|
|
13769
|
+
ky = kx;
|
|
13770
|
+
heightPx = height > 0 ? marginY + height / ky : widthPx;
|
|
13771
|
+
// limit height if max_height is defined
|
|
13772
|
+
if (opts.max_height > 0 && heightPx > opts.max_height) {
|
|
13773
|
+
ky = kx * heightPx / opts.max_height;
|
|
13774
|
+
heightPx = opts.max_height;
|
|
13775
|
+
}
|
|
13776
|
+
}
|
|
13777
|
+
|
|
13778
|
+
if (kx > ky) { // content is wide -- need to pad vertically
|
|
13779
|
+
ky = kx;
|
|
13780
|
+
padY = ky * (heightPx - marginY) - height;
|
|
13781
|
+
} else if (ky > kx) { // content is tall -- need to pad horizontally
|
|
13782
|
+
kx = ky;
|
|
13783
|
+
padX = kx * (widthPx - marginX) - width;
|
|
13784
|
+
}
|
|
13785
|
+
|
|
13786
|
+
bounds.padBounds(
|
|
13787
|
+
margins[0] * kx + padX * wx,
|
|
13788
|
+
margins[1] * ky + padY * wy,
|
|
13789
|
+
margins[2] * kx + padX * (1 - wx),
|
|
13790
|
+
margins[3] * ky + padY * (1 - wy));
|
|
13791
|
+
|
|
13792
|
+
if (!(widthPx > 0 && heightPx > 0)) {
|
|
13793
|
+
error("Missing valid height and width parameters");
|
|
13794
|
+
}
|
|
13795
|
+
if (!(kx === ky && kx > 0)) {
|
|
13796
|
+
error("Missing valid margin parameters");
|
|
13797
|
+
}
|
|
13798
|
+
|
|
13799
|
+
return new Bounds(offX, offY, widthPx + offX, heightPx + offY);
|
|
13800
|
+
}
|
|
13801
|
+
|
|
13802
|
+
function parseMarginOption(opt) {
|
|
13803
|
+
var str = utils.isNumber(opt) ? String(opt) : opt || '';
|
|
13804
|
+
var margins = str.trim().split(/[, ] */);
|
|
13805
|
+
if (margins.length == 1) margins.push(margins[0]);
|
|
13806
|
+
if (margins.length == 2) margins.push(margins[0], margins[1]);
|
|
13807
|
+
if (margins.length == 3) margins.push(margins[2]);
|
|
13808
|
+
return margins.map(function(str) {
|
|
13809
|
+
var px = parseFloat(str);
|
|
13810
|
+
return isNaN(px) ? 1 : px; // 1 is default
|
|
13811
|
+
});
|
|
13812
|
+
}
|
|
13813
|
+
|
|
13814
|
+
var FrameData = /*#__PURE__*/Object.freeze({
|
|
13815
|
+
__proto__: null,
|
|
13816
|
+
getFrameData: getFrameData,
|
|
13817
|
+
getFrameLayerData: getFrameLayerData,
|
|
13818
|
+
getFrameSize: getFrameSize,
|
|
13819
|
+
findFrameLayerInDataset: findFrameLayerInDataset,
|
|
13820
|
+
findFrameDataset: findFrameDataset,
|
|
13821
|
+
findFrameLayer: findFrameLayer,
|
|
13822
|
+
getFrameLayerBounds: getFrameLayerBounds,
|
|
13823
|
+
getMapFrameMetersPerPixel: getMapFrameMetersPerPixel,
|
|
13824
|
+
calcOutputSizeInPixels: calcOutputSizeInPixels,
|
|
13825
|
+
parseMarginOption: parseMarginOption
|
|
13826
|
+
});
|
|
13827
|
+
|
|
13610
13828
|
// Apply rotation, scale and/or shift to some or all of the features in a dataset
|
|
13611
13829
|
//
|
|
13612
13830
|
cmd.affine = function(targetLayers, dataset, opts) {
|
|
@@ -13744,6 +13962,14 @@
|
|
|
13744
13962
|
return p.length > 2 && p[2] == 'C';
|
|
13745
13963
|
}
|
|
13746
13964
|
|
|
13965
|
+
function stringifyPolygonCoords(coords) {
|
|
13966
|
+
var parts = [];
|
|
13967
|
+
for (var i=0; i<coords.length; i++) {
|
|
13968
|
+
parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
|
|
13969
|
+
}
|
|
13970
|
+
return parts.length > 0 ? parts.join(' ') : '';
|
|
13971
|
+
}
|
|
13972
|
+
|
|
13747
13973
|
function stringifyLineStringCoords(coords) {
|
|
13748
13974
|
if (coords.length === 0) return '';
|
|
13749
13975
|
var d = 'M';
|
|
@@ -13774,6 +14000,7 @@
|
|
|
13774
14000
|
|
|
13775
14001
|
var SvgPathUtils = /*#__PURE__*/Object.freeze({
|
|
13776
14002
|
__proto__: null,
|
|
14003
|
+
stringifyPolygonCoords: stringifyPolygonCoords,
|
|
13777
14004
|
stringifyLineStringCoords: stringifyLineStringCoords
|
|
13778
14005
|
});
|
|
13779
14006
|
|
|
@@ -14023,6 +14250,7 @@
|
|
|
14023
14250
|
// null values indicate the lack of a function for parsing/identifying this property
|
|
14024
14251
|
// (in which case a heuristic is used for distinguishing a string literal from an expression)
|
|
14025
14252
|
var stylePropertyTypes = {
|
|
14253
|
+
css: null,
|
|
14026
14254
|
class: 'classname',
|
|
14027
14255
|
dx: 'measure',
|
|
14028
14256
|
dy: 'measure',
|
|
@@ -14041,6 +14269,7 @@
|
|
|
14041
14269
|
'stroke-dasharray': 'dasharray',
|
|
14042
14270
|
'stroke-width': 'number',
|
|
14043
14271
|
'stroke-opacity': 'number',
|
|
14272
|
+
'stroke-miterlimit': 'number',
|
|
14044
14273
|
'fill-opacity': 'number',
|
|
14045
14274
|
'text-anchor': null
|
|
14046
14275
|
};
|
|
@@ -14071,11 +14300,11 @@
|
|
|
14071
14300
|
effect: null // e.g. "fade"
|
|
14072
14301
|
}, stylePropertyTypes);
|
|
14073
14302
|
|
|
14074
|
-
var commonProperties = 'class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity'.split(',');
|
|
14303
|
+
var commonProperties = 'css,class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity'.split(',');
|
|
14075
14304
|
|
|
14076
14305
|
var propertiesBySymbolType = {
|
|
14077
14306
|
polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
|
|
14078
|
-
polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin')),
|
|
14307
|
+
polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit')),
|
|
14079
14308
|
point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
|
|
14080
14309
|
label: utils.arrayToIndex(commonProperties.concat(
|
|
14081
14310
|
'fill,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
|
|
@@ -14172,7 +14401,6 @@
|
|
|
14172
14401
|
// treating the string as a literal value
|
|
14173
14402
|
literalVal = strVal;
|
|
14174
14403
|
}
|
|
14175
|
-
// console.log("literalVal:", mightBeExpression(strVal, fields), strVal, fields)
|
|
14176
14404
|
if (accessor) return accessor;
|
|
14177
14405
|
if (literalVal !== null) return function(id) {return literalVal;};
|
|
14178
14406
|
stop('Unexpected value for', svgName + ':', strVal);
|
|
@@ -14268,6 +14496,138 @@
|
|
|
14268
14496
|
isSvgColor: isSvgColor
|
|
14269
14497
|
});
|
|
14270
14498
|
|
|
14499
|
+
var geojsonImporters = {
|
|
14500
|
+
Point: importPoint,
|
|
14501
|
+
Polygon: importPolygon,
|
|
14502
|
+
LineString: importLineString,
|
|
14503
|
+
MultiPoint: importMultiPoint,
|
|
14504
|
+
MultiLineString: importMultiLineString,
|
|
14505
|
+
MultiPolygon: importMultiPolygon
|
|
14506
|
+
};
|
|
14507
|
+
|
|
14508
|
+
function importGeoJSONFeatures(features, opts) {
|
|
14509
|
+
opts = opts || {};
|
|
14510
|
+
return features.map(function(obj, i) {
|
|
14511
|
+
var geom = obj.type == 'Feature' ? obj.geometry : obj; // could be null
|
|
14512
|
+
var geomType = geom && geom.type;
|
|
14513
|
+
var msType = GeoJSON.translateGeoJSONType(geomType);
|
|
14514
|
+
var d = obj.properties || {};
|
|
14515
|
+
var svgObj = null;
|
|
14516
|
+
if (geomType && geom.coordinates) {
|
|
14517
|
+
svgObj = geojsonImporters[geomType](geom.coordinates, d);
|
|
14518
|
+
}
|
|
14519
|
+
if (!svgObj) {
|
|
14520
|
+
return {tag: 'g'}; // empty element
|
|
14521
|
+
} else if (msType == 'polyline' || msType == 'polygon') {
|
|
14522
|
+
applyStyleAttributes(svgObj, msType, d);
|
|
14523
|
+
} else if (msType == 'point' && isSimpleCircle(d)) {
|
|
14524
|
+
// kludge -- maintains bw compatibility/passes tests -- style attributes
|
|
14525
|
+
// are applied to the <g> container, 'r' property is applied to circle
|
|
14526
|
+
applyStyleAttributes(svgObj, msType, d, simpleCircleFilter);
|
|
14527
|
+
} else {
|
|
14528
|
+
// other point symbols: attributes are complicated, added downstream
|
|
14529
|
+
}
|
|
14530
|
+
if ('id' in obj) {
|
|
14531
|
+
if (!svgObj.properties) {
|
|
14532
|
+
svgObj.properties = {};
|
|
14533
|
+
}
|
|
14534
|
+
svgObj.properties.id = (opts.id_prefix || '') + obj.id;
|
|
14535
|
+
}
|
|
14536
|
+
return svgObj;
|
|
14537
|
+
});
|
|
14538
|
+
}
|
|
14539
|
+
|
|
14540
|
+
function importPoint(coords, rec) {
|
|
14541
|
+
rec = rec || {};
|
|
14542
|
+
if (isSimpleCircle(rec)) {
|
|
14543
|
+
return {
|
|
14544
|
+
tag: 'circle',
|
|
14545
|
+
properties: {
|
|
14546
|
+
cx: coords[0],
|
|
14547
|
+
cy: coords[1],
|
|
14548
|
+
r: rec.r
|
|
14549
|
+
}
|
|
14550
|
+
};
|
|
14551
|
+
}
|
|
14552
|
+
var o = renderPoint(rec, coords);
|
|
14553
|
+
if (o) o.properties.transform = getTransform(coords);
|
|
14554
|
+
return o;
|
|
14555
|
+
}
|
|
14556
|
+
|
|
14557
|
+
function simpleCircleFilter(k) {
|
|
14558
|
+
return k != 'r';
|
|
14559
|
+
}
|
|
14560
|
+
|
|
14561
|
+
// just a dot, no label or icon
|
|
14562
|
+
function isSimpleCircle(rec) {
|
|
14563
|
+
return rec && (rec.r > 0 && !rec['svg-symbol'] && !rec['label-text']);
|
|
14564
|
+
}
|
|
14565
|
+
|
|
14566
|
+
function importMultiPoint(coords, rec) {
|
|
14567
|
+
var children = [], p;
|
|
14568
|
+
for (var i=0; i<coords.length; i++) {
|
|
14569
|
+
p = importPoint(coords[i], rec);
|
|
14570
|
+
if (!p) continue;
|
|
14571
|
+
if (p.tag == 'g' && p.children) {
|
|
14572
|
+
children = children.concat(p.children);
|
|
14573
|
+
} else {
|
|
14574
|
+
children.push(p);
|
|
14575
|
+
}
|
|
14576
|
+
}
|
|
14577
|
+
return children.length > 0 ? {tag: 'g', children: children} : null;
|
|
14578
|
+
}
|
|
14579
|
+
|
|
14580
|
+
function importLineString(coords) {
|
|
14581
|
+
var d = stringifyLineStringCoords(coords);
|
|
14582
|
+
return {
|
|
14583
|
+
tag: 'path',
|
|
14584
|
+
properties: {d: d}
|
|
14585
|
+
};
|
|
14586
|
+
}
|
|
14587
|
+
|
|
14588
|
+
function importMultiLineString(coords) {
|
|
14589
|
+
var d = coords.map(stringifyLineStringCoords).join(' ');
|
|
14590
|
+
return {
|
|
14591
|
+
tag: 'path',
|
|
14592
|
+
properties: {d: d}
|
|
14593
|
+
};
|
|
14594
|
+
}
|
|
14595
|
+
|
|
14596
|
+
function importMultiPolygon(coords) {
|
|
14597
|
+
return importPolygon(flattenMultiPolygonCoords(coords));
|
|
14598
|
+
}
|
|
14599
|
+
|
|
14600
|
+
function flattenMultiPolygonCoords(coords) {
|
|
14601
|
+
return coords.reduce(function(memo, poly) {
|
|
14602
|
+
return memo.concat(poly);
|
|
14603
|
+
}, []);
|
|
14604
|
+
}
|
|
14605
|
+
|
|
14606
|
+
function importPolygon(coords) {
|
|
14607
|
+
if (coords.length === 0) return null;
|
|
14608
|
+
var o = {
|
|
14609
|
+
tag: 'path',
|
|
14610
|
+
properties: {
|
|
14611
|
+
d: stringifyPolygonCoords(coords)
|
|
14612
|
+
}
|
|
14613
|
+
};
|
|
14614
|
+
if (coords.length > 1) {
|
|
14615
|
+
o.properties['fill-rule'] = 'evenodd'; // support polygons with holes
|
|
14616
|
+
}
|
|
14617
|
+
return o;
|
|
14618
|
+
}
|
|
14619
|
+
|
|
14620
|
+
var GeojsonToSvg = /*#__PURE__*/Object.freeze({
|
|
14621
|
+
__proto__: null,
|
|
14622
|
+
importGeoJSONFeatures: importGeoJSONFeatures,
|
|
14623
|
+
importPoint: importPoint,
|
|
14624
|
+
importLineString: importLineString,
|
|
14625
|
+
importMultiLineString: importMultiLineString,
|
|
14626
|
+
importMultiPolygon: importMultiPolygon,
|
|
14627
|
+
flattenMultiPolygonCoords: flattenMultiPolygonCoords,
|
|
14628
|
+
importPolygon: importPolygon
|
|
14629
|
+
});
|
|
14630
|
+
|
|
14271
14631
|
function toLabelString(val) {
|
|
14272
14632
|
if (val || val === 0 || val === false) return String(val);
|
|
14273
14633
|
return '';
|
|
@@ -14331,7 +14691,6 @@
|
|
|
14331
14691
|
|
|
14332
14692
|
// convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
|
|
14333
14693
|
|
|
14334
|
-
|
|
14335
14694
|
function getTransform(xy, scale) {
|
|
14336
14695
|
var str = 'translate(' + roundToTenths(xy[0]) + ' ' + roundToTenths(xy[1]) + ')';
|
|
14337
14696
|
if (scale && scale != 1) {
|
|
@@ -14348,7 +14707,8 @@
|
|
|
14348
14707
|
square: square,
|
|
14349
14708
|
image: image,
|
|
14350
14709
|
group: group,
|
|
14351
|
-
label: label
|
|
14710
|
+
label: label,
|
|
14711
|
+
offset: offset
|
|
14352
14712
|
};
|
|
14353
14713
|
|
|
14354
14714
|
// render label and/or point symbol
|
|
@@ -14377,16 +14737,24 @@
|
|
|
14377
14737
|
return empty();
|
|
14378
14738
|
}
|
|
14379
14739
|
|
|
14380
|
-
function renderComplexSymbol(sym) {
|
|
14740
|
+
function renderComplexSymbol(sym, x, y) {
|
|
14381
14741
|
if (utils.isString(sym)) {
|
|
14382
14742
|
sym = JSON.parse(sym);
|
|
14383
14743
|
}
|
|
14744
|
+
if (sym.tag) {
|
|
14745
|
+
// symbol appears to already use mapshaper's svg notation... pass through
|
|
14746
|
+
return sym;
|
|
14747
|
+
}
|
|
14384
14748
|
var renderer = symbolRenderers[sym.type];
|
|
14385
14749
|
if (!renderer) {
|
|
14386
14750
|
message(sym.type ? 'Unknown symbol type: ' + sym.type : 'Symbol is missing a type property');
|
|
14387
14751
|
return empty();
|
|
14388
14752
|
}
|
|
14389
|
-
|
|
14753
|
+
var o = renderer(sym, x || 0, y || 0);
|
|
14754
|
+
if (sym.opacity) {
|
|
14755
|
+
o.properties.opacity = sym.opacity;
|
|
14756
|
+
}
|
|
14757
|
+
return o;
|
|
14390
14758
|
}
|
|
14391
14759
|
|
|
14392
14760
|
function empty() {
|
|
@@ -14406,7 +14774,13 @@
|
|
|
14406
14774
|
}
|
|
14407
14775
|
|
|
14408
14776
|
function label(d, x, y) {
|
|
14409
|
-
|
|
14777
|
+
var o = renderStyledLabel(d);
|
|
14778
|
+
if (x || y) {
|
|
14779
|
+
// set x, y here, rather than adding to dx, dy -- so dy, dy can
|
|
14780
|
+
// have CSS units (like ems)
|
|
14781
|
+
o.properties.transform = getTransform([x, y]);
|
|
14782
|
+
}
|
|
14783
|
+
return o;
|
|
14410
14784
|
}
|
|
14411
14785
|
|
|
14412
14786
|
function image(d, x, y) {
|
|
@@ -14417,8 +14791,8 @@
|
|
|
14417
14791
|
properties: {
|
|
14418
14792
|
width: w,
|
|
14419
14793
|
height: h,
|
|
14420
|
-
x: x - w / 2,
|
|
14421
|
-
y: y - h / 2,
|
|
14794
|
+
x: (x || 0) - w / 2,
|
|
14795
|
+
y: (y || 0) - h / 2,
|
|
14422
14796
|
href: d.href || ''
|
|
14423
14797
|
}
|
|
14424
14798
|
};
|
|
@@ -14448,6 +14822,7 @@
|
|
|
14448
14822
|
return o;
|
|
14449
14823
|
}
|
|
14450
14824
|
|
|
14825
|
+
// polyline coords are like GeoJSON MultiLineString coords: an array of 0 or more paths
|
|
14451
14826
|
function polyline(d, x, y) {
|
|
14452
14827
|
var coords = d.coordinates || [];
|
|
14453
14828
|
var o = importMultiLineString(coords);
|
|
@@ -14455,6 +14830,7 @@
|
|
|
14455
14830
|
return o;
|
|
14456
14831
|
}
|
|
14457
14832
|
|
|
14833
|
+
// polygon coords are an array of rings (and holes), like flattened MultiPolygon coords
|
|
14458
14834
|
function polygon(d, x, y) {
|
|
14459
14835
|
var coords = d.coordinates || [];
|
|
14460
14836
|
var o = importPolygon(coords);
|
|
@@ -14462,20 +14838,40 @@
|
|
|
14462
14838
|
return o;
|
|
14463
14839
|
}
|
|
14464
14840
|
|
|
14841
|
+
function offset(d, x, y) {
|
|
14842
|
+
var dx = (x || 0) + (d.dx || 0);
|
|
14843
|
+
var dy = (y || 0) + (d.dy || 0);
|
|
14844
|
+
return {
|
|
14845
|
+
tag: 'g',
|
|
14846
|
+
properties: {
|
|
14847
|
+
transform: getTransform([dx, dy])
|
|
14848
|
+
},
|
|
14849
|
+
children: []
|
|
14850
|
+
};
|
|
14851
|
+
}
|
|
14852
|
+
|
|
14465
14853
|
function group(d, x, y) {
|
|
14466
|
-
var
|
|
14467
|
-
|
|
14468
|
-
|
|
14854
|
+
var o = {
|
|
14855
|
+
tag: 'g',
|
|
14856
|
+
children: []
|
|
14857
|
+
};
|
|
14858
|
+
var children = o.children;
|
|
14859
|
+
(d.parts || []).forEach(function(o) {
|
|
14860
|
+
var sym = renderComplexSymbol(o, x, y);
|
|
14861
|
+
children.push(sym);
|
|
14862
|
+
//if (d.chained) {
|
|
14863
|
+
//if (o.chained) {
|
|
14864
|
+
if (o.type == 'line') {
|
|
14469
14865
|
x += (o.dx || 0);
|
|
14470
14866
|
y += (o.dy || 0);
|
|
14471
14867
|
}
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14868
|
+
if (o.type == 'offset') {
|
|
14869
|
+
children = sym.children;
|
|
14870
|
+
x = y = 0;
|
|
14871
|
+
}
|
|
14872
|
+
});
|
|
14873
|
+
if (o.children.length == 1 && !o.properties) return o.children[0];
|
|
14874
|
+
return o;
|
|
14479
14875
|
}
|
|
14480
14876
|
|
|
14481
14877
|
var SvgSymbols = /*#__PURE__*/Object.freeze({
|
|
@@ -14485,424 +14881,186 @@
|
|
|
14485
14881
|
renderPoint: renderPoint
|
|
14486
14882
|
});
|
|
14487
14883
|
|
|
14488
|
-
function
|
|
14489
|
-
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
var msType = GeoJSON.translateGeoJSONType(geomType);
|
|
14494
|
-
var d = obj.properties || {};
|
|
14495
|
-
var svgObj = null;
|
|
14496
|
-
if (geomType && geom.coordinates) {
|
|
14497
|
-
svgObj = geojsonImporters[geomType](geom.coordinates, d);
|
|
14498
|
-
}
|
|
14499
|
-
if (!svgObj) {
|
|
14500
|
-
return {tag: 'g'}; // empty element
|
|
14501
|
-
} else if (msType == 'polyline' || msType == 'polygon') {
|
|
14502
|
-
applyStyleAttributes(svgObj, msType, d);
|
|
14503
|
-
} else if (msType == 'point' && isSimpleCircle(d)) {
|
|
14504
|
-
// kludge -- maintains bw compatibility/passes tests -- style attributes
|
|
14505
|
-
// are applied to the <g> container, 'r' property is applied to circle
|
|
14506
|
-
applyStyleAttributes(svgObj, msType, d, simpleCircleFilter);
|
|
14507
|
-
} else {
|
|
14508
|
-
// other point symbols: attributes are complicated, added downstream
|
|
14509
|
-
}
|
|
14510
|
-
if ('id' in obj) {
|
|
14511
|
-
if (!svgObj.properties) {
|
|
14512
|
-
svgObj.properties = {};
|
|
14513
|
-
}
|
|
14514
|
-
svgObj.properties.id = (opts.id_prefix || '') + obj.id;
|
|
14515
|
-
}
|
|
14516
|
-
return svgObj;
|
|
14517
|
-
});
|
|
14518
|
-
}
|
|
14519
|
-
|
|
14520
|
-
function importPoint(coords, rec) {
|
|
14521
|
-
rec = rec || {};
|
|
14522
|
-
if (isSimpleCircle(rec)) {
|
|
14523
|
-
return {
|
|
14524
|
-
tag: 'circle',
|
|
14525
|
-
properties: {
|
|
14526
|
-
cx: coords[0],
|
|
14527
|
-
cy: coords[1],
|
|
14528
|
-
r: rec.r
|
|
14529
|
-
}
|
|
14530
|
-
};
|
|
14531
|
-
}
|
|
14532
|
-
var o = renderPoint(rec, coords);
|
|
14533
|
-
if (o) o.properties.transform = getTransform(coords);
|
|
14534
|
-
return o;
|
|
14535
|
-
}
|
|
14536
|
-
|
|
14537
|
-
function simpleCircleFilter(k) {
|
|
14538
|
-
return k != 'r';
|
|
14539
|
-
}
|
|
14540
|
-
|
|
14541
|
-
// just a dot, no label or icon
|
|
14542
|
-
function isSimpleCircle(rec) {
|
|
14543
|
-
return rec && (rec.r > 0 && !rec['svg-symbol'] && !rec['label-text']);
|
|
14544
|
-
}
|
|
14545
|
-
|
|
14546
|
-
function importMultiPoint(coords, rec) {
|
|
14547
|
-
var children = [], p;
|
|
14548
|
-
for (var i=0; i<coords.length; i++) {
|
|
14549
|
-
p = importPoint(coords[i], rec);
|
|
14550
|
-
if (!p) continue;
|
|
14551
|
-
if (p.tag == 'g' && p.children) {
|
|
14552
|
-
children = children.concat(p.children);
|
|
14553
|
-
} else {
|
|
14554
|
-
children.push(p);
|
|
14555
|
-
}
|
|
14556
|
-
}
|
|
14557
|
-
return children.length > 0 ? {tag: 'g', children: children} : null;
|
|
14558
|
-
}
|
|
14559
|
-
|
|
14560
|
-
function importMultiPath(coords, importer) {
|
|
14561
|
-
var o;
|
|
14562
|
-
for (var i=0; i<coords.length; i++) {
|
|
14563
|
-
if (i === 0) {
|
|
14564
|
-
o = importer(coords[i]);
|
|
14565
|
-
} else {
|
|
14566
|
-
o.properties.d += ' ' + importer(coords[i]).properties.d;
|
|
14567
|
-
}
|
|
14884
|
+
cmd.scalebar = function(catalog, opts) {
|
|
14885
|
+
var frame = findFrameDataset(catalog);
|
|
14886
|
+
var obj, lyr;
|
|
14887
|
+
if (!frame) {
|
|
14888
|
+
stop('Missing a map frame');
|
|
14568
14889
|
}
|
|
14569
|
-
|
|
14570
|
-
|
|
14890
|
+
lyr = getScalebarLayer(opts);
|
|
14891
|
+
frame.layers.push(lyr);
|
|
14892
|
+
};
|
|
14571
14893
|
|
|
14572
|
-
function
|
|
14573
|
-
var
|
|
14894
|
+
function getScalebarLayer(opts) {
|
|
14895
|
+
var obj = utils.defaults({type: 'scalebar'}, opts);
|
|
14574
14896
|
return {
|
|
14575
|
-
|
|
14576
|
-
|
|
14897
|
+
name: opts.name || 'scalebar',
|
|
14898
|
+
data: new DataTable([obj])
|
|
14577
14899
|
};
|
|
14578
14900
|
}
|
|
14579
14901
|
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14902
|
+
// TODO: generalize to other kinds of furniture as they are developed
|
|
14903
|
+
function getScalebarPosition(d) {
|
|
14904
|
+
var opts = { // defaults
|
|
14905
|
+
valign: 'top',
|
|
14906
|
+
halign: 'left',
|
|
14907
|
+
voffs: 10,
|
|
14908
|
+
hoffs: 10
|
|
14585
14909
|
};
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
function importPolygon(coords) {
|
|
14589
|
-
var d, o;
|
|
14590
|
-
for (var i=0; i<coords.length; i++) {
|
|
14591
|
-
d = o ? o.properties.d + ' ' : '';
|
|
14592
|
-
o = importLineString(coords[i]);
|
|
14593
|
-
o.properties.d = d + o.properties.d + ' Z';
|
|
14910
|
+
if (+d.left > 0) {
|
|
14911
|
+
opts.hoffs = +d.left;
|
|
14594
14912
|
}
|
|
14595
|
-
if (
|
|
14596
|
-
|
|
14913
|
+
if (+d.top > 0) {
|
|
14914
|
+
opts.voffs = +d.top;
|
|
14597
14915
|
}
|
|
14598
|
-
|
|
14916
|
+
if (+d.right > 0) {
|
|
14917
|
+
opts.hoffs = +d.right;
|
|
14918
|
+
opts.halign = 'right';
|
|
14919
|
+
}
|
|
14920
|
+
if (+d.bottom > 0) {
|
|
14921
|
+
opts.voffs = +d.bottom;
|
|
14922
|
+
opts.valign = 'bottom';
|
|
14923
|
+
}
|
|
14924
|
+
return opts;
|
|
14599
14925
|
}
|
|
14600
14926
|
|
|
14601
|
-
|
|
14602
|
-
Point: importPoint,
|
|
14603
|
-
Polygon: importPolygon,
|
|
14604
|
-
LineString: importLineString,
|
|
14605
|
-
MultiPoint: function(coords, rec) {
|
|
14606
|
-
return importMultiPoint(coords, rec);
|
|
14607
|
-
},
|
|
14608
|
-
MultiLineString: function(coords) {
|
|
14609
|
-
return importMultiPath(coords, importLineString);
|
|
14610
|
-
},
|
|
14611
|
-
MultiPolygon: function(coords) {
|
|
14612
|
-
return importMultiPath(coords, importPolygon);
|
|
14613
|
-
}
|
|
14614
|
-
};
|
|
14927
|
+
furnitureRenderers.scalebar = renderScalebar;
|
|
14615
14928
|
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
|
|
14625
|
-
|
|
14929
|
+
function renderScalebar(d, frame) {
|
|
14930
|
+
var pos = getScalebarPosition(d);
|
|
14931
|
+
var metersPerPx = getMapFrameMetersPerPixel(frame);
|
|
14932
|
+
var label = d.label_text || getAutoScalebarLabel(frame.width, metersPerPx);
|
|
14933
|
+
var scalebarKm = parseScalebarLabelToKm(label);
|
|
14934
|
+
var barHeight = 3;
|
|
14935
|
+
var labelOffs = 4;
|
|
14936
|
+
var fontSize = +d.font_size || 12;
|
|
14937
|
+
var width = Math.round(scalebarKm / metersPerPx * 1000);
|
|
14938
|
+
var height = Math.round(barHeight + labelOffs + fontSize * 0.8);
|
|
14939
|
+
var labelPos = d.label_position == 'top' ? 'top' : 'bottom';
|
|
14940
|
+
var anchorX = pos.halign == 'left' ? 0 : width;
|
|
14941
|
+
var anchorY = barHeight + labelOffs;
|
|
14942
|
+
var dx = pos.halign == 'right' ? frame.width - width - pos.hoffs : pos.hoffs;
|
|
14943
|
+
var dy = pos.valign == 'bottom' ? frame.height - height - pos.voffs : pos.voffs;
|
|
14626
14944
|
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
stop("Missing a width or area");
|
|
14631
|
-
}
|
|
14632
|
-
if (opts.width && opts.height) {
|
|
14633
|
-
opts = utils.extend({}, opts);
|
|
14634
|
-
// Height is a string containing either a number or a
|
|
14635
|
-
// comma-sep. pair of numbers (range); here we convert height to
|
|
14636
|
-
// an aspect-ratio parameter for the rectangle() function
|
|
14637
|
-
opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
|
|
14638
|
-
// TODO: currently returns max,min aspect ratio, should return in min,max order
|
|
14639
|
-
// (rectangle() function should handle max,min argument correctly now anyway)
|
|
14640
|
-
}
|
|
14641
|
-
tmp = cmd.rectangle(source, opts);
|
|
14642
|
-
bounds = getDatasetBounds(tmp);
|
|
14643
|
-
if (probablyDecimalDegreeBounds(bounds)) {
|
|
14644
|
-
stop('Frames require projected, not geographical coordinates');
|
|
14645
|
-
} else if (!getDatasetCRS(tmp)) {
|
|
14646
|
-
message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
|
|
14647
|
-
}
|
|
14648
|
-
size = getFrameSize(bounds, opts);
|
|
14649
|
-
if (size[0] > 0 === false) {
|
|
14650
|
-
stop('Missing a valid frame width');
|
|
14651
|
-
}
|
|
14652
|
-
if (size[1] > 0 === false) {
|
|
14653
|
-
stop('Missing a valid frame height');
|
|
14945
|
+
if (!frame.crs) {
|
|
14946
|
+
message('Unable to render a scalebar: unknown CRS.');
|
|
14947
|
+
return [];
|
|
14654
14948
|
}
|
|
14655
|
-
dataset = {info: {}, layers:[{
|
|
14656
|
-
name: opts.name || 'frame',
|
|
14657
|
-
data: new DataTable([{
|
|
14658
|
-
width: size[0],
|
|
14659
|
-
height: size[1],
|
|
14660
|
-
bbox: bounds.toArray(),
|
|
14661
|
-
type: 'frame'
|
|
14662
|
-
}])
|
|
14663
|
-
}]};
|
|
14664
|
-
catalog.addDataset(dataset);
|
|
14665
|
-
};
|
|
14666
14949
|
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
// comma-sep. pair of numbers (range);
|
|
14671
|
-
return heightArg.split(',').map(function(opt) {
|
|
14672
|
-
var height = Number(opt),
|
|
14673
|
-
width = Number(widthArg);
|
|
14674
|
-
if (!opt) return '';
|
|
14675
|
-
return width / height;
|
|
14676
|
-
}).reverse().join(',');
|
|
14677
|
-
}
|
|
14678
|
-
|
|
14679
|
-
function getFrameSize(bounds, opts) {
|
|
14680
|
-
var aspectRatio = bounds.width() / bounds.height();
|
|
14681
|
-
var height, width;
|
|
14682
|
-
if (opts.pixels) {
|
|
14683
|
-
width = Math.sqrt(+opts.pixels * aspectRatio);
|
|
14684
|
-
} else {
|
|
14685
|
-
width = +opts.width;
|
|
14950
|
+
if (labelPos == 'top') {
|
|
14951
|
+
anchorY = -labelOffs;
|
|
14952
|
+
dy += Math.round(labelOffs + fontSize * 0.8);
|
|
14686
14953
|
}
|
|
14687
|
-
height = width / aspectRatio;
|
|
14688
|
-
return [Math.round(width), Math.round(height)];
|
|
14689
|
-
}
|
|
14690
14954
|
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
if (frameLyr) {
|
|
14694
|
-
// TODO: check for coordinate issues (non-intersection with other layers, etc)
|
|
14695
|
-
return getFrameLayerBounds(frameLyr);
|
|
14955
|
+
if (width > 0 === false) {
|
|
14956
|
+
stop("Null scalebar length");
|
|
14696
14957
|
}
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14958
|
+
var barObj = {
|
|
14959
|
+
tag: 'rect',
|
|
14960
|
+
properties: {
|
|
14961
|
+
fill: 'black',
|
|
14962
|
+
x: 0,
|
|
14963
|
+
y: 0,
|
|
14964
|
+
width: width,
|
|
14965
|
+
height: barHeight
|
|
14966
|
+
}
|
|
14967
|
+
};
|
|
14968
|
+
var labelOpts = {
|
|
14969
|
+
'label-text': label,
|
|
14970
|
+
'font-size': fontSize,
|
|
14971
|
+
'text-anchor': pos.halign == 'left' ? 'start': 'end',
|
|
14972
|
+
'dominant-baseline': labelPos == 'top' ? 'auto' : 'hanging'
|
|
14973
|
+
//// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
|
|
14974
|
+
// 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
|
|
14975
|
+
// so I'm using 'hanging' and 'auto', which seem to be well supported.
|
|
14976
|
+
// downside: requires a kludgy multiplier to calculate scalebar height (see above)
|
|
14977
|
+
};
|
|
14978
|
+
var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY);
|
|
14979
|
+
var g = {
|
|
14980
|
+
tag: 'g',
|
|
14981
|
+
children: [barObj, labelObj],
|
|
14982
|
+
properties: {
|
|
14983
|
+
transform: 'translate(' + dx + ' ' + dy + ')'
|
|
14984
|
+
}
|
|
14985
|
+
};
|
|
14986
|
+
return [g];
|
|
14703
14987
|
}
|
|
14704
14988
|
|
|
14705
|
-
function
|
|
14706
|
-
|
|
14707
|
-
|
|
14708
|
-
|
|
14989
|
+
function getAutoScalebarLabel(mapWidth, metersPerPx) {
|
|
14990
|
+
var minWidth = 70; // 100; // TODO: vary min size based on map width
|
|
14991
|
+
var minKm = metersPerPx * minWidth / 1000;
|
|
14992
|
+
// note: removed 1.5 12 and 1,200
|
|
14993
|
+
var options = ('1/8 1/5 1/4 1/2 1 2 3 4 5 8 10 15 20 25 30 40 50 75 ' +
|
|
14994
|
+
'100 150 200 250 300 350 400 500 750 1,000 1,500 2,000 ' +
|
|
14995
|
+
'2,500 3,000 4,000 5,000').split(' ');
|
|
14996
|
+
return options.reduce(function(memo, str) {
|
|
14997
|
+
if (memo) return memo;
|
|
14998
|
+
var label = formatDistanceLabelAsMiles(str);
|
|
14999
|
+
if (parseScalebarLabelToKm(label) > minKm) {
|
|
15000
|
+
return label;
|
|
15001
|
+
}
|
|
15002
|
+
}, null) || '';
|
|
14709
15003
|
}
|
|
14710
15004
|
|
|
14711
|
-
function
|
|
14712
|
-
var
|
|
14713
|
-
|
|
14714
|
-
});
|
|
14715
|
-
return target ? target.dataset : null;
|
|
15005
|
+
function formatDistanceLabelAsMiles(str) {
|
|
15006
|
+
var num = parseScalebarNumber(str);
|
|
15007
|
+
return str + (num > 1 ? ' MILES' : ' MILE');
|
|
14716
15008
|
}
|
|
14717
15009
|
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
15010
|
+
// See test/mapshaper-scalebar.js for examples of supported formats
|
|
15011
|
+
function parseScalebarLabelToKm(str) {
|
|
15012
|
+
var units = parseScalebarUnits(str);
|
|
15013
|
+
var value = parseScalebarNumber(str);
|
|
15014
|
+
if (!units || !value) return NaN;
|
|
15015
|
+
return units == 'mile' ? value * 1.60934 : value;
|
|
14723
15016
|
}
|
|
14724
15017
|
|
|
14725
|
-
function
|
|
14726
|
-
|
|
15018
|
+
function parseScalebarUnits(str) {
|
|
15019
|
+
var isMiles = /miles?$/.test(str.toLowerCase());
|
|
15020
|
+
var isKm = /(km|kilometers?|kilometres?)$/.test(str.toLowerCase());
|
|
15021
|
+
return isMiles && 'mile' || isKm && 'km' || '';
|
|
14727
15022
|
}
|
|
14728
15023
|
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
// TODO: handle CRS without inverse projections
|
|
14737
|
-
// scale factor is the ratio of coordinate distance to true distance at a point
|
|
14738
|
-
k = getScaleFactorAtXY(bounds.centerX(), bounds.centerY(), data.crs);
|
|
14739
|
-
toMeters = data.crs.to_meter;
|
|
15024
|
+
function parseScalebarNumber(str) {
|
|
15025
|
+
var fractionRxp = /^([0-9]+) ?\/ ?([0-9]+)/;
|
|
15026
|
+
var match, value;
|
|
15027
|
+
str = str.replace(/[\s]/g, '').replace(/,/g, '');
|
|
15028
|
+
if (fractionRxp.test(str)) {
|
|
15029
|
+
match = fractionRxp.exec(str);
|
|
15030
|
+
value = +match[1] / +match[2];
|
|
14740
15031
|
} else {
|
|
14741
|
-
|
|
14742
|
-
// A warning should be displayed when relevant furniture element is created
|
|
14743
|
-
k = 1;
|
|
14744
|
-
toMeters = 1;
|
|
15032
|
+
value = parseFloat(str);
|
|
14745
15033
|
}
|
|
14746
|
-
|
|
14747
|
-
return metersPerPixel;
|
|
15034
|
+
return value > 0 && value < Infinity ? value : NaN;
|
|
14748
15035
|
}
|
|
14749
15036
|
|
|
14750
|
-
|
|
14751
|
-
var lineWidth = 1,
|
|
14752
|
-
// inset stroke by half of line width
|
|
14753
|
-
off = lineWidth / 2,
|
|
14754
|
-
obj = importPolygon([[[off, off], [off, d.height - off],
|
|
14755
|
-
[d.width - off, d.height - off],
|
|
14756
|
-
[d.width - off, off], [off, off]]]);
|
|
14757
|
-
utils.extend(obj.properties, {
|
|
14758
|
-
fill: 'none',
|
|
14759
|
-
stroke: d.stroke || 'black',
|
|
14760
|
-
'stroke-width': d['stroke-width'] || lineWidth
|
|
14761
|
-
});
|
|
14762
|
-
return [obj];
|
|
14763
|
-
};
|
|
14764
|
-
|
|
14765
|
-
var Frame = /*#__PURE__*/Object.freeze({
|
|
15037
|
+
var Scalebar = /*#__PURE__*/Object.freeze({
|
|
14766
15038
|
__proto__: null,
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
findFrameLayer: findFrameLayer,
|
|
14772
|
-
getFrameLayerBounds: getFrameLayerBounds,
|
|
14773
|
-
getMapFrameMetersPerPixel: getMapFrameMetersPerPixel
|
|
15039
|
+
getScalebarLayer: getScalebarLayer,
|
|
15040
|
+
renderScalebar: renderScalebar,
|
|
15041
|
+
formatDistanceLabelAsMiles: formatDistanceLabelAsMiles,
|
|
15042
|
+
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
14774
15043
|
});
|
|
14775
15044
|
|
|
14776
15045
|
function transformDatasetToPixels(dataset, opts) {
|
|
14777
|
-
var
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
// TODO: handle options like width, height margin when a frame is present
|
|
14781
|
-
// TODO: check that aspect ratios match
|
|
14782
|
-
frameData = getFurnitureLayerData(frameLyr);
|
|
14783
|
-
bounds = new Bounds(frameData.bbox);
|
|
14784
|
-
bounds2 = new Bounds(0, 0, frameData.width, frameData.height);
|
|
14785
|
-
} else {
|
|
14786
|
-
bounds = getDatasetBounds(dataset);
|
|
14787
|
-
bounds2 = calcOutputSizeInPixels(bounds, opts);
|
|
14788
|
-
}
|
|
14789
|
-
fwd = bounds.getTransform(bounds2, opts.invert_y);
|
|
14790
|
-
transformPoints(dataset, function(x, y) {
|
|
14791
|
-
return fwd.transform(x, y);
|
|
14792
|
-
});
|
|
14793
|
-
return [Math.round(bounds2.width()), Math.round(bounds2.height()) || 1];
|
|
15046
|
+
var frame = getFrameData(dataset, opts);
|
|
15047
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15048
|
+
return [frame.width, frame.height];
|
|
14794
15049
|
}
|
|
14795
15050
|
|
|
14796
|
-
function
|
|
14797
|
-
var
|
|
14798
|
-
var
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
return margins.map(function(str) {
|
|
14803
|
-
var px = parseFloat(str);
|
|
14804
|
-
return isNaN(px) ? 1 : px; // 1 is default
|
|
15051
|
+
function fitDatasetToFrame(dataset, frame, opts) {
|
|
15052
|
+
var bounds = new Bounds(frame.bbox);
|
|
15053
|
+
var bounds2 = new Bounds(0, 0, frame.width, frame.height);
|
|
15054
|
+
var fwd = bounds.getTransform(bounds2, opts.invert_y);
|
|
15055
|
+
transformPoints(dataset, function(x, y) {
|
|
15056
|
+
return fwd.transform(x, y);
|
|
14805
15057
|
});
|
|
14806
15058
|
}
|
|
14807
15059
|
|
|
14808
|
-
// bounds: Bounds object containing bounds of content in geographic coordinates
|
|
14809
|
-
// returns Bounds object containing bounds of pixel output
|
|
14810
|
-
// side effect: bounds param is modified to match the output frame
|
|
14811
|
-
function calcOutputSizeInPixels(bounds, opts) {
|
|
14812
|
-
var padX = 0,
|
|
14813
|
-
padY = 0,
|
|
14814
|
-
offX = 0,
|
|
14815
|
-
offY = 0,
|
|
14816
|
-
width = bounds.width(),
|
|
14817
|
-
height = bounds.height(),
|
|
14818
|
-
margins = parseMarginOption(opts.margin),
|
|
14819
|
-
marginX = margins[0] + margins[2],
|
|
14820
|
-
marginY = margins[1] + margins[3],
|
|
14821
|
-
// TODO: add option to tweak alignment of content when both width and height are given
|
|
14822
|
-
wx = 0.5, // how padding is distributed horizontally (0: left aligned, 0.5: centered, 1: right aligned)
|
|
14823
|
-
wy = 0.5, // vertical padding distribution
|
|
14824
|
-
widthPx, heightPx, size, kx, ky;
|
|
14825
|
-
|
|
14826
|
-
if (opts.fit_bbox) {
|
|
14827
|
-
// scale + shift content to fit within a bbox
|
|
14828
|
-
offX = opts.fit_bbox[0];
|
|
14829
|
-
offY = opts.fit_bbox[1];
|
|
14830
|
-
widthPx = opts.fit_bbox[2] - offX;
|
|
14831
|
-
heightPx = opts.fit_bbox[3] - offY;
|
|
14832
|
-
if (width / height > widthPx / heightPx) {
|
|
14833
|
-
// data is wider than fit box...
|
|
14834
|
-
// scale the data to fit widthwise
|
|
14835
|
-
heightPx = 0;
|
|
14836
|
-
} else {
|
|
14837
|
-
widthPx = 0; // fit the data to the height
|
|
14838
|
-
}
|
|
14839
|
-
marginX = marginY = 0; // TODO: support margins
|
|
14840
|
-
|
|
14841
|
-
} else if (opts.svg_scale > 0) {
|
|
14842
|
-
// alternative to using a fixed width (e.g. when generating multiple files
|
|
14843
|
-
// at a consistent geographic scale)
|
|
14844
|
-
widthPx = width / opts.svg_scale + marginX;
|
|
14845
|
-
heightPx = 0;
|
|
14846
|
-
} else if (+opts.pixels) {
|
|
14847
|
-
size = getFrameSize(bounds, opts);
|
|
14848
|
-
widthPx = size[0];
|
|
14849
|
-
heightPx = size[1];
|
|
14850
|
-
} else {
|
|
14851
|
-
heightPx = opts.height || 0;
|
|
14852
|
-
widthPx = opts.width || (heightPx > 0 ? 0 : 800); // 800 is default width
|
|
14853
|
-
}
|
|
14854
|
-
|
|
14855
|
-
if (heightPx > 0) {
|
|
14856
|
-
// vertical meters per pixel to fit height param
|
|
14857
|
-
ky = (height || width || 1) / (heightPx - marginY);
|
|
14858
|
-
}
|
|
14859
|
-
if (widthPx > 0) {
|
|
14860
|
-
// horizontal meters per pixel to fit width param
|
|
14861
|
-
kx = (width || height || 1) / (widthPx - marginX);
|
|
14862
|
-
}
|
|
14863
|
-
|
|
14864
|
-
if (!widthPx) { // heightPx and ky are defined, set width to match
|
|
14865
|
-
kx = ky;
|
|
14866
|
-
widthPx = width > 0 ? marginX + width / kx : heightPx; // export square graphic if content has 0 width (reconsider this?)
|
|
14867
|
-
} else if (!heightPx) { // widthPx and kx are set, set height to match
|
|
14868
|
-
ky = kx;
|
|
14869
|
-
heightPx = height > 0 ? marginY + height / ky : widthPx;
|
|
14870
|
-
// limit height if max_height is defined
|
|
14871
|
-
if (opts.max_height > 0 && heightPx > opts.max_height) {
|
|
14872
|
-
ky = kx * heightPx / opts.max_height;
|
|
14873
|
-
heightPx = opts.max_height;
|
|
14874
|
-
}
|
|
14875
|
-
}
|
|
14876
|
-
|
|
14877
|
-
if (kx > ky) { // content is wide -- need to pad vertically
|
|
14878
|
-
ky = kx;
|
|
14879
|
-
padY = ky * (heightPx - marginY) - height;
|
|
14880
|
-
} else if (ky > kx) { // content is tall -- need to pad horizontally
|
|
14881
|
-
kx = ky;
|
|
14882
|
-
padX = kx * (widthPx - marginX) - width;
|
|
14883
|
-
}
|
|
14884
|
-
|
|
14885
|
-
bounds.padBounds(
|
|
14886
|
-
margins[0] * kx + padX * wx,
|
|
14887
|
-
margins[1] * ky + padY * wy,
|
|
14888
|
-
margins[2] * kx + padX * (1 - wx),
|
|
14889
|
-
margins[3] * ky + padY * (1 - wy));
|
|
14890
|
-
|
|
14891
|
-
if (!(widthPx > 0 && heightPx > 0)) {
|
|
14892
|
-
error("Missing valid height and width parameters");
|
|
14893
|
-
}
|
|
14894
|
-
if (!(kx === ky && kx > 0)) {
|
|
14895
|
-
error("Missing valid margin parameters");
|
|
14896
|
-
}
|
|
14897
|
-
|
|
14898
|
-
return new Bounds(offX, offY, widthPx + offX, heightPx + offY);
|
|
14899
|
-
}
|
|
14900
|
-
|
|
14901
15060
|
var PixelTransform = /*#__PURE__*/Object.freeze({
|
|
14902
15061
|
__proto__: null,
|
|
14903
15062
|
transformDatasetToPixels: transformDatasetToPixels,
|
|
14904
|
-
|
|
14905
|
-
calcOutputSizeInPixels: calcOutputSizeInPixels
|
|
15063
|
+
fitDatasetToFrame: fitDatasetToFrame
|
|
14906
15064
|
});
|
|
14907
15065
|
|
|
14908
15066
|
function stringify(obj) {
|
|
@@ -14959,6 +15117,9 @@
|
|
|
14959
15117
|
if (key == 'href') {
|
|
14960
15118
|
key = 'xlink:href';
|
|
14961
15119
|
}
|
|
15120
|
+
if (key == 'css') {
|
|
15121
|
+
key = 'style'; // inline style
|
|
15122
|
+
}
|
|
14962
15123
|
return memo + ' ' + key + '="' + stringEscape(strval) + '"';
|
|
14963
15124
|
}, '');
|
|
14964
15125
|
}
|
|
@@ -15077,6 +15238,7 @@
|
|
|
15077
15238
|
return svg;
|
|
15078
15239
|
}
|
|
15079
15240
|
|
|
15241
|
+
// href: A URL or a local path
|
|
15080
15242
|
// TODO: download SVG files asynchronously
|
|
15081
15243
|
// (currently, files are downloaded synchronously, which is obviously undesirable)
|
|
15082
15244
|
//
|
|
@@ -15084,7 +15246,7 @@
|
|
|
15084
15246
|
var content;
|
|
15085
15247
|
if (href.indexOf('http') === 0) {
|
|
15086
15248
|
content = fetchFileSync(href);
|
|
15087
|
-
} else if (require('fs').existsSync(href)) {
|
|
15249
|
+
} else if (require('fs').existsSync(href)) {
|
|
15088
15250
|
content = require('fs').readFileSync(href, 'utf8');
|
|
15089
15251
|
} else {
|
|
15090
15252
|
stop("Invalid SVG location:", href);
|
|
@@ -15125,12 +15287,11 @@
|
|
|
15125
15287
|
}
|
|
15126
15288
|
*/
|
|
15127
15289
|
|
|
15128
|
-
//
|
|
15129
15290
|
//
|
|
15130
15291
|
function exportSVG(dataset, opts) {
|
|
15131
15292
|
var namespace = 'xmlns="http://www.w3.org/2000/svg"';
|
|
15132
15293
|
var defs = [];
|
|
15133
|
-
var
|
|
15294
|
+
var frame, svg, layers;
|
|
15134
15295
|
var style = '';
|
|
15135
15296
|
|
|
15136
15297
|
// kludge for map keys
|
|
@@ -15147,62 +15308,70 @@
|
|
|
15147
15308
|
} else {
|
|
15148
15309
|
dataset = copyDataset(dataset); // Modify a copy of the dataset
|
|
15149
15310
|
}
|
|
15311
|
+
|
|
15150
15312
|
// invert_y setting for screen coordinates and geojson polygon generation
|
|
15151
15313
|
utils.extend(opts, {invert_y: true});
|
|
15152
|
-
|
|
15314
|
+
frame = getFrameData(dataset, opts);
|
|
15315
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15316
|
+
setCoordinatePrecision(dataset, opts.precision || 0.0001);
|
|
15153
15317
|
|
|
15154
15318
|
// error if one or more svg_data fields are not present in any layers
|
|
15155
15319
|
if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
|
|
15156
15320
|
|
|
15157
|
-
|
|
15158
|
-
|
|
15321
|
+
layers = dataset.layers;
|
|
15322
|
+
if (opts.scalebar) {
|
|
15323
|
+
layers.push(getScalebarLayer({})); // default options
|
|
15324
|
+
}
|
|
15325
|
+
svg = layers.map(function(lyr) {
|
|
15326
|
+
var obj;
|
|
15327
|
+
if (layerHasFurniture(lyr)) {
|
|
15328
|
+
obj = exportFurnitureForSVG(lyr, frame, opts);
|
|
15329
|
+
} else {
|
|
15330
|
+
obj = exportLayerForSVG(lyr, dataset, opts);
|
|
15331
|
+
}
|
|
15159
15332
|
convertPropertiesToDefinitions(obj, defs);
|
|
15160
15333
|
return stringify(obj);
|
|
15161
15334
|
}).join('\n');
|
|
15335
|
+
|
|
15162
15336
|
if (defs.length > 0) {
|
|
15163
15337
|
svg = '<defs>\n' + utils.pluck(defs, 'svg').join('') + '</defs>\n' + svg;
|
|
15164
15338
|
}
|
|
15339
|
+
|
|
15165
15340
|
if (svg.includes('xlink:')) {
|
|
15166
15341
|
namespace += ' xmlns:xlink="http://www.w3.org/1999/xlink"';
|
|
15167
15342
|
}
|
|
15343
|
+
|
|
15344
|
+
// default line style properties
|
|
15168
15345
|
var capStyle = opts.default_linecap || 'round';
|
|
15346
|
+
var lineProps = `stroke-linecap="${capStyle}" stroke-linejoin="round"`;
|
|
15347
|
+
if (svg.includes('stroke-linejoin="miter"')) {
|
|
15348
|
+
// the default limit in Illustrator seems to be 10 -- too large for mapping
|
|
15349
|
+
// (Mapbox uses 2 as the default in their styles)
|
|
15350
|
+
lineProps += ' stroke-miterlimit="2"';
|
|
15351
|
+
}
|
|
15169
15352
|
var template = `<?xml version="1.0"?>
|
|
15170
|
-
<svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s"
|
|
15353
|
+
<svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s" ${lineProps}>${style}
|
|
15171
15354
|
${svg}
|
|
15172
15355
|
</svg>`;
|
|
15173
|
-
svg = utils.format(template,
|
|
15356
|
+
svg = utils.format(template, frame.width, frame.height, 0, 0, frame.width, frame.height);
|
|
15174
15357
|
return [{
|
|
15175
15358
|
content: svg,
|
|
15176
15359
|
filename: opts.file || getOutputFileBase(dataset) + '.svg'
|
|
15177
15360
|
}];
|
|
15178
15361
|
}
|
|
15179
15362
|
|
|
15180
|
-
function
|
|
15181
|
-
var
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
return size;
|
|
15363
|
+
function exportFurnitureForSVG(lyr, frame, opts) {
|
|
15364
|
+
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15365
|
+
layerObj.children = importFurniture(getFurnitureLayerData(lyr), frame);
|
|
15366
|
+
return layerObj;
|
|
15185
15367
|
}
|
|
15186
15368
|
|
|
15187
15369
|
function exportLayerForSVG(lyr, dataset, opts) {
|
|
15188
15370
|
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15189
|
-
|
|
15190
|
-
layerObj.children = exportFurnitureForSVG(lyr, dataset, opts);
|
|
15191
|
-
} else {
|
|
15192
|
-
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15193
|
-
}
|
|
15371
|
+
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15194
15372
|
return layerObj;
|
|
15195
15373
|
}
|
|
15196
15374
|
|
|
15197
|
-
function exportFurnitureForSVG(lyr, dataset, opts) {
|
|
15198
|
-
var frameLyr = findFrameLayerInDataset(dataset);
|
|
15199
|
-
var frameData;
|
|
15200
|
-
if (!frameLyr) return [];
|
|
15201
|
-
frameData = getFurnitureLayerData(frameLyr);
|
|
15202
|
-
frameData.crs = getDatasetCRS(dataset); // required by e.g. scalebar
|
|
15203
|
-
return importFurniture(getFurnitureLayerData(lyr), frameData);
|
|
15204
|
-
}
|
|
15205
|
-
|
|
15206
15375
|
function exportSymbolsForSVG(lyr, dataset, opts) {
|
|
15207
15376
|
// TODO: convert geojson features one at a time
|
|
15208
15377
|
var d = utils.defaults({layers: [lyr]}, dataset);
|
|
@@ -15357,6 +15526,7 @@ ${svg}
|
|
|
15357
15526
|
var Svg = /*#__PURE__*/Object.freeze({
|
|
15358
15527
|
__proto__: null,
|
|
15359
15528
|
exportSVG: exportSVG,
|
|
15529
|
+
exportFurnitureForSVG: exportFurnitureForSVG,
|
|
15360
15530
|
exportLayerForSVG: exportLayerForSVG,
|
|
15361
15531
|
validateSvgDataFields: validateSvgDataFields,
|
|
15362
15532
|
exportDataAttributesForSVG: exportDataAttributesForSVG,
|
|
@@ -19252,6 +19422,10 @@ ${svg}
|
|
|
19252
19422
|
.option('id-prefix', {
|
|
19253
19423
|
describe: '[SVG] prefix for namespacing layer and feature ids'
|
|
19254
19424
|
})
|
|
19425
|
+
.option('scalebar', {
|
|
19426
|
+
type: 'flag',
|
|
19427
|
+
// describe: '[SVG] add a scalebar showing scale at the center of the map'
|
|
19428
|
+
})
|
|
19255
19429
|
.option('delimiter', {
|
|
19256
19430
|
describe: '[CSV] field delimiter'
|
|
19257
19431
|
})
|
|
@@ -20445,14 +20619,21 @@ ${svg}
|
|
|
20445
20619
|
.option('stroke', {})
|
|
20446
20620
|
.option('stroke-width', {})
|
|
20447
20621
|
.option('fill', {
|
|
20448
|
-
describe: 'symbol fill color'
|
|
20622
|
+
describe: 'symbol fill color (filled symbols only)'
|
|
20623
|
+
})
|
|
20624
|
+
.option('stroke', {
|
|
20625
|
+
describe: 'symbol line color (linear symbols only)'
|
|
20449
20626
|
})
|
|
20450
|
-
.option('
|
|
20451
|
-
describe: '
|
|
20627
|
+
.option('stroke-width', {
|
|
20628
|
+
describe: 'symbol line width (linear symbols only)'
|
|
20629
|
+
})
|
|
20630
|
+
.option('geographic', {
|
|
20631
|
+
old_alias: 'polygons',
|
|
20632
|
+
describe: 'make geographic shapes instead of SVG objects',
|
|
20452
20633
|
type: 'flag'
|
|
20453
20634
|
})
|
|
20454
20635
|
.option('pixel-scale', {
|
|
20455
|
-
describe: 'set symbol scale in meters
|
|
20636
|
+
describe: 'set symbol scale in meters per pixel (geographic option)',
|
|
20456
20637
|
type: 'number',
|
|
20457
20638
|
})
|
|
20458
20639
|
// .option('flipped', {
|
|
@@ -20489,6 +20670,9 @@ ${svg}
|
|
|
20489
20670
|
.option('radii', {
|
|
20490
20671
|
describe: '(ring) comma-sep. list of concentric radii, ascending order'
|
|
20491
20672
|
})
|
|
20673
|
+
.option('arrow-style', {
|
|
20674
|
+
describe: '(arrow) options: stick, standard (default is standard)'
|
|
20675
|
+
})
|
|
20492
20676
|
.option('length', {
|
|
20493
20677
|
old_alias: 'arrow-length',
|
|
20494
20678
|
describe: '(arrow) length of arrow in pixels'
|
|
@@ -32492,6 +32676,80 @@ ${svg}
|
|
|
32492
32676
|
return parsed[0];
|
|
32493
32677
|
}
|
|
32494
32678
|
|
|
32679
|
+
/* require mapshaper-rectangle, mapshaper-furniture */
|
|
32680
|
+
|
|
32681
|
+
cmd.frame = function(catalog, source, opts) {
|
|
32682
|
+
var size, bounds, tmp, dataset;
|
|
32683
|
+
if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
|
|
32684
|
+
stop("Missing a width or area");
|
|
32685
|
+
}
|
|
32686
|
+
if (opts.width && opts.height) {
|
|
32687
|
+
opts = utils.extend({}, opts);
|
|
32688
|
+
// Height is a string containing either a number or a
|
|
32689
|
+
// comma-sep. pair of numbers (range); here we convert height to
|
|
32690
|
+
// an aspect-ratio parameter for the rectangle() function
|
|
32691
|
+
opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
|
|
32692
|
+
// TODO: currently returns max,min aspect ratio, should return in min,max order
|
|
32693
|
+
// (rectangle() function should handle max,min argument correctly now anyway)
|
|
32694
|
+
}
|
|
32695
|
+
tmp = cmd.rectangle(source, opts);
|
|
32696
|
+
bounds = getDatasetBounds(tmp);
|
|
32697
|
+
if (probablyDecimalDegreeBounds(bounds)) {
|
|
32698
|
+
stop('Frames require projected, not geographical coordinates');
|
|
32699
|
+
} else if (!getDatasetCRS(tmp)) {
|
|
32700
|
+
message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
|
|
32701
|
+
}
|
|
32702
|
+
size = getFrameSize(bounds, opts);
|
|
32703
|
+
if (size[0] > 0 === false) {
|
|
32704
|
+
stop('Missing a valid frame width');
|
|
32705
|
+
}
|
|
32706
|
+
if (size[1] > 0 === false) {
|
|
32707
|
+
stop('Missing a valid frame height');
|
|
32708
|
+
}
|
|
32709
|
+
dataset = {info: {}, layers:[{
|
|
32710
|
+
name: opts.name || 'frame',
|
|
32711
|
+
data: new DataTable([{
|
|
32712
|
+
width: size[0],
|
|
32713
|
+
height: size[1],
|
|
32714
|
+
bbox: bounds.toArray(),
|
|
32715
|
+
type: 'frame'
|
|
32716
|
+
}])
|
|
32717
|
+
}]};
|
|
32718
|
+
catalog.addDataset(dataset);
|
|
32719
|
+
};
|
|
32720
|
+
|
|
32721
|
+
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
32722
|
+
function getAspectRatioArg(widthArg, heightArg) {
|
|
32723
|
+
// heightArg is a string containing either a number or a
|
|
32724
|
+
// comma-sep. pair of numbers (range);
|
|
32725
|
+
return heightArg.split(',').map(function(opt) {
|
|
32726
|
+
var height = Number(opt),
|
|
32727
|
+
width = Number(widthArg);
|
|
32728
|
+
if (!opt) return '';
|
|
32729
|
+
return width / height;
|
|
32730
|
+
}).reverse().join(',');
|
|
32731
|
+
}
|
|
32732
|
+
|
|
32733
|
+
furnitureRenderers.frame = function(d) {
|
|
32734
|
+
var lineWidth = 1,
|
|
32735
|
+
// inset stroke by half of line width
|
|
32736
|
+
off = lineWidth / 2,
|
|
32737
|
+
obj = importPolygon([[[off, off], [off, d.height - off],
|
|
32738
|
+
[d.width - off, d.height - off],
|
|
32739
|
+
[d.width - off, off], [off, off]]]);
|
|
32740
|
+
utils.extend(obj.properties, {
|
|
32741
|
+
fill: 'none',
|
|
32742
|
+
stroke: d.stroke || 'black',
|
|
32743
|
+
'stroke-width': d['stroke-width'] || lineWidth
|
|
32744
|
+
});
|
|
32745
|
+
return [obj];
|
|
32746
|
+
};
|
|
32747
|
+
|
|
32748
|
+
var Frame = /*#__PURE__*/Object.freeze({
|
|
32749
|
+
__proto__: null,
|
|
32750
|
+
getAspectRatioArg: getAspectRatioArg
|
|
32751
|
+
});
|
|
32752
|
+
|
|
32495
32753
|
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
32496
32754
|
if (!layerHasGeometry(lyr)) {
|
|
32497
32755
|
stop("Layer is missing geometry");
|
|
@@ -37379,153 +37637,6 @@ ${svg}
|
|
|
37379
37637
|
}
|
|
37380
37638
|
};
|
|
37381
37639
|
|
|
37382
|
-
cmd.scalebar = function(catalog, opts) {
|
|
37383
|
-
var frame = findFrameDataset(catalog);
|
|
37384
|
-
var obj, lyr;
|
|
37385
|
-
if (!frame) {
|
|
37386
|
-
stop('Missing a map frame');
|
|
37387
|
-
}
|
|
37388
|
-
obj = utils.defaults({type: 'scalebar'}, opts);
|
|
37389
|
-
lyr = {
|
|
37390
|
-
name: opts.name || 'scalebar',
|
|
37391
|
-
data: new DataTable([obj])
|
|
37392
|
-
};
|
|
37393
|
-
frame.layers.push(lyr);
|
|
37394
|
-
};
|
|
37395
|
-
|
|
37396
|
-
// TODO: generalize to other kinds of furniture as they are developed
|
|
37397
|
-
function getScalebarPosition(d) {
|
|
37398
|
-
var opts = { // defaults
|
|
37399
|
-
valign: 'top',
|
|
37400
|
-
halign: 'left',
|
|
37401
|
-
voffs: 10,
|
|
37402
|
-
hoffs: 10
|
|
37403
|
-
};
|
|
37404
|
-
if (+d.left > 0) {
|
|
37405
|
-
opts.hoffs = +d.left;
|
|
37406
|
-
}
|
|
37407
|
-
if (+d.top > 0) {
|
|
37408
|
-
opts.voffs = +d.top;
|
|
37409
|
-
}
|
|
37410
|
-
if (+d.right > 0) {
|
|
37411
|
-
opts.hoffs = +d.right;
|
|
37412
|
-
opts.halign = 'right';
|
|
37413
|
-
}
|
|
37414
|
-
if (+d.bottom > 0) {
|
|
37415
|
-
opts.voffs = +d.bottom;
|
|
37416
|
-
opts.valign = 'bottom';
|
|
37417
|
-
}
|
|
37418
|
-
return opts;
|
|
37419
|
-
}
|
|
37420
|
-
|
|
37421
|
-
furnitureRenderers.scalebar = function(d, frame) {
|
|
37422
|
-
var pos = getScalebarPosition(d);
|
|
37423
|
-
var metersPerPx = getMapFrameMetersPerPixel(frame);
|
|
37424
|
-
var label = d.label_text || getAutoScalebarLabel(frame.width, metersPerPx);
|
|
37425
|
-
var scalebarKm = parseScalebarLabelToKm(label);
|
|
37426
|
-
var barHeight = 3;
|
|
37427
|
-
var labelOffs = 4;
|
|
37428
|
-
var fontSize = +d.font_size || 13;
|
|
37429
|
-
var width = Math.round(scalebarKm / metersPerPx * 1000);
|
|
37430
|
-
var height = Math.round(barHeight + labelOffs + fontSize * 0.8);
|
|
37431
|
-
var labelPos = d.label_position == 'top' ? 'top' : 'bottom';
|
|
37432
|
-
var anchorX = pos.halign == 'left' ? 0 : width;
|
|
37433
|
-
var anchorY = barHeight + labelOffs;
|
|
37434
|
-
var dx = pos.halign == 'right' ? frame.width - width - pos.hoffs : pos.hoffs;
|
|
37435
|
-
var dy = pos.valign == 'bottom' ? frame.height - height - pos.voffs : pos.voffs;
|
|
37436
|
-
|
|
37437
|
-
if (labelPos == 'top') {
|
|
37438
|
-
anchorY = -labelOffs;
|
|
37439
|
-
dy += Math.round(labelOffs + fontSize * 0.8);
|
|
37440
|
-
}
|
|
37441
|
-
|
|
37442
|
-
if (width > 0 === false) {
|
|
37443
|
-
stop("Null scalebar length");
|
|
37444
|
-
}
|
|
37445
|
-
var barObj = {
|
|
37446
|
-
tag: 'rect',
|
|
37447
|
-
properties: {
|
|
37448
|
-
fill: 'black',
|
|
37449
|
-
x: 0,
|
|
37450
|
-
y: 0,
|
|
37451
|
-
width: width,
|
|
37452
|
-
height: barHeight
|
|
37453
|
-
}
|
|
37454
|
-
};
|
|
37455
|
-
var labelOpts = {
|
|
37456
|
-
'label-text': label,
|
|
37457
|
-
'font-size': fontSize,
|
|
37458
|
-
'text-anchor': pos.halign == 'left' ? 'start': 'end',
|
|
37459
|
-
'dominant-baseline': labelPos == 'top' ? 'auto' : 'hanging'
|
|
37460
|
-
//// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
|
|
37461
|
-
// 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
|
|
37462
|
-
// so I'm using 'hanging' and 'auto', which seem to be well supported.
|
|
37463
|
-
// downside: requires a kludgy multiplier to calculate scalebar height (see above)
|
|
37464
|
-
};
|
|
37465
|
-
var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY);
|
|
37466
|
-
var g = {
|
|
37467
|
-
tag: 'g',
|
|
37468
|
-
children: [barObj, labelObj],
|
|
37469
|
-
properties: {
|
|
37470
|
-
transform: 'translate(' + dx + ' ' + dy + ')'
|
|
37471
|
-
}
|
|
37472
|
-
};
|
|
37473
|
-
return [g];
|
|
37474
|
-
};
|
|
37475
|
-
|
|
37476
|
-
function getAutoScalebarLabel(mapWidth, metersPerPx) {
|
|
37477
|
-
var minWidth = 100; // TODO: vary min size based on map width
|
|
37478
|
-
var minKm = metersPerPx * minWidth / 1000;
|
|
37479
|
-
var options = ('1/8 1/5 1/4 1/2 1 1.5 2 3 4 5 8 10 12 15 20 25 30 40 50 75 ' +
|
|
37480
|
-
'100 150 200 250 300 350 400 500 750 1,000 1,200 1,500 2,000 ' +
|
|
37481
|
-
'2,500 3,000 4,000 5,000').split(' ');
|
|
37482
|
-
return options.reduce(function(memo, str) {
|
|
37483
|
-
if (memo) return memo;
|
|
37484
|
-
var label = formatDistanceLabelAsMiles(str);
|
|
37485
|
-
if (parseScalebarLabelToKm(label) > minKm) {
|
|
37486
|
-
return label;
|
|
37487
|
-
}
|
|
37488
|
-
}, null) || '';
|
|
37489
|
-
}
|
|
37490
|
-
|
|
37491
|
-
function formatDistanceLabelAsMiles(str) {
|
|
37492
|
-
var num = parseScalebarNumber(str);
|
|
37493
|
-
return str + (num > 1 ? ' MILES' : ' MILE');
|
|
37494
|
-
}
|
|
37495
|
-
|
|
37496
|
-
// See test/mapshaper-scalebar.js for examples of supported formats
|
|
37497
|
-
function parseScalebarLabelToKm(str) {
|
|
37498
|
-
var units = parseScalebarUnits(str);
|
|
37499
|
-
var value = parseScalebarNumber(str);
|
|
37500
|
-
if (!units || !value) return NaN;
|
|
37501
|
-
return units == 'mile' ? value * 1.60934 : value;
|
|
37502
|
-
}
|
|
37503
|
-
|
|
37504
|
-
function parseScalebarUnits(str) {
|
|
37505
|
-
var isMiles = /miles?$/.test(str.toLowerCase());
|
|
37506
|
-
var isKm = /(km|kilometers?|kilometres?)$/.test(str.toLowerCase());
|
|
37507
|
-
return isMiles && 'mile' || isKm && 'km' || '';
|
|
37508
|
-
}
|
|
37509
|
-
|
|
37510
|
-
function parseScalebarNumber(str) {
|
|
37511
|
-
var fractionRxp = /^([0-9]+) ?\/ ?([0-9]+)/;
|
|
37512
|
-
var match, value;
|
|
37513
|
-
str = str.replace(/[\s]/g, '').replace(/,/g, '');
|
|
37514
|
-
if (fractionRxp.test(str)) {
|
|
37515
|
-
match = fractionRxp.exec(str);
|
|
37516
|
-
value = +match[1] / +match[2];
|
|
37517
|
-
} else {
|
|
37518
|
-
value = parseFloat(str);
|
|
37519
|
-
}
|
|
37520
|
-
return value > 0 && value < Infinity ? value : NaN;
|
|
37521
|
-
}
|
|
37522
|
-
|
|
37523
|
-
var Scalebar = /*#__PURE__*/Object.freeze({
|
|
37524
|
-
__proto__: null,
|
|
37525
|
-
formatDistanceLabelAsMiles: formatDistanceLabelAsMiles,
|
|
37526
|
-
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
37527
|
-
});
|
|
37528
|
-
|
|
37529
37640
|
cmd.shape = function(targetDataset, opts) {
|
|
37530
37641
|
var geojson, dataset;
|
|
37531
37642
|
if (opts.coordinates) {
|
|
@@ -38713,10 +38824,14 @@ ${svg}
|
|
|
38713
38824
|
|
|
38714
38825
|
var roundCoord = getRoundingFunction(0.01);
|
|
38715
38826
|
|
|
38716
|
-
function
|
|
38827
|
+
function getSymbolFillColor(d) {
|
|
38717
38828
|
return d.fill || 'magenta';
|
|
38718
38829
|
}
|
|
38719
38830
|
|
|
38831
|
+
function getSymbolStrokeColor(d) {
|
|
38832
|
+
return d.stroke || d.fill || 'magenta';
|
|
38833
|
+
}
|
|
38834
|
+
|
|
38720
38835
|
function getSymbolRadius(d) {
|
|
38721
38836
|
if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
|
|
38722
38837
|
return d.radius || d.length || d.r || 5; // use a default value
|
|
@@ -38790,57 +38905,104 @@ ${svg}
|
|
|
38790
38905
|
points.push(p2);
|
|
38791
38906
|
}
|
|
38792
38907
|
|
|
38793
|
-
|
|
38794
|
-
|
|
38795
|
-
|
|
38796
|
-
// var curve = d['arrow-stem-curve'] || 0;
|
|
38797
|
-
// var unscaledHeadWidth = d['arrow-head-width'] || 9;
|
|
38798
|
-
// var unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle);
|
|
38799
|
-
// var scale = getScale(totalLen, unscaledHeadLen, minStemRatio);
|
|
38800
|
-
// var headWidth = unscaledHeadWidth * scale;
|
|
38801
|
-
// var headLen = unscaledHeadLen * scale;
|
|
38802
|
-
// var tip = getStickArrowTip(totalLen, curve);
|
|
38803
|
-
// var stem = [[0, 0], tip.concat()];
|
|
38804
|
-
// if (curve) {
|
|
38805
|
-
// addBezierArcControlPoints(stem, curve);
|
|
38806
|
-
// }
|
|
38807
|
-
// if (!headLen) return [stem];
|
|
38808
|
-
// var head = [addPoints([-headWidth / 2, -headLen], tip), tip.concat(), addPoints([headWidth / 2, -headLen], tip)];
|
|
38908
|
+
function getStickArrowCoords(d) {
|
|
38909
|
+
return getArrowCoords(d, 'stick');
|
|
38910
|
+
}
|
|
38809
38911
|
|
|
38810
|
-
|
|
38811
|
-
|
|
38812
|
-
|
|
38813
|
-
// }
|
|
38912
|
+
function getFilledArrowCoords(d) {
|
|
38913
|
+
return getArrowCoords(d, 'standard');
|
|
38914
|
+
}
|
|
38814
38915
|
|
|
38815
|
-
|
|
38816
|
-
|
|
38817
|
-
|
|
38818
|
-
|
|
38819
|
-
|
|
38820
|
-
|
|
38821
|
-
|
|
38916
|
+
function getArrowCoords(d, style) {
|
|
38917
|
+
var stickArrow = style == 'stick',
|
|
38918
|
+
// direction = d.rotation || d.direction || 0,
|
|
38919
|
+
direction = d.direction || 0, // rotation is an independent parameter
|
|
38920
|
+
stemTaper = d['stem-taper'] || 0,
|
|
38921
|
+
curvature = d['stem-curve'] || 0,
|
|
38922
|
+
size = calcArrowSize(d, stickArrow);
|
|
38923
|
+
if (!size) return null;
|
|
38924
|
+
var stemLen = size.stemLen,
|
|
38925
|
+
headLen = size.headLen,
|
|
38926
|
+
headDx = size.headWidth / 2,
|
|
38927
|
+
stemDx = size.stemWidth / 2,
|
|
38928
|
+
baseDx = stemDx * (1 - stemTaper),
|
|
38929
|
+
head, stem, coords, dx, dy;
|
|
38822
38930
|
|
|
38823
|
-
|
|
38824
|
-
|
|
38825
|
-
|
|
38931
|
+
if (curvature) {
|
|
38932
|
+
// make curved stem
|
|
38933
|
+
if (direction > 0) curvature = -curvature;
|
|
38934
|
+
var theta = Math.abs(curvature) / 180 * Math.PI;
|
|
38935
|
+
var sign = curvature > 0 ? 1 : -1;
|
|
38936
|
+
var ax = baseDx * Math.cos(theta); // rotate arrow base
|
|
38937
|
+
var ay = baseDx * Math.sin(theta) * -sign;
|
|
38938
|
+
dx = stemLen * Math.sin(theta / 2) * sign;
|
|
38939
|
+
dy = stemLen * Math.cos(theta / 2);
|
|
38940
|
+
|
|
38941
|
+
if (stickArrow) {
|
|
38942
|
+
stem = getCurvedStemCoords(-ax, -ay, dx, dy, theta);
|
|
38943
|
+
} else {
|
|
38944
|
+
var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
|
|
38945
|
+
var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
|
|
38946
|
+
stem = leftStem.concat(rightStem.reverse());
|
|
38947
|
+
}
|
|
38948
|
+
|
|
38949
|
+
} else {
|
|
38950
|
+
// make straight stem
|
|
38951
|
+
dx = 0;
|
|
38952
|
+
dy = stemLen;
|
|
38953
|
+
if (stickArrow) {
|
|
38954
|
+
stem = [[0, 0], [0, stemLen]];
|
|
38955
|
+
} else {
|
|
38956
|
+
stem = [[-baseDx, 0], [baseDx, 0]];
|
|
38957
|
+
}
|
|
38958
|
+
}
|
|
38959
|
+
|
|
38960
|
+
if (stickArrow) {
|
|
38961
|
+
// make stick arrow
|
|
38962
|
+
head = [[-headDx + dx, stemLen - headLen], [dx, stemLen], [headDx + dx, stemLen - headLen]];
|
|
38963
|
+
coords = [stem, head]; // MultiLineString coords
|
|
38964
|
+
} else {
|
|
38965
|
+
// make filled arrow
|
|
38966
|
+
// coordinates go counter clockwise, starting from the leftmost head coordinate
|
|
38967
|
+
head = [[stemDx + dx, dy], [headDx + dx, dy],
|
|
38968
|
+
[dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
|
|
38969
|
+
coords = stem.concat(head);
|
|
38970
|
+
coords.push(stem[0].concat()); // closed path
|
|
38971
|
+
coords = [coords]; // Polygon coords
|
|
38972
|
+
}
|
|
38973
|
+
|
|
38974
|
+
if (d.anchor == 'end') {
|
|
38975
|
+
scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
|
|
38976
|
+
} else if (d.anchor == 'middle') {
|
|
38977
|
+
// shift midpoint away from the head a bit for a more balanced placement
|
|
38978
|
+
// scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
|
|
38979
|
+
scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
|
|
38980
|
+
}
|
|
38981
|
+
|
|
38982
|
+
rotateCoords(coords, direction);
|
|
38983
|
+
if (d.flipped) {
|
|
38984
|
+
flipY(coords);
|
|
38985
|
+
}
|
|
38986
|
+
return coords;
|
|
38987
|
+
}
|
|
38826
38988
|
|
|
38827
38989
|
function calcStraightArrowCoords(stemLen, headLen, stemDx, headDx, baseDx) {
|
|
38828
38990
|
return [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
|
|
38829
38991
|
[-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
|
|
38830
38992
|
}
|
|
38831
38993
|
|
|
38832
|
-
function calcArrowSize(d) {
|
|
38994
|
+
function calcArrowSize(d, stickArrow) {
|
|
38833
38995
|
// don't display arrows with negative length
|
|
38834
38996
|
var totalLen = Math.max(d.radius || d.length || d.r || 0, 0),
|
|
38835
38997
|
scale = 1,
|
|
38836
38998
|
o = initArrowSize(d); // calc several parameters
|
|
38837
38999
|
|
|
38838
|
-
if (totalLen
|
|
39000
|
+
if (totalLen >= 0) {
|
|
38839
39001
|
scale = calcScale(totalLen, o.headLen, d);
|
|
38840
39002
|
o.stemWidth *= scale;
|
|
38841
39003
|
o.headWidth *= scale;
|
|
38842
39004
|
o.headLen *= scale;
|
|
38843
|
-
o.stemLen = totalLen - o.headLen;
|
|
39005
|
+
o.stemLen = stickArrow ? totalLen : totalLen - o.headLen;
|
|
38844
39006
|
}
|
|
38845
39007
|
|
|
38846
39008
|
if (o.headWidth < o.stemWidth) {
|
|
@@ -38861,7 +39023,7 @@ ${svg}
|
|
|
38861
39023
|
} else if (stemLen + headLen > totalLen) {
|
|
38862
39024
|
scale = totalLen / (stemLen + headLen);
|
|
38863
39025
|
}
|
|
38864
|
-
return scale;
|
|
39026
|
+
return scale || 0;
|
|
38865
39027
|
}
|
|
38866
39028
|
|
|
38867
39029
|
function initArrowSize(d) {
|
|
@@ -38891,56 +39053,6 @@ ${svg}
|
|
|
38891
39053
|
return 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2;
|
|
38892
39054
|
}
|
|
38893
39055
|
|
|
38894
|
-
function getFilledArrowCoords(d) {
|
|
38895
|
-
var direction = d.rotation || d.direction || 0,
|
|
38896
|
-
stemTaper = d['stem-taper'] || 0,
|
|
38897
|
-
curvature = d['stem-curve'] || 0,
|
|
38898
|
-
size = calcArrowSize(d);
|
|
38899
|
-
if (!size) return null;
|
|
38900
|
-
var stemLen = size.stemLen,
|
|
38901
|
-
headLen = size.headLen,
|
|
38902
|
-
headDx = size.headWidth / 2,
|
|
38903
|
-
stemDx = size.stemWidth / 2,
|
|
38904
|
-
baseDx = stemDx * (1 - stemTaper),
|
|
38905
|
-
head, stem, coords, dx, dy;
|
|
38906
|
-
|
|
38907
|
-
if (curvature) {
|
|
38908
|
-
if (direction > 0) curvature = -curvature;
|
|
38909
|
-
var theta = Math.abs(curvature) / 180 * Math.PI;
|
|
38910
|
-
var sign = curvature > 0 ? 1 : -1;
|
|
38911
|
-
var ax = baseDx * Math.cos(theta); // rotate arrow base
|
|
38912
|
-
var ay = baseDx * Math.sin(theta) * -sign;
|
|
38913
|
-
dx = stemLen * Math.sin(theta / 2) * sign;
|
|
38914
|
-
dy = stemLen * Math.cos(theta / 2);
|
|
38915
|
-
var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
|
|
38916
|
-
var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
|
|
38917
|
-
stem = leftStem.concat(rightStem.reverse());
|
|
38918
|
-
|
|
38919
|
-
} else {
|
|
38920
|
-
dx = 0;
|
|
38921
|
-
dy = stemLen;
|
|
38922
|
-
stem = [[-baseDx, 0], [baseDx, 0], [baseDx, 0]];
|
|
38923
|
-
}
|
|
38924
|
-
|
|
38925
|
-
// coordinates go counter clockwise, starting from the leftmost head coordinate
|
|
38926
|
-
head = [[stemDx + dx, dy], [headDx + dx, dy],
|
|
38927
|
-
[dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
|
|
38928
|
-
|
|
38929
|
-
coords = stem.concat(head);
|
|
38930
|
-
if (d.anchor == 'end') {
|
|
38931
|
-
scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
|
|
38932
|
-
} else if (d.anchor == 'middle') {
|
|
38933
|
-
// shift midpoint away from the head a bit for a more balanced placement
|
|
38934
|
-
// scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
|
|
38935
|
-
scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
|
|
38936
|
-
}
|
|
38937
|
-
|
|
38938
|
-
rotateCoords(coords, direction);
|
|
38939
|
-
if (d.flipped) {
|
|
38940
|
-
flipY(coords);
|
|
38941
|
-
}
|
|
38942
|
-
return [coords];
|
|
38943
|
-
}
|
|
38944
39056
|
|
|
38945
39057
|
// ax, ay: point on the base
|
|
38946
39058
|
// bx, by: point on the stem
|
|
@@ -38978,11 +39090,11 @@ ${svg}
|
|
|
38978
39090
|
|
|
38979
39091
|
function makeCircleSymbol(d, opts) {
|
|
38980
39092
|
var radius = getSymbolRadius(d);
|
|
38981
|
-
// TODO: remove
|
|
39093
|
+
// TODO: remove duplication with svg-symbols.js
|
|
38982
39094
|
if (+opts.scale) radius *= +opts.scale;
|
|
38983
39095
|
return {
|
|
38984
39096
|
type: 'circle',
|
|
38985
|
-
fill:
|
|
39097
|
+
fill: getSymbolFillColor(d),
|
|
38986
39098
|
r: radius
|
|
38987
39099
|
};
|
|
38988
39100
|
}
|
|
@@ -39070,11 +39182,12 @@ ${svg}
|
|
|
39070
39182
|
return 180 - centerAngle;
|
|
39071
39183
|
}
|
|
39072
39184
|
|
|
39185
|
+
// Returns a svg-symbol object
|
|
39073
39186
|
function makeRingSymbol(d, opts) {
|
|
39074
39187
|
var scale = +opts.scale || 1;
|
|
39075
39188
|
var radii = parseRings(d.radii || '2').map(function(r) { return r * scale; });
|
|
39076
39189
|
var solidCenter = utils.isOdd(radii.length);
|
|
39077
|
-
var color =
|
|
39190
|
+
var color = getSymbolFillColor(d);
|
|
39078
39191
|
var parts = [];
|
|
39079
39192
|
if (solidCenter) {
|
|
39080
39193
|
parts.push({
|
|
@@ -39130,33 +39243,35 @@ ${svg}
|
|
|
39130
39243
|
}
|
|
39131
39244
|
|
|
39132
39245
|
// Returns an svg-symbol data object for one symbol
|
|
39133
|
-
function
|
|
39134
|
-
|
|
39135
|
-
|
|
39136
|
-
|
|
39246
|
+
function makePathSymbol(coords, properties, geojsonType) {
|
|
39247
|
+
var sym;
|
|
39248
|
+
if (geojsonType == 'MultiPolygon' || geojsonType == 'Polygon') {
|
|
39249
|
+
sym = {
|
|
39250
|
+
type: 'polygon',
|
|
39251
|
+
fill: getSymbolFillColor(properties),
|
|
39252
|
+
coordinates: geojsonType == 'Polygon' ? coords : flattenMultiPolygonCoords(coords)
|
|
39253
|
+
};
|
|
39254
|
+
} else if (geojsonType == 'LineString' || geojsonType == 'MultiLineString') {
|
|
39255
|
+
sym = {
|
|
39256
|
+
type: 'polyline',
|
|
39257
|
+
stroke: getSymbolStrokeColor(properties),
|
|
39258
|
+
'stroke-width': properties['stroke-width'] || 2,
|
|
39259
|
+
coordinates: geojsonType == 'LineString' ? [coords] : coords
|
|
39260
|
+
};
|
|
39261
|
+
} else {
|
|
39137
39262
|
error('Unsupported type:', geojsonType);
|
|
39138
39263
|
}
|
|
39139
|
-
roundCoordsForSVG(
|
|
39140
|
-
return
|
|
39141
|
-
type: 'polygon',
|
|
39142
|
-
coordinates: coords,
|
|
39143
|
-
fill: getSymbolColor(properties)
|
|
39144
|
-
};
|
|
39145
|
-
}
|
|
39146
|
-
|
|
39147
|
-
function convertMultiPolygonCoords(coords) {
|
|
39148
|
-
return coords.reduce(function(memo, poly) {
|
|
39149
|
-
return memo.concat(poly);
|
|
39150
|
-
}, []);
|
|
39264
|
+
roundCoordsForSVG(sym.coordinates);
|
|
39265
|
+
return sym;
|
|
39151
39266
|
}
|
|
39152
39267
|
|
|
39153
39268
|
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
39154
39269
|
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
39155
39270
|
requireSinglePointLayer(inputLyr);
|
|
39156
39271
|
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
39157
|
-
var
|
|
39272
|
+
var shapeMode = !!opts.geographic;
|
|
39158
39273
|
var metersPerPx;
|
|
39159
|
-
if (
|
|
39274
|
+
if (shapeMode) {
|
|
39160
39275
|
requireProjectedDataset(dataset);
|
|
39161
39276
|
metersPerPx = opts.pixel_scale || getMetersPerPixel(lyr, dataset);
|
|
39162
39277
|
}
|
|
@@ -39168,11 +39283,11 @@ ${svg}
|
|
|
39168
39283
|
var rec = records[i] || {};
|
|
39169
39284
|
|
|
39170
39285
|
// non-polygon symbols
|
|
39171
|
-
if (!
|
|
39286
|
+
if (!shapeMode && d.type == 'circle') {
|
|
39172
39287
|
rec['svg-symbol'] = makeCircleSymbol(d, opts);
|
|
39173
39288
|
return;
|
|
39174
39289
|
}
|
|
39175
|
-
if (!
|
|
39290
|
+
if (!shapeMode && d.type == 'ring') {
|
|
39176
39291
|
rec['svg-symbol'] = makeRingSymbol(d, opts);
|
|
39177
39292
|
return;
|
|
39178
39293
|
}
|
|
@@ -39180,7 +39295,10 @@ ${svg}
|
|
|
39180
39295
|
var geojsonType = 'Polygon';
|
|
39181
39296
|
var coords;
|
|
39182
39297
|
// these symbols get converted to polygon shapes
|
|
39183
|
-
if (d.type == 'arrow') {
|
|
39298
|
+
if (d.type == 'arrow' && opts.arrow_style == 'stick') {
|
|
39299
|
+
coords = getStickArrowCoords(d);
|
|
39300
|
+
geojsonType = 'MultiLineString';
|
|
39301
|
+
} else if (d.type == 'arrow') {
|
|
39184
39302
|
coords = getFilledArrowCoords(d);
|
|
39185
39303
|
} else if (d.type == 'ring') {
|
|
39186
39304
|
coords = getRingCoords(d);
|
|
@@ -39192,23 +39310,24 @@ ${svg}
|
|
|
39192
39310
|
}
|
|
39193
39311
|
if (!coords) return null;
|
|
39194
39312
|
rotateCoords(coords, +d.rotation || 0);
|
|
39195
|
-
if (!
|
|
39313
|
+
if (!shapeMode) {
|
|
39196
39314
|
flipY(coords);
|
|
39197
39315
|
}
|
|
39198
39316
|
if (+opts.scale) {
|
|
39199
39317
|
scaleAndShiftCoords(coords, +opts.scale, [0, 0]);
|
|
39200
39318
|
}
|
|
39201
|
-
if (
|
|
39319
|
+
if (shapeMode) {
|
|
39202
39320
|
scaleAndShiftCoords(coords, metersPerPx, shp[0]);
|
|
39203
|
-
if (d.
|
|
39321
|
+
if (d.fill) rec.fill = d.fill;
|
|
39322
|
+
if (d.stroke) rec.stroke = d.stroke;
|
|
39204
39323
|
return createGeometry(coords, geojsonType);
|
|
39205
39324
|
} else {
|
|
39206
|
-
rec['svg-symbol'] =
|
|
39325
|
+
rec['svg-symbol'] = makePathSymbol(coords, d, geojsonType);
|
|
39207
39326
|
}
|
|
39208
39327
|
});
|
|
39209
39328
|
|
|
39210
39329
|
var outputLyr, dataset2;
|
|
39211
|
-
if (
|
|
39330
|
+
if (shapeMode) {
|
|
39212
39331
|
dataset2 = importGeometries(geometries, records);
|
|
39213
39332
|
outputLyr = mergeOutputLayerIntoDataset(inputLyr, dataset, dataset2, opts);
|
|
39214
39333
|
outputLyr.data = lyr.data;
|
|
@@ -39242,9 +39361,10 @@ ${svg}
|
|
|
39242
39361
|
}
|
|
39243
39362
|
|
|
39244
39363
|
function getMetersPerPixel(lyr, dataset) {
|
|
39245
|
-
// TODO: handle single point, no extent
|
|
39246
39364
|
var bounds = getLayerBounds(lyr);
|
|
39247
|
-
|
|
39365
|
+
// TODO: need a better way to handle a single point with no extent
|
|
39366
|
+
var extent = bounds.width() || bounds.height() || 1000;
|
|
39367
|
+
return extent / 800;
|
|
39248
39368
|
}
|
|
39249
39369
|
|
|
39250
39370
|
var Symbols = /*#__PURE__*/Object.freeze({
|
|
@@ -40818,6 +40938,7 @@ ${svg}
|
|
|
40818
40938
|
FileTypes,
|
|
40819
40939
|
FilterGeom,
|
|
40820
40940
|
Frame,
|
|
40941
|
+
FrameData,
|
|
40821
40942
|
Furniture,
|
|
40822
40943
|
Geodesic,
|
|
40823
40944
|
GeojsonExport,
|