mapshaper 0.5.113 → 0.5.116
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 +9 -0
- package/mapshaper.js +630 -558
- package/package.json +1 -1
- package/www/mapshaper-gui.js +2 -2
- package/www/mapshaper.js +630 -558
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.5.
|
|
3
|
+
var VERSION = "0.5.116";
|
|
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,186 @@
|
|
|
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
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14927
|
+
furnitureRenderers.scalebar = renderScalebar;
|
|
14928
|
+
|
|
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 (!frame.crs) {
|
|
14946
|
+
message('Unable to render a scalebar: unknown CRS.');
|
|
14947
|
+
return [];
|
|
14707
14948
|
}
|
|
14708
|
-
height = width / aspectRatio;
|
|
14709
|
-
return [Math.round(width), Math.round(height)];
|
|
14710
|
-
}
|
|
14711
14949
|
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
// TODO: check for coordinate issues (non-intersection with other layers, etc)
|
|
14716
|
-
return getFrameLayerBounds(frameLyr);
|
|
14950
|
+
if (labelPos == 'top') {
|
|
14951
|
+
anchorY = -labelOffs;
|
|
14952
|
+
dy += Math.round(labelOffs + fontSize * 0.8);
|
|
14717
14953
|
}
|
|
14718
|
-
return getDatasetBounds(dataset);
|
|
14719
|
-
}
|
|
14720
14954
|
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14955
|
+
if (width > 0 === false) {
|
|
14956
|
+
stop("Null scalebar length");
|
|
14957
|
+
}
|
|
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];
|
|
14724
14987
|
}
|
|
14725
14988
|
|
|
14726
|
-
function
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
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) || '';
|
|
14730
15003
|
}
|
|
14731
15004
|
|
|
14732
|
-
function
|
|
14733
|
-
var
|
|
14734
|
-
|
|
14735
|
-
});
|
|
14736
|
-
return target ? target.dataset : null;
|
|
15005
|
+
function formatDistanceLabelAsMiles(str) {
|
|
15006
|
+
var num = parseScalebarNumber(str);
|
|
15007
|
+
return str + (num > 1 ? ' MILES' : ' MILE');
|
|
14737
15008
|
}
|
|
14738
15009
|
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
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;
|
|
14744
15016
|
}
|
|
14745
15017
|
|
|
14746
|
-
function
|
|
14747
|
-
|
|
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' || '';
|
|
14748
15022
|
}
|
|
14749
15023
|
|
|
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;
|
|
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];
|
|
14761
15031
|
} else {
|
|
14762
|
-
|
|
14763
|
-
// A warning should be displayed when relevant furniture element is created
|
|
14764
|
-
k = 1;
|
|
14765
|
-
toMeters = 1;
|
|
15032
|
+
value = parseFloat(str);
|
|
14766
15033
|
}
|
|
14767
|
-
|
|
14768
|
-
return metersPerPixel;
|
|
15034
|
+
return value > 0 && value < Infinity ? value : NaN;
|
|
14769
15035
|
}
|
|
14770
15036
|
|
|
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({
|
|
15037
|
+
var Scalebar = /*#__PURE__*/Object.freeze({
|
|
14787
15038
|
__proto__: null,
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
findFrameLayer: findFrameLayer,
|
|
14793
|
-
getFrameLayerBounds: getFrameLayerBounds,
|
|
14794
|
-
getMapFrameMetersPerPixel: getMapFrameMetersPerPixel
|
|
15039
|
+
getScalebarLayer: getScalebarLayer,
|
|
15040
|
+
renderScalebar: renderScalebar,
|
|
15041
|
+
formatDistanceLabelAsMiles: formatDistanceLabelAsMiles,
|
|
15042
|
+
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
14795
15043
|
});
|
|
14796
15044
|
|
|
14797
15045
|
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];
|
|
15046
|
+
var frame = getFrameData(dataset, opts);
|
|
15047
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15048
|
+
return [frame.width, frame.height];
|
|
14815
15049
|
}
|
|
14816
15050
|
|
|
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
|
|
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);
|
|
14826
15057
|
});
|
|
14827
15058
|
}
|
|
14828
15059
|
|
|
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
15060
|
var PixelTransform = /*#__PURE__*/Object.freeze({
|
|
14923
15061
|
__proto__: null,
|
|
14924
15062
|
transformDatasetToPixels: transformDatasetToPixels,
|
|
14925
|
-
|
|
14926
|
-
calcOutputSizeInPixels: calcOutputSizeInPixels
|
|
15063
|
+
fitDatasetToFrame: fitDatasetToFrame
|
|
14927
15064
|
});
|
|
14928
15065
|
|
|
14929
15066
|
function stringify(obj) {
|
|
@@ -14980,6 +15117,9 @@
|
|
|
14980
15117
|
if (key == 'href') {
|
|
14981
15118
|
key = 'xlink:href';
|
|
14982
15119
|
}
|
|
15120
|
+
if (key == 'css') {
|
|
15121
|
+
key = 'style'; // inline style
|
|
15122
|
+
}
|
|
14983
15123
|
return memo + ' ' + key + '="' + stringEscape(strval) + '"';
|
|
14984
15124
|
}, '');
|
|
14985
15125
|
}
|
|
@@ -15147,12 +15287,11 @@
|
|
|
15147
15287
|
}
|
|
15148
15288
|
*/
|
|
15149
15289
|
|
|
15150
|
-
//
|
|
15151
15290
|
//
|
|
15152
15291
|
function exportSVG(dataset, opts) {
|
|
15153
15292
|
var namespace = 'xmlns="http://www.w3.org/2000/svg"';
|
|
15154
15293
|
var defs = [];
|
|
15155
|
-
var
|
|
15294
|
+
var frame, svg, layers;
|
|
15156
15295
|
var style = '';
|
|
15157
15296
|
|
|
15158
15297
|
// kludge for map keys
|
|
@@ -15169,15 +15308,27 @@
|
|
|
15169
15308
|
} else {
|
|
15170
15309
|
dataset = copyDataset(dataset); // Modify a copy of the dataset
|
|
15171
15310
|
}
|
|
15311
|
+
|
|
15172
15312
|
// invert_y setting for screen coordinates and geojson polygon generation
|
|
15173
15313
|
utils.extend(opts, {invert_y: true});
|
|
15174
|
-
|
|
15314
|
+
frame = getFrameData(dataset, opts);
|
|
15315
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15316
|
+
setCoordinatePrecision(dataset, opts.precision || 0.0001);
|
|
15175
15317
|
|
|
15176
15318
|
// error if one or more svg_data fields are not present in any layers
|
|
15177
15319
|
if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
|
|
15178
15320
|
|
|
15179
|
-
|
|
15180
|
-
|
|
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
|
+
}
|
|
15181
15332
|
convertPropertiesToDefinitions(obj, defs);
|
|
15182
15333
|
return stringify(obj);
|
|
15183
15334
|
}).join('\n');
|
|
@@ -15202,39 +15353,25 @@
|
|
|
15202
15353
|
<svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s" ${lineProps}>${style}
|
|
15203
15354
|
${svg}
|
|
15204
15355
|
</svg>`;
|
|
15205
|
-
svg = utils.format(template,
|
|
15356
|
+
svg = utils.format(template, frame.width, frame.height, 0, 0, frame.width, frame.height);
|
|
15206
15357
|
return [{
|
|
15207
15358
|
content: svg,
|
|
15208
15359
|
filename: opts.file || getOutputFileBase(dataset) + '.svg'
|
|
15209
15360
|
}];
|
|
15210
15361
|
}
|
|
15211
15362
|
|
|
15212
|
-
function
|
|
15213
|
-
var
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
return size;
|
|
15363
|
+
function exportFurnitureForSVG(lyr, frame, opts) {
|
|
15364
|
+
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15365
|
+
layerObj.children = importFurniture(getFurnitureLayerData(lyr), frame);
|
|
15366
|
+
return layerObj;
|
|
15217
15367
|
}
|
|
15218
15368
|
|
|
15219
15369
|
function exportLayerForSVG(lyr, dataset, opts) {
|
|
15220
15370
|
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15221
|
-
|
|
15222
|
-
layerObj.children = exportFurnitureForSVG(lyr, dataset, opts);
|
|
15223
|
-
} else {
|
|
15224
|
-
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15225
|
-
}
|
|
15371
|
+
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15226
15372
|
return layerObj;
|
|
15227
15373
|
}
|
|
15228
15374
|
|
|
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
15375
|
function exportSymbolsForSVG(lyr, dataset, opts) {
|
|
15239
15376
|
// TODO: convert geojson features one at a time
|
|
15240
15377
|
var d = utils.defaults({layers: [lyr]}, dataset);
|
|
@@ -15389,6 +15526,7 @@ ${svg}
|
|
|
15389
15526
|
var Svg = /*#__PURE__*/Object.freeze({
|
|
15390
15527
|
__proto__: null,
|
|
15391
15528
|
exportSVG: exportSVG,
|
|
15529
|
+
exportFurnitureForSVG: exportFurnitureForSVG,
|
|
15392
15530
|
exportLayerForSVG: exportLayerForSVG,
|
|
15393
15531
|
validateSvgDataFields: validateSvgDataFields,
|
|
15394
15532
|
exportDataAttributesForSVG: exportDataAttributesForSVG,
|
|
@@ -15868,7 +16006,9 @@ ${svg}
|
|
|
15868
16006
|
|
|
15869
16007
|
function initStringField(info, arr, name, encoding) {
|
|
15870
16008
|
var formatter = encoding == 'ascii' ? encodeValueAsAscii : getStringWriterEncoded(encoding);
|
|
15871
|
-
|
|
16009
|
+
// Set minimum field size to 1 byte, for interoperability with PostGIS
|
|
16010
|
+
// (see https://github.com/mbloch/mapshaper/issues/541)
|
|
16011
|
+
var size = 1;
|
|
15872
16012
|
var truncated = 0;
|
|
15873
16013
|
var buffers = arr.map(function(rec) {
|
|
15874
16014
|
var strval = convertValueToString(rec[name]);
|
|
@@ -19284,6 +19424,10 @@ ${svg}
|
|
|
19284
19424
|
.option('id-prefix', {
|
|
19285
19425
|
describe: '[SVG] prefix for namespacing layer and feature ids'
|
|
19286
19426
|
})
|
|
19427
|
+
.option('scalebar', {
|
|
19428
|
+
type: 'flag',
|
|
19429
|
+
// describe: '[SVG] add a scalebar showing scale at the center of the map'
|
|
19430
|
+
})
|
|
19287
19431
|
.option('delimiter', {
|
|
19288
19432
|
describe: '[CSV] field delimiter'
|
|
19289
19433
|
})
|
|
@@ -32534,6 +32678,80 @@ ${svg}
|
|
|
32534
32678
|
return parsed[0];
|
|
32535
32679
|
}
|
|
32536
32680
|
|
|
32681
|
+
/* require mapshaper-rectangle, mapshaper-furniture */
|
|
32682
|
+
|
|
32683
|
+
cmd.frame = function(catalog, source, opts) {
|
|
32684
|
+
var size, bounds, tmp, dataset;
|
|
32685
|
+
if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
|
|
32686
|
+
stop("Missing a width or area");
|
|
32687
|
+
}
|
|
32688
|
+
if (opts.width && opts.height) {
|
|
32689
|
+
opts = utils.extend({}, opts);
|
|
32690
|
+
// Height is a string containing either a number or a
|
|
32691
|
+
// comma-sep. pair of numbers (range); here we convert height to
|
|
32692
|
+
// an aspect-ratio parameter for the rectangle() function
|
|
32693
|
+
opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
|
|
32694
|
+
// TODO: currently returns max,min aspect ratio, should return in min,max order
|
|
32695
|
+
// (rectangle() function should handle max,min argument correctly now anyway)
|
|
32696
|
+
}
|
|
32697
|
+
tmp = cmd.rectangle(source, opts);
|
|
32698
|
+
bounds = getDatasetBounds(tmp);
|
|
32699
|
+
if (probablyDecimalDegreeBounds(bounds)) {
|
|
32700
|
+
stop('Frames require projected, not geographical coordinates');
|
|
32701
|
+
} else if (!getDatasetCRS(tmp)) {
|
|
32702
|
+
message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
|
|
32703
|
+
}
|
|
32704
|
+
size = getFrameSize(bounds, opts);
|
|
32705
|
+
if (size[0] > 0 === false) {
|
|
32706
|
+
stop('Missing a valid frame width');
|
|
32707
|
+
}
|
|
32708
|
+
if (size[1] > 0 === false) {
|
|
32709
|
+
stop('Missing a valid frame height');
|
|
32710
|
+
}
|
|
32711
|
+
dataset = {info: {}, layers:[{
|
|
32712
|
+
name: opts.name || 'frame',
|
|
32713
|
+
data: new DataTable([{
|
|
32714
|
+
width: size[0],
|
|
32715
|
+
height: size[1],
|
|
32716
|
+
bbox: bounds.toArray(),
|
|
32717
|
+
type: 'frame'
|
|
32718
|
+
}])
|
|
32719
|
+
}]};
|
|
32720
|
+
catalog.addDataset(dataset);
|
|
32721
|
+
};
|
|
32722
|
+
|
|
32723
|
+
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
32724
|
+
function getAspectRatioArg(widthArg, heightArg) {
|
|
32725
|
+
// heightArg is a string containing either a number or a
|
|
32726
|
+
// comma-sep. pair of numbers (range);
|
|
32727
|
+
return heightArg.split(',').map(function(opt) {
|
|
32728
|
+
var height = Number(opt),
|
|
32729
|
+
width = Number(widthArg);
|
|
32730
|
+
if (!opt) return '';
|
|
32731
|
+
return width / height;
|
|
32732
|
+
}).reverse().join(',');
|
|
32733
|
+
}
|
|
32734
|
+
|
|
32735
|
+
furnitureRenderers.frame = function(d) {
|
|
32736
|
+
var lineWidth = 1,
|
|
32737
|
+
// inset stroke by half of line width
|
|
32738
|
+
off = lineWidth / 2,
|
|
32739
|
+
obj = importPolygon([[[off, off], [off, d.height - off],
|
|
32740
|
+
[d.width - off, d.height - off],
|
|
32741
|
+
[d.width - off, off], [off, off]]]);
|
|
32742
|
+
utils.extend(obj.properties, {
|
|
32743
|
+
fill: 'none',
|
|
32744
|
+
stroke: d.stroke || 'black',
|
|
32745
|
+
'stroke-width': d['stroke-width'] || lineWidth
|
|
32746
|
+
});
|
|
32747
|
+
return [obj];
|
|
32748
|
+
};
|
|
32749
|
+
|
|
32750
|
+
var Frame = /*#__PURE__*/Object.freeze({
|
|
32751
|
+
__proto__: null,
|
|
32752
|
+
getAspectRatioArg: getAspectRatioArg
|
|
32753
|
+
});
|
|
32754
|
+
|
|
32537
32755
|
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
32538
32756
|
if (!layerHasGeometry(lyr)) {
|
|
32539
32757
|
stop("Layer is missing geometry");
|
|
@@ -37421,153 +37639,6 @@ ${svg}
|
|
|
37421
37639
|
}
|
|
37422
37640
|
};
|
|
37423
37641
|
|
|
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
37642
|
cmd.shape = function(targetDataset, opts) {
|
|
37572
37643
|
var geojson, dataset;
|
|
37573
37644
|
if (opts.coordinates) {
|
|
@@ -40869,6 +40940,7 @@ ${svg}
|
|
|
40869
40940
|
FileTypes,
|
|
40870
40941
|
FilterGeom,
|
|
40871
40942
|
Frame,
|
|
40943
|
+
FrameData,
|
|
40872
40944
|
Furniture,
|
|
40873
40945
|
Geodesic,
|
|
40874
40946
|
GeojsonExport,
|