mapshaper 0.5.111 → 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 +12 -0
- package/mapshaper.js +973 -765
- package/package.json +1 -1
- package/www/mapshaper-gui.js +33 -23
- package/www/mapshaper.js +973 -765
package/www/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) {
|
|
@@ -13744,6 +13962,14 @@
|
|
|
13744
13962
|
return p.length > 2 && p[2] == 'C';
|
|
13745
13963
|
}
|
|
13746
13964
|
|
|
13965
|
+
function stringifyPolygonCoords(coords) {
|
|
13966
|
+
var parts = [];
|
|
13967
|
+
for (var i=0; i<coords.length; i++) {
|
|
13968
|
+
parts.push(stringifyLineStringCoords(coords[i]) + ' Z');
|
|
13969
|
+
}
|
|
13970
|
+
return parts.length > 0 ? parts.join(' ') : '';
|
|
13971
|
+
}
|
|
13972
|
+
|
|
13747
13973
|
function stringifyLineStringCoords(coords) {
|
|
13748
13974
|
if (coords.length === 0) return '';
|
|
13749
13975
|
var d = 'M';
|
|
@@ -13774,6 +14000,7 @@
|
|
|
13774
14000
|
|
|
13775
14001
|
var SvgPathUtils = /*#__PURE__*/Object.freeze({
|
|
13776
14002
|
__proto__: null,
|
|
14003
|
+
stringifyPolygonCoords: stringifyPolygonCoords,
|
|
13777
14004
|
stringifyLineStringCoords: stringifyLineStringCoords
|
|
13778
14005
|
});
|
|
13779
14006
|
|
|
@@ -14023,6 +14250,7 @@
|
|
|
14023
14250
|
// null values indicate the lack of a function for parsing/identifying this property
|
|
14024
14251
|
// (in which case a heuristic is used for distinguishing a string literal from an expression)
|
|
14025
14252
|
var stylePropertyTypes = {
|
|
14253
|
+
css: null,
|
|
14026
14254
|
class: 'classname',
|
|
14027
14255
|
dx: 'measure',
|
|
14028
14256
|
dy: 'measure',
|
|
@@ -14041,6 +14269,7 @@
|
|
|
14041
14269
|
'stroke-dasharray': 'dasharray',
|
|
14042
14270
|
'stroke-width': 'number',
|
|
14043
14271
|
'stroke-opacity': 'number',
|
|
14272
|
+
'stroke-miterlimit': 'number',
|
|
14044
14273
|
'fill-opacity': 'number',
|
|
14045
14274
|
'text-anchor': null
|
|
14046
14275
|
};
|
|
@@ -14071,16 +14300,34 @@
|
|
|
14071
14300
|
effect: null // e.g. "fade"
|
|
14072
14301
|
}, stylePropertyTypes);
|
|
14073
14302
|
|
|
14074
|
-
var commonProperties = 'class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity'.split(',');
|
|
14303
|
+
var commonProperties = 'css,class,opacity,stroke,stroke-width,stroke-dasharray,stroke-opacity,fill-opacity'.split(',');
|
|
14075
14304
|
|
|
14076
14305
|
var propertiesBySymbolType = {
|
|
14077
14306
|
polygon: utils.arrayToIndex(commonProperties.concat('fill', 'fill-pattern')),
|
|
14078
|
-
polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin')),
|
|
14307
|
+
polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit')),
|
|
14079
14308
|
point: utils.arrayToIndex(commonProperties.concat('fill', 'r')),
|
|
14080
14309
|
label: utils.arrayToIndex(commonProperties.concat(
|
|
14081
|
-
'fill,
|
|
14310
|
+
'fill,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(',')))
|
|
14082
14311
|
};
|
|
14083
14312
|
|
|
14313
|
+
// symType: point, polygon, polyline, label
|
|
14314
|
+
function applyStyleAttributes(svgObj, symType, rec, filter) {
|
|
14315
|
+
var fields = findPropertiesBySymbolGeom(Object.keys(rec || {}), symType);
|
|
14316
|
+
for (var i=0, n=fields.length; i<n; i++) {
|
|
14317
|
+
if (filter && !filter(fields[i])) continue;
|
|
14318
|
+
setAttribute(svgObj, fields[i], rec[fields[i]]);
|
|
14319
|
+
}
|
|
14320
|
+
}
|
|
14321
|
+
|
|
14322
|
+
function setAttribute(obj, k, v) {
|
|
14323
|
+
if (!obj.properties) obj.properties = {};
|
|
14324
|
+
obj.properties[k] = v;
|
|
14325
|
+
if (k == 'stroke-dasharray' && v) {
|
|
14326
|
+
// kludge for cleaner dashes... make butt the default?
|
|
14327
|
+
obj.properties['stroke-linecap'] = 'butt';
|
|
14328
|
+
}
|
|
14329
|
+
}
|
|
14330
|
+
|
|
14084
14331
|
function isSupportedSvgStyleProperty(name) {
|
|
14085
14332
|
return name in stylePropertyTypes;
|
|
14086
14333
|
}
|
|
@@ -14154,7 +14401,6 @@
|
|
|
14154
14401
|
// treating the string as a literal value
|
|
14155
14402
|
literalVal = strVal;
|
|
14156
14403
|
}
|
|
14157
|
-
// console.log("literalVal:", mightBeExpression(strVal, fields), strVal, fields)
|
|
14158
14404
|
if (accessor) return accessor;
|
|
14159
14405
|
if (literalVal !== null) return function(id) {return literalVal;};
|
|
14160
14406
|
stop('Unexpected value for', svgName + ':', strVal);
|
|
@@ -14236,6 +14482,7 @@
|
|
|
14236
14482
|
|
|
14237
14483
|
var SvgProperties = /*#__PURE__*/Object.freeze({
|
|
14238
14484
|
__proto__: null,
|
|
14485
|
+
applyStyleAttributes: applyStyleAttributes,
|
|
14239
14486
|
isSupportedSvgStyleProperty: isSupportedSvgStyleProperty,
|
|
14240
14487
|
findPropertiesBySymbolGeom: findPropertiesBySymbolGeom,
|
|
14241
14488
|
getSymbolDataAccessor: getSymbolDataAccessor,
|
|
@@ -14249,37 +14496,36 @@
|
|
|
14249
14496
|
isSvgColor: isSvgColor
|
|
14250
14497
|
});
|
|
14251
14498
|
|
|
14252
|
-
var
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
}
|
|
14261
|
-
|
|
14262
|
-
var SvgCommon = /*#__PURE__*/Object.freeze({
|
|
14263
|
-
__proto__: null,
|
|
14264
|
-
symbolRenderers: symbolRenderers,
|
|
14265
|
-
getTransform: getTransform
|
|
14266
|
-
});
|
|
14499
|
+
var geojsonImporters = {
|
|
14500
|
+
Point: importPoint,
|
|
14501
|
+
Polygon: importPolygon,
|
|
14502
|
+
LineString: importLineString,
|
|
14503
|
+
MultiPoint: importMultiPoint,
|
|
14504
|
+
MultiLineString: importMultiLineString,
|
|
14505
|
+
MultiPolygon: importMultiPolygon
|
|
14506
|
+
};
|
|
14267
14507
|
|
|
14268
14508
|
function importGeoJSONFeatures(features, opts) {
|
|
14269
14509
|
opts = opts || {};
|
|
14270
14510
|
return features.map(function(obj, i) {
|
|
14271
14511
|
var geom = obj.type == 'Feature' ? obj.geometry : obj; // could be null
|
|
14272
14512
|
var geomType = geom && geom.type;
|
|
14513
|
+
var msType = GeoJSON.translateGeoJSONType(geomType);
|
|
14514
|
+
var d = obj.properties || {};
|
|
14273
14515
|
var svgObj = null;
|
|
14274
14516
|
if (geomType && geom.coordinates) {
|
|
14275
|
-
svgObj = geojsonImporters[geomType](geom.coordinates,
|
|
14517
|
+
svgObj = geojsonImporters[geomType](geom.coordinates, d);
|
|
14276
14518
|
}
|
|
14277
14519
|
if (!svgObj) {
|
|
14278
14520
|
return {tag: 'g'}; // empty element
|
|
14279
|
-
}
|
|
14280
|
-
|
|
14281
|
-
if (
|
|
14282
|
-
|
|
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
|
|
14283
14529
|
}
|
|
14284
14530
|
if ('id' in obj) {
|
|
14285
14531
|
if (!svgObj.properties) {
|
|
@@ -14291,34 +14537,37 @@
|
|
|
14291
14537
|
});
|
|
14292
14538
|
}
|
|
14293
14539
|
|
|
14294
|
-
function
|
|
14295
|
-
|
|
14296
|
-
if (
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
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
|
+
};
|
|
14302
14551
|
}
|
|
14552
|
+
var o = renderPoint(rec, coords);
|
|
14553
|
+
if (o) o.properties.transform = getTransform(coords);
|
|
14554
|
+
return o;
|
|
14303
14555
|
}
|
|
14304
14556
|
|
|
14305
|
-
function
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
// kludge for cleaner dashes... make butt the default?
|
|
14313
|
-
obj.properties['stroke-linecap'] = 'butt';
|
|
14314
|
-
}
|
|
14315
|
-
}
|
|
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']);
|
|
14316
14564
|
}
|
|
14317
14565
|
|
|
14318
|
-
function importMultiPoint(coords, rec
|
|
14566
|
+
function importMultiPoint(coords, rec) {
|
|
14319
14567
|
var children = [], p;
|
|
14320
14568
|
for (var i=0; i<coords.length; i++) {
|
|
14321
|
-
p = importPoint(coords[i], rec
|
|
14569
|
+
p = importPoint(coords[i], rec);
|
|
14570
|
+
if (!p) continue;
|
|
14322
14571
|
if (p.tag == 'g' && p.children) {
|
|
14323
14572
|
children = children.concat(p.children);
|
|
14324
14573
|
} else {
|
|
@@ -14328,18 +14577,6 @@
|
|
|
14328
14577
|
return children.length > 0 ? {tag: 'g', children: children} : null;
|
|
14329
14578
|
}
|
|
14330
14579
|
|
|
14331
|
-
function importMultiPath(coords, importer) {
|
|
14332
|
-
var o;
|
|
14333
|
-
for (var i=0; i<coords.length; i++) {
|
|
14334
|
-
if (i === 0) {
|
|
14335
|
-
o = importer(coords[i]);
|
|
14336
|
-
} else {
|
|
14337
|
-
o.properties.d += ' ' + importer(coords[i]).properties.d;
|
|
14338
|
-
}
|
|
14339
|
-
}
|
|
14340
|
-
return o;
|
|
14341
|
-
}
|
|
14342
|
-
|
|
14343
14580
|
function importLineString(coords) {
|
|
14344
14581
|
var d = stringifyLineStringCoords(coords);
|
|
14345
14582
|
return {
|
|
@@ -14348,7 +14585,6 @@
|
|
|
14348
14585
|
};
|
|
14349
14586
|
}
|
|
14350
14587
|
|
|
14351
|
-
|
|
14352
14588
|
function importMultiLineString(coords) {
|
|
14353
14589
|
var d = coords.map(stringifyLineStringCoords).join(' ');
|
|
14354
14590
|
return {
|
|
@@ -14357,20 +14593,55 @@
|
|
|
14357
14593
|
};
|
|
14358
14594
|
}
|
|
14359
14595
|
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
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
|
+
}
|
|
14365
14617
|
return o;
|
|
14366
14618
|
}
|
|
14367
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
|
+
|
|
14368
14631
|
function toLabelString(val) {
|
|
14369
14632
|
if (val || val === 0 || val === false) return String(val);
|
|
14370
14633
|
return '';
|
|
14371
14634
|
}
|
|
14372
14635
|
|
|
14373
|
-
|
|
14636
|
+
// Kludge for applying fill and other styles to a <text> element
|
|
14637
|
+
// (for rendering labels in the GUI with the dot in Canvas, not SVG)
|
|
14638
|
+
function renderStyledLabel(rec) {
|
|
14639
|
+
var o = renderLabel(rec);
|
|
14640
|
+
applyStyleAttributes(o, 'label', rec);
|
|
14641
|
+
return o;
|
|
14642
|
+
}
|
|
14643
|
+
|
|
14644
|
+
function renderLabel(rec) {
|
|
14374
14645
|
var line = toLabelString(rec['label-text']);
|
|
14375
14646
|
var morelines, obj;
|
|
14376
14647
|
// Accepting \n (two chars) as an alternative to the newline character
|
|
@@ -14385,9 +14656,6 @@
|
|
|
14385
14656
|
y: dy,
|
|
14386
14657
|
x: dx
|
|
14387
14658
|
};
|
|
14388
|
-
if (p) {
|
|
14389
|
-
properties.transform = getTransform(p);
|
|
14390
|
-
}
|
|
14391
14659
|
if (newline.test(line)) {
|
|
14392
14660
|
morelines = line.split(newline);
|
|
14393
14661
|
line = morelines.shift();
|
|
@@ -14415,406 +14683,378 @@
|
|
|
14415
14683
|
return obj;
|
|
14416
14684
|
}
|
|
14417
14685
|
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14686
|
+
var SvgLabels = /*#__PURE__*/Object.freeze({
|
|
14687
|
+
__proto__: null,
|
|
14688
|
+
renderStyledLabel: renderStyledLabel,
|
|
14689
|
+
renderLabel: renderLabel
|
|
14690
|
+
});
|
|
14421
14691
|
|
|
14692
|
+
// convert data records (properties like svg-symbol, label-text, fill, r) to svg symbols
|
|
14422
14693
|
|
|
14423
|
-
function
|
|
14424
|
-
var
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
}
|
|
14428
|
-
return renderer(d, x || 0, y || 0);
|
|
14429
|
-
}
|
|
14430
|
-
|
|
14431
|
-
// d: svg-symbol object from feature data object
|
|
14432
|
-
function importSymbol(d, xy) {
|
|
14433
|
-
var renderer;
|
|
14434
|
-
if (!d) {
|
|
14435
|
-
return getEmptySymbol();
|
|
14436
|
-
}
|
|
14437
|
-
if (utils.isString(d)) {
|
|
14438
|
-
d = JSON.parse(d);
|
|
14694
|
+
function getTransform(xy, scale) {
|
|
14695
|
+
var str = 'translate(' + roundToTenths(xy[0]) + ' ' + roundToTenths(xy[1]) + ')';
|
|
14696
|
+
if (scale && scale != 1) {
|
|
14697
|
+
str += ' scale(' + scale + ')';
|
|
14439
14698
|
}
|
|
14440
|
-
return
|
|
14441
|
-
tag: 'g',
|
|
14442
|
-
properties: {
|
|
14443
|
-
'class': 'mapshaper-svg-symbol',
|
|
14444
|
-
transform: xy ? getTransform(xy) : null
|
|
14445
|
-
},
|
|
14446
|
-
children: renderSymbol(d)
|
|
14447
|
-
};
|
|
14699
|
+
return str;
|
|
14448
14700
|
}
|
|
14449
14701
|
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14702
|
+
var symbolRenderers = {
|
|
14703
|
+
line: line,
|
|
14704
|
+
polygon: polygon,
|
|
14705
|
+
polyline: polyline,
|
|
14706
|
+
circle: circle,
|
|
14707
|
+
square: square,
|
|
14708
|
+
image: image,
|
|
14709
|
+
group: group,
|
|
14710
|
+
label: label,
|
|
14711
|
+
offset: offset
|
|
14712
|
+
};
|
|
14457
14713
|
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14714
|
+
// render label and/or point symbol
|
|
14715
|
+
function renderPoint(rec) {
|
|
14716
|
+
var children = [];
|
|
14717
|
+
// var halfSize = rec.r || 0; // radius or half of symbol size
|
|
14718
|
+
if (featureHasSvgSymbol(rec)) {
|
|
14719
|
+
children.push(renderSymbol(rec));
|
|
14464
14720
|
}
|
|
14465
|
-
if (
|
|
14466
|
-
|
|
14721
|
+
if (featureHasLabel(rec)) {
|
|
14722
|
+
children.push(renderStyledLabel(rec));
|
|
14467
14723
|
}
|
|
14724
|
+
var o = children.length > 1 ? {tag: 'g', children: children} : children[0];
|
|
14725
|
+
if (!o) return null;
|
|
14726
|
+
o.properties = o.properties || {};
|
|
14468
14727
|
return o;
|
|
14469
14728
|
}
|
|
14470
14729
|
|
|
14471
|
-
function
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
var children = [];
|
|
14475
|
-
var halfSize = rec.r || 0; // radius or half of symbol size
|
|
14476
|
-
var p;
|
|
14477
|
-
// if not a label, create a symbol even without a size
|
|
14478
|
-
// (circle radius can be set via CSS)
|
|
14479
|
-
if (halfSize > 0 || !isLabel) {
|
|
14480
|
-
if (symbolType == 'square') {
|
|
14481
|
-
p = {
|
|
14482
|
-
tag: 'rect',
|
|
14483
|
-
properties: {
|
|
14484
|
-
x: coords[0] - halfSize,
|
|
14485
|
-
y: coords[1] - halfSize,
|
|
14486
|
-
width: halfSize * 2,
|
|
14487
|
-
height: halfSize * 2
|
|
14488
|
-
}};
|
|
14489
|
-
} else { // default is circle
|
|
14490
|
-
p = {
|
|
14491
|
-
tag: 'circle',
|
|
14492
|
-
properties: {
|
|
14493
|
-
cx: coords[0],
|
|
14494
|
-
cy: coords[1]
|
|
14495
|
-
}};
|
|
14496
|
-
if (halfSize > 0) {
|
|
14497
|
-
p.properties.r = halfSize;
|
|
14498
|
-
}
|
|
14499
|
-
}
|
|
14500
|
-
children.push(p);
|
|
14730
|
+
function renderSymbol(d) {
|
|
14731
|
+
if (d['svg-symbol']) {
|
|
14732
|
+
return renderComplexSymbol(d['svg-symbol']);
|
|
14501
14733
|
}
|
|
14502
|
-
if (
|
|
14503
|
-
|
|
14734
|
+
if (d.r > 0) {
|
|
14735
|
+
return circle(d);
|
|
14504
14736
|
}
|
|
14505
|
-
return
|
|
14737
|
+
return empty();
|
|
14506
14738
|
}
|
|
14507
14739
|
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
LineString: importLineString,
|
|
14512
|
-
MultiPoint: function(coords, rec, opts) {
|
|
14513
|
-
return importMultiPoint(coords, rec, opts);
|
|
14514
|
-
},
|
|
14515
|
-
MultiLineString: function(coords) {
|
|
14516
|
-
return importMultiPath(coords, importLineString);
|
|
14517
|
-
},
|
|
14518
|
-
MultiPolygon: function(coords) {
|
|
14519
|
-
return importMultiPath(coords, importPolygon);
|
|
14520
|
-
}
|
|
14521
|
-
};
|
|
14522
|
-
|
|
14523
|
-
var GeojsonToSvg = /*#__PURE__*/Object.freeze({
|
|
14524
|
-
__proto__: null,
|
|
14525
|
-
importGeoJSONFeatures: importGeoJSONFeatures,
|
|
14526
|
-
applyStyleAttributes: applyStyleAttributes,
|
|
14527
|
-
importLineString: importLineString,
|
|
14528
|
-
importMultiLineString: importMultiLineString,
|
|
14529
|
-
importStyledLabel: importStyledLabel,
|
|
14530
|
-
importLabel: importLabel,
|
|
14531
|
-
renderSymbol: renderSymbol,
|
|
14532
|
-
importSymbol: importSymbol,
|
|
14533
|
-
importPoint: importPoint,
|
|
14534
|
-
importPolygon: importPolygon
|
|
14535
|
-
});
|
|
14536
|
-
|
|
14537
|
-
/* require mapshaper-rectangle, mapshaper-furniture */
|
|
14538
|
-
|
|
14539
|
-
cmd.frame = function(catalog, source, opts) {
|
|
14540
|
-
var size, bounds, tmp, dataset;
|
|
14541
|
-
if (+opts.width > 0 === false && +opts.pixels > 0 === false) {
|
|
14542
|
-
stop("Missing a width or area");
|
|
14740
|
+
function renderComplexSymbol(sym, x, y) {
|
|
14741
|
+
if (utils.isString(sym)) {
|
|
14742
|
+
sym = JSON.parse(sym);
|
|
14543
14743
|
}
|
|
14544
|
-
if (
|
|
14545
|
-
|
|
14546
|
-
|
|
14547
|
-
// comma-sep. pair of numbers (range); here we convert height to
|
|
14548
|
-
// an aspect-ratio parameter for the rectangle() function
|
|
14549
|
-
opts.aspect_ratio = getAspectRatioArg(opts.width, opts.height);
|
|
14550
|
-
// TODO: currently returns max,min aspect ratio, should return in min,max order
|
|
14551
|
-
// (rectangle() function should handle max,min argument correctly now anyway)
|
|
14744
|
+
if (sym.tag) {
|
|
14745
|
+
// symbol appears to already use mapshaper's svg notation... pass through
|
|
14746
|
+
return sym;
|
|
14552
14747
|
}
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
} else if (!getDatasetCRS(tmp)) {
|
|
14558
|
-
message('Warning: missing projection data. Assuming coordinates are meters and k (scale factor) is 1');
|
|
14559
|
-
}
|
|
14560
|
-
size = getFrameSize(bounds, opts);
|
|
14561
|
-
if (size[0] > 0 === false) {
|
|
14562
|
-
stop('Missing a valid frame width');
|
|
14748
|
+
var renderer = symbolRenderers[sym.type];
|
|
14749
|
+
if (!renderer) {
|
|
14750
|
+
message(sym.type ? 'Unknown symbol type: ' + sym.type : 'Symbol is missing a type property');
|
|
14751
|
+
return empty();
|
|
14563
14752
|
}
|
|
14564
|
-
|
|
14565
|
-
|
|
14753
|
+
var o = renderer(sym, x || 0, y || 0);
|
|
14754
|
+
if (sym.opacity) {
|
|
14755
|
+
o.properties.opacity = sym.opacity;
|
|
14566
14756
|
}
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
data: new DataTable([{
|
|
14570
|
-
width: size[0],
|
|
14571
|
-
height: size[1],
|
|
14572
|
-
bbox: bounds.toArray(),
|
|
14573
|
-
type: 'frame'
|
|
14574
|
-
}])
|
|
14575
|
-
}]};
|
|
14576
|
-
catalog.addDataset(dataset);
|
|
14577
|
-
};
|
|
14757
|
+
return o;
|
|
14758
|
+
}
|
|
14578
14759
|
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
// heightArg is a string containing either a number or a
|
|
14582
|
-
// comma-sep. pair of numbers (range);
|
|
14583
|
-
return heightArg.split(',').map(function(opt) {
|
|
14584
|
-
var height = Number(opt),
|
|
14585
|
-
width = Number(widthArg);
|
|
14586
|
-
if (!opt) return '';
|
|
14587
|
-
return width / height;
|
|
14588
|
-
}).reverse().join(',');
|
|
14760
|
+
function empty() {
|
|
14761
|
+
return {tag: 'g', properties: {}, children: []};
|
|
14589
14762
|
}
|
|
14590
14763
|
|
|
14591
|
-
function
|
|
14592
|
-
var
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
}
|
|
14599
|
-
|
|
14600
|
-
return
|
|
14764
|
+
function circle(d, x, y) {
|
|
14765
|
+
var o = {
|
|
14766
|
+
tag: 'circle',
|
|
14767
|
+
properties: {
|
|
14768
|
+
cx: x || 0,
|
|
14769
|
+
cy: y || 0
|
|
14770
|
+
}
|
|
14771
|
+
};
|
|
14772
|
+
applyStyleAttributes(o, 'point', d);
|
|
14773
|
+
return o;
|
|
14601
14774
|
}
|
|
14602
14775
|
|
|
14603
|
-
function
|
|
14604
|
-
var
|
|
14605
|
-
if (
|
|
14606
|
-
//
|
|
14607
|
-
|
|
14776
|
+
function label(d, x, y) {
|
|
14777
|
+
var o = renderStyledLabel(d);
|
|
14778
|
+
if (x || y) {
|
|
14779
|
+
// set x, y here, rather than adding to dx, dy -- so dy, dy can
|
|
14780
|
+
// have CSS units (like ems)
|
|
14781
|
+
o.properties.transform = getTransform([x, y]);
|
|
14608
14782
|
}
|
|
14609
|
-
return
|
|
14783
|
+
return o;
|
|
14610
14784
|
}
|
|
14611
14785
|
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14786
|
+
function image(d, x, y) {
|
|
14787
|
+
var w = d.width || 20,
|
|
14788
|
+
h = d.height || 20;
|
|
14789
|
+
var o = {
|
|
14790
|
+
tag: 'image',
|
|
14791
|
+
properties: {
|
|
14792
|
+
width: w,
|
|
14793
|
+
height: h,
|
|
14794
|
+
x: (x || 0) - w / 2,
|
|
14795
|
+
y: (y || 0) - h / 2,
|
|
14796
|
+
href: d.href || ''
|
|
14797
|
+
}
|
|
14798
|
+
};
|
|
14799
|
+
return o;
|
|
14615
14800
|
}
|
|
14616
14801
|
|
|
14617
|
-
function
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14802
|
+
function square(d, x, y) {
|
|
14803
|
+
var r = d.r || 0;
|
|
14804
|
+
var o = {
|
|
14805
|
+
tag: 'rect',
|
|
14806
|
+
properties: {
|
|
14807
|
+
x: x - r,
|
|
14808
|
+
y: y - r,
|
|
14809
|
+
width: r * 2,
|
|
14810
|
+
height: r * 2
|
|
14811
|
+
}
|
|
14812
|
+
};
|
|
14813
|
+
applyStyleAttributes(o, 'point', d);
|
|
14814
|
+
return o;
|
|
14621
14815
|
}
|
|
14622
14816
|
|
|
14623
|
-
function
|
|
14624
|
-
var
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14817
|
+
function line(d, x, y) {
|
|
14818
|
+
var coords, o;
|
|
14819
|
+
coords = [[x, y], [x + (d.dx || 0), y + (d.dy || 0)]];
|
|
14820
|
+
o = importLineString(coords);
|
|
14821
|
+
applyStyleAttributes(o, 'polyline', d);
|
|
14822
|
+
return o;
|
|
14628
14823
|
}
|
|
14629
14824
|
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14825
|
+
// polyline coords are like GeoJSON MultiLineString coords: an array of 0 or more paths
|
|
14826
|
+
function polyline(d, x, y) {
|
|
14827
|
+
var coords = d.coordinates || [];
|
|
14828
|
+
var o = importMultiLineString(coords);
|
|
14829
|
+
applyStyleAttributes(o, 'polyline', d);
|
|
14830
|
+
return o;
|
|
14635
14831
|
}
|
|
14636
14832
|
|
|
14637
|
-
|
|
14638
|
-
|
|
14833
|
+
// polygon coords are an array of rings (and holes), like flattened MultiPolygon coords
|
|
14834
|
+
function polygon(d, x, y) {
|
|
14835
|
+
var coords = d.coordinates || [];
|
|
14836
|
+
var o = importPolygon(coords);
|
|
14837
|
+
applyStyleAttributes(o, 'polygon', d);
|
|
14838
|
+
return o;
|
|
14639
14839
|
}
|
|
14640
14840
|
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
toMeters = data.crs.to_meter;
|
|
14652
|
-
} else {
|
|
14653
|
-
// Assuming coordinates are meters and k is 1 (not safe)
|
|
14654
|
-
// A warning should be displayed when relevant furniture element is created
|
|
14655
|
-
k = 1;
|
|
14656
|
-
toMeters = 1;
|
|
14657
|
-
}
|
|
14658
|
-
metersPerPixel = bounds.width() / k * toMeters / data.width;
|
|
14659
|
-
return metersPerPixel;
|
|
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
|
+
};
|
|
14660
14851
|
}
|
|
14661
14852
|
|
|
14662
|
-
|
|
14663
|
-
var
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14853
|
+
function group(d, x, y) {
|
|
14854
|
+
var o = {
|
|
14855
|
+
tag: 'g',
|
|
14856
|
+
children: []
|
|
14857
|
+
};
|
|
14858
|
+
var children = o.children;
|
|
14859
|
+
(d.parts || []).forEach(function(o) {
|
|
14860
|
+
var sym = renderComplexSymbol(o, x, y);
|
|
14861
|
+
children.push(sym);
|
|
14862
|
+
//if (d.chained) {
|
|
14863
|
+
//if (o.chained) {
|
|
14864
|
+
if (o.type == 'line') {
|
|
14865
|
+
x += (o.dx || 0);
|
|
14866
|
+
y += (o.dy || 0);
|
|
14867
|
+
}
|
|
14868
|
+
if (o.type == 'offset') {
|
|
14869
|
+
children = sym.children;
|
|
14870
|
+
x = y = 0;
|
|
14871
|
+
}
|
|
14673
14872
|
});
|
|
14674
|
-
return [
|
|
14675
|
-
|
|
14873
|
+
if (o.children.length == 1 && !o.properties) return o.children[0];
|
|
14874
|
+
return o;
|
|
14875
|
+
}
|
|
14676
14876
|
|
|
14677
|
-
var
|
|
14877
|
+
var SvgSymbols = /*#__PURE__*/Object.freeze({
|
|
14678
14878
|
__proto__: null,
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
findFrameDataset: findFrameDataset,
|
|
14683
|
-
findFrameLayer: findFrameLayer,
|
|
14684
|
-
getFrameLayerBounds: getFrameLayerBounds,
|
|
14685
|
-
getMapFrameMetersPerPixel: getMapFrameMetersPerPixel
|
|
14879
|
+
getTransform: getTransform,
|
|
14880
|
+
symbolRenderers: symbolRenderers,
|
|
14881
|
+
renderPoint: renderPoint
|
|
14686
14882
|
});
|
|
14687
14883
|
|
|
14688
|
-
function
|
|
14689
|
-
var
|
|
14690
|
-
var
|
|
14691
|
-
if (
|
|
14692
|
-
|
|
14693
|
-
// TODO: check that aspect ratios match
|
|
14694
|
-
frameData = getFurnitureLayerData(frameLyr);
|
|
14695
|
-
bounds = new Bounds(frameData.bbox);
|
|
14696
|
-
bounds2 = new Bounds(0, 0, frameData.width, frameData.height);
|
|
14697
|
-
} else {
|
|
14698
|
-
bounds = getDatasetBounds(dataset);
|
|
14699
|
-
bounds2 = calcOutputSizeInPixels(bounds, opts);
|
|
14884
|
+
cmd.scalebar = function(catalog, opts) {
|
|
14885
|
+
var frame = findFrameDataset(catalog);
|
|
14886
|
+
var obj, lyr;
|
|
14887
|
+
if (!frame) {
|
|
14888
|
+
stop('Missing a map frame');
|
|
14700
14889
|
}
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14890
|
+
lyr = getScalebarLayer(opts);
|
|
14891
|
+
frame.layers.push(lyr);
|
|
14892
|
+
};
|
|
14893
|
+
|
|
14894
|
+
function getScalebarLayer(opts) {
|
|
14895
|
+
var obj = utils.defaults({type: 'scalebar'}, opts);
|
|
14896
|
+
return {
|
|
14897
|
+
name: opts.name || 'scalebar',
|
|
14898
|
+
data: new DataTable([obj])
|
|
14899
|
+
};
|
|
14706
14900
|
}
|
|
14707
14901
|
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
var
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
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
|
|
14909
|
+
};
|
|
14910
|
+
if (+d.left > 0) {
|
|
14911
|
+
opts.hoffs = +d.left;
|
|
14912
|
+
}
|
|
14913
|
+
if (+d.top > 0) {
|
|
14914
|
+
opts.voffs = +d.top;
|
|
14915
|
+
}
|
|
14916
|
+
if (+d.right > 0) {
|
|
14917
|
+
opts.hoffs = +d.right;
|
|
14918
|
+
opts.halign = 'right';
|
|
14919
|
+
}
|
|
14920
|
+
if (+d.bottom > 0) {
|
|
14921
|
+
opts.voffs = +d.bottom;
|
|
14922
|
+
opts.valign = 'bottom';
|
|
14923
|
+
}
|
|
14924
|
+
return opts;
|
|
14718
14925
|
}
|
|
14719
14926
|
|
|
14720
|
-
|
|
14721
|
-
// returns Bounds object containing bounds of pixel output
|
|
14722
|
-
// side effect: bounds param is modified to match the output frame
|
|
14723
|
-
function calcOutputSizeInPixels(bounds, opts) {
|
|
14724
|
-
var padX = 0,
|
|
14725
|
-
padY = 0,
|
|
14726
|
-
offX = 0,
|
|
14727
|
-
offY = 0,
|
|
14728
|
-
width = bounds.width(),
|
|
14729
|
-
height = bounds.height(),
|
|
14730
|
-
margins = parseMarginOption(opts.margin),
|
|
14731
|
-
marginX = margins[0] + margins[2],
|
|
14732
|
-
marginY = margins[1] + margins[3],
|
|
14733
|
-
// TODO: add option to tweak alignment of content when both width and height are given
|
|
14734
|
-
wx = 0.5, // how padding is distributed horizontally (0: left aligned, 0.5: centered, 1: right aligned)
|
|
14735
|
-
wy = 0.5, // vertical padding distribution
|
|
14736
|
-
widthPx, heightPx, size, kx, ky;
|
|
14927
|
+
furnitureRenderers.scalebar = renderScalebar;
|
|
14737
14928
|
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
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;
|
|
14752
14944
|
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
widthPx = width / opts.svg_scale + marginX;
|
|
14757
|
-
heightPx = 0;
|
|
14758
|
-
} else if (+opts.pixels) {
|
|
14759
|
-
size = getFrameSize(bounds, opts);
|
|
14760
|
-
widthPx = size[0];
|
|
14761
|
-
heightPx = size[1];
|
|
14762
|
-
} else {
|
|
14763
|
-
heightPx = opts.height || 0;
|
|
14764
|
-
widthPx = opts.width || (heightPx > 0 ? 0 : 800); // 800 is default width
|
|
14945
|
+
if (labelPos == 'top') {
|
|
14946
|
+
anchorY = -labelOffs;
|
|
14947
|
+
dy += Math.round(labelOffs + fontSize * 0.8);
|
|
14765
14948
|
}
|
|
14766
14949
|
|
|
14767
|
-
if (
|
|
14768
|
-
|
|
14769
|
-
ky = (height || width || 1) / (heightPx - marginY);
|
|
14770
|
-
}
|
|
14771
|
-
if (widthPx > 0) {
|
|
14772
|
-
// horizontal meters per pixel to fit width param
|
|
14773
|
-
kx = (width || height || 1) / (widthPx - marginX);
|
|
14950
|
+
if (width > 0 === false) {
|
|
14951
|
+
stop("Null scalebar length");
|
|
14774
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];
|
|
14982
|
+
}
|
|
14775
14983
|
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
if (
|
|
14784
|
-
|
|
14785
|
-
|
|
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;
|
|
14786
14995
|
}
|
|
14787
|
-
}
|
|
14996
|
+
}, null) || '';
|
|
14997
|
+
}
|
|
14788
14998
|
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
kx = ky;
|
|
14794
|
-
padX = kx * (widthPx - marginX) - width;
|
|
14795
|
-
}
|
|
14999
|
+
function formatDistanceLabelAsMiles(str) {
|
|
15000
|
+
var num = parseScalebarNumber(str);
|
|
15001
|
+
return str + (num > 1 ? ' MILES' : ' MILE');
|
|
15002
|
+
}
|
|
14796
15003
|
|
|
14797
|
-
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
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;
|
|
15010
|
+
}
|
|
14802
15011
|
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
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' || '';
|
|
15016
|
+
}
|
|
15017
|
+
|
|
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];
|
|
15025
|
+
} else {
|
|
15026
|
+
value = parseFloat(str);
|
|
14808
15027
|
}
|
|
15028
|
+
return value > 0 && value < Infinity ? value : NaN;
|
|
15029
|
+
}
|
|
14809
15030
|
|
|
14810
|
-
|
|
15031
|
+
var Scalebar = /*#__PURE__*/Object.freeze({
|
|
15032
|
+
__proto__: null,
|
|
15033
|
+
getScalebarLayer: getScalebarLayer,
|
|
15034
|
+
renderScalebar: renderScalebar,
|
|
15035
|
+
formatDistanceLabelAsMiles: formatDistanceLabelAsMiles,
|
|
15036
|
+
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
15037
|
+
});
|
|
15038
|
+
|
|
15039
|
+
function transformDatasetToPixels(dataset, opts) {
|
|
15040
|
+
var frame = getFrameData(dataset, opts);
|
|
15041
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15042
|
+
return [frame.width, frame.height];
|
|
15043
|
+
}
|
|
15044
|
+
|
|
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);
|
|
15051
|
+
});
|
|
14811
15052
|
}
|
|
14812
15053
|
|
|
14813
15054
|
var PixelTransform = /*#__PURE__*/Object.freeze({
|
|
14814
15055
|
__proto__: null,
|
|
14815
15056
|
transformDatasetToPixels: transformDatasetToPixels,
|
|
14816
|
-
|
|
14817
|
-
calcOutputSizeInPixels: calcOutputSizeInPixels
|
|
15057
|
+
fitDatasetToFrame: fitDatasetToFrame
|
|
14818
15058
|
});
|
|
14819
15059
|
|
|
14820
15060
|
function stringify(obj) {
|
|
@@ -14871,6 +15111,9 @@
|
|
|
14871
15111
|
if (key == 'href') {
|
|
14872
15112
|
key = 'xlink:href';
|
|
14873
15113
|
}
|
|
15114
|
+
if (key == 'css') {
|
|
15115
|
+
key = 'style'; // inline style
|
|
15116
|
+
}
|
|
14874
15117
|
return memo + ' ' + key + '="' + stringEscape(strval) + '"';
|
|
14875
15118
|
}, '');
|
|
14876
15119
|
}
|
|
@@ -14989,6 +15232,7 @@
|
|
|
14989
15232
|
return svg;
|
|
14990
15233
|
}
|
|
14991
15234
|
|
|
15235
|
+
// href: A URL or a local path
|
|
14992
15236
|
// TODO: download SVG files asynchronously
|
|
14993
15237
|
// (currently, files are downloaded synchronously, which is obviously undesirable)
|
|
14994
15238
|
//
|
|
@@ -14996,7 +15240,7 @@
|
|
|
14996
15240
|
var content;
|
|
14997
15241
|
if (href.indexOf('http') === 0) {
|
|
14998
15242
|
content = fetchFileSync(href);
|
|
14999
|
-
} else if (require('fs').existsSync(href)) {
|
|
15243
|
+
} else if (require('fs').existsSync(href)) {
|
|
15000
15244
|
content = require('fs').readFileSync(href, 'utf8');
|
|
15001
15245
|
} else {
|
|
15002
15246
|
stop("Invalid SVG location:", href);
|
|
@@ -15037,12 +15281,11 @@
|
|
|
15037
15281
|
}
|
|
15038
15282
|
*/
|
|
15039
15283
|
|
|
15040
|
-
//
|
|
15041
15284
|
//
|
|
15042
15285
|
function exportSVG(dataset, opts) {
|
|
15043
15286
|
var namespace = 'xmlns="http://www.w3.org/2000/svg"';
|
|
15044
15287
|
var defs = [];
|
|
15045
|
-
var
|
|
15288
|
+
var frame, svg, layers;
|
|
15046
15289
|
var style = '';
|
|
15047
15290
|
|
|
15048
15291
|
// kludge for map keys
|
|
@@ -15059,62 +15302,70 @@
|
|
|
15059
15302
|
} else {
|
|
15060
15303
|
dataset = copyDataset(dataset); // Modify a copy of the dataset
|
|
15061
15304
|
}
|
|
15305
|
+
|
|
15062
15306
|
// invert_y setting for screen coordinates and geojson polygon generation
|
|
15063
15307
|
utils.extend(opts, {invert_y: true});
|
|
15064
|
-
|
|
15308
|
+
frame = getFrameData(dataset, opts);
|
|
15309
|
+
fitDatasetToFrame(dataset, frame, opts);
|
|
15310
|
+
setCoordinatePrecision(dataset, opts.precision || 0.0001);
|
|
15065
15311
|
|
|
15066
15312
|
// error if one or more svg_data fields are not present in any layers
|
|
15067
15313
|
if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
|
|
15068
15314
|
|
|
15069
|
-
|
|
15070
|
-
|
|
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
|
+
}
|
|
15071
15326
|
convertPropertiesToDefinitions(obj, defs);
|
|
15072
15327
|
return stringify(obj);
|
|
15073
15328
|
}).join('\n');
|
|
15329
|
+
|
|
15074
15330
|
if (defs.length > 0) {
|
|
15075
15331
|
svg = '<defs>\n' + utils.pluck(defs, 'svg').join('') + '</defs>\n' + svg;
|
|
15076
15332
|
}
|
|
15333
|
+
|
|
15077
15334
|
if (svg.includes('xlink:')) {
|
|
15078
15335
|
namespace += ' xmlns:xlink="http://www.w3.org/1999/xlink"';
|
|
15079
15336
|
}
|
|
15337
|
+
|
|
15338
|
+
// default line style properties
|
|
15080
15339
|
var capStyle = opts.default_linecap || 'round';
|
|
15340
|
+
var lineProps = `stroke-linecap="${capStyle}" stroke-linejoin="round"`;
|
|
15341
|
+
if (svg.includes('stroke-linejoin="miter"')) {
|
|
15342
|
+
// the default limit in Illustrator seems to be 10 -- too large for mapping
|
|
15343
|
+
// (Mapbox uses 2 as the default in their styles)
|
|
15344
|
+
lineProps += ' stroke-miterlimit="2"';
|
|
15345
|
+
}
|
|
15081
15346
|
var template = `<?xml version="1.0"?>
|
|
15082
|
-
<svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s"
|
|
15347
|
+
<svg ${namespace} version="1.2" baseProfile="tiny" width="%d" height="%d" viewBox="%s %s %s %s" ${lineProps}>${style}
|
|
15083
15348
|
${svg}
|
|
15084
15349
|
</svg>`;
|
|
15085
|
-
svg = utils.format(template,
|
|
15350
|
+
svg = utils.format(template, frame.width, frame.height, 0, 0, frame.width, frame.height);
|
|
15086
15351
|
return [{
|
|
15087
15352
|
content: svg,
|
|
15088
15353
|
filename: opts.file || getOutputFileBase(dataset) + '.svg'
|
|
15089
15354
|
}];
|
|
15090
15355
|
}
|
|
15091
15356
|
|
|
15092
|
-
function
|
|
15093
|
-
var
|
|
15094
|
-
|
|
15095
|
-
|
|
15096
|
-
return size;
|
|
15357
|
+
function exportFurnitureForSVG(lyr, frame, opts) {
|
|
15358
|
+
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15359
|
+
layerObj.children = importFurniture(getFurnitureLayerData(lyr), frame);
|
|
15360
|
+
return layerObj;
|
|
15097
15361
|
}
|
|
15098
15362
|
|
|
15099
15363
|
function exportLayerForSVG(lyr, dataset, opts) {
|
|
15100
15364
|
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
15101
|
-
|
|
15102
|
-
layerObj.children = exportFurnitureForSVG(lyr, dataset, opts);
|
|
15103
|
-
} else {
|
|
15104
|
-
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15105
|
-
}
|
|
15365
|
+
layerObj.children = exportSymbolsForSVG(lyr, dataset, opts);
|
|
15106
15366
|
return layerObj;
|
|
15107
15367
|
}
|
|
15108
15368
|
|
|
15109
|
-
function exportFurnitureForSVG(lyr, dataset, opts) {
|
|
15110
|
-
var frameLyr = findFrameLayerInDataset(dataset);
|
|
15111
|
-
var frameData;
|
|
15112
|
-
if (!frameLyr) return [];
|
|
15113
|
-
frameData = getFurnitureLayerData(frameLyr);
|
|
15114
|
-
frameData.crs = getDatasetCRS(dataset); // required by e.g. scalebar
|
|
15115
|
-
return importFurniture(getFurnitureLayerData(lyr), frameData);
|
|
15116
|
-
}
|
|
15117
|
-
|
|
15118
15369
|
function exportSymbolsForSVG(lyr, dataset, opts) {
|
|
15119
15370
|
// TODO: convert geojson features one at a time
|
|
15120
15371
|
var d = utils.defaults({layers: [lyr]}, dataset);
|
|
@@ -15245,6 +15496,15 @@ ${svg}
|
|
|
15245
15496
|
return layerObj;
|
|
15246
15497
|
}
|
|
15247
15498
|
|
|
15499
|
+
function featureHasSvgSymbol(d) {
|
|
15500
|
+
return !!(d && (d['svg-symbol'] || d.r));
|
|
15501
|
+
}
|
|
15502
|
+
|
|
15503
|
+
function featureHasLabel(d) {
|
|
15504
|
+
var text = d && d['label-text'];
|
|
15505
|
+
return text || text === 0; // accept numerical 0 as label text
|
|
15506
|
+
}
|
|
15507
|
+
|
|
15248
15508
|
function layerHasSvgSymbols(lyr) {
|
|
15249
15509
|
return lyr.geometry_type == 'point' && lyr.data && lyr.data.fieldExists('svg-symbol');
|
|
15250
15510
|
}
|
|
@@ -15260,10 +15520,13 @@ ${svg}
|
|
|
15260
15520
|
var Svg = /*#__PURE__*/Object.freeze({
|
|
15261
15521
|
__proto__: null,
|
|
15262
15522
|
exportSVG: exportSVG,
|
|
15523
|
+
exportFurnitureForSVG: exportFurnitureForSVG,
|
|
15263
15524
|
exportLayerForSVG: exportLayerForSVG,
|
|
15264
15525
|
validateSvgDataFields: validateSvgDataFields,
|
|
15265
15526
|
exportDataAttributesForSVG: exportDataAttributesForSVG,
|
|
15266
15527
|
getEmptyLayerForSVG: getEmptyLayerForSVG,
|
|
15528
|
+
featureHasSvgSymbol: featureHasSvgSymbol,
|
|
15529
|
+
featureHasLabel: featureHasLabel,
|
|
15267
15530
|
layerHasSvgSymbols: layerHasSvgSymbols,
|
|
15268
15531
|
layerHasLabels: layerHasLabels
|
|
15269
15532
|
});
|
|
@@ -19153,6 +19416,10 @@ ${svg}
|
|
|
19153
19416
|
.option('id-prefix', {
|
|
19154
19417
|
describe: '[SVG] prefix for namespacing layer and feature ids'
|
|
19155
19418
|
})
|
|
19419
|
+
.option('scalebar', {
|
|
19420
|
+
type: 'flag',
|
|
19421
|
+
// describe: '[SVG] add a scalebar showing scale at the center of the map'
|
|
19422
|
+
})
|
|
19156
19423
|
.option('delimiter', {
|
|
19157
19424
|
describe: '[CSV] field delimiter'
|
|
19158
19425
|
})
|
|
@@ -20346,14 +20613,21 @@ ${svg}
|
|
|
20346
20613
|
.option('stroke', {})
|
|
20347
20614
|
.option('stroke-width', {})
|
|
20348
20615
|
.option('fill', {
|
|
20349
|
-
describe: 'symbol fill color'
|
|
20616
|
+
describe: 'symbol fill color (filled symbols only)'
|
|
20350
20617
|
})
|
|
20351
|
-
.option('
|
|
20352
|
-
describe: '
|
|
20618
|
+
.option('stroke', {
|
|
20619
|
+
describe: 'symbol line color (linear symbols only)'
|
|
20620
|
+
})
|
|
20621
|
+
.option('stroke-width', {
|
|
20622
|
+
describe: 'symbol line width (linear symbols only)'
|
|
20623
|
+
})
|
|
20624
|
+
.option('geographic', {
|
|
20625
|
+
old_alias: 'polygons',
|
|
20626
|
+
describe: 'make geographic shapes instead of SVG objects',
|
|
20353
20627
|
type: 'flag'
|
|
20354
20628
|
})
|
|
20355
20629
|
.option('pixel-scale', {
|
|
20356
|
-
describe: 'set symbol scale in meters
|
|
20630
|
+
describe: 'set symbol scale in meters per pixel (geographic option)',
|
|
20357
20631
|
type: 'number',
|
|
20358
20632
|
})
|
|
20359
20633
|
// .option('flipped', {
|
|
@@ -20390,6 +20664,9 @@ ${svg}
|
|
|
20390
20664
|
.option('radii', {
|
|
20391
20665
|
describe: '(ring) comma-sep. list of concentric radii, ascending order'
|
|
20392
20666
|
})
|
|
20667
|
+
.option('arrow-style', {
|
|
20668
|
+
describe: '(arrow) options: stick, standard (default is standard)'
|
|
20669
|
+
})
|
|
20393
20670
|
.option('length', {
|
|
20394
20671
|
old_alias: 'arrow-length',
|
|
20395
20672
|
describe: '(arrow) length of arrow in pixels'
|
|
@@ -32393,6 +32670,80 @@ ${svg}
|
|
|
32393
32670
|
return parsed[0];
|
|
32394
32671
|
}
|
|
32395
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
|
+
|
|
32396
32747
|
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
32397
32748
|
if (!layerHasGeometry(lyr)) {
|
|
32398
32749
|
stop("Layer is missing geometry");
|
|
@@ -37280,219 +37631,6 @@ ${svg}
|
|
|
37280
37631
|
}
|
|
37281
37632
|
};
|
|
37282
37633
|
|
|
37283
|
-
symbolRenderers.circle = function(d, x, y) {
|
|
37284
|
-
var o = importPoint([x, y], d, {});
|
|
37285
|
-
applyStyleAttributes(o, 'Point', d);
|
|
37286
|
-
return [o];
|
|
37287
|
-
};
|
|
37288
|
-
|
|
37289
|
-
symbolRenderers.label = function(d, x, y) {
|
|
37290
|
-
var o = importStyledLabel(d, [x, y]);
|
|
37291
|
-
return [o];
|
|
37292
|
-
};
|
|
37293
|
-
|
|
37294
|
-
symbolRenderers.image = function(d, x, y) {
|
|
37295
|
-
var w = d.width || 20,
|
|
37296
|
-
h = d.height || 20;
|
|
37297
|
-
var o = {
|
|
37298
|
-
tag: 'image',
|
|
37299
|
-
properties: {
|
|
37300
|
-
width: w,
|
|
37301
|
-
height: h,
|
|
37302
|
-
x: x - w / 2,
|
|
37303
|
-
y: y - h / 2,
|
|
37304
|
-
href: d.href || ''
|
|
37305
|
-
}
|
|
37306
|
-
};
|
|
37307
|
-
return [o];
|
|
37308
|
-
};
|
|
37309
|
-
|
|
37310
|
-
symbolRenderers.square = function(d, x, y) {
|
|
37311
|
-
var o = importPoint([x, y], d, {point_symbol: 'square'});
|
|
37312
|
-
applyStyleAttributes(o, 'Point', d);
|
|
37313
|
-
return [o];
|
|
37314
|
-
};
|
|
37315
|
-
|
|
37316
|
-
symbolRenderers.line = function(d, x, y) {
|
|
37317
|
-
var coords, o;
|
|
37318
|
-
coords = [[x, y], [x + (d.dx || 0), y + (d.dy || 0)]];
|
|
37319
|
-
o = importLineString(coords);
|
|
37320
|
-
applyStyleAttributes(o, 'LineString', d);
|
|
37321
|
-
return [o];
|
|
37322
|
-
};
|
|
37323
|
-
|
|
37324
|
-
symbolRenderers.polyline = function(d, x, y) {
|
|
37325
|
-
var coords = d.coordinates || [];
|
|
37326
|
-
var o = importMultiLineString(coords);
|
|
37327
|
-
applyStyleAttributes(o, 'LineString', d);
|
|
37328
|
-
return [o];
|
|
37329
|
-
};
|
|
37330
|
-
|
|
37331
|
-
symbolRenderers.polygon = function(d, x, y) {
|
|
37332
|
-
var coords = d.coordinates || [];
|
|
37333
|
-
var o = importPolygon(coords);
|
|
37334
|
-
applyStyleAttributes(o, 'Polygon', d);
|
|
37335
|
-
return [o];
|
|
37336
|
-
};
|
|
37337
|
-
|
|
37338
|
-
symbolRenderers.group = function(d, x, y) {
|
|
37339
|
-
return (d.parts || []).reduce(function(memo, o) {
|
|
37340
|
-
var sym = renderSymbol(o, x, y);
|
|
37341
|
-
if (d.chained) {
|
|
37342
|
-
x += (o.dx || 0);
|
|
37343
|
-
y += (o.dy || 0);
|
|
37344
|
-
}
|
|
37345
|
-
return memo.concat(sym);
|
|
37346
|
-
}, []);
|
|
37347
|
-
};
|
|
37348
|
-
|
|
37349
|
-
cmd.scalebar = function(catalog, opts) {
|
|
37350
|
-
var frame = findFrameDataset(catalog);
|
|
37351
|
-
var obj, lyr;
|
|
37352
|
-
if (!frame) {
|
|
37353
|
-
stop('Missing a map frame');
|
|
37354
|
-
}
|
|
37355
|
-
obj = utils.defaults({type: 'scalebar'}, opts);
|
|
37356
|
-
lyr = {
|
|
37357
|
-
name: opts.name || 'scalebar',
|
|
37358
|
-
data: new DataTable([obj])
|
|
37359
|
-
};
|
|
37360
|
-
frame.layers.push(lyr);
|
|
37361
|
-
};
|
|
37362
|
-
|
|
37363
|
-
// TODO: generalize to other kinds of furniture as they are developed
|
|
37364
|
-
function getScalebarPosition(d) {
|
|
37365
|
-
var opts = { // defaults
|
|
37366
|
-
valign: 'top',
|
|
37367
|
-
halign: 'left',
|
|
37368
|
-
voffs: 10,
|
|
37369
|
-
hoffs: 10
|
|
37370
|
-
};
|
|
37371
|
-
if (+d.left > 0) {
|
|
37372
|
-
opts.hoffs = +d.left;
|
|
37373
|
-
}
|
|
37374
|
-
if (+d.top > 0) {
|
|
37375
|
-
opts.voffs = +d.top;
|
|
37376
|
-
}
|
|
37377
|
-
if (+d.right > 0) {
|
|
37378
|
-
opts.hoffs = +d.right;
|
|
37379
|
-
opts.halign = 'right';
|
|
37380
|
-
}
|
|
37381
|
-
if (+d.bottom > 0) {
|
|
37382
|
-
opts.voffs = +d.bottom;
|
|
37383
|
-
opts.valign = 'bottom';
|
|
37384
|
-
}
|
|
37385
|
-
return opts;
|
|
37386
|
-
}
|
|
37387
|
-
|
|
37388
|
-
furnitureRenderers.scalebar = function(d, frame) {
|
|
37389
|
-
var pos = getScalebarPosition(d);
|
|
37390
|
-
var metersPerPx = getMapFrameMetersPerPixel(frame);
|
|
37391
|
-
var label = d.label_text || getAutoScalebarLabel(frame.width, metersPerPx);
|
|
37392
|
-
var scalebarKm = parseScalebarLabelToKm(label);
|
|
37393
|
-
var barHeight = 3;
|
|
37394
|
-
var labelOffs = 4;
|
|
37395
|
-
var fontSize = +d.font_size || 13;
|
|
37396
|
-
var width = Math.round(scalebarKm / metersPerPx * 1000);
|
|
37397
|
-
var height = Math.round(barHeight + labelOffs + fontSize * 0.8);
|
|
37398
|
-
var labelPos = d.label_position == 'top' ? 'top' : 'bottom';
|
|
37399
|
-
var anchorX = pos.halign == 'left' ? 0 : width;
|
|
37400
|
-
var anchorY = barHeight + labelOffs;
|
|
37401
|
-
var dx = pos.halign == 'right' ? frame.width - width - pos.hoffs : pos.hoffs;
|
|
37402
|
-
var dy = pos.valign == 'bottom' ? frame.height - height - pos.voffs : pos.voffs;
|
|
37403
|
-
|
|
37404
|
-
if (labelPos == 'top') {
|
|
37405
|
-
anchorY = -labelOffs;
|
|
37406
|
-
dy += Math.round(labelOffs + fontSize * 0.8);
|
|
37407
|
-
}
|
|
37408
|
-
|
|
37409
|
-
if (width > 0 === false) {
|
|
37410
|
-
stop("Null scalebar length");
|
|
37411
|
-
}
|
|
37412
|
-
var barObj = {
|
|
37413
|
-
tag: 'rect',
|
|
37414
|
-
properties: {
|
|
37415
|
-
fill: 'black',
|
|
37416
|
-
x: 0,
|
|
37417
|
-
y: 0,
|
|
37418
|
-
width: width,
|
|
37419
|
-
height: barHeight
|
|
37420
|
-
}
|
|
37421
|
-
};
|
|
37422
|
-
var labelOpts = {
|
|
37423
|
-
'label-text': label,
|
|
37424
|
-
'font-size': fontSize,
|
|
37425
|
-
'text-anchor': pos.halign == 'left' ? 'start': 'end',
|
|
37426
|
-
'dominant-baseline': labelPos == 'top' ? 'auto' : 'hanging'
|
|
37427
|
-
//// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
|
|
37428
|
-
// 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
|
|
37429
|
-
// so I'm using 'hanging' and 'auto', which seem to be well supported.
|
|
37430
|
-
// downside: requires a kludgy multiplier to calculate scalebar height (see above)
|
|
37431
|
-
};
|
|
37432
|
-
var labelObj = symbolRenderers.label(labelOpts, anchorX, anchorY)[0];
|
|
37433
|
-
var g = {
|
|
37434
|
-
tag: 'g',
|
|
37435
|
-
children: [barObj, labelObj],
|
|
37436
|
-
properties: {
|
|
37437
|
-
transform: 'translate(' + dx + ' ' + dy + ')'
|
|
37438
|
-
}
|
|
37439
|
-
};
|
|
37440
|
-
return [g];
|
|
37441
|
-
};
|
|
37442
|
-
|
|
37443
|
-
function getAutoScalebarLabel(mapWidth, metersPerPx) {
|
|
37444
|
-
var minWidth = 100; // TODO: vary min size based on map width
|
|
37445
|
-
var minKm = metersPerPx * minWidth / 1000;
|
|
37446
|
-
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 ' +
|
|
37447
|
-
'100 150 200 250 300 350 400 500 750 1,000 1,200 1,500 2,000 ' +
|
|
37448
|
-
'2,500 3,000 4,000 5,000').split(' ');
|
|
37449
|
-
return options.reduce(function(memo, str) {
|
|
37450
|
-
if (memo) return memo;
|
|
37451
|
-
var label = formatDistanceLabelAsMiles(str);
|
|
37452
|
-
if (parseScalebarLabelToKm(label) > minKm) {
|
|
37453
|
-
return label;
|
|
37454
|
-
}
|
|
37455
|
-
}, null) || '';
|
|
37456
|
-
}
|
|
37457
|
-
|
|
37458
|
-
function formatDistanceLabelAsMiles(str) {
|
|
37459
|
-
var num = parseScalebarNumber(str);
|
|
37460
|
-
return str + (num > 1 ? ' MILES' : ' MILE');
|
|
37461
|
-
}
|
|
37462
|
-
|
|
37463
|
-
// See test/mapshaper-scalebar.js for examples of supported formats
|
|
37464
|
-
function parseScalebarLabelToKm(str) {
|
|
37465
|
-
var units = parseScalebarUnits(str);
|
|
37466
|
-
var value = parseScalebarNumber(str);
|
|
37467
|
-
if (!units || !value) return NaN;
|
|
37468
|
-
return units == 'mile' ? value * 1.60934 : value;
|
|
37469
|
-
}
|
|
37470
|
-
|
|
37471
|
-
function parseScalebarUnits(str) {
|
|
37472
|
-
var isMiles = /miles?$/.test(str.toLowerCase());
|
|
37473
|
-
var isKm = /(km|kilometers?|kilometres?)$/.test(str.toLowerCase());
|
|
37474
|
-
return isMiles && 'mile' || isKm && 'km' || '';
|
|
37475
|
-
}
|
|
37476
|
-
|
|
37477
|
-
function parseScalebarNumber(str) {
|
|
37478
|
-
var fractionRxp = /^([0-9]+) ?\/ ?([0-9]+)/;
|
|
37479
|
-
var match, value;
|
|
37480
|
-
str = str.replace(/[\s]/g, '').replace(/,/g, '');
|
|
37481
|
-
if (fractionRxp.test(str)) {
|
|
37482
|
-
match = fractionRxp.exec(str);
|
|
37483
|
-
value = +match[1] / +match[2];
|
|
37484
|
-
} else {
|
|
37485
|
-
value = parseFloat(str);
|
|
37486
|
-
}
|
|
37487
|
-
return value > 0 && value < Infinity ? value : NaN;
|
|
37488
|
-
}
|
|
37489
|
-
|
|
37490
|
-
var Scalebar = /*#__PURE__*/Object.freeze({
|
|
37491
|
-
__proto__: null,
|
|
37492
|
-
formatDistanceLabelAsMiles: formatDistanceLabelAsMiles,
|
|
37493
|
-
parseScalebarLabelToKm: parseScalebarLabelToKm
|
|
37494
|
-
});
|
|
37495
|
-
|
|
37496
37634
|
cmd.shape = function(targetDataset, opts) {
|
|
37497
37635
|
var geojson, dataset;
|
|
37498
37636
|
if (opts.coordinates) {
|
|
@@ -38680,6 +38818,19 @@ ${svg}
|
|
|
38680
38818
|
|
|
38681
38819
|
var roundCoord = getRoundingFunction(0.01);
|
|
38682
38820
|
|
|
38821
|
+
function getSymbolFillColor(d) {
|
|
38822
|
+
return d.fill || 'magenta';
|
|
38823
|
+
}
|
|
38824
|
+
|
|
38825
|
+
function getSymbolStrokeColor(d) {
|
|
38826
|
+
return d.stroke || d.fill || 'magenta';
|
|
38827
|
+
}
|
|
38828
|
+
|
|
38829
|
+
function getSymbolRadius(d) {
|
|
38830
|
+
if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
|
|
38831
|
+
return d.radius || d.length || d.r || 5; // use a default value
|
|
38832
|
+
}
|
|
38833
|
+
|
|
38683
38834
|
function forEachSymbolCoord(coords, cb) {
|
|
38684
38835
|
var isPoint = coords && utils.isNumber(coords[0]);
|
|
38685
38836
|
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
@@ -38748,57 +38899,104 @@ ${svg}
|
|
|
38748
38899
|
points.push(p2);
|
|
38749
38900
|
}
|
|
38750
38901
|
|
|
38751
|
-
|
|
38752
|
-
|
|
38753
|
-
|
|
38754
|
-
// var curve = d['arrow-stem-curve'] || 0;
|
|
38755
|
-
// var unscaledHeadWidth = d['arrow-head-width'] || 9;
|
|
38756
|
-
// var unscaledHeadLen = getHeadLength(unscaledHeadWidth, headAngle);
|
|
38757
|
-
// var scale = getScale(totalLen, unscaledHeadLen, minStemRatio);
|
|
38758
|
-
// var headWidth = unscaledHeadWidth * scale;
|
|
38759
|
-
// var headLen = unscaledHeadLen * scale;
|
|
38760
|
-
// var tip = getStickArrowTip(totalLen, curve);
|
|
38761
|
-
// var stem = [[0, 0], tip.concat()];
|
|
38762
|
-
// if (curve) {
|
|
38763
|
-
// addBezierArcControlPoints(stem, curve);
|
|
38764
|
-
// }
|
|
38765
|
-
// if (!headLen) return [stem];
|
|
38766
|
-
// var head = [addPoints([-headWidth / 2, -headLen], tip), tip.concat(), addPoints([headWidth / 2, -headLen], tip)];
|
|
38902
|
+
function getStickArrowCoords(d) {
|
|
38903
|
+
return getArrowCoords(d, 'stick');
|
|
38904
|
+
}
|
|
38767
38905
|
|
|
38768
|
-
|
|
38769
|
-
|
|
38770
|
-
|
|
38771
|
-
// }
|
|
38906
|
+
function getFilledArrowCoords(d) {
|
|
38907
|
+
return getArrowCoords(d, 'standard');
|
|
38908
|
+
}
|
|
38772
38909
|
|
|
38773
|
-
|
|
38774
|
-
|
|
38775
|
-
|
|
38776
|
-
|
|
38777
|
-
|
|
38778
|
-
|
|
38779
|
-
|
|
38910
|
+
function getArrowCoords(d, style) {
|
|
38911
|
+
var stickArrow = style == 'stick',
|
|
38912
|
+
// direction = d.rotation || d.direction || 0,
|
|
38913
|
+
direction = d.direction || 0, // rotation is an independent parameter
|
|
38914
|
+
stemTaper = d['stem-taper'] || 0,
|
|
38915
|
+
curvature = d['stem-curve'] || 0,
|
|
38916
|
+
size = calcArrowSize(d, stickArrow);
|
|
38917
|
+
if (!size) return null;
|
|
38918
|
+
var stemLen = size.stemLen,
|
|
38919
|
+
headLen = size.headLen,
|
|
38920
|
+
headDx = size.headWidth / 2,
|
|
38921
|
+
stemDx = size.stemWidth / 2,
|
|
38922
|
+
baseDx = stemDx * (1 - stemTaper),
|
|
38923
|
+
head, stem, coords, dx, dy;
|
|
38780
38924
|
|
|
38781
|
-
|
|
38782
|
-
|
|
38783
|
-
|
|
38925
|
+
if (curvature) {
|
|
38926
|
+
// make curved stem
|
|
38927
|
+
if (direction > 0) curvature = -curvature;
|
|
38928
|
+
var theta = Math.abs(curvature) / 180 * Math.PI;
|
|
38929
|
+
var sign = curvature > 0 ? 1 : -1;
|
|
38930
|
+
var ax = baseDx * Math.cos(theta); // rotate arrow base
|
|
38931
|
+
var ay = baseDx * Math.sin(theta) * -sign;
|
|
38932
|
+
dx = stemLen * Math.sin(theta / 2) * sign;
|
|
38933
|
+
dy = stemLen * Math.cos(theta / 2);
|
|
38934
|
+
|
|
38935
|
+
if (stickArrow) {
|
|
38936
|
+
stem = getCurvedStemCoords(-ax, -ay, dx, dy, theta);
|
|
38937
|
+
} else {
|
|
38938
|
+
var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
|
|
38939
|
+
var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
|
|
38940
|
+
stem = leftStem.concat(rightStem.reverse());
|
|
38941
|
+
}
|
|
38942
|
+
|
|
38943
|
+
} else {
|
|
38944
|
+
// make straight stem
|
|
38945
|
+
dx = 0;
|
|
38946
|
+
dy = stemLen;
|
|
38947
|
+
if (stickArrow) {
|
|
38948
|
+
stem = [[0, 0], [0, stemLen]];
|
|
38949
|
+
} else {
|
|
38950
|
+
stem = [[-baseDx, 0], [baseDx, 0]];
|
|
38951
|
+
}
|
|
38952
|
+
}
|
|
38953
|
+
|
|
38954
|
+
if (stickArrow) {
|
|
38955
|
+
// make stick arrow
|
|
38956
|
+
head = [[-headDx + dx, stemLen - headLen], [dx, stemLen], [headDx + dx, stemLen - headLen]];
|
|
38957
|
+
coords = [stem, head]; // MultiLineString coords
|
|
38958
|
+
} else {
|
|
38959
|
+
// make filled arrow
|
|
38960
|
+
// coordinates go counter clockwise, starting from the leftmost head coordinate
|
|
38961
|
+
head = [[stemDx + dx, dy], [headDx + dx, dy],
|
|
38962
|
+
[dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
|
|
38963
|
+
coords = stem.concat(head);
|
|
38964
|
+
coords.push(stem[0].concat()); // closed path
|
|
38965
|
+
coords = [coords]; // Polygon coords
|
|
38966
|
+
}
|
|
38967
|
+
|
|
38968
|
+
if (d.anchor == 'end') {
|
|
38969
|
+
scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
|
|
38970
|
+
} else if (d.anchor == 'middle') {
|
|
38971
|
+
// shift midpoint away from the head a bit for a more balanced placement
|
|
38972
|
+
// scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
|
|
38973
|
+
scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
|
|
38974
|
+
}
|
|
38975
|
+
|
|
38976
|
+
rotateCoords(coords, direction);
|
|
38977
|
+
if (d.flipped) {
|
|
38978
|
+
flipY(coords);
|
|
38979
|
+
}
|
|
38980
|
+
return coords;
|
|
38981
|
+
}
|
|
38784
38982
|
|
|
38785
38983
|
function calcStraightArrowCoords(stemLen, headLen, stemDx, headDx, baseDx) {
|
|
38786
38984
|
return [[baseDx, 0], [stemDx, stemLen], [headDx, stemLen], [0, stemLen + headLen],
|
|
38787
38985
|
[-headDx, stemLen], [-stemDx, stemLen], [-baseDx, 0], [baseDx, 0]];
|
|
38788
38986
|
}
|
|
38789
38987
|
|
|
38790
|
-
function calcArrowSize(d) {
|
|
38988
|
+
function calcArrowSize(d, stickArrow) {
|
|
38791
38989
|
// don't display arrows with negative length
|
|
38792
38990
|
var totalLen = Math.max(d.radius || d.length || d.r || 0, 0),
|
|
38793
38991
|
scale = 1,
|
|
38794
38992
|
o = initArrowSize(d); // calc several parameters
|
|
38795
38993
|
|
|
38796
|
-
if (totalLen
|
|
38994
|
+
if (totalLen >= 0) {
|
|
38797
38995
|
scale = calcScale(totalLen, o.headLen, d);
|
|
38798
38996
|
o.stemWidth *= scale;
|
|
38799
38997
|
o.headWidth *= scale;
|
|
38800
38998
|
o.headLen *= scale;
|
|
38801
|
-
o.stemLen = totalLen - o.headLen;
|
|
38999
|
+
o.stemLen = stickArrow ? totalLen : totalLen - o.headLen;
|
|
38802
39000
|
}
|
|
38803
39001
|
|
|
38804
39002
|
if (o.headWidth < o.stemWidth) {
|
|
@@ -38819,7 +39017,7 @@ ${svg}
|
|
|
38819
39017
|
} else if (stemLen + headLen > totalLen) {
|
|
38820
39018
|
scale = totalLen / (stemLen + headLen);
|
|
38821
39019
|
}
|
|
38822
|
-
return scale;
|
|
39020
|
+
return scale || 0;
|
|
38823
39021
|
}
|
|
38824
39022
|
|
|
38825
39023
|
function initArrowSize(d) {
|
|
@@ -38849,56 +39047,6 @@ ${svg}
|
|
|
38849
39047
|
return 1 / Math.tan(Math.PI * headAngle / 180 / 2) / 2;
|
|
38850
39048
|
}
|
|
38851
39049
|
|
|
38852
|
-
function getFilledArrowCoords(d) {
|
|
38853
|
-
var direction = d.rotation || d.direction || 0,
|
|
38854
|
-
stemTaper = d['stem-taper'] || 0,
|
|
38855
|
-
curvature = d['stem-curve'] || 0,
|
|
38856
|
-
size = calcArrowSize(d);
|
|
38857
|
-
if (!size) return null;
|
|
38858
|
-
var stemLen = size.stemLen,
|
|
38859
|
-
headLen = size.headLen,
|
|
38860
|
-
headDx = size.headWidth / 2,
|
|
38861
|
-
stemDx = size.stemWidth / 2,
|
|
38862
|
-
baseDx = stemDx * (1 - stemTaper),
|
|
38863
|
-
head, stem, coords, dx, dy;
|
|
38864
|
-
|
|
38865
|
-
if (curvature) {
|
|
38866
|
-
if (direction > 0) curvature = -curvature;
|
|
38867
|
-
var theta = Math.abs(curvature) / 180 * Math.PI;
|
|
38868
|
-
var sign = curvature > 0 ? 1 : -1;
|
|
38869
|
-
var ax = baseDx * Math.cos(theta); // rotate arrow base
|
|
38870
|
-
var ay = baseDx * Math.sin(theta) * -sign;
|
|
38871
|
-
dx = stemLen * Math.sin(theta / 2) * sign;
|
|
38872
|
-
dy = stemLen * Math.cos(theta / 2);
|
|
38873
|
-
var leftStem = getCurvedStemCoords(-ax, -ay, -stemDx + dx, dy, theta);
|
|
38874
|
-
var rightStem = getCurvedStemCoords(ax, ay, stemDx + dx, dy, theta);
|
|
38875
|
-
stem = leftStem.concat(rightStem.reverse());
|
|
38876
|
-
|
|
38877
|
-
} else {
|
|
38878
|
-
dx = 0;
|
|
38879
|
-
dy = stemLen;
|
|
38880
|
-
stem = [[-baseDx, 0], [baseDx, 0], [baseDx, 0]];
|
|
38881
|
-
}
|
|
38882
|
-
|
|
38883
|
-
// coordinates go counter clockwise, starting from the leftmost head coordinate
|
|
38884
|
-
head = [[stemDx + dx, dy], [headDx + dx, dy],
|
|
38885
|
-
[dx, headLen + dy], [-headDx + dx, dy], [-stemDx + dx, dy]];
|
|
38886
|
-
|
|
38887
|
-
coords = stem.concat(head);
|
|
38888
|
-
if (d.anchor == 'end') {
|
|
38889
|
-
scaleAndShiftCoords(coords, 1, [-dx, -dy - headLen]);
|
|
38890
|
-
} else if (d.anchor == 'middle') {
|
|
38891
|
-
// shift midpoint away from the head a bit for a more balanced placement
|
|
38892
|
-
// scaleAndShiftCoords(coords, 1, [-dx/2, (-dy - headLen)/2]);
|
|
38893
|
-
scaleAndShiftCoords(coords, 1, [-dx * 0.5, -dy * 0.5 - headLen * 0.25]);
|
|
38894
|
-
}
|
|
38895
|
-
|
|
38896
|
-
rotateCoords(coords, direction);
|
|
38897
|
-
if (d.flipped) {
|
|
38898
|
-
flipY(coords);
|
|
38899
|
-
}
|
|
38900
|
-
return [coords];
|
|
38901
|
-
}
|
|
38902
39050
|
|
|
38903
39051
|
// ax, ay: point on the base
|
|
38904
39052
|
// bx, by: point on the stem
|
|
@@ -38934,8 +39082,19 @@ ${svg}
|
|
|
38934
39082
|
return coords;
|
|
38935
39083
|
}
|
|
38936
39084
|
|
|
39085
|
+
function makeCircleSymbol(d, opts) {
|
|
39086
|
+
var radius = getSymbolRadius(d);
|
|
39087
|
+
// TODO: remove duplication with svg-symbols.js
|
|
39088
|
+
if (+opts.scale) radius *= +opts.scale;
|
|
39089
|
+
return {
|
|
39090
|
+
type: 'circle',
|
|
39091
|
+
fill: getSymbolFillColor(d),
|
|
39092
|
+
r: radius
|
|
39093
|
+
};
|
|
39094
|
+
}
|
|
39095
|
+
|
|
38937
39096
|
function getPolygonCoords(d) {
|
|
38938
|
-
var radius = d
|
|
39097
|
+
var radius = getSymbolRadius(d),
|
|
38939
39098
|
sides = +d.sides || getSidesByType(d.type),
|
|
38940
39099
|
rotated = sides % 2 == 1,
|
|
38941
39100
|
coords = [],
|
|
@@ -38972,7 +39131,7 @@ ${svg}
|
|
|
38972
39131
|
}
|
|
38973
39132
|
|
|
38974
39133
|
function getStarCoords(d) {
|
|
38975
|
-
var radius = d
|
|
39134
|
+
var radius = getSymbolRadius(d),
|
|
38976
39135
|
points = d.points || d.sides && d.sides / 2 || 5,
|
|
38977
39136
|
sides = points * 2,
|
|
38978
39137
|
minorRadius = getMinorRadius(points) * radius,
|
|
@@ -39017,6 +39176,35 @@ ${svg}
|
|
|
39017
39176
|
return 180 - centerAngle;
|
|
39018
39177
|
}
|
|
39019
39178
|
|
|
39179
|
+
// Returns a svg-symbol object
|
|
39180
|
+
function makeRingSymbol(d, opts) {
|
|
39181
|
+
var scale = +opts.scale || 1;
|
|
39182
|
+
var radii = parseRings(d.radii || '2').map(function(r) { return r * scale; });
|
|
39183
|
+
var solidCenter = utils.isOdd(radii.length);
|
|
39184
|
+
var color = getSymbolFillColor(d);
|
|
39185
|
+
var parts = [];
|
|
39186
|
+
if (solidCenter) {
|
|
39187
|
+
parts.push({
|
|
39188
|
+
type: 'circle',
|
|
39189
|
+
fill: color,
|
|
39190
|
+
r: radii.shift()
|
|
39191
|
+
});
|
|
39192
|
+
}
|
|
39193
|
+
for (var i=0; i<radii.length; i+= 2) {
|
|
39194
|
+
parts.push({
|
|
39195
|
+
type: 'circle',
|
|
39196
|
+
fill: 'none', // TODO remove default black fill so this is not needed
|
|
39197
|
+
stroke: color,
|
|
39198
|
+
'stroke-width': roundToTenths(radii[i+1] - radii[i]),
|
|
39199
|
+
r: roundToTenths(radii[i+1] * 0.5 + radii[i] * 0.5)
|
|
39200
|
+
});
|
|
39201
|
+
}
|
|
39202
|
+
return {
|
|
39203
|
+
type: 'group',
|
|
39204
|
+
parts: parts
|
|
39205
|
+
};
|
|
39206
|
+
}
|
|
39207
|
+
|
|
39020
39208
|
// Returns GeoJSON MultiPolygon coords
|
|
39021
39209
|
function getRingCoords(d) {
|
|
39022
39210
|
var radii = parseRings(d.radii || '2');
|
|
@@ -39048,13 +39236,36 @@ ${svg}
|
|
|
39048
39236
|
return utils.uniq(arr);
|
|
39049
39237
|
}
|
|
39050
39238
|
|
|
39239
|
+
// Returns an svg-symbol data object for one symbol
|
|
39240
|
+
function makePathSymbol(coords, properties, geojsonType) {
|
|
39241
|
+
var sym;
|
|
39242
|
+
if (geojsonType == 'MultiPolygon' || geojsonType == 'Polygon') {
|
|
39243
|
+
sym = {
|
|
39244
|
+
type: 'polygon',
|
|
39245
|
+
fill: getSymbolFillColor(properties),
|
|
39246
|
+
coordinates: geojsonType == 'Polygon' ? coords : flattenMultiPolygonCoords(coords)
|
|
39247
|
+
};
|
|
39248
|
+
} else if (geojsonType == 'LineString' || geojsonType == 'MultiLineString') {
|
|
39249
|
+
sym = {
|
|
39250
|
+
type: 'polyline',
|
|
39251
|
+
stroke: getSymbolStrokeColor(properties),
|
|
39252
|
+
'stroke-width': properties['stroke-width'] || 2,
|
|
39253
|
+
coordinates: geojsonType == 'LineString' ? [coords] : coords
|
|
39254
|
+
};
|
|
39255
|
+
} else {
|
|
39256
|
+
error('Unsupported type:', geojsonType);
|
|
39257
|
+
}
|
|
39258
|
+
roundCoordsForSVG(sym.coordinates);
|
|
39259
|
+
return sym;
|
|
39260
|
+
}
|
|
39261
|
+
|
|
39051
39262
|
// TODO: refactor to remove duplication in mapshaper-svg-style.js
|
|
39052
39263
|
cmd.symbols = function(inputLyr, dataset, opts) {
|
|
39053
39264
|
requireSinglePointLayer(inputLyr);
|
|
39054
39265
|
var lyr = opts.no_replace ? copyLayer(inputLyr) : inputLyr;
|
|
39055
|
-
var
|
|
39266
|
+
var shapeMode = !!opts.geographic;
|
|
39056
39267
|
var metersPerPx;
|
|
39057
|
-
if (
|
|
39268
|
+
if (shapeMode) {
|
|
39058
39269
|
requireProjectedDataset(dataset);
|
|
39059
39270
|
metersPerPx = opts.pixel_scale || getMetersPerPixel(lyr, dataset);
|
|
39060
39271
|
}
|
|
@@ -39064,9 +39275,24 @@ ${svg}
|
|
|
39064
39275
|
if (!shp) return null;
|
|
39065
39276
|
var d = getSymbolData(i);
|
|
39066
39277
|
var rec = records[i] || {};
|
|
39278
|
+
|
|
39279
|
+
// non-polygon symbols
|
|
39280
|
+
if (!shapeMode && d.type == 'circle') {
|
|
39281
|
+
rec['svg-symbol'] = makeCircleSymbol(d, opts);
|
|
39282
|
+
return;
|
|
39283
|
+
}
|
|
39284
|
+
if (!shapeMode && d.type == 'ring') {
|
|
39285
|
+
rec['svg-symbol'] = makeRingSymbol(d, opts);
|
|
39286
|
+
return;
|
|
39287
|
+
}
|
|
39288
|
+
|
|
39067
39289
|
var geojsonType = 'Polygon';
|
|
39068
39290
|
var coords;
|
|
39069
|
-
|
|
39291
|
+
// these symbols get converted to polygon shapes
|
|
39292
|
+
if (d.type == 'arrow' && opts.arrow_style == 'stick') {
|
|
39293
|
+
coords = getStickArrowCoords(d);
|
|
39294
|
+
geojsonType = 'MultiLineString';
|
|
39295
|
+
} else if (d.type == 'arrow') {
|
|
39070
39296
|
coords = getFilledArrowCoords(d);
|
|
39071
39297
|
} else if (d.type == 'ring') {
|
|
39072
39298
|
coords = getRingCoords(d);
|
|
@@ -39078,23 +39304,24 @@ ${svg}
|
|
|
39078
39304
|
}
|
|
39079
39305
|
if (!coords) return null;
|
|
39080
39306
|
rotateCoords(coords, +d.rotation || 0);
|
|
39081
|
-
if (!
|
|
39307
|
+
if (!shapeMode) {
|
|
39082
39308
|
flipY(coords);
|
|
39083
39309
|
}
|
|
39084
39310
|
if (+opts.scale) {
|
|
39085
39311
|
scaleAndShiftCoords(coords, +opts.scale, [0, 0]);
|
|
39086
39312
|
}
|
|
39087
|
-
if (
|
|
39313
|
+
if (shapeMode) {
|
|
39088
39314
|
scaleAndShiftCoords(coords, metersPerPx, shp[0]);
|
|
39089
|
-
if (d.
|
|
39315
|
+
if (d.fill) rec.fill = d.fill;
|
|
39316
|
+
if (d.stroke) rec.stroke = d.stroke;
|
|
39090
39317
|
return createGeometry(coords, geojsonType);
|
|
39091
39318
|
} else {
|
|
39092
|
-
rec['svg-symbol'] =
|
|
39319
|
+
rec['svg-symbol'] = makePathSymbol(coords, d, geojsonType);
|
|
39093
39320
|
}
|
|
39094
39321
|
});
|
|
39095
39322
|
|
|
39096
39323
|
var outputLyr, dataset2;
|
|
39097
|
-
if (
|
|
39324
|
+
if (shapeMode) {
|
|
39098
39325
|
dataset2 = importGeometries(geometries, records);
|
|
39099
39326
|
outputLyr = mergeOutputLayerIntoDataset(inputLyr, dataset, dataset2, opts);
|
|
39100
39327
|
outputLyr.data = lyr.data;
|
|
@@ -39128,31 +39355,10 @@ ${svg}
|
|
|
39128
39355
|
}
|
|
39129
39356
|
|
|
39130
39357
|
function getMetersPerPixel(lyr, dataset) {
|
|
39131
|
-
|
|
39132
|
-
// TODO: handle single point, no extent
|
|
39133
39358
|
var bounds = getLayerBounds(lyr);
|
|
39134
|
-
|
|
39135
|
-
|
|
39136
|
-
|
|
39137
|
-
// Returns an svg-symbol data object for one symbol
|
|
39138
|
-
function makeSvgPolygonSymbol(coords, properties, geojsonType) {
|
|
39139
|
-
if (geojsonType == 'MultiPolygon') {
|
|
39140
|
-
coords = convertMultiPolygonCoords(coords);
|
|
39141
|
-
} else if (geojsonType != 'Polygon') {
|
|
39142
|
-
error('Unsupported type:', geojsonType);
|
|
39143
|
-
}
|
|
39144
|
-
roundCoordsForSVG(coords);
|
|
39145
|
-
return {
|
|
39146
|
-
type: 'polygon',
|
|
39147
|
-
coordinates: coords,
|
|
39148
|
-
fill: properties.fill || 'magenta'
|
|
39149
|
-
};
|
|
39150
|
-
}
|
|
39151
|
-
|
|
39152
|
-
function convertMultiPolygonCoords(coords) {
|
|
39153
|
-
return coords.reduce(function(memo, poly) {
|
|
39154
|
-
return memo.concat(poly);
|
|
39155
|
-
}, []);
|
|
39359
|
+
// TODO: need a better way to handle a single point with no extent
|
|
39360
|
+
var extent = bounds.width() || bounds.height() || 1000;
|
|
39361
|
+
return extent / 800;
|
|
39156
39362
|
}
|
|
39157
39363
|
|
|
39158
39364
|
var Symbols = /*#__PURE__*/Object.freeze({
|
|
@@ -40661,7 +40867,8 @@ ${svg}
|
|
|
40661
40867
|
// export only functions called by the GUI.
|
|
40662
40868
|
|
|
40663
40869
|
var internal = {};
|
|
40664
|
-
|
|
40870
|
+
|
|
40871
|
+
internal.svg = Object.assign({}, SvgStringify, SvgPathUtils, GeojsonToSvg, SvgLabels, SvgSymbols);
|
|
40665
40872
|
|
|
40666
40873
|
// Assign functions and objects exported from modules to the 'internal' namespace
|
|
40667
40874
|
// to maintain compatibility with tests and to expose (some of) them to the GUI.
|
|
@@ -40725,6 +40932,7 @@ ${svg}
|
|
|
40725
40932
|
FileTypes,
|
|
40726
40933
|
FilterGeom,
|
|
40727
40934
|
Frame,
|
|
40935
|
+
FrameData,
|
|
40728
40936
|
Furniture,
|
|
40729
40937
|
Geodesic,
|
|
40730
40938
|
GeojsonExport,
|