mapshaper 0.5.113 → 0.5.114
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 +3 -0
- package/mapshaper.js +622 -558
- package/package.json +1 -1
- package/www/mapshaper-gui.js +2 -2
- package/www/mapshaper.js +622 -558
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.5.
|
|
3
|
+
var VERSION = "0.5.114";
|
|
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) {
|
|
@@ -14032,6 +14250,7 @@
|
|
|
14032
14250
|
// null values indicate the lack of a function for parsing/identifying this property
|
|
14033
14251
|
// (in which case a heuristic is used for distinguishing a string literal from an expression)
|
|
14034
14252
|
var stylePropertyTypes = {
|
|
14253
|
+
css: null,
|
|
14035
14254
|
class: 'classname',
|
|
14036
14255
|
dx: 'measure',
|
|
14037
14256
|
dy: 'measure',
|
|
@@ -14081,7 +14300,7 @@
|
|
|
14081
14300
|
effect: null // e.g. "fade"
|
|
14082
14301
|
}, stylePropertyTypes);
|
|
14083
14302
|
|
|
14084
|
-
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(',');
|
|
14085
14304
|
|
|
14086
14305
|
var propertiesBySymbolType = {
|
|
14087
14306
|
polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
|
|
@@ -14182,7 +14401,6 @@
|
|
|
14182
14401
|
// treating the string as a literal value
|
|
14183
14402
|
literalVal = strVal;
|
|
14184
14403
|
}
|
|
14185
|
-
// console.log("literalVal:", mightBeExpression(strVal, fields), strVal, fields)
|
|
14186
14404
|
if (accessor) return accessor;
|
|
14187
14405
|
if (literalVal !== null) return function(id) {return literalVal;};
|
|
14188
14406
|
stop('Unexpected value for', svgName + ':', strVal);
|
|
@@ -14278,6 +14496,138 @@
|
|
|
14278
14496
|
isSvgColor: isSvgColor
|
|
14279
14497
|
});
|
|
14280
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
|
+
|
|
14281
14631
|
function toLabelString(val) {
|
|
14282
14632
|
if (val || val === 0 || val === false) return String(val);
|
|
14283
14633
|
return '';
|
|
@@ -14341,7 +14691,6 @@
|
|
|
14341
14691
|
|
|
14342
14692
|
// convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
|
|
14343
14693
|
|
|
14344
|
-
|
|
14345
14694
|
function getTransform(xy, scale) {
|
|
14346
14695
|
var str = 'translate(' + roundToTenths(xy[0]) + ' ' + roundToTenths(xy[1]) + ')';
|
|
14347
14696
|
if (scale && scale != 1) {
|
|
@@ -14358,7 +14707,8 @@
|
|
|
14358
14707
|
square: square,
|
|
14359
14708
|
image: image,
|
|
14360
14709
|
group: group,
|
|
14361
|
-
label: label
|
|
14710
|
+
label: label,
|
|
14711
|
+
offset: offset
|
|
14362
14712
|
};
|
|
14363
14713
|
|
|
14364
14714
|
// render label and/or point symbol
|
|
@@ -14426,8 +14776,9 @@
|
|
|
14426
14776
|
function label(d, x, y) {
|
|
14427
14777
|
var o = renderStyledLabel(d);
|
|
14428
14778
|
if (x || y) {
|
|
14429
|
-
|
|
14430
|
-
|
|
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]);
|
|
14431
14782
|
}
|
|
14432
14783
|
return o;
|
|
14433
14784
|
}
|
|
@@ -14487,21 +14838,40 @@
|
|
|
14487
14838
|
return o;
|
|
14488
14839
|
}
|
|
14489
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
|
+
|
|
14490
14853
|
function group(d, x, y) {
|
|
14491
|
-
var
|
|
14854
|
+
var o = {
|
|
14855
|
+
tag: 'g',
|
|
14856
|
+
children: []
|
|
14857
|
+
};
|
|
14858
|
+
var children = o.children;
|
|
14859
|
+
(d.parts || []).forEach(function(o) {
|
|
14492
14860
|
var sym = renderComplexSymbol(o, x, y);
|
|
14493
|
-
|
|
14861
|
+
children.push(sym);
|
|
14862
|
+
//if (d.chained) {
|
|
14494
14863
|
//if (o.chained) {
|
|
14864
|
+
if (o.type == 'line') {
|
|
14495
14865
|
x += (o.dx || 0);
|
|
14496
14866
|
y += (o.dy || 0);
|
|
14497
14867
|
}
|
|
14498
|
-
|
|
14868
|
+
if (o.type == 'offset') {
|
|
14869
|
+
children = sym.children;
|
|
14870
|
+
x = y = 0;
|
|
14871
|
+
}
|
|
14499
14872
|
});
|
|
14500
|
-
if (
|
|
14501
|
-
return
|
|
14502
|
-
tag: 'g',
|
|
14503
|
-
children: parts
|
|
14504
|
-
};
|
|
14873
|
+
if (o.children.length == 1 && !o.properties) return o.children[0];
|
|
14874
|
+
return o;
|
|
14505
14875
|
}
|
|
14506
14876
|
|
|
14507
14877
|
var SvgSymbols = /*#__PURE__*/Object.freeze({
|
|
@@ -14511,419 +14881,180 @@
|
|
|
14511
14881
|
renderPoint: renderPoint
|
|
14512
14882
|
});
|
|
14513
14883
|
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
MultiLineString: importMultiLineString,
|
|
14520
|
-
MultiPolygon: importMultiPolygon
|
|
14521
|
-
};
|
|
14522
|
-
|
|
14523
|
-
function importGeoJSONFeatures(features, opts) {
|
|
14524
|
-
opts = opts || {};
|
|
14525
|
-
return features.map(function(obj, i) {
|
|
14526
|
-
var geom = obj.type == 'Feature' ? obj.geometry : obj; // could be null
|
|
14527
|
-
var geomType = geom && geom.type;
|
|
14528
|
-
var msType = GeoJSON.translateGeoJSONType(geomType);
|
|
14529
|
-
var d = obj.properties || {};
|
|
14530
|
-
var svgObj = null;
|
|
14531
|
-
if (geomType && geom.coordinates) {
|
|
14532
|
-
svgObj = geojsonImporters[geomType](geom.coordinates, d);
|
|
14533
|
-
}
|
|
14534
|
-
if (!svgObj) {
|
|
14535
|
-
return {tag: 'g'}; // empty element
|
|
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
|
|
14544
|
-
}
|
|
14545
|
-
if ('id' in obj) {
|
|
14546
|
-
if (!svgObj.properties) {
|
|
14547
|
-
svgObj.properties = {};
|
|
14548
|
-
}
|
|
14549
|
-
svgObj.properties.id = (opts.id_prefix || '') + obj.id;
|
|
14550
|
-
}
|
|
14551
|
-
return svgObj;
|
|
14552
|
-
});
|
|
14553
|
-
}
|
|
14554
|
-
|
|
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
|
-
};
|
|
14566
|
-
}
|
|
14567
|
-
var o = renderPoint(rec, coords);
|
|
14568
|
-
if (o) o.properties.transform = getTransform(coords);
|
|
14569
|
-
return o;
|
|
14570
|
-
}
|
|
14571
|
-
|
|
14572
|
-
function simpleCircleFilter(k) {
|
|
14573
|
-
return k != 'r';
|
|
14574
|
-
}
|
|
14575
|
-
|
|
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) {
|
|
14582
|
-
var children = [], p;
|
|
14583
|
-
for (var i=0; i<coords.length; i++) {
|
|
14584
|
-
p = importPoint(coords[i], rec);
|
|
14585
|
-
if (!p) continue;
|
|
14586
|
-
if (p.tag == 'g' && p.children) {
|
|
14587
|
-
children = children.concat(p.children);
|
|
14588
|
-
} else {
|
|
14589
|
-
children.push(p);
|
|
14590
|
-
}
|
|
14884
|
+
cmd.scalebar = function(catalog, opts) {
|
|
14885
|
+
var frame = findFrameDataset(catalog);
|
|
14886
|
+
var obj, lyr;
|
|
14887
|
+
if (!frame) {
|
|
14888
|
+
stop('Missing a map frame');
|
|
14591
14889
|
}
|
|
14592
|
-
|
|
14593
|
-
|
|
14890
|
+
lyr = getScalebarLayer(opts);
|
|
14891
|
+
frame.layers.push(lyr);
|
|
14892
|
+
};
|
|
14594
14893
|
|
|
14595
|
-
function
|
|
14596
|
-
var
|
|
14894
|
+
function getScalebarLayer(opts) {
|
|
14895
|
+
var obj = utils.defaults({type: 'scalebar'}, opts);
|
|
14597
14896
|
return {
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
};
|
|
14601
|
-
}
|
|
14602
|
-
|
|
14603
|
-
function importMultiLineString(coords) {
|
|
14604
|
-
var d = coords.map(stringifyLineStringCoords).join(' ');
|
|
14605
|
-
return {
|
|
14606
|
-
tag: 'path',
|
|
14607
|
-
properties: {d: d}
|
|
14897
|
+
name: opts.name || 'scalebar',
|
|
14898
|
+
data: new DataTable([obj])
|
|
14608
14899
|
};
|
|
14609
14900
|
}
|
|
14610
14901
|
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
}, []);
|
|
14619
|
-
}
|
|
14620
|
-
|
|
14621
|
-
function importPolygon(coords) {
|
|
14622
|
-
if (coords.length === 0) return null;
|
|
14623
|
-
var o = {
|
|
14624
|
-
tag: 'path',
|
|
14625
|
-
properties: {
|
|
14626
|
-
d: stringifyPolygonCoords(coords)
|
|
14627
|
-
}
|
|
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
|
|
14628
14909
|
};
|
|
14629
|
-
if (
|
|
14630
|
-
|
|
14631
|
-
}
|
|
14632
|
-
return o;
|
|
14633
|
-
}
|
|
14634
|
-
|
|
14635
|
-
var GeojsonToSvg = /*#__PURE__*/Object.freeze({
|
|
14636
|
-
__proto__: null,
|
|
14637
|
-
importGeoJSONFeatures: importGeoJSONFeatures,
|
|
14638
|
-
importPoint: importPoint,
|
|
14639
|
-
importLineString: importLineString,
|
|
14640
|
-
importMultiLineString: importMultiLineString,
|
|
14641
|
-
importMultiPolygon: importMultiPolygon,
|
|
14642
|
-
flattenMultiPolygonCoords: flattenMultiPolygonCoords,
|
|
14643
|
-
importPolygon: importPolygon
|
|
14644
|
-
});
|
|
14645
|
-
|
|
14646
|
-
/* require mapshaper-rectangle, mapshaper-furniture */
|
|
14647
|
-
|
|
14648
|
-
cmd.frame = function(catalog, source, opts) {
|
|
14649
|
-
var size, bounds, tmp, dataset;
|
|
14650
|
-
if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
|
|
14651
|
-
stop("Missing a width or area");
|
|
14652
|
-
}
|
|
14653
|
-
if (opts.width && opts.height) {
|
|
14654
|
-
opts = utils.extend({}, opts);
|
|
14655
|
-
// Height is a string containing either a number or a
|
|
14656
|
-
// comma-sep. pair of numbers (range); here we convert height to
|
|
14657
|
-
// an aspect-ratio parameter for the rectangle() function
|
|
14658
|
-
opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
|
|
14659
|
-
// TODO: currently returns max,min aspect ratio, should return in min,max order
|
|
14660
|
-
// (rectangle() function should handle max,min argument correctly now anyway)
|
|
14910
|
+
if (+d.left > 0) {
|
|
14911
|
+
opts.hoffs = +d.left;
|
|
14661
14912
|
}
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
if (probablyDecimalDegreeBounds(bounds)) {
|
|
14665
|
-
stop('Frames require projected, not geographical coordinates');
|
|
14666
|
-
} else if (!getDatasetCRS(tmp)) {
|
|
14667
|
-
message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
|
|
14913
|
+
if (+d.top > 0) {
|
|
14914
|
+
opts.voffs = +d.top;
|
|
14668
14915
|
}
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14916
|
+
if (+d.right > 0) {
|
|
14917
|
+
opts.hoffs = +d.right;
|
|
14918
|
+
opts.halign = 'right';
|
|
14672
14919
|
}
|
|
14673
|
-
if (
|
|
14674
|
-
|
|
14920
|
+
if (+d.bottom > 0) {
|
|
14921
|
+
opts.voffs = +d.bottom;
|
|
14922
|
+
opts.valign = 'bottom';
|
|
14675
14923
|
}
|
|
14676
|
-
|
|
14677
|
-
name: opts.name || 'frame',
|
|
14678
|
-
data: new DataTable([{
|
|
14679
|
-
width: size[0],
|
|
14680
|
-
height: size[1],
|
|
14681
|
-
bbox: bounds.toArray(),
|
|
14682
|
-
type: 'frame'
|
|
14683
|
-
}])
|
|
14684
|
-
}]};
|
|
14685
|
-
catalog.addDataset(dataset);
|
|
14686
|
-
};
|
|
14687
|
-
|
|
14688
|
-
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
14689
|
-
function getAspectRatioArg(widthArg, heightArg) {
|
|
14690
|
-
// heightArg is a string containing either a number or a
|
|
14691
|
-
// comma-sep. pair of numbers (range);
|
|
14692
|
-
return heightArg.split(',').map(function(opt) {
|
|
14693
|
-
var height = Number(opt),
|
|
14694
|
-
width = Number(widthArg);
|
|
14695
|
-
if (!opt) return '';
|
|
14696
|
-
return width / height;
|
|
14697
|
-
}).reverse().join(',');
|
|
14924
|
+
return opts;
|
|
14698
14925
|
}
|
|
14699
14926
|
|
|
14700
|
-
|
|
14701
|
-
var aspectRatio = bounds.width() / bounds.height();
|
|
14702
|
-
var height, width;
|
|
14703
|
-
if (opts.pixels) {
|
|
14704
|
-
width = Math.sqrt(+opts.pixels * aspectRatio);
|
|
14705
|
-
} else {
|
|
14706
|
-
width = +opts.width;
|
|
14707
|
-
}
|
|
14708
|
-
height = width / aspectRatio;
|
|
14709
|
-
return [Math.round(width), Math.round(height)];
|
|
14710
|
-
}
|
|
14927
|
+
furnitureRenderers.scalebar = renderScalebar;
|
|
14711
14928
|
|
|
14712
|
-
function
|
|
14713
|
-
var
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
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;
|
|
14944
|
+
|
|
14945
|
+
if (labelPos == 'top') {
|
|
14946
|
+
anchorY = -labelOffs;
|
|
14947
|
+
dy += Math.round(labelOffs + fontSize * 0.8);
|
|
14717
14948
|
}
|
|
14718
|
-
return getDatasetBounds(dataset);
|
|
14719
|
-
}
|
|
14720
14949
|
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14950
|
+
if (width > 0 === false) {
|
|
14951
|
+
stop("Null scalebar length");
|
|
14952
|
+
}
|
|
14953
|
+
var barObj = {
|
|
14954
|
+
tag: 'rect',
|
|
14955
|
+
properties: {
|
|
14956
|
+
fill: 'black',
|
|
14957
|
+
x: 0,
|
|
14958
|
+
y: 0,
|
|
14959
|
+
width: width,
|
|
14960
|
+
height: barHeight
|
|
14961
|
+
}
|
|
14962
|
+
};
|
|
14963
|
+
var labelOpts = {
|
|
14964
|
+
'label-text': label,
|
|
14965
|
+
'font-size': fontSize,
|
|
14966
|
+
'text-anchor': pos.halign == 'left' ? 'start': 'end',
|
|
14967
|
+
'dominant-baseline': labelPos == 'top' ? 'auto' : 'hanging'
|
|
14968
|
+
//// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
|
|
14969
|
+
// 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
|
|
14970
|
+
// so I'm using 'hanging' and 'auto', which seem to be well supported.
|
|
14971
|
+
// downside: requires a kludgy multiplier to calculate scalebar height (see above)
|
|
14972
|
+
};
|
|
14973
|
+
var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY);
|
|
14974
|
+
var g = {
|
|
14975
|
+
tag: 'g',
|
|
14976
|
+
children: [barObj, labelObj],
|
|
14977
|
+
properties: {
|
|
14978
|
+
transform: 'translate(' + dx + ' ' + dy + ')'
|
|
14979
|
+
}
|
|
14980
|
+
};
|
|
14981
|
+
return [g];
|
|
14724
14982
|
}
|
|
14725
14983
|
|
|
14726
|
-
function
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14984
|
+
function getAutoScalebarLabel(mapWidth, metersPerPx) {
|
|
14985
|
+
var minWidth = 75; // 100; // TODO: vary min size based on map width
|
|
14986
|
+
var minKm = metersPerPx * minWidth / 1000;
|
|
14987
|
+
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 ' +
|
|
14988
|
+
'100 150 200 250 300 350 400 500 750 1,000 1,200 1,500 2,000 ' +
|
|
14989
|
+
'2,500 3,000 4,000 5,000').split(' ');
|
|
14990
|
+
return options.reduce(function(memo, str) {
|
|
14991
|
+
if (memo) return memo;
|
|
14992
|
+
var label = formatDistanceLabelAsMiles(str);
|
|
14993
|
+
if (parseScalebarLabelToKm(label) > minKm) {
|
|
14994
|
+
return label;
|
|
14995
|
+
}
|
|
14996
|
+
}, null) || '';
|
|
14730
14997
|
}
|
|
14731
14998
|
|
|
14732
|
-
function
|
|
14733
|
-
var
|
|
14734
|
-
|
|
14735
|
-
});
|
|
14736
|
-
return target ? target.dataset : null;
|
|
14999
|
+
function formatDistanceLabelAsMiles(str) {
|
|
15000
|
+
var num = parseScalebarNumber(str);
|
|
15001
|
+
return str + (num > 1 ? ' MILES' : ' MILE');
|
|
14737
15002
|
}
|
|
14738
15003
|
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
15004
|
+
// See test/mapshaper-scalebar.js for examples of supported formats
|
|
15005
|
+
function parseScalebarLabelToKm(str) {
|
|
15006
|
+
var units = parseScalebarUnits(str);
|
|
15007
|
+
var value = parseScalebarNumber(str);
|
|
15008
|
+
if (!units || !value) return NaN;
|
|
15009
|
+
return units == 'mile' ? value * 1.60934 : value;
|
|
14744
15010
|
}
|
|
14745
15011
|
|
|
14746
|
-
function
|
|
14747
|
-
|
|
15012
|
+
function parseScalebarUnits(str) {
|
|
15013
|
+
var isMiles = /miles?$/.test(str.toLowerCase());
|
|
15014
|
+
var isKm = /(km|kilometers?|kilometres?)$/.test(str.toLowerCase());
|
|
15015
|
+
return isMiles && 'mile' || isKm && 'km' || '';
|
|
14748
15016
|
}
|
|
14749
15017
|
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
// TODO: handle CRS without inverse projections
|
|
14758
|
-
// scale factor is the ratio of coordinate distance to true distance at a point
|
|
14759
|
-
k = getScaleFactorAtXY(bounds.centerX(), bounds.centerY(), data.crs);
|
|
14760
|
-
toMeters = data.crs.to_meter;
|
|
15018
|
+
function parseScalebarNumber(str) {
|
|
15019
|
+
var fractionRxp = /^([0-9]+) ?\/ ?([0-9]+)/;
|
|
15020
|
+
var match, value;
|
|
15021
|
+
str = str.replace(/[\s]/g, '').replace(/,/g, '');
|
|
15022
|
+
if (fractionRxp.test(str)) {
|
|
15023
|
+
match = fractionRxp.exec(str);
|
|
15024
|
+
value = +match[1] / +match[2];
|
|
14761
15025
|
} else {
|
|
14762
|
-
|
|
14763
|
-
// A warning should be displayed when relevant furniture element is created
|
|
14764
|
-
k = 1;
|
|
14765
|
-
toMeters = 1;
|
|
15026
|
+
value = parseFloat(str);
|
|
14766
15027
|
}
|
|
14767
|
-
|
|
14768
|
-
return metersPerPixel;
|
|
15028
|
+
return value > 0 && value < Infinity ? value : NaN;
|
|
14769
15029
|
}
|
|
14770
15030
|
|
|
14771
|
-
|
|
14772
|
-
var lineWidth = 1,
|
|
14773
|
-
// inset stroke by half of line width
|
|
14774
|
-
off = lineWidth / 2,
|
|
14775
|
-
obj = importPolygon([[[off, off], [off, d.height - off],
|
|
14776
|
-
[d.width - off, d.height - off],
|
|
14777
|
-
[d.width - off, off], [off, off]]]);
|
|
14778
|
-
utils.extend(obj.properties, {
|
|
14779
|
-
fill: 'none',
|
|
14780
|
-
stroke: d.stroke || 'black',
|
|
14781
|
-
'stroke-width': d['stroke-width'] || lineWidth
|
|
14782
|
-
});
|
|
14783
|
-
return [obj];
|
|
14784
|
-
};
|
|
14785
|
-
|
|
14786
|
-
var Frame = /*#__PURE__*/Object.freeze({
|
|
15031
|
+
var Scalebar = /*#__PURE__*/Object.freeze({
|
|
14787
15032
|
__proto__: null,
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
findFrameLayer: findFrameLayer,
|
|
14793
|
-
getFrameLayerBounds: getFrameLayerBounds,
|
|
14794
|
-
getMapFrameMetersPerPixel: getMapFrameMetersPerPixel
|
|
15033
|
+
getScalebarLayer: getScalebarLayer,
|
|
15034
|
+
renderScalebar: renderScalebar,
|
|
15035
|
+
formatDistanceLabelAsMiles: formatDistanceLabelAsMiles,
|
|
15036
|
+
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
14795
15037
|
});
|
|
14796
15038
|
|
|
14797
15039
|
function transformDatasetToPixels(dataset, opts) {
|
|
14798
|
-
var
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
// TODO: handle options like width, height margin when a frame is present
|
|
14802
|
-
// TODO: check that aspect ratios match
|
|
14803
|
-
frameData = getFurnitureLayerData(frameLyr);
|
|
14804
|
-
bounds = new Bounds(frameData.bbox);
|
|
14805
|
-
bounds2 = new Bounds(0, 0, frameData.width, frameData.height);
|
|
14806
|
-
} else {
|
|
14807
|
-
bounds = getDatasetBounds(dataset);
|
|
14808
|
-
bounds2 = calcOutputSizeInPixels(bounds, opts);
|
|
14809
|
-
}
|
|
14810
|
-
fwd = bounds.getTransform(bounds2, opts.invert_y);
|
|
14811
|
-
transformPoints(dataset, function(x, y) {
|
|
14812
|
-
return fwd.transform(x, y);
|
|
14813
|
-
});
|
|
14814
|
-
return [Math.round(bounds2.width()), Math.round(bounds2.height()) || 1];
|
|
15040
|
+
var frame = getFrameData(dataset, opts);
|
|
15041
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15042
|
+
return [frame.width, frame.height];
|
|
14815
15043
|
}
|
|
14816
15044
|
|
|
14817
|
-
function
|
|
14818
|
-
var
|
|
14819
|
-
var
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
return margins.map(function(str) {
|
|
14824
|
-
var px = parseFloat(str);
|
|
14825
|
-
return isNaN(px) ? 1 : px; // 1 is default
|
|
15045
|
+
function fitDatasetToFrame(dataset, frame, opts) {
|
|
15046
|
+
var bounds = new Bounds(frame.bbox);
|
|
15047
|
+
var bounds2 = new Bounds(0, 0, frame.width, frame.height);
|
|
15048
|
+
var fwd = bounds.getTransform(bounds2, opts.invert_y);
|
|
15049
|
+
transformPoints(dataset, function(x, y) {
|
|
15050
|
+
return fwd.transform(x, y);
|
|
14826
15051
|
});
|
|
14827
15052
|
}
|
|
14828
15053
|
|
|
14829
|
-
// bounds: Bounds object containing bounds of content in geographic coordinates
|
|
14830
|
-
// returns Bounds object containing bounds of pixel output
|
|
14831
|
-
// side effect: bounds param is modified to match the output frame
|
|
14832
|
-
function calcOutputSizeInPixels(bounds, opts) {
|
|
14833
|
-
var padX = 0,
|
|
14834
|
-
padY = 0,
|
|
14835
|
-
offX = 0,
|
|
14836
|
-
offY = 0,
|
|
14837
|
-
width = bounds.width(),
|
|
14838
|
-
height = bounds.height(),
|
|
14839
|
-
margins = parseMarginOption(opts.margin),
|
|
14840
|
-
marginX = margins[0] + margins[2],
|
|
14841
|
-
marginY = margins[1] + margins[3],
|
|
14842
|
-
// TODO: add option to tweak alignment of content when both width and height are given
|
|
14843
|
-
wx = 0.5, // how padding is distributed horizontally (0: left aligned, 0.5: centered, 1: right aligned)
|
|
14844
|
-
wy = 0.5, // vertical padding distribution
|
|
14845
|
-
widthPx, heightPx, size, kx, ky;
|
|
14846
|
-
|
|
14847
|
-
if (opts.fit_bbox) {
|
|
14848
|
-
// scale + shift content to fit within a bbox
|
|
14849
|
-
offX = opts.fit_bbox[0];
|
|
14850
|
-
offY = opts.fit_bbox[1];
|
|
14851
|
-
widthPx = opts.fit_bbox[2] - offX;
|
|
14852
|
-
heightPx = opts.fit_bbox[3] - offY;
|
|
14853
|
-
if (width / height > widthPx / heightPx) {
|
|
14854
|
-
// data is wider than fit box...
|
|
14855
|
-
// scale the data to fit widthwise
|
|
14856
|
-
heightPx = 0;
|
|
14857
|
-
} else {
|
|
14858
|
-
widthPx = 0; // fit the data to the height
|
|
14859
|
-
}
|
|
14860
|
-
marginX = marginY = 0; // TODO: support margins
|
|
14861
|
-
|
|
14862
|
-
} else if (opts.svg_scale > 0) {
|
|
14863
|
-
// alternative to using a fixed width (e.g. when generating multiple files
|
|
14864
|
-
// at a consistent geographic scale)
|
|
14865
|
-
widthPx = width / opts.svg_scale + marginX;
|
|
14866
|
-
heightPx = 0;
|
|
14867
|
-
} else if (+opts.pixels) {
|
|
14868
|
-
size = getFrameSize(bounds, opts);
|
|
14869
|
-
widthPx = size[0];
|
|
14870
|
-
heightPx = size[1];
|
|
14871
|
-
} else {
|
|
14872
|
-
heightPx = opts.height || 0;
|
|
14873
|
-
widthPx = opts.width || (heightPx > 0 ? 0 : 800); // 800 is default width
|
|
14874
|
-
}
|
|
14875
|
-
|
|
14876
|
-
if (heightPx > 0) {
|
|
14877
|
-
// vertical meters per pixel to fit height param
|
|
14878
|
-
ky = (height || width || 1) / (heightPx - marginY);
|
|
14879
|
-
}
|
|
14880
|
-
if (widthPx > 0) {
|
|
14881
|
-
// horizontal meters per pixel to fit width param
|
|
14882
|
-
kx = (width || height || 1) / (widthPx - marginX);
|
|
14883
|
-
}
|
|
14884
|
-
|
|
14885
|
-
if (!widthPx) { // heightPx and ky are defined, set width to match
|
|
14886
|
-
kx = ky;
|
|
14887
|
-
widthPx = width > 0 ? marginX + width / kx : heightPx; // export square graphic if content has 0 width (reconsider this?)
|
|
14888
|
-
} else if (!heightPx) { // widthPx and kx are set, set height to match
|
|
14889
|
-
ky = kx;
|
|
14890
|
-
heightPx = height > 0 ? marginY + height / ky : widthPx;
|
|
14891
|
-
// limit height if max_height is defined
|
|
14892
|
-
if (opts.max_height > 0 && heightPx > opts.max_height) {
|
|
14893
|
-
ky = kx * heightPx / opts.max_height;
|
|
14894
|
-
heightPx = opts.max_height;
|
|
14895
|
-
}
|
|
14896
|
-
}
|
|
14897
|
-
|
|
14898
|
-
if (kx > ky) { // content is wide -- need to pad vertically
|
|
14899
|
-
ky = kx;
|
|
14900
|
-
padY = ky * (heightPx - marginY) - height;
|
|
14901
|
-
} else if (ky > kx) { // content is tall -- need to pad horizontally
|
|
14902
|
-
kx = ky;
|
|
14903
|
-
padX = kx * (widthPx - marginX) - width;
|
|
14904
|
-
}
|
|
14905
|
-
|
|
14906
|
-
bounds.padBounds(
|
|
14907
|
-
margins[0] * kx + padX * wx,
|
|
14908
|
-
margins[1] * ky + padY * wy,
|
|
14909
|
-
margins[2] * kx + padX * (1 - wx),
|
|
14910
|
-
margins[3] * ky + padY * (1 - wy));
|
|
14911
|
-
|
|
14912
|
-
if (!(widthPx > 0 && heightPx > 0)) {
|
|
14913
|
-
error("Missing valid height and width parameters");
|
|
14914
|
-
}
|
|
14915
|
-
if (!(kx === ky && kx > 0)) {
|
|
14916
|
-
error("Missing valid margin parameters");
|
|
14917
|
-
}
|
|
14918
|
-
|
|
14919
|
-
return new Bounds(offX, offY, widthPx + offX, heightPx + offY);
|
|
14920
|
-
}
|
|
14921
|
-
|
|
14922
15054
|
var PixelTransform = /*#__PURE__*/Object.freeze({
|
|
14923
15055
|
__proto__: null,
|
|
14924
15056
|
transformDatasetToPixels: transformDatasetToPixels,
|
|
14925
|
-
|
|
14926
|
-
calcOutputSizeInPixels: calcOutputSizeInPixels
|
|
15057
|
+
fitDatasetToFrame: fitDatasetToFrame
|
|
14927
15058
|
});
|
|
14928
15059
|
|
|
14929
15060
|
function stringify(obj) {
|
|
@@ -14980,6 +15111,9 @@
|
|
|
14980
15111
|
if (key == 'href') {
|
|
14981
15112
|
key = 'xlink:href';
|
|
14982
15113
|
}
|
|
15114
|
+
if (key == 'css') {
|
|
15115
|
+
key = 'style'; // inline style
|
|
15116
|
+
}
|
|
14983
15117
|
return memo + ' ' + key + '="' + stringEscape(strval) + '"';
|
|
14984
15118
|
}, '');
|
|
14985
15119
|
}
|
|
@@ -15147,12 +15281,11 @@
|
|
|
15147
15281
|
}
|
|
15148
15282
|
*/
|
|
15149
15283
|
|
|
15150
|
-
//
|
|
15151
15284
|
//
|
|
15152
15285
|
function exportSVG(dataset, opts) {
|
|
15153
15286
|
var namespace = 'xmlns="http://www.w3.org/2000/svg"';
|
|
15154
15287
|
var defs = [];
|
|
15155
|
-
var
|
|
15288
|
+
var frame, svg, layers;
|
|
15156
15289
|
var style = '';
|
|
15157
15290
|
|
|
15158
15291
|
// kludge for map keys
|
|
@@ -15169,15 +15302,27 @@
|
|
|
15169
15302
|
} else {
|
|
15170
15303
|
dataset = copyDataset(dataset); // Modify a copy of the dataset
|
|
15171
15304
|
}
|
|
15305
|
+
|
|
15172
15306
|
// invert_y setting for screen coordinates and geojson polygon generation
|
|
15173
15307
|
utils.extend(opts, {invert_y: true});
|
|
15174
|
-
|
|
15308
|
+
frame = getFrameData(dataset, opts);
|
|
15309
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15310
|
+
setCoordinatePrecision(dataset, opts.precision || 0.0001);
|
|
15175
15311
|
|
|
15176
15312
|
// error if one or more svg_data fields are not present in any layers
|
|
15177
15313
|
if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
|
|
15178
15314
|
|
|
15179
|
-
|
|
15180
|
-
|
|
15315
|
+
layers = dataset.layers;
|
|
15316
|
+
if (opts.scalebar) {
|
|
15317
|
+
layers.push(getScalebarLayer({})); // default options
|
|
15318
|
+
}
|
|
15319
|
+
svg = layers.map(function(lyr) {
|
|
15320
|
+
var obj;
|
|
15321
|
+
if (layerHasFurniture(lyr)) {
|
|
15322
|
+
obj = exportFurnitureForSVG(lyr, frame, opts);
|
|
15323
|
+
} else {
|
|
15324
|
+
obj = exportLayerForSVG(lyr, dataset, opts);
|
|
15325
|
+
}
|
|
15181
15326
|
convertPropertiesToDefinitions(obj, defs);
|
|
15182
15327
|
return stringify(obj);
|
|
15183
15328
|
}).join('\n');
|
|
@@ -15202,39 +15347,25 @@
|
|
|
15202
15347
|
<svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s" ${lineProps}>${style}
|
|
15203
15348
|
${svg}
|
|
15204
15349
|
</svg>`;
|
|
15205
|
-
svg = utils.format(template,
|
|
15350
|
+
svg = utils.format(template, frame.width, frame.height, 0, 0, frame.width, frame.height);
|
|
15206
15351
|
return [{
|
|
15207
15352
|
content: svg,
|
|
15208
15353
|
filename: opts.file || getOutputFileBase(dataset) + '.svg'
|
|
15209
15354
|
}];
|
|
15210
15355
|
}
|
|
15211
15356
|
|
|
15212
|
-
function
|
|
15213
|
-
var
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
return size;
|
|
15357
|
+
function exportFurnitureForSVG(lyr, frame, opts) {
|
|
15358
|
+
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15359
|
+
layerObj.children = importFurniture(getFurnitureLayerData(lyr), frame);
|
|
15360
|
+
return layerObj;
|
|
15217
15361
|
}
|
|
15218
15362
|
|
|
15219
15363
|
function exportLayerForSVG(lyr, dataset, opts) {
|
|
15220
15364
|
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15221
|
-
|
|
15222
|
-
layerObj.children = exportFurnitureForSVG(lyr, dataset, opts);
|
|
15223
|
-
} else {
|
|
15224
|
-
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15225
|
-
}
|
|
15365
|
+
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15226
15366
|
return layerObj;
|
|
15227
15367
|
}
|
|
15228
15368
|
|
|
15229
|
-
function exportFurnitureForSVG(lyr, dataset, opts) {
|
|
15230
|
-
var frameLyr = findFrameLayerInDataset(dataset);
|
|
15231
|
-
var frameData;
|
|
15232
|
-
if (!frameLyr) return [];
|
|
15233
|
-
frameData = getFurnitureLayerData(frameLyr);
|
|
15234
|
-
frameData.crs = getDatasetCRS(dataset); // required by e.g. scalebar
|
|
15235
|
-
return importFurniture(getFurnitureLayerData(lyr), frameData);
|
|
15236
|
-
}
|
|
15237
|
-
|
|
15238
15369
|
function exportSymbolsForSVG(lyr, dataset, opts) {
|
|
15239
15370
|
// TODO: convert geojson features one at a time
|
|
15240
15371
|
var d = utils.defaults({layers: [lyr]}, dataset);
|
|
@@ -15389,6 +15520,7 @@ ${svg}
|
|
|
15389
15520
|
var Svg = /*#__PURE__*/Object.freeze({
|
|
15390
15521
|
__proto__: null,
|
|
15391
15522
|
exportSVG: exportSVG,
|
|
15523
|
+
exportFurnitureForSVG: exportFurnitureForSVG,
|
|
15392
15524
|
exportLayerForSVG: exportLayerForSVG,
|
|
15393
15525
|
validateSvgDataFields: validateSvgDataFields,
|
|
15394
15526
|
exportDataAttributesForSVG: exportDataAttributesForSVG,
|
|
@@ -19284,6 +19416,10 @@ ${svg}
|
|
|
19284
19416
|
.option('id-prefix', {
|
|
19285
19417
|
describe: '[SVG] prefix for namespacing layer and feature ids'
|
|
19286
19418
|
})
|
|
19419
|
+
.option('scalebar', {
|
|
19420
|
+
type: 'flag',
|
|
19421
|
+
// describe: '[SVG] add a scalebar showing scale at the center of the map'
|
|
19422
|
+
})
|
|
19287
19423
|
.option('delimiter', {
|
|
19288
19424
|
describe: '[CSV] field delimiter'
|
|
19289
19425
|
})
|
|
@@ -32534,6 +32670,80 @@ ${svg}
|
|
|
32534
32670
|
return parsed[0];
|
|
32535
32671
|
}
|
|
32536
32672
|
|
|
32673
|
+
/* require mapshaper-rectangle, mapshaper-furniture */
|
|
32674
|
+
|
|
32675
|
+
cmd.frame = function(catalog, source, opts) {
|
|
32676
|
+
var size, bounds, tmp, dataset;
|
|
32677
|
+
if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
|
|
32678
|
+
stop("Missing a width or area");
|
|
32679
|
+
}
|
|
32680
|
+
if (opts.width && opts.height) {
|
|
32681
|
+
opts = utils.extend({}, opts);
|
|
32682
|
+
// Height is a string containing either a number or a
|
|
32683
|
+
// comma-sep. pair of numbers (range); here we convert height to
|
|
32684
|
+
// an aspect-ratio parameter for the rectangle() function
|
|
32685
|
+
opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
|
|
32686
|
+
// TODO: currently returns max,min aspect ratio, should return in min,max order
|
|
32687
|
+
// (rectangle() function should handle max,min argument correctly now anyway)
|
|
32688
|
+
}
|
|
32689
|
+
tmp = cmd.rectangle(source, opts);
|
|
32690
|
+
bounds = getDatasetBounds(tmp);
|
|
32691
|
+
if (probablyDecimalDegreeBounds(bounds)) {
|
|
32692
|
+
stop('Frames require projected, not geographical coordinates');
|
|
32693
|
+
} else if (!getDatasetCRS(tmp)) {
|
|
32694
|
+
message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
|
|
32695
|
+
}
|
|
32696
|
+
size = getFrameSize(bounds, opts);
|
|
32697
|
+
if (size[0] > 0 === false) {
|
|
32698
|
+
stop('Missing a valid frame width');
|
|
32699
|
+
}
|
|
32700
|
+
if (size[1] > 0 === false) {
|
|
32701
|
+
stop('Missing a valid frame height');
|
|
32702
|
+
}
|
|
32703
|
+
dataset = {info: {}, layers:[{
|
|
32704
|
+
name: opts.name || 'frame',
|
|
32705
|
+
data: new DataTable([{
|
|
32706
|
+
width: size[0],
|
|
32707
|
+
height: size[1],
|
|
32708
|
+
bbox: bounds.toArray(),
|
|
32709
|
+
type: 'frame'
|
|
32710
|
+
}])
|
|
32711
|
+
}]};
|
|
32712
|
+
catalog.addDataset(dataset);
|
|
32713
|
+
};
|
|
32714
|
+
|
|
32715
|
+
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
32716
|
+
function getAspectRatioArg(widthArg, heightArg) {
|
|
32717
|
+
// heightArg is a string containing either a number or a
|
|
32718
|
+
// comma-sep. pair of numbers (range);
|
|
32719
|
+
return heightArg.split(',').map(function(opt) {
|
|
32720
|
+
var height = Number(opt),
|
|
32721
|
+
width = Number(widthArg);
|
|
32722
|
+
if (!opt) return '';
|
|
32723
|
+
return width / height;
|
|
32724
|
+
}).reverse().join(',');
|
|
32725
|
+
}
|
|
32726
|
+
|
|
32727
|
+
furnitureRenderers.frame = function(d) {
|
|
32728
|
+
var lineWidth = 1,
|
|
32729
|
+
// inset stroke by half of line width
|
|
32730
|
+
off = lineWidth / 2,
|
|
32731
|
+
obj = importPolygon([[[off, off], [off, d.height - off],
|
|
32732
|
+
[d.width - off, d.height - off],
|
|
32733
|
+
[d.width - off, off], [off, off]]]);
|
|
32734
|
+
utils.extend(obj.properties, {
|
|
32735
|
+
fill: 'none',
|
|
32736
|
+
stroke: d.stroke || 'black',
|
|
32737
|
+
'stroke-width': d['stroke-width'] || lineWidth
|
|
32738
|
+
});
|
|
32739
|
+
return [obj];
|
|
32740
|
+
};
|
|
32741
|
+
|
|
32742
|
+
var Frame = /*#__PURE__*/Object.freeze({
|
|
32743
|
+
__proto__: null,
|
|
32744
|
+
getAspectRatioArg: getAspectRatioArg
|
|
32745
|
+
});
|
|
32746
|
+
|
|
32537
32747
|
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
32538
32748
|
if (!layerHasGeometry(lyr)) {
|
|
32539
32749
|
stop("Layer is missing geometry");
|
|
@@ -37421,153 +37631,6 @@ ${svg}
|
|
|
37421
37631
|
}
|
|
37422
37632
|
};
|
|
37423
37633
|
|
|
37424
|
-
cmd.scalebar = function(catalog, opts) {
|
|
37425
|
-
var frame = findFrameDataset(catalog);
|
|
37426
|
-
var obj, lyr;
|
|
37427
|
-
if (!frame) {
|
|
37428
|
-
stop('Missing a map frame');
|
|
37429
|
-
}
|
|
37430
|
-
obj = utils.defaults({type: 'scalebar'}, opts);
|
|
37431
|
-
lyr = {
|
|
37432
|
-
name: opts.name || 'scalebar',
|
|
37433
|
-
data: new DataTable([obj])
|
|
37434
|
-
};
|
|
37435
|
-
frame.layers.push(lyr);
|
|
37436
|
-
};
|
|
37437
|
-
|
|
37438
|
-
// TODO: generalize to other kinds of furniture as they are developed
|
|
37439
|
-
function getScalebarPosition(d) {
|
|
37440
|
-
var opts = { // defaults
|
|
37441
|
-
valign: 'top',
|
|
37442
|
-
halign: 'left',
|
|
37443
|
-
voffs: 10,
|
|
37444
|
-
hoffs: 10
|
|
37445
|
-
};
|
|
37446
|
-
if (+d.left > 0) {
|
|
37447
|
-
opts.hoffs = +d.left;
|
|
37448
|
-
}
|
|
37449
|
-
if (+d.top > 0) {
|
|
37450
|
-
opts.voffs = +d.top;
|
|
37451
|
-
}
|
|
37452
|
-
if (+d.right > 0) {
|
|
37453
|
-
opts.hoffs = +d.right;
|
|
37454
|
-
opts.halign = 'right';
|
|
37455
|
-
}
|
|
37456
|
-
if (+d.bottom > 0) {
|
|
37457
|
-
opts.voffs = +d.bottom;
|
|
37458
|
-
opts.valign = 'bottom';
|
|
37459
|
-
}
|
|
37460
|
-
return opts;
|
|
37461
|
-
}
|
|
37462
|
-
|
|
37463
|
-
furnitureRenderers.scalebar = function(d, frame) {
|
|
37464
|
-
var pos = getScalebarPosition(d);
|
|
37465
|
-
var metersPerPx = getMapFrameMetersPerPixel(frame);
|
|
37466
|
-
var label = d.label_text || getAutoScalebarLabel(frame.width, metersPerPx);
|
|
37467
|
-
var scalebarKm = parseScalebarLabelToKm(label);
|
|
37468
|
-
var barHeight = 3;
|
|
37469
|
-
var labelOffs = 4;
|
|
37470
|
-
var fontSize = +d.font_size || 13;
|
|
37471
|
-
var width = Math.round(scalebarKm / metersPerPx * 1000);
|
|
37472
|
-
var height = Math.round(barHeight + labelOffs + fontSize * 0.8);
|
|
37473
|
-
var labelPos = d.label_position == 'top' ? 'top' : 'bottom';
|
|
37474
|
-
var anchorX = pos.halign == 'left' ? 0 : width;
|
|
37475
|
-
var anchorY = barHeight + labelOffs;
|
|
37476
|
-
var dx = pos.halign == 'right' ? frame.width - width - pos.hoffs : pos.hoffs;
|
|
37477
|
-
var dy = pos.valign == 'bottom' ? frame.height - height - pos.voffs : pos.voffs;
|
|
37478
|
-
|
|
37479
|
-
if (labelPos == 'top') {
|
|
37480
|
-
anchorY = -labelOffs;
|
|
37481
|
-
dy += Math.round(labelOffs + fontSize * 0.8);
|
|
37482
|
-
}
|
|
37483
|
-
|
|
37484
|
-
if (width > 0 === false) {
|
|
37485
|
-
stop("Null scalebar length");
|
|
37486
|
-
}
|
|
37487
|
-
var barObj = {
|
|
37488
|
-
tag: 'rect',
|
|
37489
|
-
properties: {
|
|
37490
|
-
fill: 'black',
|
|
37491
|
-
x: 0,
|
|
37492
|
-
y: 0,
|
|
37493
|
-
width: width,
|
|
37494
|
-
height: barHeight
|
|
37495
|
-
}
|
|
37496
|
-
};
|
|
37497
|
-
var labelOpts = {
|
|
37498
|
-
'label-text': label,
|
|
37499
|
-
'font-size': fontSize,
|
|
37500
|
-
'text-anchor': pos.halign == 'left' ? 'start': 'end',
|
|
37501
|
-
'dominant-baseline': labelPos == 'top' ? 'auto' : 'hanging'
|
|
37502
|
-
//// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
|
|
37503
|
-
// 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
|
|
37504
|
-
// so I'm using 'hanging' and 'auto', which seem to be well supported.
|
|
37505
|
-
// downside: requires a kludgy multiplier to calculate scalebar height (see above)
|
|
37506
|
-
};
|
|
37507
|
-
var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY);
|
|
37508
|
-
var g = {
|
|
37509
|
-
tag: 'g',
|
|
37510
|
-
children: [barObj, labelObj],
|
|
37511
|
-
properties: {
|
|
37512
|
-
transform: 'translate(' + dx + ' ' + dy + ')'
|
|
37513
|
-
}
|
|
37514
|
-
};
|
|
37515
|
-
return [g];
|
|
37516
|
-
};
|
|
37517
|
-
|
|
37518
|
-
function getAutoScalebarLabel(mapWidth, metersPerPx) {
|
|
37519
|
-
var minWidth = 100; // TODO: vary min size based on map width
|
|
37520
|
-
var minKm = metersPerPx * minWidth / 1000;
|
|
37521
|
-
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 ' +
|
|
37522
|
-
'100 150 200 250 300 350 400 500 750 1,000 1,200 1,500 2,000 ' +
|
|
37523
|
-
'2,500 3,000 4,000 5,000').split(' ');
|
|
37524
|
-
return options.reduce(function(memo, str) {
|
|
37525
|
-
if (memo) return memo;
|
|
37526
|
-
var label = formatDistanceLabelAsMiles(str);
|
|
37527
|
-
if (parseScalebarLabelToKm(label) > minKm) {
|
|
37528
|
-
return label;
|
|
37529
|
-
}
|
|
37530
|
-
}, null) || '';
|
|
37531
|
-
}
|
|
37532
|
-
|
|
37533
|
-
function formatDistanceLabelAsMiles(str) {
|
|
37534
|
-
var num = parseScalebarNumber(str);
|
|
37535
|
-
return str + (num > 1 ? ' MILES' : ' MILE');
|
|
37536
|
-
}
|
|
37537
|
-
|
|
37538
|
-
// See test/mapshaper-scalebar.js for examples of supported formats
|
|
37539
|
-
function parseScalebarLabelToKm(str) {
|
|
37540
|
-
var units = parseScalebarUnits(str);
|
|
37541
|
-
var value = parseScalebarNumber(str);
|
|
37542
|
-
if (!units || !value) return NaN;
|
|
37543
|
-
return units == 'mile' ? value * 1.60934 : value;
|
|
37544
|
-
}
|
|
37545
|
-
|
|
37546
|
-
function parseScalebarUnits(str) {
|
|
37547
|
-
var isMiles = /miles?$/.test(str.toLowerCase());
|
|
37548
|
-
var isKm = /(km|kilometers?|kilometres?)$/.test(str.toLowerCase());
|
|
37549
|
-
return isMiles && 'mile' || isKm && 'km' || '';
|
|
37550
|
-
}
|
|
37551
|
-
|
|
37552
|
-
function parseScalebarNumber(str) {
|
|
37553
|
-
var fractionRxp = /^([0-9]+) ?\/ ?([0-9]+)/;
|
|
37554
|
-
var match, value;
|
|
37555
|
-
str = str.replace(/[\s]/g, '').replace(/,/g, '');
|
|
37556
|
-
if (fractionRxp.test(str)) {
|
|
37557
|
-
match = fractionRxp.exec(str);
|
|
37558
|
-
value = +match[1] / +match[2];
|
|
37559
|
-
} else {
|
|
37560
|
-
value = parseFloat(str);
|
|
37561
|
-
}
|
|
37562
|
-
return value > 0 && value < Infinity ? value : NaN;
|
|
37563
|
-
}
|
|
37564
|
-
|
|
37565
|
-
var Scalebar = /*#__PURE__*/Object.freeze({
|
|
37566
|
-
__proto__: null,
|
|
37567
|
-
formatDistanceLabelAsMiles: formatDistanceLabelAsMiles,
|
|
37568
|
-
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
37569
|
-
});
|
|
37570
|
-
|
|
37571
37634
|
cmd.shape = function(targetDataset, opts) {
|
|
37572
37635
|
var geojson, dataset;
|
|
37573
37636
|
if (opts.coordinates) {
|
|
@@ -40869,6 +40932,7 @@ ${svg}
|
|
|
40869
40932
|
FileTypes,
|
|
40870
40933
|
FilterGeom,
|
|
40871
40934
|
Frame,
|
|
40935
|
+
FrameData,
|
|
40872
40936
|
Furniture,
|
|
40873
40937
|
Geodesic,
|
|
40874
40938
|
GeojsonExport,
|