mapshaper 0.6.65 → 0.6.66
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 +147 -33
- package/package.json +1 -1
- package/www/index.html +4 -3
- package/www/mapshaper-gui.js +379 -241
- package/www/mapshaper.js +147 -33
- package/www/page.css +7 -1
package/mapshaper.js
CHANGED
|
@@ -4010,6 +4010,59 @@
|
|
|
4010
4010
|
return applyFieldOrder(names, order);
|
|
4011
4011
|
}
|
|
4012
4012
|
|
|
4013
|
+
|
|
4014
|
+
function parseUnknownType(str) {
|
|
4015
|
+
var val = getInputParser('number')(str);
|
|
4016
|
+
if (val !== null) return val;
|
|
4017
|
+
val = getInputParser('object')(str);
|
|
4018
|
+
if (val !== null) return val;
|
|
4019
|
+
return str;
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
// used by GUI data editing
|
|
4023
|
+
function getInputParser(type) {
|
|
4024
|
+
return inputParsers[type || 'multiple'];
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
var inputParsers = {
|
|
4028
|
+
date: function(raw) {
|
|
4029
|
+
var d = new Date(raw);
|
|
4030
|
+
return isNaN(+d) ? null : d;
|
|
4031
|
+
},
|
|
4032
|
+
string: function(raw) {
|
|
4033
|
+
return raw;
|
|
4034
|
+
},
|
|
4035
|
+
number: function(raw) {
|
|
4036
|
+
var val = Number(raw);
|
|
4037
|
+
if (raw == 'NaN') {
|
|
4038
|
+
val = NaN;
|
|
4039
|
+
} else if (isNaN(val)) {
|
|
4040
|
+
val = null;
|
|
4041
|
+
}
|
|
4042
|
+
return val;
|
|
4043
|
+
},
|
|
4044
|
+
object: function(raw) {
|
|
4045
|
+
var val = null;
|
|
4046
|
+
try {
|
|
4047
|
+
val = JSON.parse(raw);
|
|
4048
|
+
} catch(e) {}
|
|
4049
|
+
return val;
|
|
4050
|
+
},
|
|
4051
|
+
boolean: function(raw) {
|
|
4052
|
+
var val = null;
|
|
4053
|
+
if (raw == 'true') {
|
|
4054
|
+
val = true;
|
|
4055
|
+
} else if (raw == 'false') {
|
|
4056
|
+
val = false;
|
|
4057
|
+
}
|
|
4058
|
+
return val;
|
|
4059
|
+
},
|
|
4060
|
+
multiple: function(raw) {
|
|
4061
|
+
var val = Number(raw);
|
|
4062
|
+
return isNaN(val) ? raw : val;
|
|
4063
|
+
}
|
|
4064
|
+
};
|
|
4065
|
+
|
|
4013
4066
|
var DataUtils = /*#__PURE__*/Object.freeze({
|
|
4014
4067
|
__proto__: null,
|
|
4015
4068
|
copyRecord: copyRecord,
|
|
@@ -4024,7 +4077,9 @@
|
|
|
4024
4077
|
getUniqFieldValues: getUniqFieldValues,
|
|
4025
4078
|
applyFieldOrder: applyFieldOrder,
|
|
4026
4079
|
getFirstNonEmptyRecord: getFirstNonEmptyRecord,
|
|
4027
|
-
findFieldNames: findFieldNames
|
|
4080
|
+
findFieldNames: findFieldNames,
|
|
4081
|
+
parseUnknownType: parseUnknownType,
|
|
4082
|
+
getInputParser: getInputParser
|
|
4028
4083
|
});
|
|
4029
4084
|
|
|
4030
4085
|
function DataTable(obj) {
|
|
@@ -4140,7 +4195,7 @@
|
|
|
4140
4195
|
[bbox[2], bbox[1]], [bbox[0], bbox[1]]];
|
|
4141
4196
|
}
|
|
4142
4197
|
|
|
4143
|
-
var
|
|
4198
|
+
var RectangleUtils = /*#__PURE__*/Object.freeze({
|
|
4144
4199
|
__proto__: null,
|
|
4145
4200
|
pathIsRectangle: pathIsRectangle,
|
|
4146
4201
|
bboxToCoords: bboxToCoords
|
|
@@ -4186,6 +4241,10 @@
|
|
|
4186
4241
|
return lyr.geometry_type == 'point' && layerHasNonNullShapes(lyr);
|
|
4187
4242
|
}
|
|
4188
4243
|
|
|
4244
|
+
function layerIsRectangle(lyr, arcs) {
|
|
4245
|
+
return layerOnlyHasRectangles(lyr, arcs) && lyr.shapes.length == 1;
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4189
4248
|
function layerOnlyHasRectangles(lyr, arcs) {
|
|
4190
4249
|
if (!layerHasPaths(lyr)) return false;
|
|
4191
4250
|
if (countMultiPartFeatures(lyr) > 0) return false;
|
|
@@ -4438,6 +4497,7 @@
|
|
|
4438
4497
|
layerHasGeometry: layerHasGeometry,
|
|
4439
4498
|
layerHasPaths: layerHasPaths,
|
|
4440
4499
|
layerHasPoints: layerHasPoints,
|
|
4500
|
+
layerIsRectangle: layerIsRectangle,
|
|
4441
4501
|
layerOnlyHasRectangles: layerOnlyHasRectangles,
|
|
4442
4502
|
layerHasNonNullShapes: layerHasNonNullShapes,
|
|
4443
4503
|
deleteFeatureById: deleteFeatureById,
|
|
@@ -13335,6 +13395,18 @@
|
|
|
13335
13395
|
miles: 1609.344 // International Statute Mile
|
|
13336
13396
|
};
|
|
13337
13397
|
|
|
13398
|
+
// str: display size in px, pt or in
|
|
13399
|
+
// using: 72pt per inch, 1pt per pixel.
|
|
13400
|
+
function parseSizeParam(str) {
|
|
13401
|
+
var num = parseFloat(str),
|
|
13402
|
+
units = /px$/.test(str) && 'px' || /pt$/.test(str) && 'pt' ||
|
|
13403
|
+
/in$/.test(str) && 'in' || !isNaN(+str) && 'px' || null;
|
|
13404
|
+
if (isNaN(num) || !units) {
|
|
13405
|
+
stop('Invalid size:', str);
|
|
13406
|
+
}
|
|
13407
|
+
return units == 'in' && num * 72 || num;
|
|
13408
|
+
}
|
|
13409
|
+
|
|
13338
13410
|
// Return coeff. for converting a distance measure to dataset coordinates
|
|
13339
13411
|
// @paramUnits: units code of distance param, or null if units are not specified
|
|
13340
13412
|
// @crs: Proj.4 CRS object, or null (unknown latlong CRS);
|
|
@@ -13478,6 +13550,7 @@
|
|
|
13478
13550
|
|
|
13479
13551
|
var Units = /*#__PURE__*/Object.freeze({
|
|
13480
13552
|
__proto__: null,
|
|
13553
|
+
parseSizeParam: parseSizeParam,
|
|
13481
13554
|
getIntervalConversionFactor: getIntervalConversionFactor,
|
|
13482
13555
|
parseMeasure: parseMeasure,
|
|
13483
13556
|
parseMeasure2: parseMeasure2,
|
|
@@ -18155,7 +18228,7 @@
|
|
|
18155
18228
|
var frameLyr = findFrameLayerInDataset(dataset);
|
|
18156
18229
|
var frameData;
|
|
18157
18230
|
if (frameLyr) {
|
|
18158
|
-
frameData =
|
|
18231
|
+
frameData = getFrameLayerData(frameLyr, dataset);
|
|
18159
18232
|
} else {
|
|
18160
18233
|
frameData = calcFrameData(dataset, exportOpts);
|
|
18161
18234
|
}
|
|
@@ -18163,6 +18236,25 @@
|
|
|
18163
18236
|
return frameData;
|
|
18164
18237
|
}
|
|
18165
18238
|
|
|
18239
|
+
|
|
18240
|
+
function getFrameLayerData(lyr, dataset) {
|
|
18241
|
+
var bounds = getLayerBounds(lyr, dataset.arcs);
|
|
18242
|
+
var d = lyr.data.getReadOnlyRecordAt(0);
|
|
18243
|
+
var w = d.width || 800;
|
|
18244
|
+
var h = w * bounds.height() / bounds.width();
|
|
18245
|
+
return {
|
|
18246
|
+
type: 'frame',
|
|
18247
|
+
width: w,
|
|
18248
|
+
height: h,
|
|
18249
|
+
bbox: bounds.toArray()
|
|
18250
|
+
};
|
|
18251
|
+
}
|
|
18252
|
+
|
|
18253
|
+
// export function getFrameLayerData(lyr) {
|
|
18254
|
+
// return lyr.data && lyr.data.getReadOnlyRecordAt(0);
|
|
18255
|
+
// }
|
|
18256
|
+
|
|
18257
|
+
|
|
18166
18258
|
function calcFrameData(dataset, opts) {
|
|
18167
18259
|
var bounds;
|
|
18168
18260
|
if (opts.svg_bbox) {
|
|
@@ -18180,10 +18272,6 @@
|
|
|
18180
18272
|
};
|
|
18181
18273
|
}
|
|
18182
18274
|
|
|
18183
|
-
function getFrameLayerData(lyr) {
|
|
18184
|
-
return lyr.data && lyr.data.getReadOnlyRecordAt(0);
|
|
18185
|
-
}
|
|
18186
|
-
|
|
18187
18275
|
// Used by mapshaper-frame and mapshaper-pixel-transform. TODO: refactor
|
|
18188
18276
|
function getFrameSize(bounds, opts) {
|
|
18189
18277
|
var aspectRatio = bounds.width() / bounds.height();
|
|
@@ -18199,26 +18287,27 @@
|
|
|
18199
18287
|
|
|
18200
18288
|
|
|
18201
18289
|
// @lyr dataset layer
|
|
18202
|
-
function isFrameLayer(lyr) {
|
|
18203
|
-
return getFurnitureLayerType(lyr) == 'frame'
|
|
18290
|
+
function isFrameLayer(lyr, dataset) {
|
|
18291
|
+
return getFurnitureLayerType(lyr) == 'frame' &&
|
|
18292
|
+
layerIsRectangle(lyr, dataset.arcs);
|
|
18204
18293
|
}
|
|
18205
18294
|
|
|
18206
18295
|
function findFrameLayerInDataset(dataset) {
|
|
18207
18296
|
return utils.find(dataset.layers, function(lyr) {
|
|
18208
|
-
return isFrameLayer(lyr);
|
|
18297
|
+
return isFrameLayer(lyr, dataset);
|
|
18209
18298
|
});
|
|
18210
18299
|
}
|
|
18211
18300
|
|
|
18212
18301
|
function findFrameDataset(catalog) {
|
|
18213
18302
|
var target = utils.find(catalog.getLayers(), function(o) {
|
|
18214
|
-
return isFrameLayer(o.layer);
|
|
18303
|
+
return isFrameLayer(o.layer, o.dataset);
|
|
18215
18304
|
});
|
|
18216
18305
|
return target ? target.dataset : null;
|
|
18217
18306
|
}
|
|
18218
18307
|
|
|
18219
18308
|
function findFrameLayer(catalog) {
|
|
18220
18309
|
var target = utils.find(catalog.getLayers(), function(o) {
|
|
18221
|
-
return isFrameLayer(o.layer);
|
|
18310
|
+
return isFrameLayer(o.layer, o.dataset);
|
|
18222
18311
|
});
|
|
18223
18312
|
return target && target.layer || null;
|
|
18224
18313
|
}
|
|
@@ -18356,7 +18445,6 @@
|
|
|
18356
18445
|
var FrameData = /*#__PURE__*/Object.freeze({
|
|
18357
18446
|
__proto__: null,
|
|
18358
18447
|
getFrameData: getFrameData,
|
|
18359
|
-
getFrameLayerData: getFrameLayerData,
|
|
18360
18448
|
getFrameSize: getFrameSize,
|
|
18361
18449
|
findFrameLayerInDataset: findFrameLayerInDataset,
|
|
18362
18450
|
findFrameDataset: findFrameDataset,
|
|
@@ -18859,6 +18947,10 @@
|
|
|
18859
18947
|
if (filter && !filter(fields[i])) continue;
|
|
18860
18948
|
setAttribute(svgObj, fields[i], rec[fields[i]]);
|
|
18861
18949
|
}
|
|
18950
|
+
// kludge to prevent default black fill on polygons with stroke styles
|
|
18951
|
+
if ((symType == 'polygon' || symType == 'circle') && rec.stroke && !rec.fill) {
|
|
18952
|
+
setAttribute(svgObj, 'fill', 'none');
|
|
18953
|
+
}
|
|
18862
18954
|
}
|
|
18863
18955
|
|
|
18864
18956
|
function setAttribute(obj, k, v) {
|
|
@@ -19686,6 +19778,7 @@
|
|
|
19686
19778
|
}).reverse().join(',');
|
|
19687
19779
|
}
|
|
19688
19780
|
|
|
19781
|
+
|
|
19689
19782
|
function renderFrame(d) {
|
|
19690
19783
|
var lineWidth = 1,
|
|
19691
19784
|
// inset stroke by half of line width
|
|
@@ -22057,7 +22150,13 @@ ${svg}
|
|
|
22057
22150
|
|
|
22058
22151
|
// @targets - non-empty output from Catalog#findCommandTargets()
|
|
22059
22152
|
//
|
|
22060
|
-
async function exportTargetLayers(targets, opts) {
|
|
22153
|
+
async function exportTargetLayers(catalog, targets, opts) {
|
|
22154
|
+
// kludge: get extent of 'fit-extent' layer (if given)
|
|
22155
|
+
if (opts.fit_extent) {
|
|
22156
|
+
var target = catalog.findSingleLayer(opts.fit_extent);
|
|
22157
|
+
var bounds = getLayerBounds(target.layer, target.dataset.arcs);
|
|
22158
|
+
opts = Object.assign({svg_bbox: bounds.toArray()}, opts);
|
|
22159
|
+
}
|
|
22061
22160
|
// convert target fmt to dataset fmt
|
|
22062
22161
|
var datasets = targets.map(function(target) {
|
|
22063
22162
|
return utils.defaults({layers: target.layers}, target.dataset);
|
|
@@ -24139,6 +24238,9 @@ ${svg}
|
|
|
24139
24238
|
describe: '[SVG] bounding box of SVG map in projected map units',
|
|
24140
24239
|
type: 'bbox'
|
|
24141
24240
|
})
|
|
24241
|
+
.option('fit-extent', {
|
|
24242
|
+
describe: '[SVG] layer to use for the map extent'
|
|
24243
|
+
})
|
|
24142
24244
|
.option('point-symbol', {
|
|
24143
24245
|
describe: '[SVG] circle or square (default is circle)'
|
|
24144
24246
|
})
|
|
@@ -25089,6 +25191,10 @@ ${svg}
|
|
|
25089
25191
|
type: 'bbox'
|
|
25090
25192
|
})
|
|
25091
25193
|
.option('offset', offsetOpt)
|
|
25194
|
+
.option('width', {
|
|
25195
|
+
// describe: 'set viewport width in pixels',
|
|
25196
|
+
type: 'number'
|
|
25197
|
+
})
|
|
25092
25198
|
.option('aspect-ratio', aspectRatioOpt)
|
|
25093
25199
|
.option('source', {
|
|
25094
25200
|
describe: 'name of layer to enclose'
|
|
@@ -38408,7 +38514,7 @@ ${svg}
|
|
|
38408
38514
|
getPointFeatureBounds(shp) : targetDataset.arcs.getMultiShapeBounds(shp);
|
|
38409
38515
|
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
38410
38516
|
if (!bounds) return null;
|
|
38411
|
-
return
|
|
38517
|
+
return bboxToPolygon(bounds.toArray(), opts);
|
|
38412
38518
|
});
|
|
38413
38519
|
var geojson = {
|
|
38414
38520
|
type: 'FeatureCollection',
|
|
@@ -38458,8 +38564,16 @@ ${svg}
|
|
|
38458
38564
|
if (!bounds || !bounds.hasBounds()) {
|
|
38459
38565
|
stop('Missing rectangle extent');
|
|
38460
38566
|
}
|
|
38461
|
-
var
|
|
38462
|
-
|
|
38567
|
+
var feature = {
|
|
38568
|
+
type: 'Feature',
|
|
38569
|
+
properties: {},
|
|
38570
|
+
geometry: bboxToPolygon(bounds.toArray(), opts)
|
|
38571
|
+
};
|
|
38572
|
+
if (opts.width > 0) {
|
|
38573
|
+
feature.properties.width = opts.width;
|
|
38574
|
+
feature.properties.type = 'frame';
|
|
38575
|
+
}
|
|
38576
|
+
var dataset = importGeoJSON(feature, {});
|
|
38463
38577
|
dataset.layers[0].name = opts.name || 'rectangle';
|
|
38464
38578
|
setDatasetCrsInfo(dataset, crsInfo);
|
|
38465
38579
|
return dataset;
|
|
@@ -38509,16 +38623,13 @@ ${svg}
|
|
|
38509
38623
|
return bounds;
|
|
38510
38624
|
}
|
|
38511
38625
|
|
|
38512
|
-
function
|
|
38626
|
+
function bboxToPolygon(bbox, optsArg) {
|
|
38513
38627
|
var opts = optsArg || {};
|
|
38514
38628
|
var coords = bboxToCoords(bbox);
|
|
38515
38629
|
if (opts.interval > 0) {
|
|
38516
38630
|
coords = densifyPathByInterval(coords, opts.interval);
|
|
38517
38631
|
}
|
|
38518
|
-
return
|
|
38519
|
-
type: 'LineString',
|
|
38520
|
-
coordinates: coords
|
|
38521
|
-
} : {
|
|
38632
|
+
return {
|
|
38522
38633
|
type: 'Polygon',
|
|
38523
38634
|
coordinates: [coords]
|
|
38524
38635
|
};
|
|
@@ -38527,7 +38638,7 @@ ${svg}
|
|
|
38527
38638
|
var Rectangle = /*#__PURE__*/Object.freeze({
|
|
38528
38639
|
__proto__: null,
|
|
38529
38640
|
applyAspectRatio: applyAspectRatio,
|
|
38530
|
-
|
|
38641
|
+
bboxToPolygon: bboxToPolygon
|
|
38531
38642
|
});
|
|
38532
38643
|
|
|
38533
38644
|
function getSemiMinorAxis(P) {
|
|
@@ -39081,8 +39192,7 @@ ${svg}
|
|
|
39081
39192
|
var rotation = getRotationParams(dest);
|
|
39082
39193
|
if (!bbox) error('Missing expected clip bbox.');
|
|
39083
39194
|
opts = Object.assign({interval: 0.5}, opts); // make sure edges can curve
|
|
39084
|
-
var
|
|
39085
|
-
var dataset = importGeoJSON(geojson);
|
|
39195
|
+
var dataset = importGeoJSON(bboxToPolygon(bbox, opts));
|
|
39086
39196
|
if (rotation) {
|
|
39087
39197
|
rotateDataset(dataset, {rotation: rotation, invert: true});
|
|
39088
39198
|
}
|
|
@@ -39271,7 +39381,7 @@ ${svg}
|
|
|
39271
39381
|
var e = 1e-8;
|
|
39272
39382
|
var bbox = [lon-e, -91, lon+e, 91];
|
|
39273
39383
|
// densify (so cut line can curve, e.g. Cupola projection)
|
|
39274
|
-
var geojson =
|
|
39384
|
+
var geojson = bboxToPolygon(bbox, {interval: 0.5});
|
|
39275
39385
|
var clip = importGeoJSON(geojson);
|
|
39276
39386
|
clipLayersInPlace(pathLayers, clip, dataset, 'erase');
|
|
39277
39387
|
}
|
|
@@ -39574,7 +39684,6 @@ ${svg}
|
|
|
39574
39684
|
|
|
39575
39685
|
function createProjectedGraticule(dest, opts) {
|
|
39576
39686
|
var src = parseCrsString('wgs84');
|
|
39577
|
-
// var outline = getOutlineDataset(src, dest, {inset: 0, geometry_type: 'polyline'});
|
|
39578
39687
|
var outline = getOutlineDataset(src, dest, {});
|
|
39579
39688
|
var graticule = importGeoJSON(createGraticule(dest, !!outline, opts));
|
|
39580
39689
|
projectDataset(graticule, src, dest, {no_clip: false}); // TODO: densify?
|
|
@@ -44825,8 +44934,9 @@ ${svg}
|
|
|
44825
44934
|
} else if (name == 'filter-slivers') {
|
|
44826
44935
|
applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
|
|
44827
44936
|
|
|
44828
|
-
|
|
44829
|
-
|
|
44937
|
+
// // 'frame' and 'rectangle' have merged
|
|
44938
|
+
// } else if (name == 'frame') {
|
|
44939
|
+
// cmd.frame(job.catalog, source, opts);
|
|
44830
44940
|
|
|
44831
44941
|
} else if (name == 'fuzzy-join') {
|
|
44832
44942
|
applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
|
|
@@ -44894,7 +45004,8 @@ ${svg}
|
|
|
44894
45004
|
outputLayers = cmd.mosaic(targetLayers, targetDataset, opts);
|
|
44895
45005
|
|
|
44896
45006
|
} else if (name == 'o') {
|
|
44897
|
-
|
|
45007
|
+
// kludge
|
|
45008
|
+
outputFiles = await exportTargetLayers(job.catalog, targets, opts);
|
|
44898
45009
|
if (opts.final) {
|
|
44899
45010
|
// don't propagate data if output is final
|
|
44900
45011
|
//// catalog = null;
|
|
@@ -44927,7 +45038,10 @@ ${svg}
|
|
|
44927
45038
|
cmd.proj(targ.dataset, job.catalog, opts);
|
|
44928
45039
|
});
|
|
44929
45040
|
|
|
44930
|
-
} else if (name == 'rectangle') {
|
|
45041
|
+
} else if (name == 'rectangle' || name == 'frame') {
|
|
45042
|
+
if (name == 'frame' && !opts.width) {
|
|
45043
|
+
stop('Command requires a width= argument');
|
|
45044
|
+
}
|
|
44931
45045
|
if (source || opts.bbox || targets.length === 0) {
|
|
44932
45046
|
job.catalog.addDataset(cmd.rectangle(source, opts));
|
|
44933
45047
|
} else {
|
|
@@ -45093,7 +45207,7 @@ ${svg}
|
|
|
45093
45207
|
});
|
|
45094
45208
|
}
|
|
45095
45209
|
|
|
45096
|
-
var version = "0.6.
|
|
45210
|
+
var version = "0.6.66";
|
|
45097
45211
|
|
|
45098
45212
|
// Parse command line args into commands and run them
|
|
45099
45213
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -45835,7 +45949,7 @@ ${svg}
|
|
|
45835
45949
|
Projections,
|
|
45836
45950
|
ProjectionParams,
|
|
45837
45951
|
Rectangle,
|
|
45838
|
-
|
|
45952
|
+
RectangleUtils,
|
|
45839
45953
|
Rounding,
|
|
45840
45954
|
RunCommands,
|
|
45841
45955
|
Scalebar,
|
package/package.json
CHANGED
package/www/index.html
CHANGED
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
<h4>Source files</h4>
|
|
105
105
|
<div class="file-list"></div>
|
|
106
106
|
<div>
|
|
107
|
-
<div id="add-file-btn" class="dialog-btn btn">Add
|
|
108
|
-
|
|
107
|
+
<div id="add-file-btn" class="dialog-btn btn">Add files</div>
|
|
108
|
+
<div id="add-empty-btn" class="dialog-btn btn">Add empty layer</div>
|
|
109
109
|
</div>
|
|
110
110
|
</div>
|
|
111
111
|
</div>
|
|
@@ -134,7 +134,8 @@
|
|
|
134
134
|
<div class="filter-btn btn dialog-btn">Keep</div>
|
|
135
135
|
<div class="duplicate-btn btn dialog-btn">Duplicate</div>
|
|
136
136
|
<div class="split-btn btn dialog-btn">Split</div>
|
|
137
|
-
<div class="coords-btn btn dialog-btn">Coords</div>
|
|
137
|
+
<div class="coords-btn btn dialog-btn toggle-btn">Coords</div>
|
|
138
|
+
<div class="data-btn btn dialog-btn toggle-btn">Edit data</div>
|
|
138
139
|
<div class="cancel-btn btn dialog-btn">Clear</div>
|
|
139
140
|
</div>
|
|
140
141
|
<div class="box-coords selectable"></div>
|