mapshaper 0.7.68 → 0.7.69
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/mapshaper.js +344 -54
- package/package.json +1 -1
- package/www/mapshaper-gui.js +850 -138
- package/www/mapshaper.js +344 -54
- package/www/page.css +101 -3
package/mapshaper.js
CHANGED
|
@@ -25424,6 +25424,9 @@
|
|
|
25424
25424
|
// type/data accessors -- doesn't form an import cycle with the
|
|
25425
25425
|
// scalebar-aware renderer registry that lives in furniture.mjs.
|
|
25426
25426
|
|
|
25427
|
+
// Must match the renderers registered in mapshaper-furniture.mjs
|
|
25428
|
+
var furnitureTypes = ['scalebar'];
|
|
25429
|
+
|
|
25427
25430
|
// @lyr dataset layer
|
|
25428
25431
|
function getFurnitureLayerType(lyr) {
|
|
25429
25432
|
var rec = lyr.data && lyr.data.getReadOnlyRecordAt(0);
|
|
@@ -25434,6 +25437,12 @@
|
|
|
25434
25437
|
return lyr.data && lyr.data.getReadOnlyRecordAt(0);
|
|
25435
25438
|
}
|
|
25436
25439
|
|
|
25440
|
+
// A data-only layer holding the settings of a map element such as a scalebar
|
|
25441
|
+
function layerIsFurniture(lyr) {
|
|
25442
|
+
return !!lyr && !lyr.geometry_type &&
|
|
25443
|
+
furnitureTypes.includes(getFurnitureLayerType(lyr));
|
|
25444
|
+
}
|
|
25445
|
+
|
|
25437
25446
|
/*
|
|
25438
25447
|
{
|
|
25439
25448
|
width: size[0],
|
|
@@ -25629,6 +25638,27 @@
|
|
|
25629
25638
|
});
|
|
25630
25639
|
}
|
|
25631
25640
|
|
|
25641
|
+
// Furniture (scalebars, etc.) belongs to a frame by sharing its dataset.
|
|
25642
|
+
// Furniture in a dataset without a frame is an ordinary standalone layer.
|
|
25643
|
+
function getFrameFurnitureLayers(dataset) {
|
|
25644
|
+
if (!dataset || !findFrameLayerInDataset(dataset)) return [];
|
|
25645
|
+
return dataset.layers.filter(layerIsFurniture);
|
|
25646
|
+
}
|
|
25647
|
+
|
|
25648
|
+
function findFrameFurnitureLayer(dataset, type) {
|
|
25649
|
+
return getFrameFurnitureLayers(dataset).find(function(lyr) {
|
|
25650
|
+
return getFurnitureLayerType(lyr) == type;
|
|
25651
|
+
}) || null;
|
|
25652
|
+
}
|
|
25653
|
+
|
|
25654
|
+
// True for a map frame and for the furniture that belongs to it: layers that
|
|
25655
|
+
// describe the map layout rather than holding map content.
|
|
25656
|
+
function isFrameComponentLayer(lyr, dataset) {
|
|
25657
|
+
if (!dataset) return false;
|
|
25658
|
+
if (isFrameLayer(lyr, dataset.arcs)) return true;
|
|
25659
|
+
return layerIsFurniture(lyr) && !!findFrameLayerInDataset(dataset);
|
|
25660
|
+
}
|
|
25661
|
+
|
|
25632
25662
|
function findFrames(catalog) {
|
|
25633
25663
|
return catalog.getLayers().filter(function(o) {
|
|
25634
25664
|
return isFrameLayer(o.layer, o.dataset.arcs);
|
|
@@ -25855,6 +25885,7 @@
|
|
|
25855
25885
|
demoteFrameLayer: demoteFrameLayer,
|
|
25856
25886
|
findFrame: findFrame,
|
|
25857
25887
|
findFrameDataset: findFrameDataset,
|
|
25888
|
+
findFrameFurnitureLayer: findFrameFurnitureLayer,
|
|
25858
25889
|
findFrameLayer: findFrameLayer,
|
|
25859
25890
|
findFrameLayerInDataset: findFrameLayerInDataset,
|
|
25860
25891
|
findFrames: findFrames,
|
|
@@ -25863,11 +25894,13 @@
|
|
|
25863
25894
|
frameReservedFields: frameReservedFields,
|
|
25864
25895
|
getActiveFrame: getActiveFrame,
|
|
25865
25896
|
getFrameData: getFrameData,
|
|
25897
|
+
getFrameFurnitureLayers: getFrameFurnitureLayers,
|
|
25866
25898
|
getFrameLayerBounds: getFrameLayerBounds,
|
|
25867
25899
|
getFrameLayerData: getFrameLayerData,
|
|
25868
25900
|
getFrameSize: getFrameSize,
|
|
25869
25901
|
getMapFrameMetersPerPixel: getMapFrameMetersPerPixel,
|
|
25870
25902
|
getSingleFrameRecord: getSingleFrameRecord,
|
|
25903
|
+
isFrameComponentLayer: isFrameComponentLayer,
|
|
25871
25904
|
isFrameLayer: isFrameLayer,
|
|
25872
25905
|
isFrameReservedField: isFrameReservedField,
|
|
25873
25906
|
parseFrameSize: parseFrameSize,
|
|
@@ -25875,12 +25908,53 @@
|
|
|
25875
25908
|
resolveExportFrame: resolveExportFrame
|
|
25876
25909
|
});
|
|
25877
25910
|
|
|
25911
|
+
// Adds a furniture layer as a standalone dataset, without changing the
|
|
25912
|
+
// default target (so a following -o still exports the content layers)
|
|
25878
25913
|
function addFurnitureLayer(lyr, catalog) {
|
|
25879
25914
|
var o = {
|
|
25880
25915
|
info: {},
|
|
25881
25916
|
layers: [lyr]
|
|
25882
25917
|
};
|
|
25918
|
+
catalog.captureCatalogBefore({operation: 'addFurnitureLayer'});
|
|
25883
25919
|
catalog.getDatasets().push(o);
|
|
25920
|
+
catalog.markCatalogChanged({operation: 'addFurnitureLayer'});
|
|
25921
|
+
}
|
|
25922
|
+
|
|
25923
|
+
// Adds a furniture layer to a frame's dataset, replacing any existing
|
|
25924
|
+
// furniture of the same type. Passing a null layer removes it.
|
|
25925
|
+
// Returns the replaced or removed layer, if any
|
|
25926
|
+
function setFrameFurnitureLayer(frameDataset, type, lyr) {
|
|
25927
|
+
var layers = frameDataset.layers;
|
|
25928
|
+
var idx = layers.findIndex(function(o) {
|
|
25929
|
+
return layerIsFurniture(o) && getFurnitureLayerType(o) == type;
|
|
25930
|
+
});
|
|
25931
|
+
var prev = idx > -1 ? layers[idx] : null;
|
|
25932
|
+
if (!prev && !lyr) return null;
|
|
25933
|
+
noteDatasetWillChange(frameDataset, {operation: 'setFrameFurniture', unit: 'layers'});
|
|
25934
|
+
layers = layers.slice();
|
|
25935
|
+
if (prev && lyr) {
|
|
25936
|
+
lyr.name = prev.name;
|
|
25937
|
+
layers[idx] = lyr;
|
|
25938
|
+
} else if (prev) {
|
|
25939
|
+
layers.splice(idx, 1);
|
|
25940
|
+
} else {
|
|
25941
|
+
layers.push(lyr);
|
|
25942
|
+
}
|
|
25943
|
+
frameDataset.layers = layers;
|
|
25944
|
+
markDatasetChanged(frameDataset, {operation: 'setFrameFurniture', unit: 'layers'});
|
|
25945
|
+
return prev;
|
|
25946
|
+
}
|
|
25947
|
+
|
|
25948
|
+
// Moves a replaced frame's furniture into the dataset of the frame replacing it
|
|
25949
|
+
function moveFrameFurniture(fromDataset, toDataset) {
|
|
25950
|
+
var furniture = fromDataset.layers.filter(layerIsFurniture);
|
|
25951
|
+
if (furniture.length === 0) return;
|
|
25952
|
+
noteDatasetWillChange(fromDataset, {operation: 'moveFrameFurniture', unit: 'layers'});
|
|
25953
|
+
fromDataset.layers = fromDataset.layers.filter(function(lyr) {
|
|
25954
|
+
return !furniture.includes(lyr);
|
|
25955
|
+
});
|
|
25956
|
+
markDatasetChanged(fromDataset, {operation: 'moveFrameFurniture', unit: 'layers'});
|
|
25957
|
+
toDataset.layers = toDataset.layers.concat(furniture);
|
|
25884
25958
|
}
|
|
25885
25959
|
|
|
25886
25960
|
// Pure geometry-to-SVG-path primitives. Extracted from geojson-to-svg.mjs
|
|
@@ -28425,22 +28499,60 @@
|
|
|
28425
28499
|
symbolRenderers: symbolRenderers
|
|
28426
28500
|
});
|
|
28427
28501
|
|
|
28502
|
+
// Command options that are stored in the scalebar's data record
|
|
28503
|
+
var scalebarFields = ['label', 'units', 'style', 'font_size', 'font_family',
|
|
28504
|
+
'font_style', 'font_weight', 'color', 'tic_length', 'bar_width', 'label_offset',
|
|
28505
|
+
'position', 'label_position', 'dual_units', 'margin'];
|
|
28506
|
+
|
|
28507
|
+
// With a map frame, the scalebar belongs to the frame, and running the
|
|
28508
|
+
// command again replaces it. Without a frame, each run adds a standalone
|
|
28509
|
+
// scalebar layer, which is exported if it is included in the output layers.
|
|
28428
28510
|
cmd.scalebar = function(catalog, opts) {
|
|
28511
|
+
var frame = getActiveFrame(catalog);
|
|
28512
|
+
if (opts.remove) {
|
|
28513
|
+
if (!frame || !setFrameFurnitureLayer(frame.dataset, 'scalebar', null)) {
|
|
28514
|
+
stop$1('The map frame has no scalebar to remove');
|
|
28515
|
+
}
|
|
28516
|
+
return;
|
|
28517
|
+
}
|
|
28518
|
+
validateScalebarOpts(opts);
|
|
28429
28519
|
var lyr = getScalebarLayer(opts);
|
|
28430
|
-
if (
|
|
28431
|
-
|
|
28520
|
+
if (frame) {
|
|
28521
|
+
setFrameFurnitureLayer(frame.dataset, 'scalebar', lyr);
|
|
28522
|
+
} else {
|
|
28523
|
+
addFurnitureLayer(lyr, catalog);
|
|
28432
28524
|
}
|
|
28433
|
-
addFurnitureLayer(lyr, catalog);
|
|
28434
28525
|
};
|
|
28435
28526
|
|
|
28436
28527
|
function getScalebarLayer(opts) {
|
|
28437
|
-
var
|
|
28528
|
+
var rec = {type: 'scalebar'};
|
|
28529
|
+
scalebarFields.forEach(function(k) {
|
|
28530
|
+
if (opts[k] !== undefined && opts[k] !== null && opts[k] !== '') {
|
|
28531
|
+
rec[k] = opts[k];
|
|
28532
|
+
}
|
|
28533
|
+
});
|
|
28438
28534
|
return {
|
|
28439
|
-
name:
|
|
28440
|
-
data: new DataTable([
|
|
28535
|
+
name: 'scalebar',
|
|
28536
|
+
data: new DataTable([rec])
|
|
28441
28537
|
};
|
|
28442
28538
|
}
|
|
28443
28539
|
|
|
28540
|
+
function validateScalebarOpts(opts) {
|
|
28541
|
+
if (opts.label) {
|
|
28542
|
+
splitScalebarLabel(opts.label).forEach(function(str) {
|
|
28543
|
+
if (!parseScalebarUnits(str) && !isBareDistance(str)) {
|
|
28544
|
+
stop$1(`Expected a distance or units of km, meters, miles or feet in scalebar label (received ${str})`);
|
|
28545
|
+
}
|
|
28546
|
+
});
|
|
28547
|
+
}
|
|
28548
|
+
if (opts.units && !parseLabelUnits(opts.units)) {
|
|
28549
|
+
stop$1('Unsupported scalebar units:', opts.units);
|
|
28550
|
+
}
|
|
28551
|
+
if (opts.style && !/^[ab]$/i.test(opts.style)) {
|
|
28552
|
+
stop$1('Unsupported scalebar style:', opts.style);
|
|
28553
|
+
}
|
|
28554
|
+
}
|
|
28555
|
+
|
|
28444
28556
|
function renderScalebar(d, frame) {
|
|
28445
28557
|
if (!frame.crs) {
|
|
28446
28558
|
message('Unable to render scalebar: unknown CRS.');
|
|
@@ -28453,14 +28565,15 @@
|
|
|
28453
28565
|
var opts = getScalebarOpts(d);
|
|
28454
28566
|
var metersPerPx = getMapFrameMetersPerPixel(frame);
|
|
28455
28567
|
var frameWidthPx = frame.width;
|
|
28456
|
-
var labels = d.label ? d.label
|
|
28457
|
-
var
|
|
28458
|
-
var
|
|
28459
|
-
var
|
|
28568
|
+
var labels = d.label ? splitScalebarLabel(d.label) : [];
|
|
28569
|
+
var units = parseLabelUnits(d.units) || 'mile';
|
|
28570
|
+
var label1 = getScalebarLabel(labels[0], frameWidthPx, metersPerPx, units);
|
|
28571
|
+
var props1 = parseScalebarLabel(label1);
|
|
28572
|
+
var label2 = null;
|
|
28460
28573
|
var props2, length2;
|
|
28461
28574
|
|
|
28462
28575
|
if (props1.km > 0 === false) {
|
|
28463
|
-
message('Unusable scalebar label:',
|
|
28576
|
+
message('Unusable scalebar label:', labels[0] || '');
|
|
28464
28577
|
return [];
|
|
28465
28578
|
}
|
|
28466
28579
|
|
|
@@ -28469,6 +28582,11 @@
|
|
|
28469
28582
|
stop$1("Null scalebar length");
|
|
28470
28583
|
}
|
|
28471
28584
|
|
|
28585
|
+
if (opts.style == 'b' && labels[1]) {
|
|
28586
|
+
label2 = completeLabel(labels[1], getOtherUnits(props1.units));
|
|
28587
|
+
} else if (opts.style == 'b' && opts.dual_units) {
|
|
28588
|
+
label2 = getAutoSecondLabel(length1, metersPerPx, getOtherUnits(props1.units));
|
|
28589
|
+
}
|
|
28472
28590
|
if (label2) {
|
|
28473
28591
|
props2 = parseScalebarLabel(label2);
|
|
28474
28592
|
length2 = Math.round(props2.km / metersPerPx * 1000);
|
|
@@ -28521,8 +28639,13 @@
|
|
|
28521
28639
|
};
|
|
28522
28640
|
|
|
28523
28641
|
function getScalebarOpts(d) {
|
|
28524
|
-
var style = d.style == 'b' || d.style == 'B' ? 'b' : 'a';
|
|
28525
|
-
|
|
28642
|
+
var style = d.style == 'b' || d.style == 'B' || !d.style && d.dual_units ? 'b' : 'a';
|
|
28643
|
+
var opts = Object.assign({}, defaultOpts, styleOpts[style]);
|
|
28644
|
+
Object.keys(d).forEach(function(k) {
|
|
28645
|
+
if (d[k] !== undefined && d[k] !== null && d[k] !== '') opts[k] = d[k];
|
|
28646
|
+
});
|
|
28647
|
+
opts.style = style;
|
|
28648
|
+
return opts;
|
|
28526
28649
|
}
|
|
28527
28650
|
|
|
28528
28651
|
function renderAsSvg(length, text, length2, text2, opts) {
|
|
@@ -28540,12 +28663,11 @@
|
|
|
28540
28663
|
};
|
|
28541
28664
|
}
|
|
28542
28665
|
|
|
28543
|
-
// TODO: generalize to other kinds of furniture as they are developed
|
|
28544
28666
|
function getScalebarPosition(opts) {
|
|
28545
28667
|
var pos = opts.position || 'top-left';
|
|
28546
28668
|
return {
|
|
28547
|
-
ypos: pos.includes('
|
|
28548
|
-
xpos: pos.includes('
|
|
28669
|
+
ypos: pos.includes('bottom') ? 'bottom' : 'top',
|
|
28670
|
+
xpos: pos.includes('right') ? 'right' : 'left'
|
|
28549
28671
|
};
|
|
28550
28672
|
}
|
|
28551
28673
|
|
|
@@ -28559,12 +28681,16 @@
|
|
|
28559
28681
|
'label-text': text,
|
|
28560
28682
|
'font-size': opts.font_size,
|
|
28561
28683
|
'text-anchor': labelPos.anchor,
|
|
28562
|
-
'dominant-baseline': labelPos.ypos == 'top' ? 'auto' : 'hanging'
|
|
28684
|
+
'dominant-baseline': labelPos.ypos == 'top' ? 'auto' : 'hanging',
|
|
28563
28685
|
//// 'dominant-baseline': labelPos == 'top' ? 'text-after-edge' : 'text-before-edge'
|
|
28564
28686
|
// 'text-after-edge' is buggy in Safari and unsupported by Illustrator,
|
|
28565
28687
|
// so I'm using 'hanging' and 'auto', which seem to be well supported.
|
|
28566
28688
|
// downside: requires a kludgy multiplier to calculate scalebar height (see above)
|
|
28567
28689
|
};
|
|
28690
|
+
if (opts.color) labelOpts.fill = opts.color;
|
|
28691
|
+
if (opts.font_family) labelOpts['font-family'] = opts.font_family;
|
|
28692
|
+
if (opts.font_style) labelOpts['font-style'] = opts.font_style;
|
|
28693
|
+
if (opts.font_weight) labelOpts['font-weight'] = opts.font_weight;
|
|
28568
28694
|
return symbolRenderers.label(labelOpts, anchorX, anchorY);
|
|
28569
28695
|
}
|
|
28570
28696
|
|
|
@@ -28624,7 +28750,7 @@
|
|
|
28624
28750
|
}
|
|
28625
28751
|
var bar = importMultiLineString(coords);
|
|
28626
28752
|
Object.assign(bar.properties, {
|
|
28627
|
-
stroke: 'black',
|
|
28753
|
+
stroke: opts.color || 'black',
|
|
28628
28754
|
fill: 'none',
|
|
28629
28755
|
'stroke-width': opts.bar_width,
|
|
28630
28756
|
'stroke-linecap': 'butt',
|
|
@@ -28633,35 +28759,132 @@
|
|
|
28633
28759
|
return bar;
|
|
28634
28760
|
}
|
|
28635
28761
|
|
|
28636
|
-
//
|
|
28762
|
+
// Kilometers per unit
|
|
28763
|
+
var unitSizes = {
|
|
28764
|
+
km: 1,
|
|
28765
|
+
m: 0.001,
|
|
28766
|
+
mile: 1.60934,
|
|
28767
|
+
ft: 0.0003048
|
|
28768
|
+
};
|
|
28769
|
+
|
|
28770
|
+
// Candidate lengths for automatic labels, in ascending order of distance
|
|
28771
|
+
var autoLengths = {
|
|
28772
|
+
imperial: [
|
|
28773
|
+
['ft', '10 20 25 50 100 200 250 500'],
|
|
28774
|
+
// note: removed 1.5 12 and 1,200
|
|
28775
|
+
['mile', '1/8 1/5 1/4 1/2 1 2 3 4 5 8 10 15 20 25 30 40 50 75 100 150 200 250 ' +
|
|
28776
|
+
'300 350 400 500 750 1,000 1,500 2,000 2,500 3,000 4,000 5,000']
|
|
28777
|
+
],
|
|
28778
|
+
metric: [
|
|
28779
|
+
['m', '5 10 20 25 50 100 200 250 500'],
|
|
28780
|
+
['km', '1 2 3 4 5 8 10 15 20 25 30 40 50 75 100 150 200 250 300 350 ' +
|
|
28781
|
+
'400 500 750 1,000 1,500 2,000 2,500 3,000 4,000 5,000']
|
|
28782
|
+
]
|
|
28783
|
+
};
|
|
28784
|
+
|
|
28785
|
+
function getAutoLabels(system) {
|
|
28786
|
+
return autoLengths[system].reduce(function(memo, group) {
|
|
28787
|
+
return memo.concat(group[1].split(' ').map(function(str) {
|
|
28788
|
+
return formatDistanceLabel(str, group[0]);
|
|
28789
|
+
}));
|
|
28790
|
+
}, []);
|
|
28791
|
+
}
|
|
28792
|
+
|
|
28793
|
+
function getUnitSystem(units) {
|
|
28794
|
+
return units == 'km' || units == 'm' ? 'metric' : 'imperial';
|
|
28795
|
+
}
|
|
28796
|
+
|
|
28797
|
+
function getOtherUnits(units) {
|
|
28798
|
+
return getUnitSystem(units) == 'metric' ? 'mile' : 'km';
|
|
28799
|
+
}
|
|
28800
|
+
|
|
28801
|
+
// The unit system of the units= option: returns 'km', 'mile' or ''
|
|
28802
|
+
function parseUnitsOption(str) {
|
|
28803
|
+
var units = parseLabelUnits(str);
|
|
28804
|
+
return units ? (getUnitSystem(units) == 'metric' ? 'km' : 'mile') : '';
|
|
28805
|
+
}
|
|
28806
|
+
|
|
28807
|
+
// Accepts the units= option, which also gives the units of a label that is a
|
|
28808
|
+
// bare number; returns 'km', 'm', 'mile', 'ft' or ''
|
|
28809
|
+
function parseLabelUnits(str) {
|
|
28810
|
+
var s = String(str || '').toLowerCase();
|
|
28811
|
+
if (/^(km|kilomet(er|re)s?|metric)$/.test(s)) return 'km';
|
|
28812
|
+
if (/^(m|meters?|metres?)$/.test(s)) return 'm';
|
|
28813
|
+
if (/^(mi|miles?|imperial)$/.test(s)) return 'mile';
|
|
28814
|
+
if (/^(ft|feet)$/.test(s)) return 'ft';
|
|
28815
|
+
return '';
|
|
28816
|
+
}
|
|
28817
|
+
|
|
28818
|
+
// Splits a dual-unit label like "100 km,50 miles". Only a comma after the units
|
|
28819
|
+
// separates two distances, so "1,000 km" and a bare "1,500" stay whole.
|
|
28820
|
+
function splitScalebarLabel(label) {
|
|
28821
|
+
return String(label).split(/(?<=[^\d\s]),\s*/).map(function(str) {
|
|
28822
|
+
return str.trim();
|
|
28823
|
+
}).filter(Boolean);
|
|
28824
|
+
}
|
|
28825
|
+
|
|
28826
|
+
// A distance with no units, e.g. "150" or "1/2" or "1,000"
|
|
28827
|
+
function isBareDistance(str) {
|
|
28828
|
+
return /^[\d\s.,/]+$/.test(str) && parseScalebarNumber(str) > 0;
|
|
28829
|
+
}
|
|
28830
|
+
|
|
28831
|
+
// Adds units to a label that is a bare number
|
|
28832
|
+
function completeLabel(label, units) {
|
|
28833
|
+
return isBareDistance(label) ? formatDistanceLabel(label.trim(), units) : label;
|
|
28834
|
+
}
|
|
28835
|
+
|
|
28836
|
+
// label: a label from the label= option, or empty for an automatic label
|
|
28837
|
+
function getScalebarLabel(label, frameWidthPx, metersPerPx, defaultUnits) {
|
|
28838
|
+
var units;
|
|
28839
|
+
if (label && parseScalebarNumber(label) > 0) return completeLabel(label, defaultUnits);
|
|
28840
|
+
// A label with units but no number gets an automatic length in those units
|
|
28841
|
+
units = label && parseScalebarUnits(label) || defaultUnits;
|
|
28842
|
+
return getAutoScalebarLabel(frameWidthPx, metersPerPx, units);
|
|
28843
|
+
}
|
|
28844
|
+
|
|
28845
|
+
// unit: 'km' || 'mile' (or 'm', 'ft' for the same systems)
|
|
28846
|
+
// The longest length that fits within 20% of the map's width, or the shortest
|
|
28847
|
+
// that is at least 70px long if the map is too narrow for that
|
|
28637
28848
|
function getAutoScalebarLabel(mapWidth, metersPerPx, unit) {
|
|
28638
|
-
var
|
|
28639
|
-
var
|
|
28640
|
-
|
|
28641
|
-
var
|
|
28642
|
-
|
|
28643
|
-
|
|
28644
|
-
|
|
28645
|
-
|
|
28646
|
-
|
|
28647
|
-
|
|
28648
|
-
|
|
28649
|
-
|
|
28650
|
-
|
|
28849
|
+
var minKm = metersPerPx * 70 / 1000;
|
|
28850
|
+
var maxKm = metersPerPx * mapWidth * 0.2 / 1000;
|
|
28851
|
+
var labels = getAutoLabels(getUnitSystem(unit));
|
|
28852
|
+
var longest = labels.filter(function(label) {
|
|
28853
|
+
var km = parseScalebarLabelToKm(label);
|
|
28854
|
+
return km >= minKm && km <= maxKm;
|
|
28855
|
+
}).pop();
|
|
28856
|
+
return longest || labels.find(function(label) {
|
|
28857
|
+
return parseScalebarLabelToKm(label) >= minKm;
|
|
28858
|
+
}) || '';
|
|
28859
|
+
}
|
|
28860
|
+
|
|
28861
|
+
// The longest automatic length in the given units that fits within the
|
|
28862
|
+
// first part of a dual-unit scalebar
|
|
28863
|
+
function getAutoSecondLabel(maxPx, metersPerPx, unit) {
|
|
28864
|
+
var maxKm = maxPx * metersPerPx / 1000;
|
|
28865
|
+
var minKm = maxKm / 4;
|
|
28866
|
+
var labels = getAutoLabels(getUnitSystem(unit)).filter(function(label) {
|
|
28867
|
+
var km = parseScalebarLabelToKm(label);
|
|
28868
|
+
return km <= maxKm && km >= minKm;
|
|
28869
|
+
});
|
|
28870
|
+
return labels.pop() || null;
|
|
28651
28871
|
}
|
|
28652
28872
|
|
|
28653
28873
|
function formatDistanceLabel(numStr, unit) {
|
|
28654
28874
|
var num = parseScalebarNumber(numStr);
|
|
28655
|
-
var unitStr = unit == 'km' && 'KM' ||
|
|
28875
|
+
var unitStr = unit == 'km' && 'KM' ||
|
|
28876
|
+
unit == 'm' && (num > 1 ? 'METERS' : 'METER') ||
|
|
28877
|
+
unit == 'ft' && (num > 1 ? 'FEET' : 'FOOT') ||
|
|
28878
|
+
num > 1 && 'MILES' || 'MILE';
|
|
28656
28879
|
return numStr + ' ' + unitStr;
|
|
28657
28880
|
}
|
|
28658
28881
|
|
|
28659
|
-
// See test/
|
|
28882
|
+
// See test/scalebar-test.mjs for examples of supported formats
|
|
28660
28883
|
function parseScalebarLabelToKm(str) {
|
|
28661
28884
|
var units = parseScalebarUnits(str);
|
|
28662
28885
|
var value = parseScalebarNumber(str);
|
|
28663
28886
|
if (!units || !value) return NaN;
|
|
28664
|
-
return
|
|
28887
|
+
return value * unitSizes[units];
|
|
28665
28888
|
}
|
|
28666
28889
|
|
|
28667
28890
|
function parseScalebarLabel(label) {
|
|
@@ -28669,7 +28892,7 @@
|
|
|
28669
28892
|
var units = label ? parseScalebarUnits(label) : 'mile';
|
|
28670
28893
|
var km = NaN;
|
|
28671
28894
|
if (units && num) {
|
|
28672
|
-
km =
|
|
28895
|
+
km = num * unitSizes[units];
|
|
28673
28896
|
}
|
|
28674
28897
|
return {
|
|
28675
28898
|
number: num,
|
|
@@ -28678,11 +28901,14 @@
|
|
|
28678
28901
|
};
|
|
28679
28902
|
}
|
|
28680
28903
|
|
|
28904
|
+
// Returns 'km', 'm', 'mile', 'ft' or ''
|
|
28681
28905
|
function parseScalebarUnits(str) {
|
|
28682
|
-
var
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
return
|
|
28906
|
+
var s = String(str).trim().toLowerCase();
|
|
28907
|
+
if (/(miles?|mi[.]?|英里)$/.test(s)) return 'mile';
|
|
28908
|
+
if (/(k\.m\.|km|kilomet(er|re)s?|kilom.tres?|公里)$/.test(s)) return 'km';
|
|
28909
|
+
if (/(^|[\d\s.])(feet|foot|ft[.]?|英尺)$/.test(s)) return 'ft';
|
|
28910
|
+
if (/(^|[\d\s.])(m|m\.|meters?|metres?|mètres?|米)$/.test(s)) return 'm';
|
|
28911
|
+
return '';
|
|
28686
28912
|
}
|
|
28687
28913
|
|
|
28688
28914
|
function parseScalebarNumber(str) {
|
|
@@ -28702,8 +28928,12 @@
|
|
|
28702
28928
|
__proto__: null,
|
|
28703
28929
|
formatDistanceLabel: formatDistanceLabel,
|
|
28704
28930
|
getScalebarLayer: getScalebarLayer,
|
|
28931
|
+
parseLabelUnits: parseLabelUnits,
|
|
28705
28932
|
parseScalebarLabelToKm: parseScalebarLabelToKm,
|
|
28706
|
-
|
|
28933
|
+
parseScalebarUnits: parseScalebarUnits,
|
|
28934
|
+
parseUnitsOption: parseUnitsOption,
|
|
28935
|
+
renderScalebar: renderScalebar,
|
|
28936
|
+
splitScalebarLabel: splitScalebarLabel
|
|
28707
28937
|
});
|
|
28708
28938
|
|
|
28709
28939
|
var furnitureRenderers = {
|
|
@@ -28725,24 +28955,33 @@
|
|
|
28725
28955
|
function renderFurnitureLayer(lyr, frame) {
|
|
28726
28956
|
var d = getFurnitureLayerData(lyr);
|
|
28727
28957
|
var renderer = furnitureRenderers[d.type];
|
|
28958
|
+
var problem;
|
|
28728
28959
|
if (!renderer) {
|
|
28729
28960
|
stop$1('Missing renderer for', d.type, 'element');
|
|
28730
28961
|
}
|
|
28731
|
-
|
|
28732
|
-
|
|
28733
|
-
|
|
28734
|
-
if (!isProjectedCRS(frame.crs)) {
|
|
28735
|
-
stop$1(`Unable to render ${d.type} (map is unprojected)`);
|
|
28962
|
+
problem = getFurnitureFrameProblem(frame);
|
|
28963
|
+
if (problem) {
|
|
28964
|
+
stop$1(`Unable to render ${d.type} (${problem})`);
|
|
28736
28965
|
}
|
|
28737
28966
|
return renderer(d, frame) || [];
|
|
28738
28967
|
}
|
|
28739
28968
|
|
|
28969
|
+
// Returns a reason why furniture can't be drawn in a frame, or null
|
|
28970
|
+
function getFurnitureFrameProblem(frame) {
|
|
28971
|
+
if (!frame) return 'missing map frame';
|
|
28972
|
+
if (!frame.crs) return 'unknown map projection';
|
|
28973
|
+
if (!isProjectedCRS(frame.crs)) return 'map is unprojected';
|
|
28974
|
+
return null;
|
|
28975
|
+
}
|
|
28976
|
+
|
|
28740
28977
|
var Furniture = /*#__PURE__*/Object.freeze({
|
|
28741
28978
|
__proto__: null,
|
|
28979
|
+
getFurnitureFrameProblem: getFurnitureFrameProblem,
|
|
28742
28980
|
getFurnitureLayerData: getFurnitureLayerData,
|
|
28743
28981
|
getFurnitureLayerType: getFurnitureLayerType,
|
|
28744
28982
|
isFurnitureLayer: isFurnitureLayer,
|
|
28745
28983
|
layerHasFurniture: layerHasFurniture,
|
|
28984
|
+
layerIsFurniture: layerIsFurniture,
|
|
28746
28985
|
renderFurnitureLayer: renderFurnitureLayer
|
|
28747
28986
|
});
|
|
28748
28987
|
|
|
@@ -39199,6 +39438,9 @@ ${svg}
|
|
|
39199
39438
|
async function exportDatasets(datasets, opts) {
|
|
39200
39439
|
var format = getOutputFormat(datasets[0], opts);
|
|
39201
39440
|
var files;
|
|
39441
|
+
if (format != 'svg' && format != PACKAGE_EXT) {
|
|
39442
|
+
datasets = removeFurnitureLayers(datasets);
|
|
39443
|
+
}
|
|
39202
39444
|
validateRasterExportFormat(datasets, format);
|
|
39203
39445
|
if (format != 'geoparquet' && opts.level !== undefined) {
|
|
39204
39446
|
error('The level= option only applies to GeoParquet output');
|
|
@@ -39274,6 +39516,25 @@ ${svg}
|
|
|
39274
39516
|
return files;
|
|
39275
39517
|
}
|
|
39276
39518
|
|
|
39519
|
+
// Scalebars and other map furniture only have a meaning in SVG output
|
|
39520
|
+
function removeFurnitureLayers(datasets) {
|
|
39521
|
+
var hasFurniture = datasets.some(function(dataset) {
|
|
39522
|
+
return dataset.layers.some(layerIsFurniture);
|
|
39523
|
+
});
|
|
39524
|
+
if (!hasFurniture) return datasets;
|
|
39525
|
+
datasets = datasets.map(function(dataset) {
|
|
39526
|
+
return utils.defaults({
|
|
39527
|
+
layers: dataset.layers.filter(function(lyr) { return !layerIsFurniture(lyr); })
|
|
39528
|
+
}, dataset);
|
|
39529
|
+
}).filter(function(dataset) {
|
|
39530
|
+
return dataset.layers.length > 0;
|
|
39531
|
+
});
|
|
39532
|
+
if (datasets.length === 0) {
|
|
39533
|
+
stop$1('Map furniture layers (e.g. scalebars) can only be exported as SVG');
|
|
39534
|
+
}
|
|
39535
|
+
return datasets;
|
|
39536
|
+
}
|
|
39537
|
+
|
|
39277
39538
|
function validateRasterExportFormat(datasets, format) {
|
|
39278
39539
|
if (format == 'geotiff') {
|
|
39279
39540
|
if (datasetsHaveVectorLayers(datasets)) {
|
|
@@ -44131,10 +44392,13 @@ ${svg}
|
|
|
44131
44392
|
.option('target', targetOpt);
|
|
44132
44393
|
|
|
44133
44394
|
parser.command('scalebar')
|
|
44134
|
-
.describe('add a
|
|
44395
|
+
.describe('add a scale bar to the map frame and SVG output')
|
|
44135
44396
|
.option('label', {
|
|
44136
44397
|
DEFAULT: true,
|
|
44137
|
-
describe: 'distance label, e.g. "35 miles"'
|
|
44398
|
+
describe: 'distance label, e.g. "35 miles" (default is automatic)'
|
|
44399
|
+
})
|
|
44400
|
+
.option('units', {
|
|
44401
|
+
describe: 'units of a bare-number or automatic label: km, meters, miles or feet (default is miles)'
|
|
44138
44402
|
})
|
|
44139
44403
|
.option('style', {
|
|
44140
44404
|
describe: 'two options: a or b'
|
|
@@ -44142,6 +44406,18 @@ ${svg}
|
|
|
44142
44406
|
.option('font-size', {
|
|
44143
44407
|
type: 'number'
|
|
44144
44408
|
})
|
|
44409
|
+
.option('font-family', {
|
|
44410
|
+
describe: 'font family of labels'
|
|
44411
|
+
})
|
|
44412
|
+
.option('font-style', {
|
|
44413
|
+
describe: 'font style of labels, e.g. italic'
|
|
44414
|
+
})
|
|
44415
|
+
.option('font-weight', {
|
|
44416
|
+
describe: 'font weight of labels, e.g. bold'
|
|
44417
|
+
})
|
|
44418
|
+
.option('color', {
|
|
44419
|
+
describe: 'color of bar and labels (default is black)'
|
|
44420
|
+
})
|
|
44145
44421
|
.option('tic-length', {
|
|
44146
44422
|
describe: 'length of tic marks (style b)',
|
|
44147
44423
|
type: 'number'
|
|
@@ -44154,18 +44430,22 @@ ${svg}
|
|
|
44154
44430
|
type: 'number'
|
|
44155
44431
|
})
|
|
44156
44432
|
.option('position', {
|
|
44157
|
-
describe: '
|
|
44433
|
+
describe: 'top-left (default), top-right, bottom-left or bottom-right'
|
|
44158
44434
|
})
|
|
44159
44435
|
.option('label-position', {
|
|
44160
44436
|
describe: 'top, bottom, top-center (style a), etc'
|
|
44161
44437
|
})
|
|
44162
44438
|
.option('dual-units', {
|
|
44163
|
-
|
|
44439
|
+
describe: 'show both metric and imperial units (style b)',
|
|
44164
44440
|
type: 'flag'
|
|
44165
44441
|
})
|
|
44166
44442
|
.option('margin', {
|
|
44167
44443
|
describe: 'offset in pixels from edge of map',
|
|
44168
44444
|
type: 'number'
|
|
44445
|
+
})
|
|
44446
|
+
.option('remove', {
|
|
44447
|
+
describe: 'remove the scale bar from the map frame',
|
|
44448
|
+
type: 'flag'
|
|
44169
44449
|
});
|
|
44170
44450
|
|
|
44171
44451
|
parser.command('shape')
|
|
@@ -52161,8 +52441,10 @@ ${svg}
|
|
|
52161
52441
|
// May return NaN for a bbox the frame can't be fitted to.
|
|
52162
52442
|
// @opts.ignore_symbols fit to the coordinates alone
|
|
52163
52443
|
function getFrameContentBbox(targets, getScale, opts) {
|
|
52444
|
+
// a data-only layer has no bounds
|
|
52164
52445
|
var bounds = targets.reduce(function(memo, o) {
|
|
52165
|
-
|
|
52446
|
+
var b = getLayerBounds(o.layer, o.dataset.arcs);
|
|
52447
|
+
return b ? memo.mergeBounds(b) : memo;
|
|
52166
52448
|
}, new Bounds());
|
|
52167
52449
|
var raw, items, bbox;
|
|
52168
52450
|
if (!bounds.hasBounds()) return null;
|
|
@@ -52309,6 +52591,11 @@ ${svg}
|
|
|
52309
52591
|
var extent = getFrameExtent(contentBbox, widthPx, heightPx, opts);
|
|
52310
52592
|
return getFrameScale(extent.bbox, extent.width, getFixedAspect$1(extent, opts));
|
|
52311
52593
|
}, opts);
|
|
52594
|
+
if (!bbox && !expandCommandTargets(targets).some(function(o) {
|
|
52595
|
+
return layerHasGeometry$1(o.layer);
|
|
52596
|
+
})) {
|
|
52597
|
+
stop$1('Unable to fit a frame to a layer with no geometry. Target a layer containing shapes, or use the bbox= option.');
|
|
52598
|
+
}
|
|
52312
52599
|
if (!bbox) {
|
|
52313
52600
|
stop$1('Command target is missing geographical bounds');
|
|
52314
52601
|
}
|
|
@@ -52332,14 +52619,17 @@ ${svg}
|
|
|
52332
52619
|
var crsInfo = getDatasetCrsInfo(targets[0].dataset);
|
|
52333
52620
|
setDatasetCrsInfo(frameDataset, crsInfo);
|
|
52334
52621
|
}
|
|
52335
|
-
frameDataset.layers[0]
|
|
52622
|
+
var frameLyr = frameDataset.layers[0];
|
|
52623
|
+
frameLyr.name = opts.name || 'frame';
|
|
52336
52624
|
if (existingFrame) {
|
|
52337
52625
|
if (!opts.replace) {
|
|
52338
52626
|
stop$1('A map frame already exists:', existingFrame.layer.name || '[unnamed frame]');
|
|
52339
52627
|
}
|
|
52628
|
+
moveFrameFurniture(existingFrame.dataset, frameDataset);
|
|
52340
52629
|
demoteFrameLayer(existingFrame.layer);
|
|
52341
52630
|
}
|
|
52342
|
-
|
|
52631
|
+
// target the frame, not any furniture that came with it
|
|
52632
|
+
catalog.setDefaultTarget([frameLyr], frameDataset);
|
|
52343
52633
|
};
|
|
52344
52634
|
|
|
52345
52635
|
// The frame's extent and nominal size, from the extent of its content and the
|
|
@@ -84111,7 +84401,7 @@ ${svg}
|
|
|
84111
84401
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
84112
84402
|
}
|
|
84113
84403
|
|
|
84114
|
-
var version = "0.7.
|
|
84404
|
+
var version = "0.7.69";
|
|
84115
84405
|
|
|
84116
84406
|
// Parse command line args into commands and run them
|
|
84117
84407
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|