mapshaper 0.7.6 → 0.7.7
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 +415 -15
- package/package.json +1 -1
- package/www/index.html +7 -11
- package/www/mapshaper-gui.js +2 -1
- package/www/mapshaper.js +415 -15
- package/www/page.css +88 -3
package/mapshaper.js
CHANGED
|
@@ -18443,7 +18443,7 @@
|
|
|
18443
18443
|
function exportSVG(dataset, opts) {
|
|
18444
18444
|
var namespace = 'xmlns="http://www.w3.org/2000/svg"';
|
|
18445
18445
|
var defs = [];
|
|
18446
|
-
var frame, svg, layers;
|
|
18446
|
+
var frame, svg, layers, metadataJSON;
|
|
18447
18447
|
var style = '';
|
|
18448
18448
|
|
|
18449
18449
|
// kludge for map keys
|
|
@@ -18467,6 +18467,9 @@
|
|
|
18467
18467
|
frame = getFrameData(dataset, opts);
|
|
18468
18468
|
fitDatasetToFrame(dataset, frame);
|
|
18469
18469
|
setCoordinatePrecision(dataset, opts.precision || 0.01);
|
|
18470
|
+
if (opts.metadata) {
|
|
18471
|
+
metadataJSON = JSON.stringify(getGeospatialMetadata(dataset, frame));
|
|
18472
|
+
}
|
|
18470
18473
|
|
|
18471
18474
|
// error if one or more svg_data fields are not present in any layers
|
|
18472
18475
|
if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
|
|
@@ -18486,6 +18489,10 @@
|
|
|
18486
18489
|
return stringify(obj);
|
|
18487
18490
|
}).join('\n');
|
|
18488
18491
|
|
|
18492
|
+
if (metadataJSON) {
|
|
18493
|
+
svg = getMetadataBlock(metadataJSON, [0, 0, frame.width, frame.height]) + svg;
|
|
18494
|
+
}
|
|
18495
|
+
|
|
18489
18496
|
if (defs.length > 0) {
|
|
18490
18497
|
svg = '<defs>\n' + utils.pluck(defs, 'svg').join('') + '</defs>\n' + svg;
|
|
18491
18498
|
}
|
|
@@ -18513,6 +18520,57 @@ ${svg}
|
|
|
18513
18520
|
}];
|
|
18514
18521
|
}
|
|
18515
18522
|
|
|
18523
|
+
function getMetadataBlock(metadataJSON, viewBox) {
|
|
18524
|
+
var x = 0;
|
|
18525
|
+
var y = 0;
|
|
18526
|
+
var width = viewBox && viewBox[2] || 0;
|
|
18527
|
+
var height = viewBox && viewBox[3] || 0;
|
|
18528
|
+
return '<metadata>' + metadataJSON + '</metadata>\n' +
|
|
18529
|
+
'<g id="mapshaper-metadata">\n' +
|
|
18530
|
+
'<rect x="' + x + '" y="' + y + '" width="' + width + '" height="' + height + '" opacity="0"/>\n' +
|
|
18531
|
+
'<text opacity="0" font-size="0.1">' + metadataJSON + '</text>\n' +
|
|
18532
|
+
'</g>\n';
|
|
18533
|
+
}
|
|
18534
|
+
|
|
18535
|
+
function getGeospatialMetadata(dataset, frame) {
|
|
18536
|
+
return {
|
|
18537
|
+
crs: getSVGMetadataCRS(dataset),
|
|
18538
|
+
bbox: frame.bbox || null
|
|
18539
|
+
};
|
|
18540
|
+
}
|
|
18541
|
+
|
|
18542
|
+
function getSVGMetadataCRS(dataset) {
|
|
18543
|
+
var crs = getDatasetCRS(dataset);
|
|
18544
|
+
var info = getDatasetCrsInfo(dataset);
|
|
18545
|
+
var proj4 = null;
|
|
18546
|
+
if (crs) {
|
|
18547
|
+
proj4 = crsToProj4(crs);
|
|
18548
|
+
}
|
|
18549
|
+
if (proj4) return proj4;
|
|
18550
|
+
return getAuthorityCodeString(info) || null;
|
|
18551
|
+
}
|
|
18552
|
+
|
|
18553
|
+
function getAuthorityCodeString(info) {
|
|
18554
|
+
var authority = null;
|
|
18555
|
+
if (info && info.crs_string) {
|
|
18556
|
+
authority = parseAuthorityCodeString(info.crs_string);
|
|
18557
|
+
}
|
|
18558
|
+
if (!authority && info && info.wkt1) {
|
|
18559
|
+
authority = parseAuthorityCodeFromWkt(info.wkt1);
|
|
18560
|
+
}
|
|
18561
|
+
if (!authority && info && info.geopackage_crs) {
|
|
18562
|
+
authority = parseGeoPackageAuthority(info.geopackage_crs);
|
|
18563
|
+
}
|
|
18564
|
+
return authority ? authority.authority + ':' + authority.code : null;
|
|
18565
|
+
}
|
|
18566
|
+
|
|
18567
|
+
function parseGeoPackageAuthority(o) {
|
|
18568
|
+
if (!o) return null;
|
|
18569
|
+
var authority = o.organization || o.authority || null;
|
|
18570
|
+
var code = o.organization_coordsys_id || o.code || null;
|
|
18571
|
+
return authority && code != null ? {authority: String(authority).toUpperCase(), code: String(code)} : null;
|
|
18572
|
+
}
|
|
18573
|
+
|
|
18516
18574
|
function exportFurnitureLayerForSVG(lyr, frame, opts) {
|
|
18517
18575
|
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
18518
18576
|
layerObj.children = renderFurnitureLayer(lyr, frame);
|
|
@@ -28017,6 +28075,10 @@ ${svg}
|
|
|
28017
28075
|
old_alias: 'json-subtree',
|
|
28018
28076
|
describe: '[JSON] path to JSON input data; separator is /'
|
|
28019
28077
|
})
|
|
28078
|
+
.option('ndjson', {
|
|
28079
|
+
type: 'flag',
|
|
28080
|
+
describe: '[JSON/GeoJSON] input is newline-delimited JSON objects'
|
|
28081
|
+
})
|
|
28020
28082
|
.option('single-part', {
|
|
28021
28083
|
type: 'flag',
|
|
28022
28084
|
// describe: '[GeoJSON] split multi-part features into single-part features'
|
|
@@ -28109,10 +28171,6 @@ ${svg}
|
|
|
28109
28171
|
describe: '[TopoJSON] export coordinates without quantization',
|
|
28110
28172
|
type: 'flag'
|
|
28111
28173
|
})
|
|
28112
|
-
.option('metadata', {
|
|
28113
|
-
// describe: '[TopoJSON] Add a metadata object containing CRS information',
|
|
28114
|
-
type: 'flag'
|
|
28115
|
-
})
|
|
28116
28174
|
.option('no-point-quantization', {
|
|
28117
28175
|
// describe: '[TopoJSON] export point coordinates without quantization',
|
|
28118
28176
|
type: 'flag'
|
|
@@ -28155,6 +28213,10 @@ ${svg}
|
|
|
28155
28213
|
describe: '[GeoJSON/JSON] output newline-delimited features or records',
|
|
28156
28214
|
type: 'flag'
|
|
28157
28215
|
})
|
|
28216
|
+
.option('metadata', {
|
|
28217
|
+
describe: '[SVG/TopoJSON] include metadata in output',
|
|
28218
|
+
type: 'flag'
|
|
28219
|
+
})
|
|
28158
28220
|
.option('width', {
|
|
28159
28221
|
describe: '[SVG/TopoJSON] pixel width of output (SVG default is 800)',
|
|
28160
28222
|
type: 'number'
|
|
@@ -28217,10 +28279,7 @@ ${svg}
|
|
|
28217
28279
|
.option('final', {
|
|
28218
28280
|
type: 'flag' // for testing
|
|
28219
28281
|
})
|
|
28220
|
-
|
|
28221
|
-
// describe: '[TopoJSON] add a metadata object',
|
|
28222
|
-
type: 'flag'
|
|
28223
|
-
});
|
|
28282
|
+
;
|
|
28224
28283
|
|
|
28225
28284
|
parser.section('Editing commands');
|
|
28226
28285
|
|
|
@@ -32479,7 +32538,13 @@ ${svg}
|
|
|
32479
32538
|
var str = readFirstChars(reader, 1000);
|
|
32480
32539
|
var type = identifyJSONString(str, opts);
|
|
32481
32540
|
var dataset, retn;
|
|
32482
|
-
if (
|
|
32541
|
+
if (opts.ndjson) {
|
|
32542
|
+
// NDJSON can represent either newline-delimited GeoJSON features/geometries
|
|
32543
|
+
// or plain JSON records. Defer type detection until after line parsing.
|
|
32544
|
+
retn = {
|
|
32545
|
+
content: reader.toString('utf8')
|
|
32546
|
+
};
|
|
32547
|
+
} else if (type == 'geojson') { // consider only for larger files
|
|
32483
32548
|
dataset = importGeoJSONFile(reader, opts);
|
|
32484
32549
|
retn = {
|
|
32485
32550
|
dataset: dataset,
|
|
@@ -32531,6 +32596,12 @@ ${svg}
|
|
|
32531
32596
|
}
|
|
32532
32597
|
|
|
32533
32598
|
if (content) {
|
|
32599
|
+
if (opts.ndjson) {
|
|
32600
|
+
var nd = importNDJSON(content, opts);
|
|
32601
|
+
retn.dataset = nd.dataset;
|
|
32602
|
+
retn.format = nd.format;
|
|
32603
|
+
return retn;
|
|
32604
|
+
}
|
|
32534
32605
|
if (utils.isString(content)) {
|
|
32535
32606
|
try {
|
|
32536
32607
|
content = JSON.parse(content); // ~3sec for 100MB string
|
|
@@ -32569,6 +32640,54 @@ ${svg}
|
|
|
32569
32640
|
});
|
|
32570
32641
|
}
|
|
32571
32642
|
|
|
32643
|
+
function importNDJSON(content, opts) {
|
|
32644
|
+
var lines = utils.isString(content) ? utils.splitLines(content) : [];
|
|
32645
|
+
var objects = [];
|
|
32646
|
+
var firstGeo = null;
|
|
32647
|
+
for (var i = 0; i < lines.length; i++) {
|
|
32648
|
+
var raw = lines[i].trim();
|
|
32649
|
+
if (!raw) continue;
|
|
32650
|
+
var obj;
|
|
32651
|
+
try {
|
|
32652
|
+
obj = JSON.parse(raw);
|
|
32653
|
+
} catch (e) {
|
|
32654
|
+
stop$1('NDJSON parsing error on line ' + (i + 1) + ': ' + e.message);
|
|
32655
|
+
}
|
|
32656
|
+
objects.push(obj);
|
|
32657
|
+
if (firstGeo === null) {
|
|
32658
|
+
firstGeo = isGeoJSONObject(obj);
|
|
32659
|
+
} else if (firstGeo !== isGeoJSONObject(obj)) {
|
|
32660
|
+
stop$1('NDJSON input mixes GeoJSON and non-GeoJSON objects');
|
|
32661
|
+
}
|
|
32662
|
+
}
|
|
32663
|
+
if (objects.length === 0) {
|
|
32664
|
+
stop$1('NDJSON input does not contain any JSON objects');
|
|
32665
|
+
}
|
|
32666
|
+
if (!firstGeo) {
|
|
32667
|
+
return {dataset: importJSONTable(objects), format: 'json'};
|
|
32668
|
+
}
|
|
32669
|
+
var parser = new GeoJSONParser(getGeoJSONImportOpts(opts));
|
|
32670
|
+
objects.forEach(function(obj) {
|
|
32671
|
+
if (obj && obj.type == 'FeatureCollection') {
|
|
32672
|
+
(obj.features || []).forEach(parser.parseObject);
|
|
32673
|
+
} else if (obj && obj.type == 'GeometryCollection') {
|
|
32674
|
+
(obj.geometries || []).forEach(parser.parseObject);
|
|
32675
|
+
} else {
|
|
32676
|
+
parser.parseObject(obj);
|
|
32677
|
+
}
|
|
32678
|
+
});
|
|
32679
|
+
return {dataset: parser.done(), format: 'geojson'};
|
|
32680
|
+
}
|
|
32681
|
+
|
|
32682
|
+
function isGeoJSONObject(obj) {
|
|
32683
|
+
if (!obj || typeof obj != 'object') return false;
|
|
32684
|
+
var type = obj.type;
|
|
32685
|
+
return type == 'Feature' || type == 'FeatureCollection' ||
|
|
32686
|
+
type == 'GeometryCollection' || type == 'Point' || type == 'MultiPoint' ||
|
|
32687
|
+
type == 'LineString' || type == 'MultiLineString' ||
|
|
32688
|
+
type == 'Polygon' || type == 'MultiPolygon';
|
|
32689
|
+
}
|
|
32690
|
+
|
|
32572
32691
|
// path: path from top-level to the target object
|
|
32573
32692
|
function selectFromObject(o, path) {
|
|
32574
32693
|
var arrayRxp = /(.*)\[([0-9]+)\]$/; // array bracket notation w/ index
|
|
@@ -33159,6 +33278,7 @@ ${svg}
|
|
|
33159
33278
|
var Parser = typeof DOMParser == 'undefined' ? require$1('@xmldom/xmldom').DOMParser : DOMParser;
|
|
33160
33279
|
var doc = new Parser().parseFromString(str, 'text/xml');
|
|
33161
33280
|
var root = doc && doc.documentElement;
|
|
33281
|
+
var geometadata = parseSVGGeometadata(root, str);
|
|
33162
33282
|
var groups = getTopLevelLayerGroups(root);
|
|
33163
33283
|
var layerData = [];
|
|
33164
33284
|
var datasets = [];
|
|
@@ -33175,7 +33295,11 @@ ${svg}
|
|
|
33175
33295
|
});
|
|
33176
33296
|
});
|
|
33177
33297
|
|
|
33178
|
-
|
|
33298
|
+
if (geometadata && geometadata.bbox && geometadata.viewBox) {
|
|
33299
|
+
remapCoordinatesFromSVGToMap(layerData, geometadata.viewBox, geometadata.bbox);
|
|
33300
|
+
} else {
|
|
33301
|
+
flipYCoordinates(layerData);
|
|
33302
|
+
}
|
|
33179
33303
|
|
|
33180
33304
|
layerData.forEach(function(layer) {
|
|
33181
33305
|
datasets.push(importLayerFeatures(layer.name, layer.features, opts));
|
|
@@ -33185,9 +33309,237 @@ ${svg}
|
|
|
33185
33309
|
return {layers: [], info: {}};
|
|
33186
33310
|
}
|
|
33187
33311
|
if (datasets.length === 1) {
|
|
33188
|
-
return datasets[0];
|
|
33312
|
+
return applySVGGeometadata(datasets[0], geometadata);
|
|
33189
33313
|
}
|
|
33190
|
-
return mergeDatasets(datasets);
|
|
33314
|
+
return applySVGGeometadata(mergeDatasets(datasets), geometadata);
|
|
33315
|
+
}
|
|
33316
|
+
|
|
33317
|
+
function applySVGGeometadata(dataset, geometadata) {
|
|
33318
|
+
if (geometadata && geometadata.crs) {
|
|
33319
|
+
setDatasetCrsInfo(dataset, getCrsInfo(geometadata.crs));
|
|
33320
|
+
}
|
|
33321
|
+
return dataset;
|
|
33322
|
+
}
|
|
33323
|
+
|
|
33324
|
+
function parseSVGGeometadata(root, svgText) {
|
|
33325
|
+
var json = findMetadataJSONString(root, svgText);
|
|
33326
|
+
var obj;
|
|
33327
|
+
var metadataViewport = getMetadataViewportRect(root);
|
|
33328
|
+
if (!json) return null;
|
|
33329
|
+
obj = parseMetadataJSONObject(json);
|
|
33330
|
+
if (!obj) return null;
|
|
33331
|
+
return {
|
|
33332
|
+
crs: normalizeMetadataCRS(obj.crs),
|
|
33333
|
+
bbox: normalizeMetadataBBox(obj.bbox),
|
|
33334
|
+
viewBox: metadataViewport || parseSVGViewBox(root && root.getAttribute && root.getAttribute('viewBox'))
|
|
33335
|
+
};
|
|
33336
|
+
}
|
|
33337
|
+
|
|
33338
|
+
function findMetadataJSONString(root, svgText) {
|
|
33339
|
+
var text = getMetadataTagText(root) || getHiddenMetadataText(root);
|
|
33340
|
+
if (!text && svgText) {
|
|
33341
|
+
text = extractHiddenMetadataFromSource(svgText);
|
|
33342
|
+
}
|
|
33343
|
+
return normalizeMetadataJSONText(text);
|
|
33344
|
+
}
|
|
33345
|
+
|
|
33346
|
+
function getMetadataTagText(root) {
|
|
33347
|
+
var nodes = getElementChildren(root);
|
|
33348
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
33349
|
+
if (getTagName(nodes[i]) == 'metadata') {
|
|
33350
|
+
return nodes[i].textContent || '';
|
|
33351
|
+
}
|
|
33352
|
+
}
|
|
33353
|
+
return null;
|
|
33354
|
+
}
|
|
33355
|
+
|
|
33356
|
+
function getHiddenMetadataText(root) {
|
|
33357
|
+
var node = getMetadataGroupNode(root);
|
|
33358
|
+
if (node) {
|
|
33359
|
+
return node.textContent || '';
|
|
33360
|
+
}
|
|
33361
|
+
return null;
|
|
33362
|
+
}
|
|
33363
|
+
|
|
33364
|
+
function getMetadataGroupNode(root) {
|
|
33365
|
+
var nodes = getElementChildren(root);
|
|
33366
|
+
var i, node;
|
|
33367
|
+
for (i = 0; i < nodes.length; i++) {
|
|
33368
|
+
node = nodes[i];
|
|
33369
|
+
if (getTagName(node) == 'g' && node.getAttribute && node.getAttribute('id') == 'mapshaper-metadata') {
|
|
33370
|
+
return node;
|
|
33371
|
+
}
|
|
33372
|
+
}
|
|
33373
|
+
return null;
|
|
33374
|
+
}
|
|
33375
|
+
|
|
33376
|
+
function getMetadataViewportRect(root) {
|
|
33377
|
+
var node = getMetadataGroupNode(root);
|
|
33378
|
+
var children, i, rect;
|
|
33379
|
+
if (!node) return null;
|
|
33380
|
+
children = getElementChildren(node);
|
|
33381
|
+
for (i = 0; i < children.length; i++) {
|
|
33382
|
+
if (getTagName(children[i]) == 'rect') {
|
|
33383
|
+
rect = parseMetadataRectNode(children[i]);
|
|
33384
|
+
if (rect) return rect;
|
|
33385
|
+
}
|
|
33386
|
+
}
|
|
33387
|
+
return null;
|
|
33388
|
+
}
|
|
33389
|
+
|
|
33390
|
+
function parseMetadataRectNode(node) {
|
|
33391
|
+
var x = parseNumber(node.getAttribute('x')) || 0;
|
|
33392
|
+
var y = parseNumber(node.getAttribute('y')) || 0;
|
|
33393
|
+
var width = parseNumber(node.getAttribute('width'));
|
|
33394
|
+
var height = parseNumber(node.getAttribute('height'));
|
|
33395
|
+
var pts, i, p;
|
|
33396
|
+
var xmin = Infinity, ymin = Infinity, xmax = -Infinity, ymax = -Infinity;
|
|
33397
|
+
if (!isFiniteNumber(width) || !isFiniteNumber(height) || width === 0 || height === 0) {
|
|
33398
|
+
return null;
|
|
33399
|
+
}
|
|
33400
|
+
pts = [
|
|
33401
|
+
[x, y],
|
|
33402
|
+
[x + width, y],
|
|
33403
|
+
[x, y + height],
|
|
33404
|
+
[x + width, y + height]
|
|
33405
|
+
];
|
|
33406
|
+
for (i = 0; i < pts.length; i++) {
|
|
33407
|
+
p = applyNodeTransformChain(node, pts[i]);
|
|
33408
|
+
xmin = Math.min(xmin, p[0]);
|
|
33409
|
+
ymin = Math.min(ymin, p[1]);
|
|
33410
|
+
xmax = Math.max(xmax, p[0]);
|
|
33411
|
+
ymax = Math.max(ymax, p[1]);
|
|
33412
|
+
}
|
|
33413
|
+
if (!isFiniteNumber(xmin) || !isFiniteNumber(ymin) || !isFiniteNumber(xmax) || !isFiniteNumber(ymax)) {
|
|
33414
|
+
return null;
|
|
33415
|
+
}
|
|
33416
|
+
return [xmin, ymin, xmax - xmin, ymax - ymin];
|
|
33417
|
+
}
|
|
33418
|
+
|
|
33419
|
+
function applyNodeTransformChain(node, point) {
|
|
33420
|
+
var chain = [];
|
|
33421
|
+
var p = [point[0], point[1]];
|
|
33422
|
+
var i;
|
|
33423
|
+
while (node && node.nodeType == 1) {
|
|
33424
|
+
chain.unshift(node);
|
|
33425
|
+
node = node.parentNode;
|
|
33426
|
+
}
|
|
33427
|
+
for (i = 0; i < chain.length; i++) {
|
|
33428
|
+
p = applyTransformString(chain[i].getAttribute && chain[i].getAttribute('transform'), p);
|
|
33429
|
+
}
|
|
33430
|
+
return p;
|
|
33431
|
+
}
|
|
33432
|
+
|
|
33433
|
+
function applyTransformString(str, point) {
|
|
33434
|
+
var re = /([a-z]+)\s*\(([^)]*)\)/ig;
|
|
33435
|
+
var m = null;
|
|
33436
|
+
var p = [point[0], point[1]];
|
|
33437
|
+
while ((m = re.exec(String(str || '')))) {
|
|
33438
|
+
p = applyTransformCommand(m[1].toLowerCase(), parseTransformNumbers(m[2]), p);
|
|
33439
|
+
}
|
|
33440
|
+
return p;
|
|
33441
|
+
}
|
|
33442
|
+
|
|
33443
|
+
function parseTransformNumbers(str) {
|
|
33444
|
+
return String(str || '')
|
|
33445
|
+
.trim()
|
|
33446
|
+
.split(/[\s,]+/)
|
|
33447
|
+
.filter(Boolean)
|
|
33448
|
+
.map(Number)
|
|
33449
|
+
.filter(function(v) { return isFiniteNumber(v); });
|
|
33450
|
+
}
|
|
33451
|
+
|
|
33452
|
+
function applyTransformCommand(cmd, nums, point) {
|
|
33453
|
+
var x = point[0], y = point[1];
|
|
33454
|
+
var sx, sy;
|
|
33455
|
+
if (cmd == 'translate') {
|
|
33456
|
+
return [x + (nums[0] || 0), y + (nums.length > 1 ? nums[1] : 0)];
|
|
33457
|
+
}
|
|
33458
|
+
if (cmd == 'scale') {
|
|
33459
|
+
sx = nums.length > 0 ? nums[0] : 1;
|
|
33460
|
+
sy = nums.length > 1 ? nums[1] : sx;
|
|
33461
|
+
return [x * sx, y * sy];
|
|
33462
|
+
}
|
|
33463
|
+
if (cmd == 'matrix' && nums.length >= 6) {
|
|
33464
|
+
return [
|
|
33465
|
+
nums[0] * x + nums[2] * y + nums[4],
|
|
33466
|
+
nums[1] * x + nums[3] * y + nums[5]
|
|
33467
|
+
];
|
|
33468
|
+
}
|
|
33469
|
+
return point;
|
|
33470
|
+
}
|
|
33471
|
+
|
|
33472
|
+
function extractHiddenMetadataFromSource(str) {
|
|
33473
|
+
var m = String(str || '').match(/<g[^>]*\bid\s*=\s*["']mapshaper-metadata["'][^>]*>([\s\S]*?)<\/g>/i);
|
|
33474
|
+
if (!m) return null;
|
|
33475
|
+
return stripMarkupTags(m[1]);
|
|
33476
|
+
}
|
|
33477
|
+
|
|
33478
|
+
function stripMarkupTags(str) {
|
|
33479
|
+
return String(str || '').replace(/<[^>]*>/g, '');
|
|
33480
|
+
}
|
|
33481
|
+
|
|
33482
|
+
function normalizeMetadataJSONText(str) {
|
|
33483
|
+
if (!str) return null;
|
|
33484
|
+
return decodeHTMLEntities(stripMarkupTags(String(str))).trim();
|
|
33485
|
+
}
|
|
33486
|
+
|
|
33487
|
+
function decodeHTMLEntities(str) {
|
|
33488
|
+
return String(str || '')
|
|
33489
|
+
.replace(/"|"/gi, '"')
|
|
33490
|
+
.replace(/'|'/gi, "'")
|
|
33491
|
+
.replace(/&|&/gi, '&')
|
|
33492
|
+
.replace(/<|</gi, '<')
|
|
33493
|
+
.replace(/>|>/gi, '>')
|
|
33494
|
+
.replace(/"/g, '"')
|
|
33495
|
+
.replace(/'/g, "'")
|
|
33496
|
+
.replace(/&/g, '&')
|
|
33497
|
+
.replace(/</g, '<')
|
|
33498
|
+
.replace(/>/g, '>');
|
|
33499
|
+
}
|
|
33500
|
+
|
|
33501
|
+
function parseMetadataJSONObject(text) {
|
|
33502
|
+
try {
|
|
33503
|
+
return JSON.parse(text);
|
|
33504
|
+
} catch (e) {
|
|
33505
|
+
return null;
|
|
33506
|
+
}
|
|
33507
|
+
}
|
|
33508
|
+
|
|
33509
|
+
function normalizeMetadataCRS(crs) {
|
|
33510
|
+
return typeof crs == 'string' && crs.trim() ? crs.trim() : null;
|
|
33511
|
+
}
|
|
33512
|
+
|
|
33513
|
+
function normalizeMetadataBBox(bbox) {
|
|
33514
|
+
if (!Array.isArray(bbox) || bbox.length != 4) return null;
|
|
33515
|
+
var out = bbox.map(function(v) { return Number(v); });
|
|
33516
|
+
return out.every(isFiniteNumber) ? out : null;
|
|
33517
|
+
}
|
|
33518
|
+
|
|
33519
|
+
function parseSVGViewBox(str) {
|
|
33520
|
+
var parts = String(str || '').trim().split(/[\s,]+/).map(Number);
|
|
33521
|
+
if (parts.length != 4 || !parts.every(isFiniteNumber) || parts[2] === 0 || parts[3] === 0) {
|
|
33522
|
+
return null;
|
|
33523
|
+
}
|
|
33524
|
+
return parts;
|
|
33525
|
+
}
|
|
33526
|
+
|
|
33527
|
+
function remapCoordinatesFromSVGToMap(layerData, viewBox, bbox) {
|
|
33528
|
+
var vx = viewBox[0], vy = viewBox[1], vw = viewBox[2], vh = viewBox[3];
|
|
33529
|
+
var minX = bbox[0], minY = bbox[1], maxX = bbox[2], maxY = bbox[3];
|
|
33530
|
+
var scaleX = (maxX - minX) / vw;
|
|
33531
|
+
var scaleY = (maxY - minY) / vh;
|
|
33532
|
+
|
|
33533
|
+
layerData.forEach(function(layer) {
|
|
33534
|
+
layer.features.forEach(function(feature) {
|
|
33535
|
+
forEachGeometryCoordinate(feature && feature.geometry, function(coord) {
|
|
33536
|
+
var sx = coord[0];
|
|
33537
|
+
var sy = coord[1];
|
|
33538
|
+
coord[0] = minX + (sx - vx) * scaleX;
|
|
33539
|
+
coord[1] = maxY - (sy - vy) * scaleY;
|
|
33540
|
+
});
|
|
33541
|
+
});
|
|
33542
|
+
});
|
|
33191
33543
|
}
|
|
33192
33544
|
|
|
33193
33545
|
function importLayerFeatures(layerName, features, opts) {
|
|
@@ -33215,6 +33567,7 @@ ${svg}
|
|
|
33215
33567
|
childNodes.forEach(function(node) {
|
|
33216
33568
|
var tag = getTagName(node);
|
|
33217
33569
|
if (tag == 'defs') return;
|
|
33570
|
+
if (tag == 'g' && node.getAttribute && node.getAttribute('id') == 'mapshaper-metadata') return;
|
|
33218
33571
|
if (tag == 'g') {
|
|
33219
33572
|
groups.push({
|
|
33220
33573
|
node: node,
|
|
@@ -41265,7 +41618,7 @@ ${svg}
|
|
|
41265
41618
|
// @type: 'clip' or 'erase'
|
|
41266
41619
|
function clipLayers(targetLayers, clipSrc, targetDataset, type, opts) {
|
|
41267
41620
|
profileStart('clipLayers');
|
|
41268
|
-
opts = opts || {
|
|
41621
|
+
opts = opts || {no_cleanup: true}; // TODO: update testing functions
|
|
41269
41622
|
var usingPathClip = utils.some(targetLayers, layerHasPaths);
|
|
41270
41623
|
var mergedDataset, clipLyr, nodes, result;
|
|
41271
41624
|
var clipDataset = normalizeOverlaySource(clipSrc, targetDataset, opts);
|
|
@@ -48147,17 +48500,62 @@ ${svg}
|
|
|
48147
48500
|
});
|
|
48148
48501
|
}
|
|
48149
48502
|
|
|
48503
|
+
function parseWKTPoint(val) {
|
|
48504
|
+
if (!utils.isString(val)) return null;
|
|
48505
|
+
// Support 2D POINT WKT, case-insensitive, with optional Z/M tokens.
|
|
48506
|
+
// Examples:
|
|
48507
|
+
// POINT (1 2)
|
|
48508
|
+
// point(1 2)
|
|
48509
|
+
// POINT Z (1 2 3)
|
|
48510
|
+
// POINT M (1 2 3)
|
|
48511
|
+
// POINT ZM (1 2 3 4)
|
|
48512
|
+
var m = /^\s*POINT(?:\s+Z(?:M)?|\s+M)?\s*\(\s*([^\s,]+)\s+([^\s,]+)(?:\s+[^)]*)?\)\s*$/i.exec(val);
|
|
48513
|
+
if (!m) return null;
|
|
48514
|
+
var x = coordinateFromValue(m[1]);
|
|
48515
|
+
var y = coordinateFromValue(m[2]);
|
|
48516
|
+
if (isNaN(x) || isNaN(y)) return null;
|
|
48517
|
+
return [x, y];
|
|
48518
|
+
}
|
|
48519
|
+
|
|
48520
|
+
function findWKTField(records, fields) {
|
|
48521
|
+
var sampleSize = Math.min(records.length, 50);
|
|
48522
|
+
return utils.find(fields, function(name) {
|
|
48523
|
+
var seen = 0;
|
|
48524
|
+
var matches = 0;
|
|
48525
|
+
for (var i = 0; i < sampleSize; i++) {
|
|
48526
|
+
var rec = records[i];
|
|
48527
|
+
if (!rec) continue;
|
|
48528
|
+
var val = rec[name];
|
|
48529
|
+
if (!utils.isString(val) || val.trim() === '') continue;
|
|
48530
|
+
seen++;
|
|
48531
|
+
if (parseWKTPoint(val)) matches++;
|
|
48532
|
+
}
|
|
48533
|
+
// Require at least one non-empty sample and majority parseable as POINT.
|
|
48534
|
+
return seen > 0 && matches / seen >= 0.6;
|
|
48535
|
+
});
|
|
48536
|
+
}
|
|
48537
|
+
|
|
48150
48538
|
function pointsFromDataTableAuto(data) {
|
|
48151
48539
|
var fields = data ? data.getFields() : [];
|
|
48540
|
+
var records = data ? data.getRecords() : [];
|
|
48152
48541
|
var opts = {
|
|
48153
48542
|
x: findXField(fields),
|
|
48154
48543
|
y: findYField(fields)
|
|
48155
48544
|
};
|
|
48545
|
+
if (!opts.x || !opts.y) {
|
|
48546
|
+
opts.wkt = findWKTField(records, fields);
|
|
48547
|
+
}
|
|
48156
48548
|
return pointsFromDataTable(data, opts);
|
|
48157
48549
|
}
|
|
48158
48550
|
|
|
48159
48551
|
function pointsFromDataTable(data, opts) {
|
|
48160
48552
|
if (!data) stop$1("Layer is missing a data table");
|
|
48553
|
+
if (opts.wkt && data.fieldExists(opts.wkt)) {
|
|
48554
|
+
return data.getRecords().map(function(rec) {
|
|
48555
|
+
var p = parseWKTPoint(rec[opts.wkt]);
|
|
48556
|
+
return p ? [p] : null;
|
|
48557
|
+
});
|
|
48558
|
+
}
|
|
48161
48559
|
if (!opts.x || !opts.y || !data.fieldExists(opts.x) || !data.fieldExists(opts.y)) {
|
|
48162
48560
|
stop$1("Missing x,y data fields");
|
|
48163
48561
|
}
|
|
@@ -48175,8 +48573,10 @@ ${svg}
|
|
|
48175
48573
|
var Points = /*#__PURE__*/Object.freeze({
|
|
48176
48574
|
__proto__: null,
|
|
48177
48575
|
coordinateFromValue: coordinateFromValue,
|
|
48576
|
+
findWKTField: findWKTField,
|
|
48178
48577
|
findXField: findXField,
|
|
48179
48578
|
findYField: findYField,
|
|
48579
|
+
parseWKTPoint: parseWKTPoint,
|
|
48180
48580
|
pointsFromPolygons: pointsFromPolygons
|
|
48181
48581
|
});
|
|
48182
48582
|
|
|
@@ -53074,7 +53474,7 @@ ${svg}
|
|
|
53074
53474
|
});
|
|
53075
53475
|
}
|
|
53076
53476
|
|
|
53077
|
-
var version = "0.7.
|
|
53477
|
+
var version = "0.7.7";
|
|
53078
53478
|
|
|
53079
53479
|
// Parse command line args into commands and run them
|
|
53080
53480
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/package.json
CHANGED
package/www/index.html
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
<title>
|
|
5
|
-
<meta name="Description" content="A tool for
|
|
4
|
+
<title>Mapshaper</title>
|
|
5
|
+
<meta name="Description" content="A topology-aware tool for editing and converting geospatial vector data. Works with Shapefile, GeoJSON, TopoJSON, GeoPackage, FlatGeobuf and KML — in the browser or on the command line.">
|
|
6
6
|
<meta charset="UTF-8">
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
8
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
9
9
|
<script>
|
|
10
|
-
// If the page is opened with preloaded data (?files= or ?catalog= in the
|
|
11
|
-
// URL), tag <html> so CSS hides the splash-screen header links right away.
|
|
12
|
-
// Without this they flash into view for ~a second before the editor mounts
|
|
13
|
-
// and hides them. Done as the first script in <head> so the class is set
|
|
14
|
-
// before any layout. bin/mapshaper-gui adds the same class server-side
|
|
15
|
-
// when files are passed on the command line.
|
|
16
10
|
(function() {
|
|
17
11
|
try {
|
|
18
12
|
var s = location.search || '';
|
|
19
|
-
if (
|
|
13
|
+
if (!window.location.hostname.endsWith("mapshaper.org") ||
|
|
14
|
+
s.indexOf('files=') !== -1 || s.indexOf('catalog=') !== -1) {
|
|
15
|
+
// hide spash-screen header links
|
|
20
16
|
document.documentElement.className += ' mapshaper-preload';
|
|
21
17
|
}
|
|
22
18
|
} catch (e) {}
|
|
@@ -26,7 +22,7 @@
|
|
|
26
22
|
<link rel="stylesheet" href="elements.css">
|
|
27
23
|
<link rel="icon" type="image/png" href="images/icon.png">
|
|
28
24
|
</head>
|
|
29
|
-
<body
|
|
25
|
+
<body>
|
|
30
26
|
<div class="hidden">
|
|
31
27
|
<svg version="1.1" id="home-icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
|
32
28
|
y="0px" width="14px" height="19px" viewBox="0 0 14 16">
|
|
@@ -72,7 +68,7 @@
|
|
|
72
68
|
<div class="page-header">
|
|
73
69
|
<div class="mapshaper-logo">map<span class="logo-highlight">shaper</span></div>
|
|
74
70
|
|
|
75
|
-
<div class="layer-control-btn"><span class="btn header-btn layer-name"></span></div>
|
|
71
|
+
<div class="layer-control-btn icon-duo"><span class="btn header-btn layer-name"></span></div>
|
|
76
72
|
|
|
77
73
|
<div class="simplify-control-wrapper"><div class="simplify-control"><div class="header-btn btn simplify-settings-btn">Settings</div>
|
|
78
74
|
<div class="slider">
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -15412,7 +15412,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
15412
15412
|
gui = new GuiInstance('body');
|
|
15413
15413
|
|
|
15414
15414
|
if (!importOpts.files?.length) {
|
|
15415
|
-
|
|
15415
|
+
// show header links if not preloading files
|
|
15416
|
+
document.documentElement.classList.remove('mapshaper-preload');
|
|
15416
15417
|
}
|
|
15417
15418
|
|
|
15418
15419
|
new AlertControl(gui);
|
package/www/mapshaper.js
CHANGED
|
@@ -18443,7 +18443,7 @@
|
|
|
18443
18443
|
function exportSVG(dataset, opts) {
|
|
18444
18444
|
var namespace = 'xmlns="http://www.w3.org/2000/svg"';
|
|
18445
18445
|
var defs = [];
|
|
18446
|
-
var frame, svg, layers;
|
|
18446
|
+
var frame, svg, layers, metadataJSON;
|
|
18447
18447
|
var style = '';
|
|
18448
18448
|
|
|
18449
18449
|
// kludge for map keys
|
|
@@ -18467,6 +18467,9 @@
|
|
|
18467
18467
|
frame = getFrameData(dataset, opts);
|
|
18468
18468
|
fitDatasetToFrame(dataset, frame);
|
|
18469
18469
|
setCoordinatePrecision(dataset, opts.precision || 0.01);
|
|
18470
|
+
if (opts.metadata) {
|
|
18471
|
+
metadataJSON = JSON.stringify(getGeospatialMetadata(dataset, frame));
|
|
18472
|
+
}
|
|
18470
18473
|
|
|
18471
18474
|
// error if one or more svg_data fields are not present in any layers
|
|
18472
18475
|
if (opts.svg_data) validateSvgDataFields(dataset.layers, opts.svg_data);
|
|
@@ -18486,6 +18489,10 @@
|
|
|
18486
18489
|
return stringify(obj);
|
|
18487
18490
|
}).join('\n');
|
|
18488
18491
|
|
|
18492
|
+
if (metadataJSON) {
|
|
18493
|
+
svg = getMetadataBlock(metadataJSON, [0, 0, frame.width, frame.height]) + svg;
|
|
18494
|
+
}
|
|
18495
|
+
|
|
18489
18496
|
if (defs.length > 0) {
|
|
18490
18497
|
svg = '<defs>\n' + utils.pluck(defs, 'svg').join('') + '</defs>\n' + svg;
|
|
18491
18498
|
}
|
|
@@ -18513,6 +18520,57 @@ ${svg}
|
|
|
18513
18520
|
}];
|
|
18514
18521
|
}
|
|
18515
18522
|
|
|
18523
|
+
function getMetadataBlock(metadataJSON, viewBox) {
|
|
18524
|
+
var x = 0;
|
|
18525
|
+
var y = 0;
|
|
18526
|
+
var width = viewBox && viewBox[2] || 0;
|
|
18527
|
+
var height = viewBox && viewBox[3] || 0;
|
|
18528
|
+
return '<metadata>' + metadataJSON + '</metadata>\n' +
|
|
18529
|
+
'<g id="mapshaper-metadata">\n' +
|
|
18530
|
+
'<rect x="' + x + '" y="' + y + '" width="' + width + '" height="' + height + '" opacity="0"/>\n' +
|
|
18531
|
+
'<text opacity="0" font-size="0.1">' + metadataJSON + '</text>\n' +
|
|
18532
|
+
'</g>\n';
|
|
18533
|
+
}
|
|
18534
|
+
|
|
18535
|
+
function getGeospatialMetadata(dataset, frame) {
|
|
18536
|
+
return {
|
|
18537
|
+
crs: getSVGMetadataCRS(dataset),
|
|
18538
|
+
bbox: frame.bbox || null
|
|
18539
|
+
};
|
|
18540
|
+
}
|
|
18541
|
+
|
|
18542
|
+
function getSVGMetadataCRS(dataset) {
|
|
18543
|
+
var crs = getDatasetCRS(dataset);
|
|
18544
|
+
var info = getDatasetCrsInfo(dataset);
|
|
18545
|
+
var proj4 = null;
|
|
18546
|
+
if (crs) {
|
|
18547
|
+
proj4 = crsToProj4(crs);
|
|
18548
|
+
}
|
|
18549
|
+
if (proj4) return proj4;
|
|
18550
|
+
return getAuthorityCodeString(info) || null;
|
|
18551
|
+
}
|
|
18552
|
+
|
|
18553
|
+
function getAuthorityCodeString(info) {
|
|
18554
|
+
var authority = null;
|
|
18555
|
+
if (info && info.crs_string) {
|
|
18556
|
+
authority = parseAuthorityCodeString(info.crs_string);
|
|
18557
|
+
}
|
|
18558
|
+
if (!authority && info && info.wkt1) {
|
|
18559
|
+
authority = parseAuthorityCodeFromWkt(info.wkt1);
|
|
18560
|
+
}
|
|
18561
|
+
if (!authority && info && info.geopackage_crs) {
|
|
18562
|
+
authority = parseGeoPackageAuthority(info.geopackage_crs);
|
|
18563
|
+
}
|
|
18564
|
+
return authority ? authority.authority + ':' + authority.code : null;
|
|
18565
|
+
}
|
|
18566
|
+
|
|
18567
|
+
function parseGeoPackageAuthority(o) {
|
|
18568
|
+
if (!o) return null;
|
|
18569
|
+
var authority = o.organization || o.authority || null;
|
|
18570
|
+
var code = o.organization_coordsys_id || o.code || null;
|
|
18571
|
+
return authority && code != null ? {authority: String(authority).toUpperCase(), code: String(code)} : null;
|
|
18572
|
+
}
|
|
18573
|
+
|
|
18516
18574
|
function exportFurnitureLayerForSVG(lyr, frame, opts) {
|
|
18517
18575
|
var layerObj = getEmptyLayerForSVG(lyr, opts);
|
|
18518
18576
|
layerObj.children = renderFurnitureLayer(lyr, frame);
|
|
@@ -28017,6 +28075,10 @@ ${svg}
|
|
|
28017
28075
|
old_alias: 'json-subtree',
|
|
28018
28076
|
describe: '[JSON] path to JSON input data; separator is /'
|
|
28019
28077
|
})
|
|
28078
|
+
.option('ndjson', {
|
|
28079
|
+
type: 'flag',
|
|
28080
|
+
describe: '[JSON/GeoJSON] input is newline-delimited JSON objects'
|
|
28081
|
+
})
|
|
28020
28082
|
.option('single-part', {
|
|
28021
28083
|
type: 'flag',
|
|
28022
28084
|
// describe: '[GeoJSON] split multi-part features into single-part features'
|
|
@@ -28109,10 +28171,6 @@ ${svg}
|
|
|
28109
28171
|
describe: '[TopoJSON] export coordinates without quantization',
|
|
28110
28172
|
type: 'flag'
|
|
28111
28173
|
})
|
|
28112
|
-
.option('metadata', {
|
|
28113
|
-
// describe: '[TopoJSON] Add a metadata object containing CRS information',
|
|
28114
|
-
type: 'flag'
|
|
28115
|
-
})
|
|
28116
28174
|
.option('no-point-quantization', {
|
|
28117
28175
|
// describe: '[TopoJSON] export point coordinates without quantization',
|
|
28118
28176
|
type: 'flag'
|
|
@@ -28155,6 +28213,10 @@ ${svg}
|
|
|
28155
28213
|
describe: '[GeoJSON/JSON] output newline-delimited features or records',
|
|
28156
28214
|
type: 'flag'
|
|
28157
28215
|
})
|
|
28216
|
+
.option('metadata', {
|
|
28217
|
+
describe: '[SVG/TopoJSON] include metadata in output',
|
|
28218
|
+
type: 'flag'
|
|
28219
|
+
})
|
|
28158
28220
|
.option('width', {
|
|
28159
28221
|
describe: '[SVG/TopoJSON] pixel width of output (SVG default is 800)',
|
|
28160
28222
|
type: 'number'
|
|
@@ -28217,10 +28279,7 @@ ${svg}
|
|
|
28217
28279
|
.option('final', {
|
|
28218
28280
|
type: 'flag' // for testing
|
|
28219
28281
|
})
|
|
28220
|
-
|
|
28221
|
-
// describe: '[TopoJSON] add a metadata object',
|
|
28222
|
-
type: 'flag'
|
|
28223
|
-
});
|
|
28282
|
+
;
|
|
28224
28283
|
|
|
28225
28284
|
parser.section('Editing commands');
|
|
28226
28285
|
|
|
@@ -32479,7 +32538,13 @@ ${svg}
|
|
|
32479
32538
|
var str = readFirstChars(reader, 1000);
|
|
32480
32539
|
var type = identifyJSONString(str, opts);
|
|
32481
32540
|
var dataset, retn;
|
|
32482
|
-
if (
|
|
32541
|
+
if (opts.ndjson) {
|
|
32542
|
+
// NDJSON can represent either newline-delimited GeoJSON features/geometries
|
|
32543
|
+
// or plain JSON records. Defer type detection until after line parsing.
|
|
32544
|
+
retn = {
|
|
32545
|
+
content: reader.toString('utf8')
|
|
32546
|
+
};
|
|
32547
|
+
} else if (type == 'geojson') { // consider only for larger files
|
|
32483
32548
|
dataset = importGeoJSONFile(reader, opts);
|
|
32484
32549
|
retn = {
|
|
32485
32550
|
dataset: dataset,
|
|
@@ -32531,6 +32596,12 @@ ${svg}
|
|
|
32531
32596
|
}
|
|
32532
32597
|
|
|
32533
32598
|
if (content) {
|
|
32599
|
+
if (opts.ndjson) {
|
|
32600
|
+
var nd = importNDJSON(content, opts);
|
|
32601
|
+
retn.dataset = nd.dataset;
|
|
32602
|
+
retn.format = nd.format;
|
|
32603
|
+
return retn;
|
|
32604
|
+
}
|
|
32534
32605
|
if (utils.isString(content)) {
|
|
32535
32606
|
try {
|
|
32536
32607
|
content = JSON.parse(content); // ~3sec for 100MB string
|
|
@@ -32569,6 +32640,54 @@ ${svg}
|
|
|
32569
32640
|
});
|
|
32570
32641
|
}
|
|
32571
32642
|
|
|
32643
|
+
function importNDJSON(content, opts) {
|
|
32644
|
+
var lines = utils.isString(content) ? utils.splitLines(content) : [];
|
|
32645
|
+
var objects = [];
|
|
32646
|
+
var firstGeo = null;
|
|
32647
|
+
for (var i = 0; i < lines.length; i++) {
|
|
32648
|
+
var raw = lines[i].trim();
|
|
32649
|
+
if (!raw) continue;
|
|
32650
|
+
var obj;
|
|
32651
|
+
try {
|
|
32652
|
+
obj = JSON.parse(raw);
|
|
32653
|
+
} catch (e) {
|
|
32654
|
+
stop$1('NDJSON parsing error on line ' + (i + 1) + ': ' + e.message);
|
|
32655
|
+
}
|
|
32656
|
+
objects.push(obj);
|
|
32657
|
+
if (firstGeo === null) {
|
|
32658
|
+
firstGeo = isGeoJSONObject(obj);
|
|
32659
|
+
} else if (firstGeo !== isGeoJSONObject(obj)) {
|
|
32660
|
+
stop$1('NDJSON input mixes GeoJSON and non-GeoJSON objects');
|
|
32661
|
+
}
|
|
32662
|
+
}
|
|
32663
|
+
if (objects.length === 0) {
|
|
32664
|
+
stop$1('NDJSON input does not contain any JSON objects');
|
|
32665
|
+
}
|
|
32666
|
+
if (!firstGeo) {
|
|
32667
|
+
return {dataset: importJSONTable(objects), format: 'json'};
|
|
32668
|
+
}
|
|
32669
|
+
var parser = new GeoJSONParser(getGeoJSONImportOpts(opts));
|
|
32670
|
+
objects.forEach(function(obj) {
|
|
32671
|
+
if (obj && obj.type == 'FeatureCollection') {
|
|
32672
|
+
(obj.features || []).forEach(parser.parseObject);
|
|
32673
|
+
} else if (obj && obj.type == 'GeometryCollection') {
|
|
32674
|
+
(obj.geometries || []).forEach(parser.parseObject);
|
|
32675
|
+
} else {
|
|
32676
|
+
parser.parseObject(obj);
|
|
32677
|
+
}
|
|
32678
|
+
});
|
|
32679
|
+
return {dataset: parser.done(), format: 'geojson'};
|
|
32680
|
+
}
|
|
32681
|
+
|
|
32682
|
+
function isGeoJSONObject(obj) {
|
|
32683
|
+
if (!obj || typeof obj != 'object') return false;
|
|
32684
|
+
var type = obj.type;
|
|
32685
|
+
return type == 'Feature' || type == 'FeatureCollection' ||
|
|
32686
|
+
type == 'GeometryCollection' || type == 'Point' || type == 'MultiPoint' ||
|
|
32687
|
+
type == 'LineString' || type == 'MultiLineString' ||
|
|
32688
|
+
type == 'Polygon' || type == 'MultiPolygon';
|
|
32689
|
+
}
|
|
32690
|
+
|
|
32572
32691
|
// path: path from top-level to the target object
|
|
32573
32692
|
function selectFromObject(o, path) {
|
|
32574
32693
|
var arrayRxp = /(.*)\[([0-9]+)\]$/; // array bracket notation w/ index
|
|
@@ -33159,6 +33278,7 @@ ${svg}
|
|
|
33159
33278
|
var Parser = typeof DOMParser == 'undefined' ? require$1('@xmldom/xmldom').DOMParser : DOMParser;
|
|
33160
33279
|
var doc = new Parser().parseFromString(str, 'text/xml');
|
|
33161
33280
|
var root = doc && doc.documentElement;
|
|
33281
|
+
var geometadata = parseSVGGeometadata(root, str);
|
|
33162
33282
|
var groups = getTopLevelLayerGroups(root);
|
|
33163
33283
|
var layerData = [];
|
|
33164
33284
|
var datasets = [];
|
|
@@ -33175,7 +33295,11 @@ ${svg}
|
|
|
33175
33295
|
});
|
|
33176
33296
|
});
|
|
33177
33297
|
|
|
33178
|
-
|
|
33298
|
+
if (geometadata && geometadata.bbox && geometadata.viewBox) {
|
|
33299
|
+
remapCoordinatesFromSVGToMap(layerData, geometadata.viewBox, geometadata.bbox);
|
|
33300
|
+
} else {
|
|
33301
|
+
flipYCoordinates(layerData);
|
|
33302
|
+
}
|
|
33179
33303
|
|
|
33180
33304
|
layerData.forEach(function(layer) {
|
|
33181
33305
|
datasets.push(importLayerFeatures(layer.name, layer.features, opts));
|
|
@@ -33185,9 +33309,237 @@ ${svg}
|
|
|
33185
33309
|
return {layers: [], info: {}};
|
|
33186
33310
|
}
|
|
33187
33311
|
if (datasets.length === 1) {
|
|
33188
|
-
return datasets[0];
|
|
33312
|
+
return applySVGGeometadata(datasets[0], geometadata);
|
|
33189
33313
|
}
|
|
33190
|
-
return mergeDatasets(datasets);
|
|
33314
|
+
return applySVGGeometadata(mergeDatasets(datasets), geometadata);
|
|
33315
|
+
}
|
|
33316
|
+
|
|
33317
|
+
function applySVGGeometadata(dataset, geometadata) {
|
|
33318
|
+
if (geometadata && geometadata.crs) {
|
|
33319
|
+
setDatasetCrsInfo(dataset, getCrsInfo(geometadata.crs));
|
|
33320
|
+
}
|
|
33321
|
+
return dataset;
|
|
33322
|
+
}
|
|
33323
|
+
|
|
33324
|
+
function parseSVGGeometadata(root, svgText) {
|
|
33325
|
+
var json = findMetadataJSONString(root, svgText);
|
|
33326
|
+
var obj;
|
|
33327
|
+
var metadataViewport = getMetadataViewportRect(root);
|
|
33328
|
+
if (!json) return null;
|
|
33329
|
+
obj = parseMetadataJSONObject(json);
|
|
33330
|
+
if (!obj) return null;
|
|
33331
|
+
return {
|
|
33332
|
+
crs: normalizeMetadataCRS(obj.crs),
|
|
33333
|
+
bbox: normalizeMetadataBBox(obj.bbox),
|
|
33334
|
+
viewBox: metadataViewport || parseSVGViewBox(root && root.getAttribute && root.getAttribute('viewBox'))
|
|
33335
|
+
};
|
|
33336
|
+
}
|
|
33337
|
+
|
|
33338
|
+
function findMetadataJSONString(root, svgText) {
|
|
33339
|
+
var text = getMetadataTagText(root) || getHiddenMetadataText(root);
|
|
33340
|
+
if (!text && svgText) {
|
|
33341
|
+
text = extractHiddenMetadataFromSource(svgText);
|
|
33342
|
+
}
|
|
33343
|
+
return normalizeMetadataJSONText(text);
|
|
33344
|
+
}
|
|
33345
|
+
|
|
33346
|
+
function getMetadataTagText(root) {
|
|
33347
|
+
var nodes = getElementChildren(root);
|
|
33348
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
33349
|
+
if (getTagName(nodes[i]) == 'metadata') {
|
|
33350
|
+
return nodes[i].textContent || '';
|
|
33351
|
+
}
|
|
33352
|
+
}
|
|
33353
|
+
return null;
|
|
33354
|
+
}
|
|
33355
|
+
|
|
33356
|
+
function getHiddenMetadataText(root) {
|
|
33357
|
+
var node = getMetadataGroupNode(root);
|
|
33358
|
+
if (node) {
|
|
33359
|
+
return node.textContent || '';
|
|
33360
|
+
}
|
|
33361
|
+
return null;
|
|
33362
|
+
}
|
|
33363
|
+
|
|
33364
|
+
function getMetadataGroupNode(root) {
|
|
33365
|
+
var nodes = getElementChildren(root);
|
|
33366
|
+
var i, node;
|
|
33367
|
+
for (i = 0; i < nodes.length; i++) {
|
|
33368
|
+
node = nodes[i];
|
|
33369
|
+
if (getTagName(node) == 'g' && node.getAttribute && node.getAttribute('id') == 'mapshaper-metadata') {
|
|
33370
|
+
return node;
|
|
33371
|
+
}
|
|
33372
|
+
}
|
|
33373
|
+
return null;
|
|
33374
|
+
}
|
|
33375
|
+
|
|
33376
|
+
function getMetadataViewportRect(root) {
|
|
33377
|
+
var node = getMetadataGroupNode(root);
|
|
33378
|
+
var children, i, rect;
|
|
33379
|
+
if (!node) return null;
|
|
33380
|
+
children = getElementChildren(node);
|
|
33381
|
+
for (i = 0; i < children.length; i++) {
|
|
33382
|
+
if (getTagName(children[i]) == 'rect') {
|
|
33383
|
+
rect = parseMetadataRectNode(children[i]);
|
|
33384
|
+
if (rect) return rect;
|
|
33385
|
+
}
|
|
33386
|
+
}
|
|
33387
|
+
return null;
|
|
33388
|
+
}
|
|
33389
|
+
|
|
33390
|
+
function parseMetadataRectNode(node) {
|
|
33391
|
+
var x = parseNumber(node.getAttribute('x')) || 0;
|
|
33392
|
+
var y = parseNumber(node.getAttribute('y')) || 0;
|
|
33393
|
+
var width = parseNumber(node.getAttribute('width'));
|
|
33394
|
+
var height = parseNumber(node.getAttribute('height'));
|
|
33395
|
+
var pts, i, p;
|
|
33396
|
+
var xmin = Infinity, ymin = Infinity, xmax = -Infinity, ymax = -Infinity;
|
|
33397
|
+
if (!isFiniteNumber(width) || !isFiniteNumber(height) || width === 0 || height === 0) {
|
|
33398
|
+
return null;
|
|
33399
|
+
}
|
|
33400
|
+
pts = [
|
|
33401
|
+
[x, y],
|
|
33402
|
+
[x + width, y],
|
|
33403
|
+
[x, y + height],
|
|
33404
|
+
[x + width, y + height]
|
|
33405
|
+
];
|
|
33406
|
+
for (i = 0; i < pts.length; i++) {
|
|
33407
|
+
p = applyNodeTransformChain(node, pts[i]);
|
|
33408
|
+
xmin = Math.min(xmin, p[0]);
|
|
33409
|
+
ymin = Math.min(ymin, p[1]);
|
|
33410
|
+
xmax = Math.max(xmax, p[0]);
|
|
33411
|
+
ymax = Math.max(ymax, p[1]);
|
|
33412
|
+
}
|
|
33413
|
+
if (!isFiniteNumber(xmin) || !isFiniteNumber(ymin) || !isFiniteNumber(xmax) || !isFiniteNumber(ymax)) {
|
|
33414
|
+
return null;
|
|
33415
|
+
}
|
|
33416
|
+
return [xmin, ymin, xmax - xmin, ymax - ymin];
|
|
33417
|
+
}
|
|
33418
|
+
|
|
33419
|
+
function applyNodeTransformChain(node, point) {
|
|
33420
|
+
var chain = [];
|
|
33421
|
+
var p = [point[0], point[1]];
|
|
33422
|
+
var i;
|
|
33423
|
+
while (node && node.nodeType == 1) {
|
|
33424
|
+
chain.unshift(node);
|
|
33425
|
+
node = node.parentNode;
|
|
33426
|
+
}
|
|
33427
|
+
for (i = 0; i < chain.length; i++) {
|
|
33428
|
+
p = applyTransformString(chain[i].getAttribute && chain[i].getAttribute('transform'), p);
|
|
33429
|
+
}
|
|
33430
|
+
return p;
|
|
33431
|
+
}
|
|
33432
|
+
|
|
33433
|
+
function applyTransformString(str, point) {
|
|
33434
|
+
var re = /([a-z]+)\s*\(([^)]*)\)/ig;
|
|
33435
|
+
var m = null;
|
|
33436
|
+
var p = [point[0], point[1]];
|
|
33437
|
+
while ((m = re.exec(String(str || '')))) {
|
|
33438
|
+
p = applyTransformCommand(m[1].toLowerCase(), parseTransformNumbers(m[2]), p);
|
|
33439
|
+
}
|
|
33440
|
+
return p;
|
|
33441
|
+
}
|
|
33442
|
+
|
|
33443
|
+
function parseTransformNumbers(str) {
|
|
33444
|
+
return String(str || '')
|
|
33445
|
+
.trim()
|
|
33446
|
+
.split(/[\s,]+/)
|
|
33447
|
+
.filter(Boolean)
|
|
33448
|
+
.map(Number)
|
|
33449
|
+
.filter(function(v) { return isFiniteNumber(v); });
|
|
33450
|
+
}
|
|
33451
|
+
|
|
33452
|
+
function applyTransformCommand(cmd, nums, point) {
|
|
33453
|
+
var x = point[0], y = point[1];
|
|
33454
|
+
var sx, sy;
|
|
33455
|
+
if (cmd == 'translate') {
|
|
33456
|
+
return [x + (nums[0] || 0), y + (nums.length > 1 ? nums[1] : 0)];
|
|
33457
|
+
}
|
|
33458
|
+
if (cmd == 'scale') {
|
|
33459
|
+
sx = nums.length > 0 ? nums[0] : 1;
|
|
33460
|
+
sy = nums.length > 1 ? nums[1] : sx;
|
|
33461
|
+
return [x * sx, y * sy];
|
|
33462
|
+
}
|
|
33463
|
+
if (cmd == 'matrix' && nums.length >= 6) {
|
|
33464
|
+
return [
|
|
33465
|
+
nums[0] * x + nums[2] * y + nums[4],
|
|
33466
|
+
nums[1] * x + nums[3] * y + nums[5]
|
|
33467
|
+
];
|
|
33468
|
+
}
|
|
33469
|
+
return point;
|
|
33470
|
+
}
|
|
33471
|
+
|
|
33472
|
+
function extractHiddenMetadataFromSource(str) {
|
|
33473
|
+
var m = String(str || '').match(/<g[^>]*\bid\s*=\s*["']mapshaper-metadata["'][^>]*>([\s\S]*?)<\/g>/i);
|
|
33474
|
+
if (!m) return null;
|
|
33475
|
+
return stripMarkupTags(m[1]);
|
|
33476
|
+
}
|
|
33477
|
+
|
|
33478
|
+
function stripMarkupTags(str) {
|
|
33479
|
+
return String(str || '').replace(/<[^>]*>/g, '');
|
|
33480
|
+
}
|
|
33481
|
+
|
|
33482
|
+
function normalizeMetadataJSONText(str) {
|
|
33483
|
+
if (!str) return null;
|
|
33484
|
+
return decodeHTMLEntities(stripMarkupTags(String(str))).trim();
|
|
33485
|
+
}
|
|
33486
|
+
|
|
33487
|
+
function decodeHTMLEntities(str) {
|
|
33488
|
+
return String(str || '')
|
|
33489
|
+
.replace(/"|"/gi, '"')
|
|
33490
|
+
.replace(/'|'/gi, "'")
|
|
33491
|
+
.replace(/&|&/gi, '&')
|
|
33492
|
+
.replace(/<|</gi, '<')
|
|
33493
|
+
.replace(/>|>/gi, '>')
|
|
33494
|
+
.replace(/"/g, '"')
|
|
33495
|
+
.replace(/'/g, "'")
|
|
33496
|
+
.replace(/&/g, '&')
|
|
33497
|
+
.replace(/</g, '<')
|
|
33498
|
+
.replace(/>/g, '>');
|
|
33499
|
+
}
|
|
33500
|
+
|
|
33501
|
+
function parseMetadataJSONObject(text) {
|
|
33502
|
+
try {
|
|
33503
|
+
return JSON.parse(text);
|
|
33504
|
+
} catch (e) {
|
|
33505
|
+
return null;
|
|
33506
|
+
}
|
|
33507
|
+
}
|
|
33508
|
+
|
|
33509
|
+
function normalizeMetadataCRS(crs) {
|
|
33510
|
+
return typeof crs == 'string' && crs.trim() ? crs.trim() : null;
|
|
33511
|
+
}
|
|
33512
|
+
|
|
33513
|
+
function normalizeMetadataBBox(bbox) {
|
|
33514
|
+
if (!Array.isArray(bbox) || bbox.length != 4) return null;
|
|
33515
|
+
var out = bbox.map(function(v) { return Number(v); });
|
|
33516
|
+
return out.every(isFiniteNumber) ? out : null;
|
|
33517
|
+
}
|
|
33518
|
+
|
|
33519
|
+
function parseSVGViewBox(str) {
|
|
33520
|
+
var parts = String(str || '').trim().split(/[\s,]+/).map(Number);
|
|
33521
|
+
if (parts.length != 4 || !parts.every(isFiniteNumber) || parts[2] === 0 || parts[3] === 0) {
|
|
33522
|
+
return null;
|
|
33523
|
+
}
|
|
33524
|
+
return parts;
|
|
33525
|
+
}
|
|
33526
|
+
|
|
33527
|
+
function remapCoordinatesFromSVGToMap(layerData, viewBox, bbox) {
|
|
33528
|
+
var vx = viewBox[0], vy = viewBox[1], vw = viewBox[2], vh = viewBox[3];
|
|
33529
|
+
var minX = bbox[0], minY = bbox[1], maxX = bbox[2], maxY = bbox[3];
|
|
33530
|
+
var scaleX = (maxX - minX) / vw;
|
|
33531
|
+
var scaleY = (maxY - minY) / vh;
|
|
33532
|
+
|
|
33533
|
+
layerData.forEach(function(layer) {
|
|
33534
|
+
layer.features.forEach(function(feature) {
|
|
33535
|
+
forEachGeometryCoordinate(feature && feature.geometry, function(coord) {
|
|
33536
|
+
var sx = coord[0];
|
|
33537
|
+
var sy = coord[1];
|
|
33538
|
+
coord[0] = minX + (sx - vx) * scaleX;
|
|
33539
|
+
coord[1] = maxY - (sy - vy) * scaleY;
|
|
33540
|
+
});
|
|
33541
|
+
});
|
|
33542
|
+
});
|
|
33191
33543
|
}
|
|
33192
33544
|
|
|
33193
33545
|
function importLayerFeatures(layerName, features, opts) {
|
|
@@ -33215,6 +33567,7 @@ ${svg}
|
|
|
33215
33567
|
childNodes.forEach(function(node) {
|
|
33216
33568
|
var tag = getTagName(node);
|
|
33217
33569
|
if (tag == 'defs') return;
|
|
33570
|
+
if (tag == 'g' && node.getAttribute && node.getAttribute('id') == 'mapshaper-metadata') return;
|
|
33218
33571
|
if (tag == 'g') {
|
|
33219
33572
|
groups.push({
|
|
33220
33573
|
node: node,
|
|
@@ -41265,7 +41618,7 @@ ${svg}
|
|
|
41265
41618
|
// @type: 'clip' or 'erase'
|
|
41266
41619
|
function clipLayers(targetLayers, clipSrc, targetDataset, type, opts) {
|
|
41267
41620
|
profileStart('clipLayers');
|
|
41268
|
-
opts = opts || {
|
|
41621
|
+
opts = opts || {no_cleanup: true}; // TODO: update testing functions
|
|
41269
41622
|
var usingPathClip = utils.some(targetLayers, layerHasPaths);
|
|
41270
41623
|
var mergedDataset, clipLyr, nodes, result;
|
|
41271
41624
|
var clipDataset = normalizeOverlaySource(clipSrc, targetDataset, opts);
|
|
@@ -48147,17 +48500,62 @@ ${svg}
|
|
|
48147
48500
|
});
|
|
48148
48501
|
}
|
|
48149
48502
|
|
|
48503
|
+
function parseWKTPoint(val) {
|
|
48504
|
+
if (!utils.isString(val)) return null;
|
|
48505
|
+
// Support 2D POINT WKT, case-insensitive, with optional Z/M tokens.
|
|
48506
|
+
// Examples:
|
|
48507
|
+
// POINT (1 2)
|
|
48508
|
+
// point(1 2)
|
|
48509
|
+
// POINT Z (1 2 3)
|
|
48510
|
+
// POINT M (1 2 3)
|
|
48511
|
+
// POINT ZM (1 2 3 4)
|
|
48512
|
+
var m = /^\s*POINT(?:\s+Z(?:M)?|\s+M)?\s*\(\s*([^\s,]+)\s+([^\s,]+)(?:\s+[^)]*)?\)\s*$/i.exec(val);
|
|
48513
|
+
if (!m) return null;
|
|
48514
|
+
var x = coordinateFromValue(m[1]);
|
|
48515
|
+
var y = coordinateFromValue(m[2]);
|
|
48516
|
+
if (isNaN(x) || isNaN(y)) return null;
|
|
48517
|
+
return [x, y];
|
|
48518
|
+
}
|
|
48519
|
+
|
|
48520
|
+
function findWKTField(records, fields) {
|
|
48521
|
+
var sampleSize = Math.min(records.length, 50);
|
|
48522
|
+
return utils.find(fields, function(name) {
|
|
48523
|
+
var seen = 0;
|
|
48524
|
+
var matches = 0;
|
|
48525
|
+
for (var i = 0; i < sampleSize; i++) {
|
|
48526
|
+
var rec = records[i];
|
|
48527
|
+
if (!rec) continue;
|
|
48528
|
+
var val = rec[name];
|
|
48529
|
+
if (!utils.isString(val) || val.trim() === '') continue;
|
|
48530
|
+
seen++;
|
|
48531
|
+
if (parseWKTPoint(val)) matches++;
|
|
48532
|
+
}
|
|
48533
|
+
// Require at least one non-empty sample and majority parseable as POINT.
|
|
48534
|
+
return seen > 0 && matches / seen >= 0.6;
|
|
48535
|
+
});
|
|
48536
|
+
}
|
|
48537
|
+
|
|
48150
48538
|
function pointsFromDataTableAuto(data) {
|
|
48151
48539
|
var fields = data ? data.getFields() : [];
|
|
48540
|
+
var records = data ? data.getRecords() : [];
|
|
48152
48541
|
var opts = {
|
|
48153
48542
|
x: findXField(fields),
|
|
48154
48543
|
y: findYField(fields)
|
|
48155
48544
|
};
|
|
48545
|
+
if (!opts.x || !opts.y) {
|
|
48546
|
+
opts.wkt = findWKTField(records, fields);
|
|
48547
|
+
}
|
|
48156
48548
|
return pointsFromDataTable(data, opts);
|
|
48157
48549
|
}
|
|
48158
48550
|
|
|
48159
48551
|
function pointsFromDataTable(data, opts) {
|
|
48160
48552
|
if (!data) stop$1("Layer is missing a data table");
|
|
48553
|
+
if (opts.wkt && data.fieldExists(opts.wkt)) {
|
|
48554
|
+
return data.getRecords().map(function(rec) {
|
|
48555
|
+
var p = parseWKTPoint(rec[opts.wkt]);
|
|
48556
|
+
return p ? [p] : null;
|
|
48557
|
+
});
|
|
48558
|
+
}
|
|
48161
48559
|
if (!opts.x || !opts.y || !data.fieldExists(opts.x) || !data.fieldExists(opts.y)) {
|
|
48162
48560
|
stop$1("Missing x,y data fields");
|
|
48163
48561
|
}
|
|
@@ -48175,8 +48573,10 @@ ${svg}
|
|
|
48175
48573
|
var Points = /*#__PURE__*/Object.freeze({
|
|
48176
48574
|
__proto__: null,
|
|
48177
48575
|
coordinateFromValue: coordinateFromValue,
|
|
48576
|
+
findWKTField: findWKTField,
|
|
48178
48577
|
findXField: findXField,
|
|
48179
48578
|
findYField: findYField,
|
|
48579
|
+
parseWKTPoint: parseWKTPoint,
|
|
48180
48580
|
pointsFromPolygons: pointsFromPolygons
|
|
48181
48581
|
});
|
|
48182
48582
|
|
|
@@ -53074,7 +53474,7 @@ ${svg}
|
|
|
53074
53474
|
});
|
|
53075
53475
|
}
|
|
53076
53476
|
|
|
53077
|
-
var version = "0.7.
|
|
53477
|
+
var version = "0.7.7";
|
|
53078
53478
|
|
|
53079
53479
|
// Parse command line args into commands and run them
|
|
53080
53480
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/www/page.css
CHANGED
|
@@ -172,12 +172,20 @@ body.map-view {
|
|
|
172
172
|
display: none;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/* On narrow screens, splash links collide with the logo; keep the header clean
|
|
176
|
+
until data is loaded and the editor controls take over. */
|
|
177
|
+
@media (max-width: 500px) {
|
|
178
|
+
#splash-buttons {
|
|
179
|
+
display: none;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
175
183
|
.page-header-buttons {
|
|
176
184
|
z-index: 20;
|
|
177
185
|
position: absolute;
|
|
178
186
|
top: 0px;
|
|
179
187
|
right: 0px;
|
|
180
|
-
margin: 0
|
|
188
|
+
margin: 0 2px 3px 0;
|
|
181
189
|
}
|
|
182
190
|
|
|
183
191
|
.btn.header-btn {
|
|
@@ -218,7 +226,6 @@ body.map-view {
|
|
|
218
226
|
|
|
219
227
|
#header-menu-btn {
|
|
220
228
|
cursor: pointer;
|
|
221
|
-
padding: 8px 7px 4px 7px;
|
|
222
229
|
user-select: none;
|
|
223
230
|
/* All header buttons are 29px tall, so top-align the hamburger to keep its
|
|
224
231
|
row baseline-neutral. Without this, the SVG-only button (no text) makes
|
|
@@ -843,6 +850,84 @@ body.simplify .layer-control-btn {
|
|
|
843
850
|
display: none;
|
|
844
851
|
}
|
|
845
852
|
|
|
853
|
+
/* On small screens, move the layer menu trigger out of the header center and
|
|
854
|
+
render it as an icon-only floating map button. */
|
|
855
|
+
@media (max-width: 700px) {
|
|
856
|
+
.info-box {
|
|
857
|
+
margin-top: 8px;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
div.nav-buttons {
|
|
861
|
+
top: 2px;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.btn.header-btn {
|
|
865
|
+
padding-left: 5px;
|
|
866
|
+
padding-right: 5px;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.layer-control-btn {
|
|
870
|
+
width: auto;
|
|
871
|
+
left: 9px;
|
|
872
|
+
top: 38px; /* below the 30px header */
|
|
873
|
+
text-align: left;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.layer-control-btn > .btn.header-btn {
|
|
877
|
+
margin-right: 0;
|
|
878
|
+
width: 30px;
|
|
879
|
+
height: 30px;
|
|
880
|
+
box-sizing: content-box;
|
|
881
|
+
padding: 0;
|
|
882
|
+
border-radius: 4px;
|
|
883
|
+
border: 2px solid rgba(255, 255, 255, 0.45);
|
|
884
|
+
display: inline-flex;
|
|
885
|
+
align-items: center;
|
|
886
|
+
justify-content: center;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/* The clickable header button element itself has class "layer-name", so
|
|
890
|
+
hide text on the button directly (not a child selector). */
|
|
891
|
+
.layer-control-btn > .btn.header-btn.layer-name {
|
|
892
|
+
color: transparent;
|
|
893
|
+
font-size: 0;
|
|
894
|
+
line-height: 0;
|
|
895
|
+
text-indent: -9999px;
|
|
896
|
+
overflow: hidden;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/* Mobile layer-button icon */
|
|
900
|
+
.layer-control-btn > .btn.header-btn::before {
|
|
901
|
+
content: "";
|
|
902
|
+
display: block;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/* Two diagonally offset squares */
|
|
906
|
+
.layer-control-btn.icon-duo > .btn.header-btn::before {
|
|
907
|
+
width: 13px;
|
|
908
|
+
height: 13px;
|
|
909
|
+
margin: 2px 0 0 2px;
|
|
910
|
+
border: 1px solid rgba(255, 255, 255, 0.95);
|
|
911
|
+
border-radius: 1px;
|
|
912
|
+
box-shadow: -4px -4px 0 -1px rgba(255, 255, 255, 0.72);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.mshp-main-map .basemap-overlay-buttons {
|
|
916
|
+
top: 10px; /* map-relative offset to align with page-level layer button */
|
|
917
|
+
right: 33px;
|
|
918
|
+
padding: 0;
|
|
919
|
+
background: transparent;
|
|
920
|
+
border-radius: 0;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.mshp-main-map .basemap-overlay-buttons .basemap-style-btn.basemap-overlay-btn {
|
|
924
|
+
width: 30px !important;
|
|
925
|
+
height: 30px !important;
|
|
926
|
+
margin: 0 0 0 4px !important;
|
|
927
|
+
border-radius: 4px !important;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
846
931
|
.layer-control .info-box {
|
|
847
932
|
padding: 0;
|
|
848
933
|
pointer-events: none;
|
|
@@ -1644,7 +1729,7 @@ body.pan.panning .map-layers:not(.drawing) {
|
|
|
1644
1729
|
position: relative;
|
|
1645
1730
|
cursor:pointer;
|
|
1646
1731
|
/* padding: 3px 3px 5px 3px; */
|
|
1647
|
-
padding: 4px;
|
|
1732
|
+
padding: 4px 5px;
|
|
1648
1733
|
line-height: 1;
|
|
1649
1734
|
}
|
|
1650
1735
|
|