mapshaper 0.6.75 → 0.6.77
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 +21 -16
- package/package.json +1 -1
- package/www/index.html +56 -73
- package/www/mapshaper-gui.js +1357 -1255
- package/www/mapshaper.js +21 -16
- package/www/page.css +152 -146
package/www/mapshaper-gui.js
CHANGED
|
@@ -760,10 +760,7 @@
|
|
|
760
760
|
//
|
|
761
761
|
//El.prototype.__domevents = utils.arrayToIndex("click,mousedown,mousemove,mouseup".split(','));
|
|
762
762
|
El.prototype.__on = El.prototype.on;
|
|
763
|
-
El.prototype.on = function(type, func
|
|
764
|
-
if (ctx) {
|
|
765
|
-
error$1("[El#on()] Third argument no longer supported.");
|
|
766
|
-
}
|
|
763
|
+
El.prototype.on = function(type, func) {
|
|
767
764
|
if (this.constructor == El) {
|
|
768
765
|
this.el.addEventListener(type, func);
|
|
769
766
|
} else {
|
|
@@ -1369,10 +1366,12 @@
|
|
|
1369
1366
|
init();
|
|
1370
1367
|
|
|
1371
1368
|
async function init() {
|
|
1372
|
-
var enabled = await isStorageEnabled();
|
|
1373
|
-
if (!enabled) return;
|
|
1374
|
-
|
|
1375
1369
|
btn = gui.buttons.addButton('#ribbon-icon').addClass('menu-btn save-btn');
|
|
1370
|
+
var enabled = await isStorageEnabled();
|
|
1371
|
+
if (!enabled) {
|
|
1372
|
+
btn.remove();
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1376
1375
|
menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
|
|
1377
1376
|
await initialCleanup();
|
|
1378
1377
|
|
|
@@ -1444,14 +1443,6 @@
|
|
|
1444
1443
|
renderMenu();
|
|
1445
1444
|
}).text('remove');
|
|
1446
1445
|
});
|
|
1447
|
-
|
|
1448
|
-
// if (snapshots.length >= 1) {
|
|
1449
|
-
// addMenuLink({
|
|
1450
|
-
// slug: 'clear',
|
|
1451
|
-
// label: 'remove all',
|
|
1452
|
-
// action: clearData
|
|
1453
|
-
// });
|
|
1454
|
-
// }
|
|
1455
1446
|
}
|
|
1456
1447
|
|
|
1457
1448
|
function addMenuLink(item) {
|
|
@@ -1484,6 +1475,7 @@
|
|
|
1484
1475
|
|
|
1485
1476
|
async function saveSnapshot(gui) {
|
|
1486
1477
|
var obj = await captureSnapshot(gui);
|
|
1478
|
+
if (!obj) return;
|
|
1487
1479
|
// storing an unpacked object is usually a bit faster (~20%)
|
|
1488
1480
|
// note: we don't know the size of unpacked snapshot objects
|
|
1489
1481
|
// obj = internal.pack(obj);
|
|
@@ -1563,12 +1555,13 @@
|
|
|
1563
1555
|
}
|
|
1564
1556
|
|
|
1565
1557
|
async function captureSnapshot(gui) {
|
|
1566
|
-
var
|
|
1567
|
-
|
|
1558
|
+
var lyr = gui.model.getActiveLayer()?.layer;
|
|
1559
|
+
if (!lyr) return null; // no data -- no snapshot
|
|
1560
|
+
lyr.active = true;
|
|
1568
1561
|
// compact: true applies compression to vector coordinates, for ~30% reduction
|
|
1569
1562
|
// in file size in a typical polygon or polyline file, but longer processing time
|
|
1570
1563
|
var opts = {compact: false};
|
|
1571
|
-
|
|
1564
|
+
var datasets = gui.model.getDatasets();
|
|
1572
1565
|
// console.time('msx');
|
|
1573
1566
|
var obj = await internal.exportDatasetsToPack(datasets, opts);
|
|
1574
1567
|
// console.timeEnd('msx')
|
|
@@ -1761,7 +1754,7 @@
|
|
|
1761
1754
|
}],
|
|
1762
1755
|
info: {}
|
|
1763
1756
|
};
|
|
1764
|
-
if (type == 'polygon' || type == '
|
|
1757
|
+
if (type == 'polygon' || type == 'polyline') {
|
|
1765
1758
|
dataset.arcs = new internal.ArcCollection();
|
|
1766
1759
|
}
|
|
1767
1760
|
if (crsInfo) {
|
|
@@ -1873,7 +1866,6 @@
|
|
|
1873
1866
|
var initialImport = true;
|
|
1874
1867
|
var importCount = 0;
|
|
1875
1868
|
var importTotal = 0;
|
|
1876
|
-
var overQuickView = false;
|
|
1877
1869
|
var useQuickView = false;
|
|
1878
1870
|
var queuedFiles = [];
|
|
1879
1871
|
var manifestFiles = opts.files || [];
|
|
@@ -1883,50 +1875,48 @@
|
|
|
1883
1875
|
catalog = new CatalogControl(gui, opts.catalog, downloadFiles);
|
|
1884
1876
|
}
|
|
1885
1877
|
|
|
1886
|
-
new SimpleButton('#import-
|
|
1887
|
-
new SimpleButton('#import-
|
|
1888
|
-
new DropControl(gui, 'body',
|
|
1889
|
-
new FileChooser('#
|
|
1890
|
-
new FileChooser('#import-buttons .add-btn', receiveFiles);
|
|
1878
|
+
var submitBtn = new SimpleButton('#import-options .submit-btn').on('click', importQueuedFiles);
|
|
1879
|
+
new SimpleButton('#import-options .cancel-btn').on('click', gui.clearMode);
|
|
1880
|
+
new DropControl(gui, 'body', receiveFilesWithOption);
|
|
1881
|
+
new FileChooser('#import-options .add-btn', receiveFilesWithOption);
|
|
1891
1882
|
new FileChooser('#add-file-btn', receiveFiles);
|
|
1892
1883
|
new SimpleButton('#add-empty-btn').on('click', function() {
|
|
1893
1884
|
gui.clearMode(); // close import dialog
|
|
1894
1885
|
openAddLayerPopup(gui);
|
|
1895
1886
|
});
|
|
1896
|
-
initDropArea('#import-quick-drop', true);
|
|
1897
|
-
initDropArea('#import-drop');
|
|
1887
|
+
// initDropArea('#import-quick-drop', true);
|
|
1888
|
+
// initDropArea('#import-drop');
|
|
1898
1889
|
gui.keyboard.onMenuSubmit(El('#import-options'), importQueuedFiles);
|
|
1899
1890
|
|
|
1900
1891
|
gui.addMode('import', turnOn, turnOff);
|
|
1901
1892
|
gui.enterMode('import');
|
|
1902
1893
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
function initDropArea(el, isQuick) {
|
|
1911
|
-
var area = El(el)
|
|
1912
|
-
.on('dragleave', onout)
|
|
1913
|
-
.on('dragover', function() {overQuickView = !!isQuick; onover();})
|
|
1914
|
-
.on('mouseover', onover)
|
|
1915
|
-
.on('mouseout', onout);
|
|
1916
|
-
|
|
1917
|
-
function onover() {
|
|
1918
|
-
area.addClass('dragover');
|
|
1894
|
+
function turnOn() {
|
|
1895
|
+
if (manifestFiles.length > 0) {
|
|
1896
|
+
downloadFiles(manifestFiles, true);
|
|
1897
|
+
manifestFiles = [];
|
|
1898
|
+
} else if (model.isEmpty()) {
|
|
1899
|
+
showImportMenu();
|
|
1919
1900
|
}
|
|
1901
|
+
}
|
|
1920
1902
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1903
|
+
function turnOff() {
|
|
1904
|
+
var target;
|
|
1905
|
+
if (catalog) catalog.reset(); // re-enable clickable catalog
|
|
1906
|
+
if (importCount > 0) {
|
|
1907
|
+
onImportComplete();
|
|
1908
|
+
importTotal += importCount;
|
|
1909
|
+
importCount = 0;
|
|
1924
1910
|
}
|
|
1911
|
+
gui.clearProgressMessage();
|
|
1912
|
+
initialImport = false; // unset 'quick view' mode, if on
|
|
1913
|
+
clearQueuedFiles();
|
|
1914
|
+
hideImportMenu();
|
|
1925
1915
|
}
|
|
1926
1916
|
|
|
1927
1917
|
async function importQueuedFiles() {
|
|
1928
|
-
gui.container.removeClass('queued-files');
|
|
1929
|
-
|
|
1918
|
+
// gui.container.removeClass('queued-files');
|
|
1919
|
+
hideImportMenu();
|
|
1930
1920
|
var files = queuedFiles;
|
|
1931
1921
|
try {
|
|
1932
1922
|
if (files.length > 0) {
|
|
@@ -1943,27 +1933,7 @@
|
|
|
1943
1933
|
}
|
|
1944
1934
|
}
|
|
1945
1935
|
|
|
1946
|
-
function turnOn() {
|
|
1947
|
-
if (manifestFiles.length > 0) {
|
|
1948
|
-
downloadFiles(manifestFiles, true);
|
|
1949
|
-
manifestFiles = [];
|
|
1950
|
-
} else if (model.isEmpty()) {
|
|
1951
|
-
gui.container.addClass('splash-screen');
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
1936
|
|
|
1955
|
-
function turnOff() {
|
|
1956
|
-
var target;
|
|
1957
|
-
if (catalog) catalog.reset(); // re-enable clickable catalog
|
|
1958
|
-
if (importCount > 0) {
|
|
1959
|
-
onImportComplete();
|
|
1960
|
-
importTotal += importCount;
|
|
1961
|
-
importCount = 0;
|
|
1962
|
-
}
|
|
1963
|
-
gui.clearProgressMessage();
|
|
1964
|
-
initialImport = false; // unset 'quick view' mode, if on
|
|
1965
|
-
clearQueuedFiles();
|
|
1966
|
-
}
|
|
1967
1937
|
|
|
1968
1938
|
function onImportComplete() {
|
|
1969
1939
|
// display last layer of last imported dataset
|
|
@@ -1996,6 +1966,7 @@
|
|
|
1996
1966
|
}, []);
|
|
1997
1967
|
}
|
|
1998
1968
|
|
|
1969
|
+
|
|
1999
1970
|
function showQueuedFiles() {
|
|
2000
1971
|
var list = gui.container.findChild('.dropped-file-list').empty();
|
|
2001
1972
|
queuedFiles.forEach(function(f) {
|
|
@@ -2015,13 +1986,20 @@
|
|
|
2015
1986
|
}
|
|
2016
1987
|
});
|
|
2017
1988
|
});
|
|
1989
|
+
submitBtn.classed('disabled', queuedFiles.length === 0);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
function receiveFilesWithOption(files) {
|
|
1993
|
+
var quickView = !El('.advanced-import-options').node().checked;
|
|
1994
|
+
receiveFiles(files, quickView);
|
|
2018
1995
|
}
|
|
2019
1996
|
|
|
2020
|
-
async function receiveFiles(files) {
|
|
1997
|
+
async function receiveFiles(files, quickView) {
|
|
2021
1998
|
var names = getFileNames(files);
|
|
2022
1999
|
var expanded = [];
|
|
2023
2000
|
if (files.length === 0) return;
|
|
2024
|
-
useQuickView = importTotal === 0 && (opts.quick_view ||
|
|
2001
|
+
useQuickView = importTotal === 0 && (opts.quick_view ||
|
|
2002
|
+
quickView);
|
|
2025
2003
|
try {
|
|
2026
2004
|
expanded = await expandFiles(files);
|
|
2027
2005
|
} catch(e) {
|
|
@@ -2039,12 +2017,23 @@
|
|
|
2039
2017
|
if (useQuickView) {
|
|
2040
2018
|
await importQueuedFiles();
|
|
2041
2019
|
} else {
|
|
2042
|
-
|
|
2043
|
-
El('#path-import-options').classed('hidden', !filesMayContainPaths(queuedFiles));
|
|
2044
|
-
showQueuedFiles();
|
|
2020
|
+
showImportMenu();
|
|
2045
2021
|
}
|
|
2046
2022
|
}
|
|
2047
2023
|
|
|
2024
|
+
function showImportMenu() {
|
|
2025
|
+
// gui.container.addClass('queued-files');
|
|
2026
|
+
El('#import-options').show();
|
|
2027
|
+
gui.container.classed('queued-files', queuedFiles.length > 0);
|
|
2028
|
+
El('#path-import-options').classed('hidden', !filesMayContainPaths(queuedFiles));
|
|
2029
|
+
showQueuedFiles();
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function hideImportMenu() {
|
|
2033
|
+
// gui.container.removeClass('queued-files');
|
|
2034
|
+
El('#import-options').hide();
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2048
2037
|
function getFileNames(files) {
|
|
2049
2038
|
return Array.from(files).map(function(f) {return f.name;});
|
|
2050
2039
|
}
|
|
@@ -2540,7 +2529,11 @@
|
|
|
2540
2529
|
// prevent GUI message popup on error
|
|
2541
2530
|
internal.setLoggingForCLI();
|
|
2542
2531
|
try {
|
|
2543
|
-
|
|
2532
|
+
if (!dataset || internal.datasetIsEmpty(dataset)) {
|
|
2533
|
+
crs = internal.parseCrsString('wgs84');
|
|
2534
|
+
} else {
|
|
2535
|
+
crs = internal.getDatasetCRS(dataset);
|
|
2536
|
+
}
|
|
2544
2537
|
} catch(e) {
|
|
2545
2538
|
err = e.message;
|
|
2546
2539
|
}
|
|
@@ -2554,7 +2547,7 @@
|
|
|
2554
2547
|
// Convert a point from display CRS coordinates to data coordinates.
|
|
2555
2548
|
// These are only different when using dynamic reprojection (basemap view).
|
|
2556
2549
|
function translateDisplayPoint(lyr, p) {
|
|
2557
|
-
return isProjectedLayer(lyr) ? lyr.invertPoint(p[0], p[1]) : p;
|
|
2550
|
+
return isProjectedLayer(lyr) ? lyr.gui.invertPoint(p[0], p[1]) : p;
|
|
2558
2551
|
}
|
|
2559
2552
|
|
|
2560
2553
|
// bbox: display coords
|
|
@@ -2571,7 +2564,7 @@
|
|
|
2571
2564
|
|
|
2572
2565
|
function isProjectedLayer(lyr) {
|
|
2573
2566
|
// TODO: could do some validation on the layer's contents
|
|
2574
|
-
return !!
|
|
2567
|
+
return !!lyr.gui.invertPoint;
|
|
2575
2568
|
}
|
|
2576
2569
|
|
|
2577
2570
|
var darkStroke = "#334",
|
|
@@ -2932,160 +2925,619 @@
|
|
|
2932
2925
|
return internal.findPropertiesBySymbolGeom(fields, lyr.geometry_type);
|
|
2933
2926
|
}
|
|
2934
2927
|
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
}
|
|
2928
|
+
// Create low-detail versions of large arc collections for faster rendering
|
|
2929
|
+
// at zoomed-out scales.
|
|
2930
|
+
function enhanceArcCollectionForDisplay(unfilteredArcs) {
|
|
2931
|
+
var size = unfilteredArcs.getPointCount(),
|
|
2932
|
+
filteredArcs, filteredSegLen;
|
|
2941
2933
|
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
lyr.arcs.setRetainedInterval(z);
|
|
2934
|
+
// Only generate low-detail arcs for larger datasets
|
|
2935
|
+
if (size > 5e5) {
|
|
2936
|
+
update();
|
|
2946
2937
|
}
|
|
2947
|
-
}
|
|
2948
2938
|
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2939
|
+
function update() {
|
|
2940
|
+
if (unfilteredArcs.getVertexData().zz) {
|
|
2941
|
+
// Use precalculated simplification data for vertex filtering, if available
|
|
2942
|
+
filteredArcs = initFilteredArcs(unfilteredArcs);
|
|
2943
|
+
filteredSegLen = internal.getAvgSegment(filteredArcs);
|
|
2944
|
+
} else {
|
|
2945
|
+
// Use fast simplification as a fallback
|
|
2946
|
+
filteredSegLen = internal.getAvgSegment(unfilteredArcs) * 4;
|
|
2947
|
+
filteredArcs = internal.simplifyArcsFast(unfilteredArcs, filteredSegLen);
|
|
2948
|
+
}
|
|
2952
2949
|
}
|
|
2953
|
-
}
|
|
2954
2950
|
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
d.r = 3; // show a black circle if layer is styled
|
|
2964
|
-
}
|
|
2965
|
-
if (layer.geometry_type == 'polyline' || layer.geometry_type == 'polygon') {
|
|
2966
|
-
if (fields.includes('stroke')) d.stroke = 'black';
|
|
2967
|
-
if (fields.includes('stroke-width')) d['stroke-width'] = 1;
|
|
2951
|
+
function initFilteredArcs(arcs) {
|
|
2952
|
+
var filterPct = 0.08;
|
|
2953
|
+
var nth = Math.ceil(arcs.getPointCount() / 5e5);
|
|
2954
|
+
var currInterval = arcs.getRetainedInterval();
|
|
2955
|
+
var filterZ = arcs.getThresholdByPct(filterPct, nth);
|
|
2956
|
+
var filteredArcs = arcs.setRetainedInterval(filterZ).getFilteredCopy();
|
|
2957
|
+
arcs.setRetainedInterval(currInterval); // reset current simplification
|
|
2958
|
+
return filteredArcs;
|
|
2968
2959
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2960
|
+
|
|
2961
|
+
// TODO: better job of detecting arc change... e.g. revision number
|
|
2962
|
+
unfilteredArcs.getScaledArcs = function(ext) {
|
|
2963
|
+
// check for changes in the number of arcs (probably due to editing)
|
|
2964
|
+
if (filteredArcs && filteredArcs.size() != unfilteredArcs.size()) {
|
|
2965
|
+
// arc count has changed... probably due to editing
|
|
2966
|
+
update();
|
|
2967
|
+
if (filteredArcs.size() != unfilteredArcs.size()) {
|
|
2968
|
+
throw Error('Internal error');
|
|
2969
|
+
}
|
|
2972
2970
|
}
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2971
|
+
if (filteredArcs) {
|
|
2972
|
+
// match simplification of unfiltered arcs
|
|
2973
|
+
filteredArcs.setRetainedInterval(unfilteredArcs.getRetainedInterval());
|
|
2974
|
+
}
|
|
2975
|
+
// switch to filtered version of arcs at small scales
|
|
2976
|
+
var unitsPerPixel = 1/ext.getTransform().mx,
|
|
2977
|
+
useFiltering = filteredArcs && unitsPerPixel > filteredSegLen * 1.5;
|
|
2978
|
+
return useFiltering ? filteredArcs : unfilteredArcs;
|
|
2979
|
+
};
|
|
2977
2980
|
}
|
|
2978
2981
|
|
|
2979
|
-
function
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2982
|
+
function getDisplayLayerForTable(tableArg) {
|
|
2983
|
+
var table = tableArg || new internal.DataTable(0),
|
|
2984
|
+
n = table.size(),
|
|
2985
|
+
cellWidth = 12,
|
|
2986
|
+
cellHeight = 5,
|
|
2987
|
+
gutter = 6,
|
|
2988
|
+
arcs = [],
|
|
2989
|
+
shapes = [],
|
|
2990
|
+
aspectRatio = 1.1,
|
|
2991
|
+
x, y, col, row, blockSize;
|
|
2985
2992
|
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2993
|
+
if (n > 10000) {
|
|
2994
|
+
arcs = null;
|
|
2995
|
+
gutter = 0;
|
|
2996
|
+
cellWidth = 4;
|
|
2997
|
+
cellHeight = 4;
|
|
2998
|
+
aspectRatio = 1.45;
|
|
2999
|
+
} else if (n > 5000) {
|
|
3000
|
+
cellWidth = 5;
|
|
3001
|
+
gutter = 3;
|
|
3002
|
+
aspectRatio = 1.45;
|
|
3003
|
+
} else if (n > 1000) {
|
|
3004
|
+
gutter = 3;
|
|
3005
|
+
cellWidth = 8;
|
|
3006
|
+
aspectRatio = 1.3;
|
|
2995
3007
|
}
|
|
2996
|
-
}
|
|
2997
3008
|
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
lyr.layer.shapes.push([[arcId]]);
|
|
3003
|
-
if (isProjectedLayer(lyr)) {
|
|
3004
|
-
// lyr.source.layer.shapes.push([[arcId]]);
|
|
3005
|
-
internal.appendEmptyArc(lyr.source.dataset.arcs);
|
|
3009
|
+
if (n < 25) {
|
|
3010
|
+
blockSize = n;
|
|
3011
|
+
} else {
|
|
3012
|
+
blockSize = Math.sqrt(n * (cellWidth + gutter) / cellHeight / aspectRatio) | 0;
|
|
3006
3013
|
}
|
|
3007
|
-
appendVertex$1(lyr, p1);
|
|
3008
|
-
appendVertex$1(lyr, p2);
|
|
3009
|
-
appendNewDataRecord(lyr.layer);
|
|
3010
|
-
}
|
|
3011
3014
|
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3015
|
+
for (var i=0; i<n; i++) {
|
|
3016
|
+
row = i % blockSize;
|
|
3017
|
+
col = Math.floor(i / blockSize);
|
|
3018
|
+
x = col * (cellWidth + gutter);
|
|
3019
|
+
y = cellHeight * (blockSize - row);
|
|
3020
|
+
if (arcs) {
|
|
3021
|
+
arcs.push(getArc(x, y, cellWidth, cellHeight));
|
|
3022
|
+
shapes.push([[i]]);
|
|
3023
|
+
} else {
|
|
3024
|
+
shapes.push([[x, y]]);
|
|
3025
|
+
}
|
|
3017
3026
|
}
|
|
3018
|
-
}
|
|
3019
3027
|
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
insertVertex$1(lyr, n, p);
|
|
3023
|
-
}
|
|
3024
|
-
|
|
3025
|
-
// TODO: make sure we're not also removing an entire arc
|
|
3026
|
-
function deleteLastVertex(lyr) {
|
|
3027
|
-
deleteVertex$1(lyr, lyr.arcs.getPointCount() - 1);
|
|
3028
|
-
}
|
|
3029
|
-
|
|
3030
|
-
function deleteVertex$1(lyr, id) {
|
|
3031
|
-
internal.deleteVertex(lyr.arcs, id);
|
|
3032
|
-
if (isProjectedLayer(lyr)) {
|
|
3033
|
-
internal.deleteVertex(lyr.source.dataset.arcs, id);
|
|
3028
|
+
function getArc(x, y, w, h) {
|
|
3029
|
+
return [[x, y], [x + w, y], [x + w, y - h], [x, y - h], [x, y]];
|
|
3034
3030
|
}
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
function getLastArcCoords(target) {
|
|
3038
|
-
var arcId = target.source.dataset.arcs.size() - 1;
|
|
3039
|
-
return internal.getUnfilteredArcCoords(arcId, target.source.dataset.arcs);
|
|
3040
|
-
}
|
|
3041
3031
|
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3032
|
+
return {
|
|
3033
|
+
layer: {
|
|
3034
|
+
geometry_type: arcs ? 'polygon' : 'point',
|
|
3035
|
+
shapes: shapes,
|
|
3036
|
+
data: table
|
|
3037
|
+
},
|
|
3038
|
+
arcs: arcs ? new internal.ArcCollection(arcs) : null
|
|
3039
|
+
};
|
|
3045
3040
|
}
|
|
3046
3041
|
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
}
|
|
3042
|
+
var R = 6378137;
|
|
3043
|
+
var D2R = Math.PI / 180;
|
|
3044
|
+
var R2D = 180 / Math.PI;
|
|
3051
3045
|
|
|
3052
|
-
|
|
3053
|
-
return internal.cloneShape(lyr.source.layer.shapes[fid]);
|
|
3054
|
-
}
|
|
3046
|
+
// Assumes projections are available
|
|
3055
3047
|
|
|
3056
|
-
function
|
|
3057
|
-
|
|
3048
|
+
function needReprojectionForDisplay(sourceCRS, displayCRS) {
|
|
3049
|
+
if (!sourceCRS || !displayCRS) {
|
|
3050
|
+
return false;
|
|
3051
|
+
}
|
|
3052
|
+
if (internal.crsAreEqual(sourceCRS, displayCRS)) {
|
|
3053
|
+
return false;
|
|
3054
|
+
}
|
|
3055
|
+
return true;
|
|
3058
3056
|
}
|
|
3059
3057
|
|
|
3060
|
-
//
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
if (
|
|
3064
|
-
|
|
3065
|
-
internal.snapVerticesToPoint(ids, p, lyr.arcs, true);
|
|
3058
|
+
// bbox in wgs84 coords
|
|
3059
|
+
// dest: CRS, which may also be wgs84
|
|
3060
|
+
function projectLatLonBBox(bbox, dest) {
|
|
3061
|
+
if (!dest || internal.isWGS84(dest)) {
|
|
3062
|
+
return bbox.concat();
|
|
3066
3063
|
}
|
|
3064
|
+
var proj = internal.getProjTransform2(internal.parseCrsString('wgs84'), dest);
|
|
3065
|
+
var bbox2 = proj(bbox[0], bbox[1]).concat(proj(bbox[2], bbox[3]));
|
|
3066
|
+
return bbox2;
|
|
3067
3067
|
}
|
|
3068
3068
|
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
if (
|
|
3073
|
-
|
|
3069
|
+
function projectArcsForDisplay(arcs, src, dest) {
|
|
3070
|
+
var copy = arcs.getCopy(); // need to flatten first?
|
|
3071
|
+
var destIsWebMerc = internal.isWebMercator(dest);
|
|
3072
|
+
if (destIsWebMerc && internal.isWebMercator(src)) {
|
|
3073
|
+
return copy;
|
|
3074
3074
|
}
|
|
3075
|
-
}
|
|
3076
3075
|
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
var
|
|
3080
|
-
internal.snapVerticesToPoint(ids, lyr.invertPoint(p[0], p[1]), lyr.source.dataset.arcs, true);
|
|
3081
|
-
}
|
|
3076
|
+
var wgs84 = internal.parseCrsString('wgs84');
|
|
3077
|
+
var toWGS84 = internal.getProjTransform2(src, wgs84);
|
|
3078
|
+
var fromWGS84 = internal.getProjTransform2(wgs84, dest);
|
|
3082
3079
|
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
internal.
|
|
3080
|
+
try {
|
|
3081
|
+
// first try projectArcs() -- it's fast and preserves arc ids
|
|
3082
|
+
// (so vertex editing doesn't break)
|
|
3083
|
+
if (!internal.isWGS84(src)) {
|
|
3084
|
+
// use wgs84 as a pivot CRS, so we can handle polar coordinates
|
|
3085
|
+
// that can't be projected to Mercator
|
|
3086
|
+
internal.projectArcs(copy, toWGS84);
|
|
3087
|
+
}
|
|
3088
|
+
if (destIsWebMerc) {
|
|
3089
|
+
// handle polar points by clamping them to they will project
|
|
3090
|
+
// (downside: may cause unexpected behavior when editing vertices interactively)
|
|
3091
|
+
clampY(copy);
|
|
3092
|
+
}
|
|
3093
|
+
internal.projectArcs(copy, fromWGS84);
|
|
3094
|
+
} catch(e) {
|
|
3095
|
+
console.error(e);
|
|
3096
|
+
// use the more robust projectArcs2 if projectArcs throws an error
|
|
3097
|
+
// downside: projectArcs2 discards Z values and changes arc indexing,
|
|
3098
|
+
// which will break vertex editing.
|
|
3099
|
+
var reproject = internal.getProjTransform2(src, dest);
|
|
3100
|
+
copy = arcs.getCopy();
|
|
3101
|
+
internal.projectArcs2(copy, reproject);
|
|
3102
|
+
}
|
|
3103
|
+
return copy;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
function clampY(arcs) {
|
|
3107
|
+
var max = 89.9,
|
|
3108
|
+
min = -89.9,
|
|
3109
|
+
bbox = arcs.getBounds().toArray();
|
|
3110
|
+
if (bbox[1] >= min && bbox[3] <= max) return;
|
|
3111
|
+
arcs.transformPoints(function(x, y) {
|
|
3112
|
+
if (y > max) return [x, max];
|
|
3113
|
+
if (y < min) return [x, min];
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
function projectPointsForDisplay(lyr, src, dest) {
|
|
3118
|
+
var copy = utils$1.extend({}, lyr);
|
|
3119
|
+
var proj = internal.getProjTransform2(src, dest);
|
|
3120
|
+
copy.shapes = internal.cloneShapes(lyr.shapes);
|
|
3121
|
+
internal.projectPointLayer(copy, proj);
|
|
3122
|
+
return copy;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
|
|
3126
|
+
function toWebMercator(lng, lat) {
|
|
3127
|
+
var k = Math.cos(lat * D2R);
|
|
3128
|
+
var x = R * lng * D2R;
|
|
3129
|
+
var y = R * Math.log(Math.tan(Math.PI * 0.25 + lat * D2R * 0.5));
|
|
3130
|
+
return [x, y];
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
function fromWebMercator(x, y) {
|
|
3134
|
+
var lon = x / R * R2D;
|
|
3135
|
+
var lat = R2D * (Math.PI * 0.5 - 2 * Math.atan(Math.exp(-y / R)));
|
|
3136
|
+
return [lon, lat];
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
function scaleToZoom(metersPerPix) {
|
|
3140
|
+
return Math.log(40075017 / 512 / metersPerPix) / Math.log(2);
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
function getMapboxBounds() {
|
|
3144
|
+
var ymax = toWebMercator(0, 84)[1];
|
|
3145
|
+
var ymin = toWebMercator(0, -84)[1];
|
|
3146
|
+
return [-Infinity, ymin, Infinity, ymax];
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
|
|
3150
|
+
// Update map extent and trigger redraw, after a new display CRS has been applied
|
|
3151
|
+
function projectMapExtent(ext, src, dest, newBounds) {
|
|
3152
|
+
var oldBounds = ext.getBounds();
|
|
3153
|
+
var oldScale = ext.scale();
|
|
3154
|
+
var newCP, proj, strictBounds;
|
|
3155
|
+
|
|
3156
|
+
if (dest && internal.isWebMercator(dest)) {
|
|
3157
|
+
// clampToMapboxBounds(newBounds);
|
|
3158
|
+
strictBounds = getMapboxBounds();
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
// if source or destination CRS is unknown, show full extent
|
|
3162
|
+
// if map is at full extent, show full extent
|
|
3163
|
+
// TODO: handle case that scale is 1 and map is panned away from center
|
|
3164
|
+
if (ext.scale() == 1 || !dest) {
|
|
3165
|
+
ext.setFullBounds(newBounds, strictBounds);
|
|
3166
|
+
ext.reset();
|
|
3167
|
+
} else {
|
|
3168
|
+
// if map is zoomed, stay centered on the same geographic location, at the same relative scale
|
|
3169
|
+
proj = internal.getProjTransform2(src, dest);
|
|
3170
|
+
newCP = proj(oldBounds.centerX(), oldBounds.centerY());
|
|
3171
|
+
ext.setFullBounds(newBounds, strictBounds);
|
|
3172
|
+
if (!newCP) {
|
|
3173
|
+
// projection of center point failed; use center of bounds
|
|
3174
|
+
// (also consider just resetting the view using ext.home())
|
|
3175
|
+
newCP = [newBounds.centerX(), newBounds.centerY()];
|
|
3176
|
+
}
|
|
3177
|
+
ext.recenter(newCP[0], newCP[1], oldScale);
|
|
3178
|
+
}
|
|
3179
|
+
// trigger full redraw, in case some SVG symbols are unprojectable
|
|
3180
|
+
ext.dispatchEvent('change', {redraw: true});
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
// Called from console; for testing dynamic crs
|
|
3184
|
+
function setDisplayProjection(gui, cmd) {
|
|
3185
|
+
var arg = cmd.replace(/^projd[ ]*/, '');
|
|
3186
|
+
if (arg) {
|
|
3187
|
+
gui.map.setDisplayCRS(internal.parseCrsString(arg));
|
|
3188
|
+
} else {
|
|
3189
|
+
gui.map.setDisplayCRS(null);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
function filterLayerByIds(lyr, ids) {
|
|
3194
|
+
var shapes;
|
|
3195
|
+
if (lyr.shapes) {
|
|
3196
|
+
shapes = ids.map(function(id) {
|
|
3197
|
+
return lyr.shapes[id];
|
|
3198
|
+
});
|
|
3199
|
+
return utils$1.defaults({shapes: shapes, data: null}, lyr);
|
|
3200
|
+
}
|
|
3201
|
+
return lyr;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
function formatLayerNameForDisplay(name) {
|
|
3205
|
+
return name || '[unnamed]';
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
function cleanLayerName(raw) {
|
|
3209
|
+
return raw.replace(/[\n\t/\\]/g, '')
|
|
3210
|
+
.replace(/^[.\s]+/, '').replace(/[.\s]+$/, '');
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
function updateLayerStackOrder(layers) {
|
|
3214
|
+
// 1. assign ascending ids to unassigned layers above the range of other layers
|
|
3215
|
+
layers.forEach(function(o, i) {
|
|
3216
|
+
if (!o.layer.menu_order) o.layer.menu_order = 1e6 + i;
|
|
3217
|
+
});
|
|
3218
|
+
// 2. sort in ascending order
|
|
3219
|
+
layers.sort(function(a, b) {
|
|
3220
|
+
return a.layer.menu_order - b.layer.menu_order;
|
|
3221
|
+
});
|
|
3222
|
+
// 3. assign consecutve ids
|
|
3223
|
+
layers.forEach(function(o, i) {
|
|
3224
|
+
o.layer.menu_order = i + 1;
|
|
3225
|
+
});
|
|
3226
|
+
return layers;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
function sortLayersForMenuDisplay(layers) {
|
|
3230
|
+
layers = updateLayerStackOrder(layers);
|
|
3231
|
+
return layers.reverse();
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
// lyr: a map layer with gui property
|
|
3235
|
+
// displayCRS: CRS to use for display, or null (which clears any current display CRS)
|
|
3236
|
+
function projectLayerForDisplay(lyr, displayCRS) {
|
|
3237
|
+
var crsInfo = getDatasetCrsInfo(lyr.gui.source.dataset);
|
|
3238
|
+
var sourceCRS = crsInfo.crs || null; // let enhanceLayerForDisplay() handle null case
|
|
3239
|
+
if (!lyr.gui.geographic) {
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
if (lyr.gui.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.gui.dynamic_crs)) {
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
var gui = lyr.gui;
|
|
3246
|
+
enhanceLayerForDisplay(lyr, lyr.gui.source.dataset, {crs: displayCRS});
|
|
3247
|
+
utils$1.defaults(lyr.gui, gui); // re-apply any properties that were lost (e.g. svg_id)
|
|
3248
|
+
if (lyr.gui.style?.ids) {
|
|
3249
|
+
// re-apply layer filter
|
|
3250
|
+
lyr.gui.displayLayer = filterLayerByIds(lyr.gui.displayLayer, lyr.gui.style.ids);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
|
|
3255
|
+
// Supplement a layer with information needed for rendering
|
|
3256
|
+
function enhanceLayerForDisplay(layer, dataset, opts) {
|
|
3257
|
+
var gui = {
|
|
3258
|
+
empty: internal.getFeatureCount(layer) === 0,
|
|
3259
|
+
geographic: false,
|
|
3260
|
+
displayArcs: null,
|
|
3261
|
+
displayLayer: null,
|
|
3262
|
+
source: {dataset},
|
|
3263
|
+
bounds: null,
|
|
3264
|
+
style: null,
|
|
3265
|
+
dynamic_crs: null,
|
|
3266
|
+
invertPoint: null,
|
|
3267
|
+
projectPoint: null
|
|
3268
|
+
};
|
|
3269
|
+
|
|
3270
|
+
var displayCRS = opts.crs || null;
|
|
3271
|
+
// display arcs may have been generated when another layer in the dataset
|
|
3272
|
+
// was converted for display... re-use if available
|
|
3273
|
+
var displayArcs = dataset.gui?.displayArcs;
|
|
3274
|
+
var unprojectable = false;
|
|
3275
|
+
var sourceCRS;
|
|
3276
|
+
var emptyArcs;
|
|
3277
|
+
|
|
3278
|
+
if (displayCRS && layer.geometry_type) {
|
|
3279
|
+
var crsInfo = getDatasetCrsInfo(dataset);
|
|
3280
|
+
if (crsInfo.error) {
|
|
3281
|
+
// unprojectable dataset -- return empty layer
|
|
3282
|
+
gui.unprojectable = true;
|
|
3283
|
+
} else {
|
|
3284
|
+
sourceCRS = crsInfo.crs;
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
// Assume that dataset.displayArcs is in the display CRS
|
|
3289
|
+
// (it must be deleted upstream if reprojection is needed)
|
|
3290
|
+
// if (!obj.empty && dataset.arcs && !displayArcs) {
|
|
3291
|
+
if (dataset.arcs && !displayArcs && !gui.unprojectable) {
|
|
3292
|
+
// project arcs, if needed
|
|
3293
|
+
if (needReprojectionForDisplay(sourceCRS, displayCRS)) {
|
|
3294
|
+
displayArcs = projectArcsForDisplay(dataset.arcs, sourceCRS, displayCRS);
|
|
3295
|
+
} else {
|
|
3296
|
+
// Use original arcs for display if there is no dynamic reprojection
|
|
3297
|
+
displayArcs = dataset.arcs;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
enhanceArcCollectionForDisplay(displayArcs);
|
|
3301
|
+
dataset.gui = {displayArcs}; // stash these in the dataset for other layers to use
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
if (internal.layerHasFurniture(layer)) {
|
|
3305
|
+
// TODO: consider how to render furniture in GUI
|
|
3306
|
+
// treating furniture layers (other than frame) as tabular for now,
|
|
3307
|
+
// so there is something to show if they are selected
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
if (gui.unprojectable) {
|
|
3311
|
+
gui.displayLayer = {shapes: []}; // TODO: improve
|
|
3312
|
+
} else if (layer.geometry_type) {
|
|
3313
|
+
gui.geographic = true;
|
|
3314
|
+
gui.displayLayer = layer;
|
|
3315
|
+
gui.displayArcs = displayArcs;
|
|
3316
|
+
} else {
|
|
3317
|
+
var table = getDisplayLayerForTable(layer.data);
|
|
3318
|
+
gui.tabular = true;
|
|
3319
|
+
gui.displayLayer = table.layer;
|
|
3320
|
+
gui.displayArcs = table.arcs;
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
// dynamic reprojection (arcs were already reprojected above)
|
|
3324
|
+
if (gui.geographic && needReprojectionForDisplay(sourceCRS, displayCRS)) {
|
|
3325
|
+
gui.dynamic_crs = displayCRS;
|
|
3326
|
+
gui.invertPoint = internal.getProjTransform2(displayCRS, sourceCRS);
|
|
3327
|
+
gui.projectPoint = internal.getProjTransform2(sourceCRS, displayCRS);
|
|
3328
|
+
if (internal.layerHasPoints(layer)) {
|
|
3329
|
+
gui.displayLayer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
|
|
3330
|
+
} else if (internal.layerHasPaths(layer)) {
|
|
3331
|
+
emptyArcs = findEmptyArcs(displayArcs);
|
|
3332
|
+
if (emptyArcs.length > 0) {
|
|
3333
|
+
// Don't try to draw paths containing coordinates that failed to project
|
|
3334
|
+
gui.displayLayer = internal.filterPathLayerByArcIds(gui.displayLayer, emptyArcs);
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
gui.bounds = getDisplayBounds(gui.displayLayer, gui.displayArcs);
|
|
3340
|
+
layer.gui = gui;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
function getDisplayBounds(lyr, arcs) {
|
|
3344
|
+
var bounds = internal.getLayerBounds(lyr, arcs) || new Bounds();
|
|
3345
|
+
if (lyr.geometry_type == 'point' && arcs && bounds.hasBounds() && bounds.area() > 0 === false) {
|
|
3346
|
+
// if a point layer has no extent (e.g. contains only a single point),
|
|
3347
|
+
// then merge with arc bounds, to place the point in context.
|
|
3348
|
+
bounds = bounds.mergeBounds(arcs.getBounds());
|
|
3349
|
+
}
|
|
3350
|
+
return bounds;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
// Returns an array of ids of empty arcs (arcs can be set to empty if errors occur while projecting them)
|
|
3354
|
+
function findEmptyArcs(arcs) {
|
|
3355
|
+
var nn = arcs.getVertexData().nn;
|
|
3356
|
+
var ids = [];
|
|
3357
|
+
for (var i=0, n=nn.length; i<n; i++) {
|
|
3358
|
+
if (nn[i] === 0) {
|
|
3359
|
+
ids.push(i);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
return ids;
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
function flattenArcs(lyr) {
|
|
3366
|
+
lyr.gui.source.dataset.arcs.flatten();
|
|
3367
|
+
if (isProjectedLayer(lyr)) {
|
|
3368
|
+
lyr.gui.displayArcs.flatten();
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
function setZ(lyr, z) {
|
|
3373
|
+
lyr.gui.source.dataset.arcs.setRetainedInterval(z);
|
|
3374
|
+
if (isProjectedLayer(lyr)) {
|
|
3375
|
+
lyr.gui.displayArcs.setRetainedInterval(z);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
function updateZ(lyr) {
|
|
3380
|
+
if (isProjectedLayer(lyr) && !lyr.gui.source.dataset.arcs.isFlat()) {
|
|
3381
|
+
lyr.gui.displayArcs.setThresholds(lyr.gui.source.dataset.arcs.getVertexData().zz);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
function appendNewDataRecord(layer) {
|
|
3386
|
+
if (!layer.data) return null;
|
|
3387
|
+
var fields = layer.data.getFields();
|
|
3388
|
+
var d = getEmptyDataRecord(layer.data);
|
|
3389
|
+
// TODO: handle SVG symbol layer
|
|
3390
|
+
if (internal.layerHasLabels(layer)) {
|
|
3391
|
+
d['label-text'] = 'TBD'; // without text, new labels will be invisible
|
|
3392
|
+
} else if (layer.geometry_type == 'point' && fields.includes('r')) {
|
|
3393
|
+
d.r = 3; // show a black circle if layer is styled
|
|
3394
|
+
}
|
|
3395
|
+
if (layer.geometry_type == 'polyline' || layer.geometry_type == 'polygon') {
|
|
3396
|
+
if (fields.includes('stroke')) d.stroke = 'black';
|
|
3397
|
+
if (fields.includes('stroke-width')) d['stroke-width'] = 1;
|
|
3398
|
+
}
|
|
3399
|
+
if (layer.geometry_type == 'polygon') {
|
|
3400
|
+
if (fields.includes('fill')) {
|
|
3401
|
+
d.fill = 'rgba(0,0,0,0.10)'; // 'rgba(249,120,249,0.20)';
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
// TODO: better styling
|
|
3405
|
+
layer.data.getRecords().push(d);
|
|
3406
|
+
return d;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
function getEmptyDataRecord(table) {
|
|
3410
|
+
return table.getFields().reduce(function(memo, name) {
|
|
3411
|
+
memo[name] = null;
|
|
3412
|
+
return memo;
|
|
3413
|
+
}, {});
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
// p1, p2: two points in source data CRS coords.
|
|
3417
|
+
function appendNewPath(lyr, p1, p2) {
|
|
3418
|
+
var arcId = lyr.gui.displayArcs.size();
|
|
3419
|
+
internal.appendEmptyArc(lyr.gui.displayArcs);
|
|
3420
|
+
lyr.shapes.push([[arcId]]);
|
|
3421
|
+
if (isProjectedLayer(lyr)) {
|
|
3422
|
+
internal.appendEmptyArc(lyr.gui.source.dataset.arcs);
|
|
3423
|
+
}
|
|
3424
|
+
appendVertex$1(lyr, p1);
|
|
3425
|
+
appendVertex$1(lyr, p2);
|
|
3426
|
+
appendNewDataRecord(lyr);
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
function deleteLastPath(lyr) {
|
|
3430
|
+
var arcId = lyr.gui.displayArcs.size() - 1;
|
|
3431
|
+
if (lyr.data) {
|
|
3432
|
+
lyr.data.getRecords().pop();
|
|
3433
|
+
}
|
|
3434
|
+
var shp = lyr.shapes.pop();
|
|
3435
|
+
internal.deleteLastArc(lyr.gui.displayArcs);
|
|
3436
|
+
if (isProjectedLayer(lyr)) {
|
|
3437
|
+
internal.deleteLastArc(lyr.gui.source.dataset.arcs);
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
// p: one point in source data coords
|
|
3442
|
+
function appendNewPoint(lyr, p) {
|
|
3443
|
+
lyr.shapes.push([p]);
|
|
3444
|
+
if (lyr.data) {
|
|
3445
|
+
appendNewDataRecord(lyr);
|
|
3446
|
+
}
|
|
3447
|
+
// this reprojects all the points... TODO: improve
|
|
3448
|
+
if (isProjectedLayer(lyr)) {
|
|
3449
|
+
projectLayerForDisplay(lyr, lyr.gui.dynamic_crs);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
function deleteLastPoint(lyr) {
|
|
3454
|
+
if (lyr.data) {
|
|
3455
|
+
lyr.data.getRecords().pop();
|
|
3456
|
+
}
|
|
3457
|
+
lyr.shapes.pop();
|
|
3458
|
+
if (isProjectedLayer(lyr)) {
|
|
3459
|
+
lyr.gui.displayLayer.shapes.pop();
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
// p: point in source data CRS coords.
|
|
3464
|
+
function insertVertex$1(lyr, id, p) {
|
|
3465
|
+
internal.insertVertex(lyr.gui.source.dataset.arcs, id, p);
|
|
3466
|
+
if (isProjectedLayer(lyr)) {
|
|
3467
|
+
internal.insertVertex(lyr.gui.displayArcs, id, lyr.gui.projectPoint(p[0], p[1]));
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
function appendVertex$1(lyr, p) {
|
|
3472
|
+
var n = lyr.gui.source.dataset.arcs.getPointCount();
|
|
3473
|
+
insertVertex$1(lyr, n, p);
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
// TODO: make sure we're not also removing an entire arc
|
|
3477
|
+
function deleteLastVertex(lyr) {
|
|
3478
|
+
deleteVertex$1(lyr, lyr.gui.displayArcs.getPointCount() - 1);
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
|
|
3482
|
+
function deleteVertex$1(lyr, id) {
|
|
3483
|
+
internal.deleteVertex(lyr.gui.displayArcs, id);
|
|
3484
|
+
if (isProjectedLayer(lyr)) {
|
|
3485
|
+
internal.deleteVertex(lyr.gui.source.dataset.arcs, id);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
function getLastArcCoords(target) {
|
|
3490
|
+
var arcId = target.gui.source.dataset.arcs.size() - 1;
|
|
3491
|
+
return internal.getUnfilteredArcCoords(arcId, target.gui.source.dataset.arcs);
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
function getLastVertexCoords(target) {
|
|
3495
|
+
var arcs = target.gui.source.dataset.arcs;
|
|
3496
|
+
return internal.getVertexCoords(arcs.getPointCount() - 1, arcs);
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
function getLastArcLength(target) {
|
|
3500
|
+
var arcId = target.gui.source.dataset.arcs.size() - 1;
|
|
3501
|
+
return internal.getUnfilteredArcLength(arcId, target.gui.source.dataset.arcs);
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
function getPointCoords(lyr, fid) {
|
|
3505
|
+
return internal.cloneShape(lyr.shapes[fid]);
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
function getVertexCoords(lyr, id) {
|
|
3509
|
+
return lyr.gui.source.dataset.arcs.getVertex2(id);
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
// set data coords (not display coords) of one or more vertices.
|
|
3513
|
+
function setVertexCoords(lyr, ids, dataPoint) {
|
|
3514
|
+
internal.snapVerticesToPoint(ids, dataPoint, lyr.gui.source.dataset.arcs);
|
|
3515
|
+
if (isProjectedLayer(lyr)) {
|
|
3516
|
+
var p = lyr.gui.projectPoint(dataPoint[0], dataPoint[1]);
|
|
3517
|
+
internal.snapVerticesToPoint(ids, p, lyr.gui.displayArcs);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
// coords: [x, y] point in data CRS (not display CRS)
|
|
3522
|
+
function setPointCoords(lyr, fid, coords) {
|
|
3523
|
+
lyr.shapes[fid] = coords;
|
|
3524
|
+
if (isProjectedLayer(lyr)) {
|
|
3525
|
+
lyr.shapes[fid] = projectPointCoords(coords, lyr.gui.projectPoint);
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
function updateVertexCoords(lyr, ids) {
|
|
3530
|
+
if (!isProjectedLayer(lyr)) return;
|
|
3531
|
+
var p = lyr.gui.displayArcs.getVertex2(ids[0]);
|
|
3532
|
+
internal.snapVerticesToPoint(ids, lyr.gui.invertPoint(p[0], p[1]), lyr.gui.source.dataset.arcs);
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
function setRectangleCoords(lyr, ids, coords) {
|
|
3536
|
+
ids.forEach(function(id, i) {
|
|
3537
|
+
var p = coords[i];
|
|
3538
|
+
internal.snapVerticesToPoint([id], p, lyr.gui.source.dataset.arcs);
|
|
3087
3539
|
if (isProjectedLayer(lyr)) {
|
|
3088
|
-
internal.snapVerticesToPoint([id], lyr.projectPoint(p[0], p[1]), lyr.
|
|
3540
|
+
internal.snapVerticesToPoint([id], lyr.gui.projectPoint(p[0], p[1]), lyr.gui.displayArcs);
|
|
3089
3541
|
}
|
|
3090
3542
|
});
|
|
3091
3543
|
}
|
|
@@ -3093,8 +3545,8 @@
|
|
|
3093
3545
|
// Update source data coordinates by projecting display coordinates
|
|
3094
3546
|
function updatePointCoords(lyr, fid) {
|
|
3095
3547
|
if (!isProjectedLayer(lyr)) return;
|
|
3096
|
-
var displayShp = lyr.
|
|
3097
|
-
lyr.
|
|
3548
|
+
var displayShp = lyr.shapes[fid];
|
|
3549
|
+
lyr.shapes[fid] = projectPointCoords(displayShp, lyr.gui.invertPoint);
|
|
3098
3550
|
}
|
|
3099
3551
|
|
|
3100
3552
|
function projectPointCoords(src, proj) {
|
|
@@ -3136,6 +3588,7 @@
|
|
|
3136
3588
|
var el = gui.container.findChild('.simplify-control-wrapper');
|
|
3137
3589
|
var menu = gui.container.findChild('.simplify-options');
|
|
3138
3590
|
var slider, text, fromPct;
|
|
3591
|
+
var menuBtn = gui.container.findChild('.simplify-btn').addClass('disabled');
|
|
3139
3592
|
|
|
3140
3593
|
// init settings menu
|
|
3141
3594
|
new SimpleButton(menu.findChild('.submit-btn').addClass('default-btn')).on('click', onSubmit);
|
|
@@ -3157,11 +3610,14 @@
|
|
|
3157
3610
|
gui.keyboard.onMenuSubmit(menu, onSubmit);
|
|
3158
3611
|
|
|
3159
3612
|
// init simplify button and mode
|
|
3160
|
-
gui.addMode('simplify', turnOn, turnOff,
|
|
3161
|
-
|
|
3613
|
+
gui.addMode('simplify', turnOn, turnOff, menuBtn);
|
|
3614
|
+
|
|
3615
|
+
model.on('update', function() {
|
|
3616
|
+
menuBtn.classed('disabled', !model.getActiveLayer());
|
|
3162
3617
|
if (gui.getMode() == 'simplify') gui.clearMode();
|
|
3163
3618
|
});
|
|
3164
3619
|
|
|
3620
|
+
|
|
3165
3621
|
// exit simplify mode when user clicks off the visible part of the menu
|
|
3166
3622
|
menu.on('click', GUI.handleDirectEvent(gui.clearMode));
|
|
3167
3623
|
|
|
@@ -3334,150 +3790,12 @@
|
|
|
3334
3790
|
}
|
|
3335
3791
|
}
|
|
3336
3792
|
|
|
3337
|
-
function updateSliderDisplay() {
|
|
3338
|
-
// TODO: display resolution and vertex count
|
|
3339
|
-
// var dataset = model.getActiveLayer().dataset;
|
|
3340
|
-
// var interval = dataset.arcs.getRetainedInterval();
|
|
3341
|
-
}
|
|
3342
|
-
};
|
|
3343
|
-
|
|
3344
|
-
var R = 6378137;
|
|
3345
|
-
var D2R = Math.PI / 180;
|
|
3346
|
-
var R2D = 180 / Math.PI;
|
|
3347
|
-
|
|
3348
|
-
// Assumes projections are available
|
|
3349
|
-
|
|
3350
|
-
function needReprojectionForDisplay(sourceCRS, displayCRS) {
|
|
3351
|
-
if (!sourceCRS || !displayCRS) {
|
|
3352
|
-
return false;
|
|
3353
|
-
}
|
|
3354
|
-
if (internal.crsAreEqual(sourceCRS, displayCRS)) {
|
|
3355
|
-
return false;
|
|
3356
|
-
}
|
|
3357
|
-
return true;
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
|
-
function projectArcsForDisplay(arcs, src, dest) {
|
|
3361
|
-
var copy = arcs.getCopy(); // need to flatten first?
|
|
3362
|
-
var destIsWebMerc = internal.isWebMercator(dest);
|
|
3363
|
-
if (destIsWebMerc && internal.isWebMercator(src)) {
|
|
3364
|
-
return copy;
|
|
3365
|
-
}
|
|
3366
|
-
|
|
3367
|
-
var wgs84 = internal.parseCrsString('wgs84');
|
|
3368
|
-
var toWGS84 = internal.getProjTransform2(src, wgs84);
|
|
3369
|
-
var fromWGS84 = internal.getProjTransform2(wgs84, dest);
|
|
3370
|
-
|
|
3371
|
-
try {
|
|
3372
|
-
// first try projectArcs() -- it's fast and preserves arc ids
|
|
3373
|
-
// (so vertex editing doesn't break)
|
|
3374
|
-
if (!internal.isWGS84(src)) {
|
|
3375
|
-
// use wgs84 as a pivot CRS, so we can handle polar coordinates
|
|
3376
|
-
// that can't be projected to Mercator
|
|
3377
|
-
internal.projectArcs(copy, toWGS84);
|
|
3378
|
-
}
|
|
3379
|
-
if (destIsWebMerc) {
|
|
3380
|
-
// handle polar points by clamping them to they will project
|
|
3381
|
-
// (downside: may cause unexpected behavior when editing vertices interactively)
|
|
3382
|
-
clampY(copy);
|
|
3383
|
-
}
|
|
3384
|
-
internal.projectArcs(copy, fromWGS84);
|
|
3385
|
-
} catch(e) {
|
|
3386
|
-
console.error(e);
|
|
3387
|
-
// use the more robust projectArcs2 if projectArcs throws an error
|
|
3388
|
-
// downside: projectArcs2 discards Z values and changes arc indexing,
|
|
3389
|
-
// which will break vertex editing.
|
|
3390
|
-
var reproject = internal.getProjTransform2(src, dest);
|
|
3391
|
-
copy = arcs.getCopy();
|
|
3392
|
-
internal.projectArcs2(copy, reproject);
|
|
3393
|
-
}
|
|
3394
|
-
return copy;
|
|
3395
|
-
}
|
|
3396
|
-
|
|
3397
|
-
function clampY(arcs) {
|
|
3398
|
-
var max = 89.9,
|
|
3399
|
-
min = -89.9,
|
|
3400
|
-
bbox = arcs.getBounds().toArray();
|
|
3401
|
-
if (bbox[1] >= min && bbox[3] <= max) return;
|
|
3402
|
-
arcs.transformPoints(function(x, y) {
|
|
3403
|
-
if (y > max) return [x, max];
|
|
3404
|
-
if (y < min) return [x, min];
|
|
3405
|
-
});
|
|
3406
|
-
}
|
|
3407
|
-
|
|
3408
|
-
function projectPointsForDisplay(lyr, src, dest) {
|
|
3409
|
-
var copy = utils$1.extend({}, lyr);
|
|
3410
|
-
var proj = internal.getProjTransform2(src, dest);
|
|
3411
|
-
copy.shapes = internal.cloneShapes(lyr.shapes);
|
|
3412
|
-
internal.projectPointLayer(copy, proj);
|
|
3413
|
-
return copy;
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
function toWebMercator(lng, lat) {
|
|
3418
|
-
var k = Math.cos(lat * D2R);
|
|
3419
|
-
var x = R * lng * D2R;
|
|
3420
|
-
var y = R * Math.log(Math.tan(Math.PI * 0.25 + lat * D2R * 0.5));
|
|
3421
|
-
return [x, y];
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
function fromWebMercator(x, y) {
|
|
3425
|
-
var lon = x / R * R2D;
|
|
3426
|
-
var lat = R2D * (Math.PI * 0.5 - 2 * Math.atan(Math.exp(-y / R)));
|
|
3427
|
-
return [lon, lat];
|
|
3428
|
-
}
|
|
3429
|
-
|
|
3430
|
-
function scaleToZoom(metersPerPix) {
|
|
3431
|
-
return Math.log(40075017 / 512 / metersPerPix) / Math.log(2);
|
|
3432
|
-
}
|
|
3433
|
-
|
|
3434
|
-
function getMapboxBounds() {
|
|
3435
|
-
var ymax = toWebMercator(0, 84)[1];
|
|
3436
|
-
var ymin = toWebMercator(0, -84)[1];
|
|
3437
|
-
return [-Infinity, ymin, Infinity, ymax];
|
|
3438
|
-
}
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
// Update map extent and trigger redraw, after a new display CRS has been applied
|
|
3442
|
-
function projectMapExtent(ext, src, dest, newBounds) {
|
|
3443
|
-
var oldBounds = ext.getBounds();
|
|
3444
|
-
var oldScale = ext.scale();
|
|
3445
|
-
var newCP, proj, strictBounds;
|
|
3446
|
-
|
|
3447
|
-
if (dest && internal.isWebMercator(dest)) {
|
|
3448
|
-
// clampToMapboxBounds(newBounds);
|
|
3449
|
-
strictBounds = getMapboxBounds();
|
|
3450
|
-
}
|
|
3451
|
-
|
|
3452
|
-
// if source or destination CRS is unknown, show full extent
|
|
3453
|
-
// if map is at full extent, show full extent
|
|
3454
|
-
// TODO: handle case that scale is 1 and map is panned away from center
|
|
3455
|
-
if (ext.scale() == 1 || !dest) {
|
|
3456
|
-
ext.setFullBounds(newBounds, strictBounds);
|
|
3457
|
-
ext.home(); // sets full extent and triggers redraw
|
|
3458
|
-
} else {
|
|
3459
|
-
// if map is zoomed, stay centered on the same geographic location, at the same relative scale
|
|
3460
|
-
proj = internal.getProjTransform2(src, dest);
|
|
3461
|
-
newCP = proj(oldBounds.centerX(), oldBounds.centerY());
|
|
3462
|
-
ext.setFullBounds(newBounds, strictBounds);
|
|
3463
|
-
if (!newCP) {
|
|
3464
|
-
// projection of center point failed; use center of bounds
|
|
3465
|
-
// (also consider just resetting the view using ext.home())
|
|
3466
|
-
newCP = [newBounds.centerX(), newBounds.centerY()];
|
|
3467
|
-
}
|
|
3468
|
-
ext.recenter(newCP[0], newCP[1], oldScale);
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
|
|
3472
|
-
// Called from console; for testing dynamic crs
|
|
3473
|
-
function setDisplayProjection(gui, cmd) {
|
|
3474
|
-
var arg = cmd.replace(/^projd[ ]*/, '');
|
|
3475
|
-
if (arg) {
|
|
3476
|
-
gui.map.setDisplayCRS(internal.parseCrsString(arg));
|
|
3477
|
-
} else {
|
|
3478
|
-
gui.map.setDisplayCRS(null);
|
|
3793
|
+
function updateSliderDisplay() {
|
|
3794
|
+
// TODO: display resolution and vertex count
|
|
3795
|
+
// var dataset = model.getActiveLayer().dataset;
|
|
3796
|
+
// var interval = dataset.arcs.getRetainedInterval();
|
|
3479
3797
|
}
|
|
3480
|
-
}
|
|
3798
|
+
};
|
|
3481
3799
|
|
|
3482
3800
|
function Console(gui) {
|
|
3483
3801
|
var model = gui.model;
|
|
@@ -3547,7 +3865,8 @@
|
|
|
3547
3865
|
}
|
|
3548
3866
|
|
|
3549
3867
|
function turnOn() {
|
|
3550
|
-
if (!_isOpen && !model.isEmpty()) {
|
|
3868
|
+
// if (!_isOpen && !model.isEmpty()) {
|
|
3869
|
+
if (!_isOpen) {
|
|
3551
3870
|
btn.addClass('active');
|
|
3552
3871
|
_isOpen = true;
|
|
3553
3872
|
// use console for messages while open
|
|
@@ -3886,8 +4205,8 @@
|
|
|
3886
4205
|
|
|
3887
4206
|
function applyParsedCommands(commands, done) {
|
|
3888
4207
|
var active = model.getActiveLayer(),
|
|
3889
|
-
prevArcs = active
|
|
3890
|
-
prevTable = active
|
|
4208
|
+
prevArcs = active?.dataset.arcs,
|
|
4209
|
+
prevTable = active?.layer.data,
|
|
3891
4210
|
prevTableSize = prevTable ? prevTable.size() : 0,
|
|
3892
4211
|
prevArcCount = prevArcs ? prevArcs.size() : 0,
|
|
3893
4212
|
job = new internal.Job(model);
|
|
@@ -3896,9 +4215,9 @@
|
|
|
3896
4215
|
internal.runParsedCommands(commands, job, function(err) {
|
|
3897
4216
|
var flags = getCommandFlags(commands),
|
|
3898
4217
|
active2 = model.getActiveLayer(),
|
|
3899
|
-
postArcs = active2
|
|
4218
|
+
postArcs = active2?.dataset.arcs,
|
|
3900
4219
|
postArcCount = postArcs ? postArcs.size() : 0,
|
|
3901
|
-
postTable = active2
|
|
4220
|
+
postTable = active2?.layer.data,
|
|
3902
4221
|
postTableSize = postTable ? postTable.size() : 0,
|
|
3903
4222
|
sameTable = prevTable == postTable && prevTableSize == postTableSize,
|
|
3904
4223
|
sameArcs = prevArcs == postArcs && postArcCount == prevArcCount;
|
|
@@ -3911,7 +4230,7 @@
|
|
|
3911
4230
|
if (sameTable) {
|
|
3912
4231
|
flags.same_table = true;
|
|
3913
4232
|
}
|
|
3914
|
-
if (active
|
|
4233
|
+
if (active && active?.layer == active2?.layer) {
|
|
3915
4234
|
// this can get set after some commands that don't set a new target
|
|
3916
4235
|
// (e.g. -dissolve)
|
|
3917
4236
|
flags.select = true;
|
|
@@ -3926,10 +4245,10 @@
|
|
|
3926
4245
|
function onError(err) {
|
|
3927
4246
|
if (utils$1.isString(err)) {
|
|
3928
4247
|
consoleStop(err);
|
|
3929
|
-
} else if (err.name == 'UserError') {
|
|
4248
|
+
} else if (err.name == 'UserError' ) {
|
|
3930
4249
|
// stop() has already been called, don't need to log
|
|
4250
|
+
console.error(err.stack);
|
|
3931
4251
|
} else if (err.name) {
|
|
3932
|
-
// log stack trace to browser console
|
|
3933
4252
|
console.error(err.stack);
|
|
3934
4253
|
// log to console window
|
|
3935
4254
|
consoleWarning(err.message);
|
|
@@ -4112,6 +4431,7 @@
|
|
|
4112
4431
|
return;
|
|
4113
4432
|
}
|
|
4114
4433
|
var e = model.getActiveLayer();
|
|
4434
|
+
if (!e) return;
|
|
4115
4435
|
if (internal.layerHasPaths(e.layer)) {
|
|
4116
4436
|
el.show();
|
|
4117
4437
|
checkBtn.show();
|
|
@@ -4190,45 +4510,9 @@
|
|
|
4190
4510
|
|
|
4191
4511
|
utils$1.inherit(RepairControl, EventDispatcher);
|
|
4192
4512
|
|
|
4193
|
-
function
|
|
4194
|
-
var
|
|
4195
|
-
|
|
4196
|
-
shapes = ids.map(function(id) {
|
|
4197
|
-
return lyr.shapes[id];
|
|
4198
|
-
});
|
|
4199
|
-
return utils$1.defaults({shapes: shapes, data: null}, lyr);
|
|
4200
|
-
}
|
|
4201
|
-
return lyr;
|
|
4202
|
-
}
|
|
4203
|
-
|
|
4204
|
-
function formatLayerNameForDisplay(name) {
|
|
4205
|
-
return name || '[unnamed]';
|
|
4206
|
-
}
|
|
4207
|
-
|
|
4208
|
-
function cleanLayerName(raw) {
|
|
4209
|
-
return raw.replace(/[\n\t/\\]/g, '')
|
|
4210
|
-
.replace(/^[.\s]+/, '').replace(/[.\s]+$/, '');
|
|
4211
|
-
}
|
|
4212
|
-
|
|
4213
|
-
function updateLayerStackOrder(layers) {
|
|
4214
|
-
// 1. assign ascending ids to unassigned layers above the range of other layers
|
|
4215
|
-
layers.forEach(function(o, i) {
|
|
4216
|
-
if (!o.layer.menu_order) o.layer.menu_order = 1e6 + i;
|
|
4217
|
-
});
|
|
4218
|
-
// 2. sort in ascending order
|
|
4219
|
-
layers.sort(function(a, b) {
|
|
4220
|
-
return a.layer.menu_order - b.layer.menu_order;
|
|
4221
|
-
});
|
|
4222
|
-
// 3. assign consecutve ids
|
|
4223
|
-
layers.forEach(function(o, i) {
|
|
4224
|
-
o.layer.menu_order = i + 1;
|
|
4225
|
-
});
|
|
4226
|
-
return layers;
|
|
4227
|
-
}
|
|
4228
|
-
|
|
4229
|
-
function sortLayersForMenuDisplay(layers) {
|
|
4230
|
-
layers = updateLayerStackOrder(layers);
|
|
4231
|
-
return layers.reverse();
|
|
4513
|
+
async function saveFileContentToClipboard(content) {
|
|
4514
|
+
var str = utils$1.isString(content) ? content : content.toString();
|
|
4515
|
+
await navigator.clipboard.writeText(str);
|
|
4232
4516
|
}
|
|
4233
4517
|
|
|
4234
4518
|
// Export buttons and their behavior
|
|
@@ -4238,7 +4522,7 @@
|
|
|
4238
4522
|
var menu = gui.container.findChild('.export-options').on('click', GUI.handleDirectEvent(gui.clearMode));
|
|
4239
4523
|
var layersArr = [];
|
|
4240
4524
|
var toggleBtn = null; // checkbox <input> for toggling layer selection
|
|
4241
|
-
var exportBtn = gui.container.findChild('.export-btn');
|
|
4525
|
+
var exportBtn = gui.container.findChild('.export-btn').addClass('disabled');
|
|
4242
4526
|
var ofileName = gui.container.findChild('#ofile-name');
|
|
4243
4527
|
new SimpleButton(menu.findChild('.close2-btn')).on('click', gui.clearMode);
|
|
4244
4528
|
|
|
@@ -4253,6 +4537,10 @@
|
|
|
4253
4537
|
return;
|
|
4254
4538
|
}
|
|
4255
4539
|
|
|
4540
|
+
model.on('update', function() {
|
|
4541
|
+
exportBtn.classed('disabled', !model.getActiveLayer());
|
|
4542
|
+
});
|
|
4543
|
+
|
|
4256
4544
|
new SimpleButton(menu.findChild('#export-btn').addClass('default-btn')).on('click', onExportClick);
|
|
4257
4545
|
gui.addMode('export', turnOn, turnOff, exportBtn);
|
|
4258
4546
|
gui.keyboard.onMenuSubmit(menu, onExportClick);
|
|
@@ -4374,12 +4662,6 @@
|
|
|
4374
4662
|
} else {
|
|
4375
4663
|
await utils$1.promisify(internal.writeFiles)(files, opts);
|
|
4376
4664
|
}
|
|
4377
|
-
|
|
4378
|
-
}
|
|
4379
|
-
|
|
4380
|
-
async function saveFileContentToClipboard(content) {
|
|
4381
|
-
var str = utils$1.isString(content) ? content : content.toString();
|
|
4382
|
-
await navigator.clipboard.writeText(str);
|
|
4383
4665
|
}
|
|
4384
4666
|
|
|
4385
4667
|
function initLayerItem(o, i) {
|
|
@@ -4589,6 +4871,14 @@
|
|
|
4589
4871
|
var layerOrderSlug;
|
|
4590
4872
|
|
|
4591
4873
|
gui.addMode('layer_menu', turnOn, turnOff, btn.findChild('.header-btn'));
|
|
4874
|
+
|
|
4875
|
+
// kludge to show menu button after initial import dialog is dismissed
|
|
4876
|
+
gui.on('mode', function(e) {
|
|
4877
|
+
if (!e.name) {
|
|
4878
|
+
updateMenuBtn();
|
|
4879
|
+
}
|
|
4880
|
+
});
|
|
4881
|
+
|
|
4592
4882
|
model.on('update', function(e) {
|
|
4593
4883
|
updateMenuBtn();
|
|
4594
4884
|
if (isOpen) render();
|
|
@@ -4679,8 +4969,9 @@
|
|
|
4679
4969
|
}
|
|
4680
4970
|
|
|
4681
4971
|
function updateMenuBtn() {
|
|
4682
|
-
var
|
|
4683
|
-
var
|
|
4972
|
+
var lyr = model.getActiveLayer()?.layer;
|
|
4973
|
+
var lyrName = lyr?.name || '';
|
|
4974
|
+
var menuTitle = lyrName || lyr && '[unnamed layer]' || '[no data]';
|
|
4684
4975
|
var pageTitle = lyrName || 'mapshaper';
|
|
4685
4976
|
btn.classed('active', 'true').findChild('.layer-name').html(menuTitle + " ▼");
|
|
4686
4977
|
window.document.title = pageTitle;
|
|
@@ -4692,6 +4983,8 @@
|
|
|
4692
4983
|
}
|
|
4693
4984
|
|
|
4694
4985
|
function renderSourceFileList() {
|
|
4986
|
+
el.findChild('.no-layer-note').classed('hidden', model.getActiveLayer());
|
|
4987
|
+
el.findChild('.source-file-section').classed('hidden', !model.getActiveLayer());
|
|
4695
4988
|
var list = el.findChild('.file-list');
|
|
4696
4989
|
var files = [];
|
|
4697
4990
|
list.empty();
|
|
@@ -5209,6 +5502,16 @@
|
|
|
5209
5502
|
addHistoryState(undo, redo);
|
|
5210
5503
|
});
|
|
5211
5504
|
|
|
5505
|
+
gui.on('point_add', function(e) {
|
|
5506
|
+
var redo = function() {
|
|
5507
|
+
appendNewPoint(e.data.target, e.p);
|
|
5508
|
+
};
|
|
5509
|
+
var undo = function() {
|
|
5510
|
+
deleteLastPoint(e.data.target);
|
|
5511
|
+
};
|
|
5512
|
+
addHistoryState(undo, redo);
|
|
5513
|
+
});
|
|
5514
|
+
|
|
5212
5515
|
gui.on('path_add', function(e) {
|
|
5213
5516
|
var redo = function() {
|
|
5214
5517
|
gui.dispatchEvent('redo_path_add', {p1: e.p1, p2: e.p2});
|
|
@@ -5277,6 +5580,7 @@
|
|
|
5277
5580
|
var _hidden = true;
|
|
5278
5581
|
gui.on('active', updateVisibility);
|
|
5279
5582
|
gui.on('inactive', updateVisibility);
|
|
5583
|
+
gui.model.on('update', updateVisibility);
|
|
5280
5584
|
|
|
5281
5585
|
// @iconRef: selector for an (svg) button icon
|
|
5282
5586
|
this.addButton = function(iconRef) {
|
|
@@ -5305,7 +5609,7 @@
|
|
|
5305
5609
|
};
|
|
5306
5610
|
|
|
5307
5611
|
function updateVisibility() {
|
|
5308
|
-
if (GUI.isActiveInstance(gui) && !_hidden) {
|
|
5612
|
+
if (GUI.isActiveInstance(gui) && !_hidden && !!gui.model.getActiveLayer()) {
|
|
5309
5613
|
buttons.show();
|
|
5310
5614
|
} else {
|
|
5311
5615
|
buttons.hide();
|
|
@@ -5326,6 +5630,7 @@
|
|
|
5326
5630
|
});
|
|
5327
5631
|
|
|
5328
5632
|
btn.on('click', function() {
|
|
5633
|
+
if (btn.hasClass('disabled')) return;
|
|
5329
5634
|
modes.enterMode(active ? null : name);
|
|
5330
5635
|
});
|
|
5331
5636
|
}
|
|
@@ -5397,19 +5702,25 @@
|
|
|
5397
5702
|
function KeyboardEvents(gui) {
|
|
5398
5703
|
var self = this;
|
|
5399
5704
|
var shiftDown = false;
|
|
5705
|
+
var ctrlDown = false;
|
|
5400
5706
|
document.addEventListener('keyup', function(e) {
|
|
5401
5707
|
if (!GUI.isActiveInstance(gui)) return;
|
|
5402
|
-
if
|
|
5708
|
+
// this can fail to fire if keyup occurs over a context menu
|
|
5709
|
+
// if (e.keyCode == 16) shiftDown = false;
|
|
5710
|
+
shiftDown = e.shiftKey;
|
|
5711
|
+
if (e.keyCode == 17) ctrlDown = false;
|
|
5403
5712
|
self.dispatchEvent('keyup', getEventData(e));
|
|
5404
5713
|
});
|
|
5405
5714
|
|
|
5406
5715
|
document.addEventListener('keydown', function(e) {
|
|
5407
5716
|
if (!GUI.isActiveInstance(gui)) return;
|
|
5408
|
-
|
|
5717
|
+
shiftDown = e.shiftKey;
|
|
5718
|
+
if (e.keyCode == 17) ctrlDown = true;
|
|
5409
5719
|
self.dispatchEvent('keydown', getEventData(e));
|
|
5410
5720
|
});
|
|
5411
5721
|
|
|
5412
5722
|
this.shiftIsPressed = function() { return shiftDown; };
|
|
5723
|
+
this.ctrlIsPressed = function() { return ctrlDown; };
|
|
5413
5724
|
|
|
5414
5725
|
this.onMenuSubmit = function(menuEl, cb) {
|
|
5415
5726
|
gui.on('enter_key', function(e) {
|
|
@@ -5426,6 +5737,7 @@
|
|
|
5426
5737
|
9: 'tab',
|
|
5427
5738
|
13: 'enter',
|
|
5428
5739
|
16: 'shift',
|
|
5740
|
+
17: 'ctrl',
|
|
5429
5741
|
27: 'esc',
|
|
5430
5742
|
32: 'space',
|
|
5431
5743
|
37: 'left',
|
|
@@ -5447,7 +5759,7 @@
|
|
|
5447
5759
|
var menus = {
|
|
5448
5760
|
standard: ['info', 'selection', 'box'],
|
|
5449
5761
|
polygons: ['info', 'selection', 'box', 'drawing'],
|
|
5450
|
-
rectangles: ['info', 'selection', 'box', 'rectangles'],
|
|
5762
|
+
rectangles: ['info', 'selection', 'box', 'rectangles'],
|
|
5451
5763
|
lines: ['info', 'selection', 'box' , 'drawing'],
|
|
5452
5764
|
table: ['info', 'selection'],
|
|
5453
5765
|
labels: ['info', 'selection', 'box', 'labels', 'location', 'add-points'],
|
|
@@ -5468,7 +5780,7 @@
|
|
|
5468
5780
|
labels: 'position labels',
|
|
5469
5781
|
location: 'drag points',
|
|
5470
5782
|
vertices: 'edit vertices',
|
|
5471
|
-
selection: '
|
|
5783
|
+
selection: 'selection tool',
|
|
5472
5784
|
'add-points': 'add points',
|
|
5473
5785
|
drawing: 'draw/reshape tool',
|
|
5474
5786
|
rectangles: 'drag-to-resize',
|
|
@@ -7261,11 +7573,11 @@
|
|
|
7261
7573
|
|
|
7262
7574
|
// featureFilter: optional test function, accepts feature id
|
|
7263
7575
|
//
|
|
7264
|
-
function getShapeHitTest(
|
|
7265
|
-
var geoType = displayLayer.
|
|
7576
|
+
function getShapeHitTest(layer, ext, interactionMode, featureFilter) {
|
|
7577
|
+
var geoType = layer.gui.displayLayer.geometry_type;
|
|
7266
7578
|
var test;
|
|
7267
|
-
if (geoType == 'point' &&
|
|
7268
|
-
test = getGraduatedCircleTest(getRadiusFunction(
|
|
7579
|
+
if (geoType == 'point' && layer.gui.style.type == 'styled') {
|
|
7580
|
+
test = getGraduatedCircleTest(getRadiusFunction(layer.gui.style));
|
|
7269
7581
|
} else if (geoType == 'point') {
|
|
7270
7582
|
test = pointTest;
|
|
7271
7583
|
} else if (interactionMode == 'drawing' && geoType == 'polygon') {
|
|
@@ -7305,13 +7617,13 @@
|
|
|
7305
7617
|
cand, hitId;
|
|
7306
7618
|
for (var i=0; i<cands.length; i++) {
|
|
7307
7619
|
cand = cands[i];
|
|
7308
|
-
if (geom.testPointInPolygon(x, y, cand.shape,
|
|
7620
|
+
if (geom.testPointInPolygon(x, y, cand.shape, layer.gui.displayArcs)) {
|
|
7309
7621
|
hits.push(cand);
|
|
7310
7622
|
}
|
|
7311
7623
|
}
|
|
7312
7624
|
if (cands.length > 0 && hits.length === 0) {
|
|
7313
7625
|
// secondary detection: proximity, if not inside a polygon
|
|
7314
|
-
sortByDistance(x, y, cands,
|
|
7626
|
+
sortByDistance(x, y, cands, layer.gui.displayArcs);
|
|
7315
7627
|
hits = pickNearestCandidates(cands, 0, maxDist);
|
|
7316
7628
|
}
|
|
7317
7629
|
return {
|
|
@@ -7336,7 +7648,7 @@
|
|
|
7336
7648
|
bufPix = bufArg >= 0 ? bufArg : 0.05, // tiny threshold for hitting almost-identical lines
|
|
7337
7649
|
bufDist = getZoomAdjustedHitBuffer(bufPix),
|
|
7338
7650
|
cands = findHitCandidates(x, y, maxDist);
|
|
7339
|
-
sortByDistance(x, y, cands,
|
|
7651
|
+
sortByDistance(x, y, cands, layer.gui.displayArcs);
|
|
7340
7652
|
cands = pickNearestCandidates(cands, bufDist, maxDist);
|
|
7341
7653
|
return {
|
|
7342
7654
|
ids: utils$1.pluck(cands, 'id')
|
|
@@ -7377,7 +7689,7 @@
|
|
|
7377
7689
|
hits = [];
|
|
7378
7690
|
|
|
7379
7691
|
// inlining forEachPoint() does not not appreciably speed this up
|
|
7380
|
-
internal.forEachPoint(displayLayer.
|
|
7692
|
+
internal.forEachPoint(layer.gui.displayLayer.shapes, function(p, id) {
|
|
7381
7693
|
var dist = geom.distance2D(x, y, p[0], p[1]) * toPx;
|
|
7382
7694
|
if (dist > hitThreshold) return;
|
|
7383
7695
|
if (dist < hitThreshold && hitThreshold > bullseyeDist) {
|
|
@@ -7411,7 +7723,7 @@
|
|
|
7411
7723
|
directHit = false,
|
|
7412
7724
|
hitRadius = 0,
|
|
7413
7725
|
hitDist;
|
|
7414
|
-
internal.forEachPoint(displayLayer.
|
|
7726
|
+
internal.forEachPoint(layer.gui.displayLayer.shapes, function(p, id) {
|
|
7415
7727
|
var distSq = geom.distanceSq(x, y, p[0], p[1]);
|
|
7416
7728
|
var isHit = false;
|
|
7417
7729
|
var isOver, isNear, r, d, rpix;
|
|
@@ -7451,11 +7763,11 @@
|
|
|
7451
7763
|
|
|
7452
7764
|
// Returns array of shape ids for shapes that pass a buffered bounding-box test
|
|
7453
7765
|
function findHitCandidates(x, y, dist) {
|
|
7454
|
-
var arcs =
|
|
7766
|
+
var arcs = layer.gui.displayArcs,
|
|
7455
7767
|
index = {},
|
|
7456
7768
|
cands = [],
|
|
7457
7769
|
bbox = [];
|
|
7458
|
-
displayLayer.
|
|
7770
|
+
layer.gui.displayLayer.shapes.forEach(function(shp, shpId) {
|
|
7459
7771
|
var cand;
|
|
7460
7772
|
if (featureFilter && !featureFilter(shpId)) {
|
|
7461
7773
|
return;
|
|
@@ -7587,7 +7899,7 @@
|
|
|
7587
7899
|
|
|
7588
7900
|
function getPointerHitTest(mapLayer, ext, interactionMode, featureFilter) {
|
|
7589
7901
|
var shapeTest, targetLayer;
|
|
7590
|
-
if (!mapLayer || !internal.layerHasGeometry(mapLayer.
|
|
7902
|
+
if (!mapLayer || !internal.layerHasGeometry(mapLayer.gui?.displayLayer)) {
|
|
7591
7903
|
return function() {return {ids: []};};
|
|
7592
7904
|
}
|
|
7593
7905
|
shapeTest = getShapeHitTest(mapLayer, ext, interactionMode, featureFilter);
|
|
@@ -7626,6 +7938,16 @@
|
|
|
7626
7938
|
// pan navigation
|
|
7627
7939
|
var priority = 2;
|
|
7628
7940
|
|
|
7941
|
+
mouse.on('contextmenu', function(e) {
|
|
7942
|
+
// shift key enables default menu (for development)
|
|
7943
|
+
if (gui.keyboard.shiftIsPressed()) {
|
|
7944
|
+
return;
|
|
7945
|
+
}
|
|
7946
|
+
e.originalEvent.preventDefault();
|
|
7947
|
+
if (!targetLayer) return; // TODO: enable menu on empty map
|
|
7948
|
+
triggerHitEvent('contextmenu', e);
|
|
7949
|
+
}, false);
|
|
7950
|
+
|
|
7629
7951
|
// init keyboard controls for pinned features
|
|
7630
7952
|
gui.keyboard.on('keydown', function(evt) {
|
|
7631
7953
|
var e = evt.originalEvent;
|
|
@@ -7653,7 +7975,7 @@
|
|
|
7653
7975
|
// to help protect against inadvertent deletion, don't delete
|
|
7654
7976
|
// when console is open or a popup menu is open
|
|
7655
7977
|
if (!gui.getMode() && !gui.consoleIsOpen()) {
|
|
7656
|
-
internal.deleteFeatureById(targetLayer
|
|
7978
|
+
internal.deleteFeatureById(targetLayer, pinnedId());
|
|
7657
7979
|
self.clearSelection();
|
|
7658
7980
|
gui.model.updated({flags: 'filter'}); // signal map to update
|
|
7659
7981
|
}
|
|
@@ -7666,7 +7988,6 @@
|
|
|
7666
7988
|
};
|
|
7667
7989
|
|
|
7668
7990
|
function updateHitTest(featureFilter) {
|
|
7669
|
-
if (!hoverable()) return;
|
|
7670
7991
|
hitTest = getPointerHitTest(targetLayer, ext, interactionMode, featureFilter);
|
|
7671
7992
|
}
|
|
7672
7993
|
|
|
@@ -7687,7 +8008,7 @@
|
|
|
7687
8008
|
}
|
|
7688
8009
|
|
|
7689
8010
|
function hoverable() {
|
|
7690
|
-
return
|
|
8011
|
+
return !!interactionMode;
|
|
7691
8012
|
}
|
|
7692
8013
|
|
|
7693
8014
|
function selectable() {
|
|
@@ -7696,6 +8017,7 @@
|
|
|
7696
8017
|
|
|
7697
8018
|
function pinnable() {
|
|
7698
8019
|
return clickable() && !selectable();
|
|
8020
|
+
// return clickable();
|
|
7699
8021
|
}
|
|
7700
8022
|
|
|
7701
8023
|
function draggable() {
|
|
@@ -7790,7 +8112,7 @@
|
|
|
7790
8112
|
|
|
7791
8113
|
self.getTargetDataTable = function() {
|
|
7792
8114
|
var targ = self.getHitTarget();
|
|
7793
|
-
return targ
|
|
8115
|
+
return targ?.data || null;
|
|
7794
8116
|
};
|
|
7795
8117
|
|
|
7796
8118
|
// get function for selecting next or prev feature within the current set of
|
|
@@ -7803,7 +8125,7 @@
|
|
|
7803
8125
|
|
|
7804
8126
|
// diff: 1 or -1
|
|
7805
8127
|
function advanceSelectedFeature(diff) {
|
|
7806
|
-
var n = internal.getFeatureCount(targetLayer
|
|
8128
|
+
var n = internal.getFeatureCount(targetLayer);
|
|
7807
8129
|
if (n < 2 || pinnedId() == -1) return;
|
|
7808
8130
|
storedData.id = (pinnedId() + n + diff) % n;
|
|
7809
8131
|
storedData.ids = [storedData.id];
|
|
@@ -7848,17 +8170,21 @@
|
|
|
7848
8170
|
|
|
7849
8171
|
mouse.on('click', function(e) {
|
|
7850
8172
|
if (!hitTest || !active) return;
|
|
8173
|
+
if (!eventIsEnabled('click')) return;
|
|
8174
|
+
|
|
7851
8175
|
e.stopPropagation();
|
|
7852
8176
|
|
|
7853
8177
|
// TODO: move pinning to inspection control?
|
|
7854
8178
|
if (clickable()) {
|
|
7855
8179
|
updateSelectionState(convertClickDataToSelectionData(hitTest(e)));
|
|
7856
8180
|
}
|
|
8181
|
+
|
|
7857
8182
|
triggerHitEvent('click', e);
|
|
7858
8183
|
}, null, priority);
|
|
7859
8184
|
|
|
7860
8185
|
// Hits are re-detected on 'hover' (if hit detection is active)
|
|
7861
8186
|
mouse.on('hover', function(e) {
|
|
8187
|
+
if (gui.contextMenu.isOpen()) return;
|
|
7862
8188
|
handlePointerEvent(e);
|
|
7863
8189
|
if (storedData.pinned || !hitTest || !active) return;
|
|
7864
8190
|
if (e.hover && isOverMap(e)) {
|
|
@@ -7872,6 +8198,7 @@
|
|
|
7872
8198
|
}
|
|
7873
8199
|
}, null, priority);
|
|
7874
8200
|
|
|
8201
|
+
|
|
7875
8202
|
function targetIsRollover(target) {
|
|
7876
8203
|
while (target.parentNode && target != target.parentNode) {
|
|
7877
8204
|
if (target.className && String(target.className).indexOf('rollover') > -1) {
|
|
@@ -7910,6 +8237,19 @@
|
|
|
7910
8237
|
return hitData;
|
|
7911
8238
|
}
|
|
7912
8239
|
|
|
8240
|
+
function mergeSelectionModeHoverData(hitData) {
|
|
8241
|
+
if (hitData.ids.length === 0 || selectionIds.includes(hitData.ids[0])) {
|
|
8242
|
+
hitData.ids = selectionIds;
|
|
8243
|
+
hitData.pinned = storedData.pinned;
|
|
8244
|
+
} else {
|
|
8245
|
+
//
|
|
8246
|
+
}
|
|
8247
|
+
|
|
8248
|
+
// kludge to inhibit hover effect while dragging a box
|
|
8249
|
+
if (gui.keydown) hitData.id = -1;
|
|
8250
|
+
return hitData;
|
|
8251
|
+
}
|
|
8252
|
+
|
|
7913
8253
|
function mergeHoverData(hitData) {
|
|
7914
8254
|
if (storedData.pinned) {
|
|
7915
8255
|
hitData.id = storedData.id;
|
|
@@ -7960,6 +8300,16 @@
|
|
|
7960
8300
|
// check if an event is used in the current interaction mode
|
|
7961
8301
|
function eventIsEnabled(type) {
|
|
7962
8302
|
if (!active) return false;
|
|
8303
|
+
if (type == 'click' && gui.keyboard.ctrlIsPressed()) {
|
|
8304
|
+
return false; // don't fire if context menu might open
|
|
8305
|
+
}
|
|
8306
|
+
if (type == 'click' && gui.contextMenu.isOpen()) {
|
|
8307
|
+
return false;
|
|
8308
|
+
}
|
|
8309
|
+
if (type == 'click' && interactionMode == 'drawing') {
|
|
8310
|
+
return true; // click events are triggered even if no shape is hit
|
|
8311
|
+
}
|
|
8312
|
+
|
|
7963
8313
|
if (interactionMode == 'drawing' && (type == 'hover' || type == 'dblclick')) {
|
|
7964
8314
|
return true; // special case -- using hover for line drawing animation
|
|
7965
8315
|
}
|
|
@@ -7969,9 +8319,6 @@
|
|
|
7969
8319
|
var hitId = self.getHitId();
|
|
7970
8320
|
if (hitId == -1) return false;
|
|
7971
8321
|
|
|
7972
|
-
if (type == 'click' && !clickable()) {
|
|
7973
|
-
return false;
|
|
7974
|
-
}
|
|
7975
8322
|
if ((type == 'drag' || type == 'dragstart' || type == 'dragend') && !draggable()) {
|
|
7976
8323
|
return false;
|
|
7977
8324
|
}
|
|
@@ -8000,10 +8347,13 @@
|
|
|
8000
8347
|
// evt: event data (may be a pointer event object, an ordinary object or null)
|
|
8001
8348
|
function triggerHitEvent(type, evt) {
|
|
8002
8349
|
var eventData = {
|
|
8003
|
-
mode: interactionMode
|
|
8004
|
-
overMap: evt ? isOverMap(evt) : null,
|
|
8005
|
-
originalEvent: evt ? evt : null
|
|
8350
|
+
mode: interactionMode
|
|
8006
8351
|
};
|
|
8352
|
+
if (evt) {
|
|
8353
|
+
eventData.coordinates = translateDisplayPoint(targetLayer, ext.translatePixelCoords(evt.x, evt.y));
|
|
8354
|
+
eventData.originalEvent = evt;
|
|
8355
|
+
eventData.overMap = isOverMap(evt);
|
|
8356
|
+
}
|
|
8007
8357
|
// Merge stored hit data into the event data
|
|
8008
8358
|
utils$1.defaults(eventData, evt && evt.data || {}, storedData);
|
|
8009
8359
|
// utils.extend(eventData, storedData);
|
|
@@ -8050,7 +8400,7 @@
|
|
|
8050
8400
|
|
|
8051
8401
|
function enabled() {
|
|
8052
8402
|
var lyr = gui.map.getActiveLayer();
|
|
8053
|
-
return !!(lyr && lyr.
|
|
8403
|
+
return !!(lyr && lyr.gui.displayLayer.geometry_type);
|
|
8054
8404
|
}
|
|
8055
8405
|
|
|
8056
8406
|
gui.model.on('update', function(e) {
|
|
@@ -8095,7 +8445,7 @@
|
|
|
8095
8445
|
var p2 = gui.map.translatePixelCoords(ext.width(), 0);
|
|
8096
8446
|
var bbox = p1.concat(p2);
|
|
8097
8447
|
var decimals = internal.getBoundsPrecisionForDisplay(bbox);
|
|
8098
|
-
|
|
8448
|
+
var str = internal.getRoundedCoordString(p, decimals);
|
|
8099
8449
|
readout.text(str).show();
|
|
8100
8450
|
}
|
|
8101
8451
|
|
|
@@ -8376,6 +8726,9 @@
|
|
|
8376
8726
|
element.addEventListener('mouseout', onAreaOut);
|
|
8377
8727
|
element.addEventListener('mousedown', onAreaDown);
|
|
8378
8728
|
element.addEventListener('dblclick', onAreaDblClick);
|
|
8729
|
+
document.addEventListener('contextmenu', function(e) {
|
|
8730
|
+
_self.dispatchEvent('contextmenu', procMouseEvent(e));
|
|
8731
|
+
});
|
|
8379
8732
|
|
|
8380
8733
|
this.enable = function() {
|
|
8381
8734
|
if (!_disabled) return;
|
|
@@ -8816,7 +9169,7 @@
|
|
|
8816
9169
|
}
|
|
8817
9170
|
|
|
8818
9171
|
gui.on('map_reset', function() {
|
|
8819
|
-
ext.
|
|
9172
|
+
ext.reset(true);
|
|
8820
9173
|
});
|
|
8821
9174
|
|
|
8822
9175
|
zoomTween.on('change', function(e) {
|
|
@@ -8834,8 +9187,9 @@
|
|
|
8834
9187
|
|
|
8835
9188
|
mouse.on('dragstart', function(e) {
|
|
8836
9189
|
if (disabled()) return;
|
|
8837
|
-
if
|
|
8838
|
-
//
|
|
9190
|
+
// allow drawing rectangles if active layer is empty
|
|
9191
|
+
// var lyr = gui.model.getActiveLayer()?.layer;
|
|
9192
|
+
// if (lyr && !internal.layerHasGeometry(lyr)) return;
|
|
8839
9193
|
shiftDrag = !!e.shiftKey;
|
|
8840
9194
|
if (shiftDrag) {
|
|
8841
9195
|
if (useBoxZoom()) zoomBox.turnOn();
|
|
@@ -8943,8 +9297,8 @@
|
|
|
8943
9297
|
gui.on('interaction_mode_change', function(e) {
|
|
8944
9298
|
if (e.mode === 'selection') {
|
|
8945
9299
|
gui.enterMode('selection_tool');
|
|
8946
|
-
} else if (
|
|
8947
|
-
|
|
9300
|
+
} else if (_on) {
|
|
9301
|
+
turnOff();
|
|
8948
9302
|
}
|
|
8949
9303
|
});
|
|
8950
9304
|
|
|
@@ -9410,8 +9764,17 @@
|
|
|
9410
9764
|
}
|
|
9411
9765
|
});
|
|
9412
9766
|
|
|
9767
|
+
hit.on('contextmenu', function(e) {
|
|
9768
|
+
var target = hit.getHitTarget();
|
|
9769
|
+
if (!e.overMap || !target) return;
|
|
9770
|
+
|
|
9771
|
+
if (e.mode == 'drawing') return; // TODO: make special menu for this mode
|
|
9772
|
+
gui.contextMenu.open(e, hit.getHitTarget());
|
|
9773
|
+
});
|
|
9774
|
+
|
|
9413
9775
|
hit.on('change', function(e) {
|
|
9414
9776
|
if (!inspecting()) return;
|
|
9777
|
+
if (gui.keyboard.ctrlIsPressed()) return;
|
|
9415
9778
|
var ids;
|
|
9416
9779
|
if (e.mode == 'selection') {
|
|
9417
9780
|
ids = e.pinned && e.ids || [];
|
|
@@ -9424,8 +9787,8 @@
|
|
|
9424
9787
|
// id: Id of a feature in the active layer, or -1
|
|
9425
9788
|
function inspect(id, ids, pin) {
|
|
9426
9789
|
var target = hit.getHitTarget();
|
|
9427
|
-
if ((id > -1 || ids && ids.length > 0) && inspecting() && target && target
|
|
9428
|
-
_popup.show(id, ids, target
|
|
9790
|
+
if ((id > -1 || ids && ids.length > 0) && inspecting() && target && target) {
|
|
9791
|
+
_popup.show(id, ids, target, pin);
|
|
9429
9792
|
} else {
|
|
9430
9793
|
_popup.hide();
|
|
9431
9794
|
}
|
|
@@ -9529,15 +9892,20 @@
|
|
|
9529
9892
|
|
|
9530
9893
|
function initLabelDragging(gui, ext, hit) {
|
|
9531
9894
|
var downEvt;
|
|
9532
|
-
var activeId;
|
|
9895
|
+
var activeId = -1;
|
|
9896
|
+
var prevHitEvt;
|
|
9533
9897
|
var activeRecord;
|
|
9534
9898
|
|
|
9535
|
-
function active(
|
|
9536
|
-
return
|
|
9899
|
+
function active() {
|
|
9900
|
+
return gui.interaction.getMode() == 'labels';
|
|
9901
|
+
}
|
|
9902
|
+
|
|
9903
|
+
function labelSelected(e) {
|
|
9904
|
+
return e.id > -1 && active();
|
|
9537
9905
|
}
|
|
9538
9906
|
|
|
9539
9907
|
hit.on('dragstart', function(e) {
|
|
9540
|
-
if (!
|
|
9908
|
+
if (!labelSelected(e)) return;
|
|
9541
9909
|
var symNode = getSymbolTarget(e);
|
|
9542
9910
|
var table = hit.getTargetDataTable();
|
|
9543
9911
|
if (!symNode || !table) {
|
|
@@ -9550,8 +9918,25 @@
|
|
|
9550
9918
|
gui.dispatchEvent('label_dragstart', {FID: activeId});
|
|
9551
9919
|
});
|
|
9552
9920
|
|
|
9921
|
+
hit.on('change', function(e) {
|
|
9922
|
+
if (!active()) return;
|
|
9923
|
+
if (prevHitEvt) clearLabelHighlight(prevHitEvt);
|
|
9924
|
+
showLabelHighlight(e);
|
|
9925
|
+
prevHitEvt = e;
|
|
9926
|
+
});
|
|
9927
|
+
|
|
9928
|
+
function clearLabelHighlight(e) {
|
|
9929
|
+
var txt = getTextNode(getSymbolTarget(e));
|
|
9930
|
+
if (txt) txt.classList.remove('active-label');
|
|
9931
|
+
}
|
|
9932
|
+
|
|
9933
|
+
function showLabelHighlight(e) {
|
|
9934
|
+
var txt = getTextNode(getSymbolTarget(e));
|
|
9935
|
+
if (txt) txt.classList.add('active-label');
|
|
9936
|
+
}
|
|
9937
|
+
|
|
9553
9938
|
hit.on('drag', function(e) {
|
|
9554
|
-
if (!
|
|
9939
|
+
if (!labelSelected(e) || activeId == -1) return;
|
|
9555
9940
|
if (e.id != activeId) {
|
|
9556
9941
|
error$1("Mismatched hit ids:", e.id, activeId);
|
|
9557
9942
|
}
|
|
@@ -9565,7 +9950,7 @@
|
|
|
9565
9950
|
// to anchor point, for better placement when eventual display font is
|
|
9566
9951
|
// different from mapshaper's font.
|
|
9567
9952
|
// if (!isMultilineLabel(textNode)) {
|
|
9568
|
-
autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget()
|
|
9953
|
+
autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget(), ext));
|
|
9569
9954
|
// }
|
|
9570
9955
|
updateNumber(activeRecord, 'dx', internal.roundToDigits(+activeRecord.dx, 3));
|
|
9571
9956
|
updateNumber(activeRecord, 'dy', internal.roundToDigits(+activeRecord.dy, 3));
|
|
@@ -9575,7 +9960,7 @@
|
|
|
9575
9960
|
});
|
|
9576
9961
|
|
|
9577
9962
|
hit.on('dragend', function(e) {
|
|
9578
|
-
if (!
|
|
9963
|
+
if (!labelSelected(e) || activeId == -1) return;
|
|
9579
9964
|
gui.dispatchEvent('label_dragend', {FID: e.id});
|
|
9580
9965
|
activeId = -1;
|
|
9581
9966
|
activeRecord = null;
|
|
@@ -9596,19 +9981,25 @@
|
|
|
9596
9981
|
}
|
|
9597
9982
|
|
|
9598
9983
|
function getSymbolTarget(e) {
|
|
9599
|
-
|
|
9600
|
-
return getSymbolNodeById(e.id, e.container);
|
|
9984
|
+
return e.id > -1 ? getSymbolNodeById(e.id) : null;
|
|
9601
9985
|
}
|
|
9602
9986
|
|
|
9603
9987
|
function getTextNode(symNode) {
|
|
9988
|
+
if (!symNode) return null;
|
|
9604
9989
|
if (symNode.tagName == 'text') return symNode;
|
|
9605
9990
|
return symNode.querySelector('text');
|
|
9606
9991
|
}
|
|
9607
9992
|
|
|
9608
|
-
function
|
|
9993
|
+
// function getSymbolNodeById_OLD(id, parent) {
|
|
9994
|
+
// var sel = '[data-id="' + id + '"]';
|
|
9995
|
+
// return parent.querySelector(sel);
|
|
9996
|
+
// }
|
|
9997
|
+
|
|
9998
|
+
function getSymbolNodeById(id) {
|
|
9609
9999
|
// TODO: optimize selector
|
|
9610
10000
|
var sel = '[data-id="' + id + '"]';
|
|
9611
|
-
|
|
10001
|
+
var activeLayer = hit.getHitTarget();
|
|
10002
|
+
return activeLayer.gui.svg_container.querySelector(sel);
|
|
9612
10003
|
}
|
|
9613
10004
|
|
|
9614
10005
|
// function getTextTarget2(e) {
|
|
@@ -9655,7 +10046,7 @@
|
|
|
9655
10046
|
// fails when symbol includes a dot (<g><circle/><text/></g> structure)
|
|
9656
10047
|
function updateSymbolNode(node, d, id) {
|
|
9657
10048
|
var o = internal.svg.renderStyledLabel(d); // TODO: symbol support
|
|
9658
|
-
var activeLayer = hit.getHitTarget()
|
|
10049
|
+
var activeLayer = hit.getHitTarget();
|
|
9659
10050
|
var xy = activeLayer.shapes[id][0];
|
|
9660
10051
|
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
9661
10052
|
var node2;
|
|
@@ -9688,7 +10079,7 @@
|
|
|
9688
10079
|
hit.on('drag', function(e) {
|
|
9689
10080
|
if (!active(e)) return;
|
|
9690
10081
|
// TODO: support multi points... get id of closest part to the pointer
|
|
9691
|
-
var p = getPointCoordsById(e.id, symbolInfo.target
|
|
10082
|
+
var p = getPointCoordsById(e.id, symbolInfo.target);
|
|
9692
10083
|
if (!p) return;
|
|
9693
10084
|
var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
|
|
9694
10085
|
p[0] += diff[0];
|
|
@@ -9719,8 +10110,8 @@
|
|
|
9719
10110
|
}
|
|
9720
10111
|
}
|
|
9721
10112
|
|
|
9722
|
-
//
|
|
9723
|
-
var HOVER_THRESHOLD
|
|
10113
|
+
// pixel distance threshold for hovering near a vertex or segment midpoint
|
|
10114
|
+
var HOVER_THRESHOLD = 10;
|
|
9724
10115
|
|
|
9725
10116
|
function initLineEditing(gui, ext, hit) {
|
|
9726
10117
|
var hoverVertexInfo;
|
|
@@ -9746,7 +10137,7 @@
|
|
|
9746
10137
|
}
|
|
9747
10138
|
|
|
9748
10139
|
function polygonMode() {
|
|
9749
|
-
return active() && hit.getHitTarget().
|
|
10140
|
+
return active() && hit.getHitTarget().geometry_type == 'polygon';
|
|
9750
10141
|
}
|
|
9751
10142
|
|
|
9752
10143
|
function clearHoverVertex() {
|
|
@@ -9762,7 +10153,7 @@
|
|
|
9762
10153
|
if (e.mode == 'drawing') {
|
|
9763
10154
|
gui.enterMode('drawing_tool');
|
|
9764
10155
|
} else if (active()) {
|
|
9765
|
-
|
|
10156
|
+
turnOff();
|
|
9766
10157
|
}
|
|
9767
10158
|
}, null, 10); // higher priority than hit control, so turnOff() has correct hit target
|
|
9768
10159
|
|
|
@@ -9789,27 +10180,10 @@
|
|
|
9789
10180
|
appendVertex$1(target, e.p);
|
|
9790
10181
|
}
|
|
9791
10182
|
if (e.shapes) {
|
|
9792
|
-
|
|
10183
|
+
replaceDrawnShapes(e.shapes);
|
|
9793
10184
|
}
|
|
9794
10185
|
});
|
|
9795
10186
|
|
|
9796
|
-
function replaceShapes(shapes) {
|
|
9797
|
-
var target = hit.getHitTarget();
|
|
9798
|
-
var records = target.layer.data?.getRecords();
|
|
9799
|
-
var prevLen = target.layer.shapes.length;
|
|
9800
|
-
var newLen = initialShapeCount + shapes.length;
|
|
9801
|
-
var recordCount = records?.length || 0;
|
|
9802
|
-
target.layer.shapes = target.layer.shapes.slice(0, initialShapeCount).concat(shapes);
|
|
9803
|
-
while (records && records.length > newLen) {
|
|
9804
|
-
records.pop();
|
|
9805
|
-
}
|
|
9806
|
-
while (records && records.length < newLen) {
|
|
9807
|
-
appendNewDataRecord(target.layer);
|
|
9808
|
-
}
|
|
9809
|
-
}
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
10187
|
gui.on('undo_path_extend', function(e) {
|
|
9814
10188
|
var target = hit.getHitTarget();
|
|
9815
10189
|
if (drawing() && prevHoverEvent) {
|
|
@@ -9822,14 +10196,14 @@
|
|
|
9822
10196
|
gui.undo.undo(); // remove the path
|
|
9823
10197
|
}
|
|
9824
10198
|
if (e.shapes) {
|
|
9825
|
-
|
|
10199
|
+
replaceDrawnShapes(e.shapes);
|
|
9826
10200
|
}
|
|
9827
10201
|
});
|
|
9828
10202
|
|
|
9829
10203
|
function turnOn() {
|
|
9830
10204
|
var target = hit.getHitTarget();
|
|
9831
|
-
initialArcCount = target.
|
|
9832
|
-
initialShapeCount = target.
|
|
10205
|
+
initialArcCount = target.gui.displayArcs.size();
|
|
10206
|
+
initialShapeCount = target.shapes.length;
|
|
9833
10207
|
if (sessionCount === 0) {
|
|
9834
10208
|
showInstructions();
|
|
9835
10209
|
}
|
|
@@ -9845,6 +10219,12 @@
|
|
|
9845
10219
|
non_blocking: true, max_width: '360px'});
|
|
9846
10220
|
}
|
|
9847
10221
|
|
|
10222
|
+
function hideInstructions() {
|
|
10223
|
+
if (!alert) return;
|
|
10224
|
+
alert.close('fade');
|
|
10225
|
+
alert = null;
|
|
10226
|
+
}
|
|
10227
|
+
|
|
9848
10228
|
function turnOff() {
|
|
9849
10229
|
var removed = 0;
|
|
9850
10230
|
finishCurrentPath();
|
|
@@ -9852,10 +10232,7 @@
|
|
|
9852
10232
|
removed = removeOpenPolygons();
|
|
9853
10233
|
}
|
|
9854
10234
|
clearDrawingInfo();
|
|
9855
|
-
|
|
9856
|
-
alert.close();
|
|
9857
|
-
alert = null;
|
|
9858
|
-
}
|
|
10235
|
+
hideInstructions();
|
|
9859
10236
|
initialArcCount = -1;
|
|
9860
10237
|
initialShapeCount = -1;
|
|
9861
10238
|
if (gui.interaction.getMode() == 'drawing') {
|
|
@@ -9870,20 +10247,21 @@
|
|
|
9870
10247
|
// returns number of removed shapes
|
|
9871
10248
|
function removeOpenPolygons() {
|
|
9872
10249
|
var target = hit.getHitTarget();
|
|
9873
|
-
var arcs = target.source.dataset.arcs;
|
|
9874
|
-
var n = target.
|
|
9875
|
-
// delete open paths
|
|
10250
|
+
var arcs = target.gui.source.dataset.arcs;
|
|
10251
|
+
var n = target.shapes.length;
|
|
10252
|
+
// delete open paths
|
|
9876
10253
|
for (var i=initialShapeCount; i<n; i++) {
|
|
9877
|
-
var shp = target.
|
|
9878
|
-
if (!geom.pathIsClosed(shp[0], arcs)) {
|
|
9879
|
-
target.
|
|
10254
|
+
var shp = target.shapes[i];
|
|
10255
|
+
if (!geom.pathIsClosed(shp[0], arcs)) { // assume open paths have one arc
|
|
10256
|
+
target.shapes[i] = null;
|
|
9880
10257
|
}
|
|
9881
10258
|
}
|
|
9882
|
-
// removes
|
|
9883
|
-
mapshaper.cmd.filterFeatures(target
|
|
9884
|
-
return n - target.
|
|
10259
|
+
// removes features with wrong winding order or null geometry
|
|
10260
|
+
mapshaper.cmd.filterFeatures(target, arcs, {remove_empty: true, quiet: true});
|
|
10261
|
+
return n - target.shapes.length;
|
|
9885
10262
|
}
|
|
9886
10263
|
|
|
10264
|
+
// updates display arcs and redraws all layers
|
|
9887
10265
|
function fullRedraw() {
|
|
9888
10266
|
gui.model.updated({arc_count: true});
|
|
9889
10267
|
}
|
|
@@ -9895,13 +10273,25 @@
|
|
|
9895
10273
|
prevClickEvent = prevHoverEvent = null;
|
|
9896
10274
|
}
|
|
9897
10275
|
|
|
10276
|
+
hit.on('contextmenu', function(e) {
|
|
10277
|
+
if (!active() || drawing() || dragging()) return;
|
|
10278
|
+
var target = hit.getHitTarget();
|
|
10279
|
+
var vInfo = hoverVertexInfo;
|
|
10280
|
+
if (hoverVertexInfo?.type == 'vertex' && !vertexIsEndpoint(vInfo, target)) {
|
|
10281
|
+
e.deleteVertex = function() {
|
|
10282
|
+
deleteActiveVertex(e, vInfo);
|
|
10283
|
+
};
|
|
10284
|
+
}
|
|
10285
|
+
// don't allow copying of open paths as geojson in polygon mode
|
|
10286
|
+
gui.contextMenu.open(e, target);
|
|
10287
|
+
});
|
|
10288
|
+
|
|
9898
10289
|
hit.on('dragstart', function(e) {
|
|
9899
10290
|
if (!active() || drawing() || !hoverVertexInfo) return;
|
|
9900
|
-
|
|
9901
|
-
alert.close('fade');
|
|
9902
|
-
}
|
|
10291
|
+
hideInstructions();
|
|
9903
10292
|
e.originalEvent.stopPropagation();
|
|
9904
10293
|
_dragging = true;
|
|
10294
|
+
updateCursor();
|
|
9905
10295
|
if (hoverVertexInfo.type == 'interpolated') {
|
|
9906
10296
|
insertVertex$1(hit.getHitTarget(), hoverVertexInfo.i, hoverVertexInfo.point);
|
|
9907
10297
|
hoverVertexInfo.ids = [hoverVertexInfo.i];
|
|
@@ -9915,11 +10305,10 @@
|
|
|
9915
10305
|
var target = hit.getHitTarget();
|
|
9916
10306
|
var p = ext.translatePixelCoords(e.x, e.y);
|
|
9917
10307
|
if (gui.keyboard.shiftIsPressed()) {
|
|
9918
|
-
internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.
|
|
10308
|
+
internal.snapPointToArcEndpoint(p, hoverVertexInfo.ids, target.gui.displayArcs);
|
|
9919
10309
|
}
|
|
9920
|
-
internal.snapVerticesToPoint(hoverVertexInfo.ids, p, target.
|
|
10310
|
+
internal.snapVerticesToPoint(hoverVertexInfo.ids, p, target.gui.displayArcs);
|
|
9921
10311
|
hit.setHoverVertex(p, '');
|
|
9922
|
-
|
|
9923
10312
|
// redrawing the whole map updates the data layer as well as the overlay layer
|
|
9924
10313
|
// gui.dispatchEvent('map-needs-refresh');
|
|
9925
10314
|
});
|
|
@@ -9927,9 +10316,9 @@
|
|
|
9927
10316
|
hit.on('dragend', function(e) {
|
|
9928
10317
|
if (!dragging()) return;
|
|
9929
10318
|
_dragging = false;
|
|
9930
|
-
// kludge to get dataset to recalculate internal bounding boxes
|
|
9931
10319
|
var target = hit.getHitTarget();
|
|
9932
|
-
|
|
10320
|
+
// kludge to get dataset to recalculate internal bounding boxes
|
|
10321
|
+
target.gui.displayArcs.transformPoints(function() {});
|
|
9933
10322
|
updateVertexCoords(target, hoverVertexInfo.ids);
|
|
9934
10323
|
gui.dispatchEvent('vertex_dragend', hoverVertexInfo);
|
|
9935
10324
|
gui.dispatchEvent('map-needs-refresh'); // redraw basemap
|
|
@@ -9953,6 +10342,7 @@
|
|
|
9953
10342
|
// ... or the closest point along the segment (for adding a new vertex)
|
|
9954
10343
|
hit.on('hover', function(e) {
|
|
9955
10344
|
if (!active() || dragging()) return;
|
|
10345
|
+
|
|
9956
10346
|
if (drawing()) {
|
|
9957
10347
|
if (!e.overMap) {
|
|
9958
10348
|
finishCurrentPath();
|
|
@@ -9975,6 +10365,7 @@
|
|
|
9975
10365
|
} else {
|
|
9976
10366
|
clearHoverVertex();
|
|
9977
10367
|
}
|
|
10368
|
+
updateCursor();
|
|
9978
10369
|
prevHoverEvent = e;
|
|
9979
10370
|
}, null, 100);
|
|
9980
10371
|
|
|
@@ -9985,23 +10376,22 @@
|
|
|
9985
10376
|
var p = pixToDataCoords(e.x, e.y);
|
|
9986
10377
|
if (drawing()) {
|
|
9987
10378
|
extendCurrentPath(hoverVertexInfo?.point || p);
|
|
9988
|
-
// extendCurrentPath(p); // just extend to current mouse position (not hover vertex)
|
|
9989
10379
|
} else if (gui.keyboard.shiftIsPressed()) {
|
|
9990
10380
|
deleteActiveVertex(e);
|
|
9991
10381
|
} else {
|
|
9992
10382
|
startNewPath(p);
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
}
|
|
10383
|
+
hideInstructions();
|
|
10384
|
+
updateCursor();
|
|
9996
10385
|
}
|
|
9997
10386
|
prevClickEvent = e;
|
|
9998
10387
|
});
|
|
9999
10388
|
|
|
10000
|
-
// esc key finishes a path
|
|
10389
|
+
// esc or enter key finishes a path
|
|
10001
10390
|
gui.keyboard.on('keydown', function(e) {
|
|
10002
10391
|
if (active() && (e.keyName == 'esc' || e.keyName == 'enter')) {
|
|
10003
10392
|
e.stopPropagation();
|
|
10004
10393
|
finishCurrentPath();
|
|
10394
|
+
e.originalEvent.preventDefault(); // block console "enter"
|
|
10005
10395
|
}
|
|
10006
10396
|
}, null, 10);
|
|
10007
10397
|
|
|
@@ -10015,16 +10405,24 @@
|
|
|
10015
10405
|
return dbl;
|
|
10016
10406
|
}
|
|
10017
10407
|
|
|
10018
|
-
function
|
|
10019
|
-
var
|
|
10020
|
-
|
|
10408
|
+
function updateCursor() {
|
|
10409
|
+
var useArrow = hoverVertexInfo && !hoverVertexInfo.extendable && !drawing();
|
|
10410
|
+
gui.container.findChild('.map-layers').classed('dragging', useArrow);
|
|
10411
|
+
}
|
|
10412
|
+
|
|
10413
|
+
function vertexIsEndpoint(info, target) {
|
|
10414
|
+
var vId = info.ids[0];
|
|
10415
|
+
return internal.vertexIsArcStart(vId, target.gui.displayArcs) ||
|
|
10416
|
+
internal.vertexIsArcEnd(vId, target.gui.displayArcs);
|
|
10417
|
+
}
|
|
10418
|
+
|
|
10419
|
+
// info: optional vertex info object
|
|
10420
|
+
function deleteActiveVertex(e, infoArg) {
|
|
10421
|
+
var info = infoArg || findDraggableVertices(e);
|
|
10422
|
+
if (!info || info.type != 'vertex') return;
|
|
10021
10423
|
var vId = info.ids[0];
|
|
10022
10424
|
var target = hit.getHitTarget();
|
|
10023
|
-
if (
|
|
10024
|
-
internal.vertexIsArcEnd(vId, target.arcs)) {
|
|
10025
|
-
// TODO: support removing arc endpoints
|
|
10026
|
-
return;
|
|
10027
|
-
}
|
|
10425
|
+
if (vertexIsEndpoint(info, target)) return;
|
|
10028
10426
|
gui.dispatchEvent('vertex_delete', {
|
|
10029
10427
|
target: target,
|
|
10030
10428
|
vertex_id: vId
|
|
@@ -10050,25 +10448,27 @@
|
|
|
10050
10448
|
var dist2 = dist / Math.sqrt(2);
|
|
10051
10449
|
var minDist = Infinity;
|
|
10052
10450
|
var cands = [
|
|
10053
|
-
{
|
|
10054
|
-
{
|
|
10055
|
-
{
|
|
10056
|
-
{
|
|
10057
|
-
{
|
|
10058
|
-
{
|
|
10059
|
-
{
|
|
10060
|
-
{
|
|
10451
|
+
{dx: 0, dy: dist},
|
|
10452
|
+
{dx: 0, dy: -dist},
|
|
10453
|
+
{dx: dist, dy: 0},
|
|
10454
|
+
{dx: -dist, dy: 0},
|
|
10455
|
+
{dx: dist2, dy: dist2},
|
|
10456
|
+
{dx: dist2, dy: -dist2},
|
|
10457
|
+
{dx: -dist2, dy: dist2},
|
|
10458
|
+
{dx: -dist2, dy: -dist2}
|
|
10061
10459
|
];
|
|
10062
10460
|
var snapped = cands.reduce(function(memo, cand) {
|
|
10063
|
-
var dist = geom.distance2D(thisEvt.x, thisEvt.y, cand.
|
|
10461
|
+
var dist = geom.distance2D(thisEvt.x, thisEvt.y, x0 + cand.dx, y0 + cand.dy);
|
|
10064
10462
|
if (dist < minDist) {
|
|
10065
10463
|
minDist = dist;
|
|
10066
10464
|
return cand;
|
|
10067
10465
|
}
|
|
10068
10466
|
return memo;
|
|
10069
10467
|
}, null);
|
|
10070
|
-
thisEvt.x = snapped.
|
|
10071
|
-
thisEvt.y = snapped.
|
|
10468
|
+
thisEvt.x = x0 + snapped.dx;
|
|
10469
|
+
thisEvt.y = y0 + snapped.dy;
|
|
10470
|
+
|
|
10471
|
+
return null;
|
|
10072
10472
|
}
|
|
10073
10473
|
|
|
10074
10474
|
function finishCurrentPath() {
|
|
@@ -10089,7 +10489,7 @@
|
|
|
10089
10489
|
var p1 = hoverVertexInfo?.point || p2;
|
|
10090
10490
|
appendNewPath(target, p1, p2);
|
|
10091
10491
|
gui.dispatchEvent('path_add', {target, p1, p2});
|
|
10092
|
-
drawingId = target.
|
|
10492
|
+
drawingId = target.shapes.length - 1;
|
|
10093
10493
|
hit.setDrawingId(drawingId);
|
|
10094
10494
|
}
|
|
10095
10495
|
|
|
@@ -10103,11 +10503,11 @@
|
|
|
10103
10503
|
error$1('Defective path');
|
|
10104
10504
|
}
|
|
10105
10505
|
if (finish && polygonMode()) {
|
|
10106
|
-
shapes1 = target.
|
|
10107
|
-
shapes2 =
|
|
10506
|
+
shapes1 = target.shapes.slice(initialShapeCount);
|
|
10507
|
+
shapes2 = convertClosedPaths(shapes1);
|
|
10108
10508
|
}
|
|
10109
10509
|
if (shapes2) {
|
|
10110
|
-
|
|
10510
|
+
replaceDrawnShapes(shapes2);
|
|
10111
10511
|
gui.dispatchEvent('path_extend', {target, p, shapes1, shapes2});
|
|
10112
10512
|
clearDrawingInfo();
|
|
10113
10513
|
fullRedraw();
|
|
@@ -10117,13 +10517,27 @@
|
|
|
10117
10517
|
gui.dispatchEvent('path_extend', {target, p});
|
|
10118
10518
|
hit.triggerChangeEvent(); // trigger overlay redraw
|
|
10119
10519
|
}
|
|
10520
|
+
}
|
|
10120
10521
|
|
|
10522
|
+
function replaceDrawnShapes(shapes) {
|
|
10523
|
+
var target = hit.getHitTarget();
|
|
10524
|
+
var records = target.data?.getRecords();
|
|
10525
|
+
var prevLen = target.shapes.length;
|
|
10526
|
+
var newLen = initialShapeCount + shapes.length;
|
|
10527
|
+
var recordCount = records?.length || 0;
|
|
10528
|
+
target.shapes = target.shapes.slice(0, initialShapeCount).concat(shapes);
|
|
10529
|
+
while (records && records.length > newLen) {
|
|
10530
|
+
records.pop();
|
|
10531
|
+
}
|
|
10532
|
+
while (records && records.length < newLen) {
|
|
10533
|
+
appendNewDataRecord(target);
|
|
10534
|
+
}
|
|
10121
10535
|
}
|
|
10122
10536
|
|
|
10123
10537
|
// p: [x, y] source data coordinates
|
|
10124
10538
|
function updatePathEndpoint(p) {
|
|
10125
10539
|
var target = hit.getHitTarget();
|
|
10126
|
-
var i = target.
|
|
10540
|
+
var i = target.gui.displayArcs.getPointCount() - 1;
|
|
10127
10541
|
if (hoverVertexInfo) {
|
|
10128
10542
|
p = hoverVertexInfo.point; // snap to selected point
|
|
10129
10543
|
}
|
|
@@ -10133,8 +10547,8 @@
|
|
|
10133
10547
|
|
|
10134
10548
|
function findPathStartInfo(e) {
|
|
10135
10549
|
var target = hit.getHitTarget();
|
|
10136
|
-
var arcId = target.
|
|
10137
|
-
var data = target.
|
|
10550
|
+
var arcId = target.gui.displayArcs.size() - 1;
|
|
10551
|
+
var data = target.gui.displayArcs.getVertexData();
|
|
10138
10552
|
var i = data.ii[arcId];
|
|
10139
10553
|
var x = data.xx[i];
|
|
10140
10554
|
var y = data.yy[i];
|
|
@@ -10142,7 +10556,7 @@
|
|
|
10142
10556
|
var dist = geom.distance2D(p[0], p[1], x, y);
|
|
10143
10557
|
var pathLen = data.nn[arcId];
|
|
10144
10558
|
var pixelDist = dist / ext.getPixelSize();
|
|
10145
|
-
if (pixelDist > HOVER_THRESHOLD
|
|
10559
|
+
if (pixelDist > HOVER_THRESHOLD || pathLen < 4) {
|
|
10146
10560
|
return null;
|
|
10147
10561
|
}
|
|
10148
10562
|
var point = translateDisplayPoint(target, [x, y]);
|
|
@@ -10156,41 +10570,38 @@
|
|
|
10156
10570
|
//
|
|
10157
10571
|
function findDraggableVertices(e) {
|
|
10158
10572
|
var target = hit.getHitTarget();
|
|
10159
|
-
var shp = target.
|
|
10573
|
+
var shp = target.shapes[e.id];
|
|
10160
10574
|
var p = ext.translatePixelCoords(e.x, e.y);
|
|
10161
|
-
var ids = internal.findNearestVertices(p, shp, target.
|
|
10162
|
-
var p2 = target.
|
|
10575
|
+
var ids = internal.findNearestVertices(p, shp, target.gui.displayArcs);
|
|
10576
|
+
var p2 = target.gui.displayArcs.getVertex2(ids[0]);
|
|
10163
10577
|
var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
|
|
10164
10578
|
var pixelDist = dist / ext.getPixelSize();
|
|
10165
|
-
if (pixelDist > HOVER_THRESHOLD
|
|
10579
|
+
if (pixelDist > HOVER_THRESHOLD) {
|
|
10166
10580
|
return null;
|
|
10167
10581
|
}
|
|
10168
10582
|
var point = getVertexCoords(target, ids[0]); // data coordinates
|
|
10169
10583
|
// find out if the vertex is the endpoint of a single path
|
|
10170
10584
|
// (which could be extended by a newly drawn path)
|
|
10171
10585
|
var extendable = ids.length == 1 &&
|
|
10172
|
-
internal.vertexIsArcEndpoint(ids[0], target.
|
|
10173
|
-
var displayPoint = target.
|
|
10586
|
+
internal.vertexIsArcEndpoint(ids[0], target.gui.displayArcs);
|
|
10587
|
+
var displayPoint = target.gui.displayArcs.getVertex2(ids[0]);
|
|
10174
10588
|
return {target, ids, extendable, point, displayPoint, type: 'vertex'};
|
|
10175
10589
|
}
|
|
10176
10590
|
|
|
10591
|
+
|
|
10177
10592
|
function findInterpolatedPoint(e) {
|
|
10178
10593
|
var target = hit.getHitTarget();
|
|
10179
10594
|
//// vertex insertion not supported with simplification
|
|
10180
10595
|
// if (!target.arcs.isFlat()) return null;
|
|
10181
10596
|
var p = ext.translatePixelCoords(e.x, e.y);
|
|
10182
10597
|
var minDist = Infinity;
|
|
10183
|
-
var shp = target.
|
|
10598
|
+
var shp = target.shapes[e.id];
|
|
10184
10599
|
var closest;
|
|
10185
|
-
internal.forEachSegmentInShape(shp, target.
|
|
10600
|
+
internal.forEachSegmentInShape(shp, target.gui.displayArcs, function(i, j, xx, yy) {
|
|
10186
10601
|
var x1 = xx[i],
|
|
10187
10602
|
y1 = yy[i],
|
|
10188
10603
|
x2 = xx[j],
|
|
10189
10604
|
y2 = yy[j],
|
|
10190
|
-
// switching from midpoint to nearest point to the mouse
|
|
10191
|
-
// cx = (x1 + x2) / 2,
|
|
10192
|
-
// cy = (y1 + y2) / 2,
|
|
10193
|
-
// p2 = [cx, cy],
|
|
10194
10605
|
p2 = internal.findClosestPointOnSeg(p[0], p[1], x1, y1, x2, y2, 0),
|
|
10195
10606
|
dist = geom.distance2D(p2[0], p2[1], p[0], p[1]);
|
|
10196
10607
|
if (dist < minDist) {
|
|
@@ -10203,7 +10614,7 @@
|
|
|
10203
10614
|
}
|
|
10204
10615
|
});
|
|
10205
10616
|
|
|
10206
|
-
if (closest.distance / ext.getPixelSize() > HOVER_THRESHOLD
|
|
10617
|
+
if (closest.distance / ext.getPixelSize() > HOVER_THRESHOLD) {
|
|
10207
10618
|
return null;
|
|
10208
10619
|
}
|
|
10209
10620
|
closest.point = translateDisplayPoint(target, closest.displayPoint);
|
|
@@ -10212,49 +10623,39 @@
|
|
|
10212
10623
|
return closest;
|
|
10213
10624
|
}
|
|
10214
10625
|
|
|
10215
|
-
//
|
|
10216
|
-
//
|
|
10217
|
-
function
|
|
10626
|
+
// Try to form polygon shapes from an array of path shapes
|
|
10627
|
+
// shapes: array of all shapes that have been drawn in the current session
|
|
10628
|
+
function convertClosedPaths(shapes) {
|
|
10218
10629
|
var target = hit.getHitTarget();
|
|
10630
|
+
// try to convert paths to polygons
|
|
10631
|
+
// NOTE: added "no_cuts" option to prevent polygons function from modifying
|
|
10632
|
+
// arcs, which would break undo/redo and cause other problems
|
|
10219
10633
|
var tmpLyr = {
|
|
10220
10634
|
geometry_type: 'polyline',
|
|
10221
10635
|
shapes: shapes.concat()
|
|
10222
10636
|
};
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
shapes.forEach(function(shp) {
|
|
10233
|
-
if (isOpenPath(shp)) {
|
|
10234
|
-
shapes2.push(shp);
|
|
10235
|
-
}
|
|
10236
|
-
});
|
|
10237
|
-
return shapes2.concat(outputLyr.shapes);
|
|
10637
|
+
var output = mapshaper.cmd.polygons([tmpLyr], target.gui.source.dataset, {no_cuts: true});
|
|
10638
|
+
var closedShapes = output[0].shapes;
|
|
10639
|
+
|
|
10640
|
+
// find paths that were not convertible to polygons
|
|
10641
|
+
var isOpenPath = getOpenPathTest(closedShapes);
|
|
10642
|
+
var openShapes = shapes.filter(function(shp) { return isOpenPath(shp); });
|
|
10643
|
+
|
|
10644
|
+
// retain both converted polygons and unconverted polylines
|
|
10645
|
+
return openShapes.concat(closedShapes);
|
|
10238
10646
|
}
|
|
10239
10647
|
|
|
10240
10648
|
// Returns a function for testing if a shape is an unclosed path, and doesn't
|
|
10241
10649
|
// overlap with an array of polygon shapes
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
if (arcId < 0) arcId = ~arcId;
|
|
10247
|
-
arcs.push(arcId);
|
|
10650
|
+
function getOpenPathTest(polygonShapes) {
|
|
10651
|
+
var polygonArcs = [];
|
|
10652
|
+
internal.forEachArcId(polygonShapes, function(arcId) {
|
|
10653
|
+
polygonArcs.push(internal.absArcId(arcId));
|
|
10248
10654
|
});
|
|
10655
|
+
|
|
10249
10656
|
return function(shp) {
|
|
10250
|
-
// assume that compound
|
|
10251
|
-
|
|
10252
|
-
if (shapeHasOneFwdArc(shp)) {
|
|
10253
|
-
var arcId = shp[0][0];
|
|
10254
|
-
if (arcId < 0) arcId = ~arcId;
|
|
10255
|
-
isOpen = !arcs.includes(arcId);
|
|
10256
|
-
}
|
|
10257
|
-
return isOpen;
|
|
10657
|
+
// assume that any compound shape is a polygon
|
|
10658
|
+
return shapeHasOneFwdArc(shp) && !polygonArcs.includes(shp[0][0]);
|
|
10258
10659
|
};
|
|
10259
10660
|
}
|
|
10260
10661
|
|
|
@@ -10266,213 +10667,29 @@
|
|
|
10266
10667
|
function initPointDrawing(gui, ext, hit) {
|
|
10267
10668
|
var mouse = gui.map.getMouse();
|
|
10268
10669
|
|
|
10269
|
-
gui.on('interaction_mode_change', function(e) {
|
|
10270
|
-
gui.container.findChild('.map-layers').classed('add-points', e.mode === 'add-points');
|
|
10271
|
-
});
|
|
10272
|
-
|
|
10273
|
-
function active() {
|
|
10274
|
-
return gui.interaction.getMode() == 'add-points';
|
|
10275
|
-
}
|
|
10276
|
-
|
|
10277
|
-
mouse.on('click', function(e) {
|
|
10278
|
-
if (!active()) return;
|
|
10279
|
-
addPoint(e.x, e.y);
|
|
10280
|
-
gui.dispatchEvent('map-needs-refresh');
|
|
10281
|
-
});
|
|
10282
|
-
|
|
10283
|
-
// x, y: pixel coordinates
|
|
10284
|
-
function addPoint(x, y) {
|
|
10285
|
-
var p = ext.translatePixelCoords(x, y);
|
|
10286
|
-
var target = hit.getHitTarget();
|
|
10287
|
-
var lyr = target.layer;
|
|
10288
|
-
var fid = lyr.shapes.length;
|
|
10289
|
-
var d = appendNewDataRecord(lyr);
|
|
10290
|
-
if (d) {
|
|
10291
|
-
// this seems to work even for projected layers -- the data tables
|
|
10292
|
-
// of projected and original data seem to be shared.
|
|
10293
|
-
lyr.data.getRecords()[fid] = d;
|
|
10294
|
-
}
|
|
10295
|
-
lyr.shapes[fid] = [p];
|
|
10296
|
-
updatePointCoords(target, fid);
|
|
10297
|
-
}
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
}
|
|
10301
|
-
|
|
10302
|
-
// pointer thresholds for hovering near a vertex or segment midpoint
|
|
10303
|
-
var HOVER_THRESHOLD = 8;
|
|
10304
|
-
var MIDPOINT_THRESHOLD = 11;
|
|
10305
|
-
|
|
10306
|
-
function initVertexDragging(gui, ext, hit) {
|
|
10307
|
-
var insertionPoint;
|
|
10308
|
-
var dragInfo;
|
|
10309
|
-
|
|
10310
|
-
function active() {
|
|
10311
|
-
return gui.interaction.getMode() == 'vertices';
|
|
10312
|
-
}
|
|
10313
|
-
|
|
10314
|
-
function dragging() {
|
|
10315
|
-
return active() && !!dragInfo;
|
|
10316
|
-
}
|
|
10317
|
-
|
|
10318
|
-
function setHoverVertex(id) {
|
|
10319
|
-
var target = hit.getHitTarget();
|
|
10320
|
-
hit.setHoverVertex(target.arcs.getVertex2(id));
|
|
10321
|
-
}
|
|
10322
|
-
|
|
10323
|
-
function clearHoverVertex() {
|
|
10324
|
-
hit.clearHoverVertex();
|
|
10325
|
-
}
|
|
10326
|
-
|
|
10327
|
-
// return data on the nearest vertex (or identical vertices) to the pointer
|
|
10328
|
-
// (if within a distance threshold)
|
|
10329
|
-
//
|
|
10330
|
-
function findDraggableVertices(e) {
|
|
10331
|
-
var target = hit.getHitTarget();
|
|
10332
|
-
var shp = target.layer.shapes[e.id];
|
|
10333
|
-
var p = ext.translatePixelCoords(e.x, e.y);
|
|
10334
|
-
var nearestIds = internal.findNearestVertices(p, shp, target.arcs);
|
|
10335
|
-
var p2 = target.arcs.getVertex2(nearestIds[0]);
|
|
10336
|
-
var dist = geom.distance2D(p[0], p[1], p2[0], p2[1]);
|
|
10337
|
-
var pixelDist = dist / ext.getPixelSize();
|
|
10338
|
-
if (pixelDist > HOVER_THRESHOLD) {
|
|
10339
|
-
return null;
|
|
10340
|
-
}
|
|
10341
|
-
var points = nearestIds.map(function(i) {
|
|
10342
|
-
return getVertexCoords(target, i); // data coordinates
|
|
10343
|
-
});
|
|
10344
|
-
return {
|
|
10345
|
-
target: target,
|
|
10346
|
-
ids: nearestIds,
|
|
10347
|
-
points: points
|
|
10348
|
-
};
|
|
10349
|
-
}
|
|
10350
|
-
|
|
10351
|
-
function findVertexInsertionPoint(e) {
|
|
10352
|
-
var target = hit.getHitTarget();
|
|
10353
|
-
//// vertex insertion not supported with simplification
|
|
10354
|
-
// if (!target.arcs.isFlat()) return null;
|
|
10355
|
-
var p = ext.translatePixelCoords(e.x, e.y);
|
|
10356
|
-
var midpoint = findNearestMidpoint(p, e.id, target);
|
|
10357
|
-
if (!midpoint ||
|
|
10358
|
-
midpoint.distance / ext.getPixelSize() > MIDPOINT_THRESHOLD) return null;
|
|
10359
|
-
return midpoint;
|
|
10360
|
-
}
|
|
10361
|
-
|
|
10362
|
-
hit.on('dragstart', function(e) {
|
|
10363
|
-
if (!active()) return;
|
|
10364
|
-
if (insertionPoint) {
|
|
10365
|
-
var target = hit.getHitTarget();
|
|
10366
|
-
insertVertex$1(target, insertionPoint.i, insertionPoint.point);
|
|
10367
|
-
dragInfo = {
|
|
10368
|
-
target: target,
|
|
10369
|
-
insertion: true,
|
|
10370
|
-
ids: [insertionPoint.i],
|
|
10371
|
-
points: [insertionPoint.point]
|
|
10372
|
-
};
|
|
10373
|
-
insertionPoint = null;
|
|
10374
|
-
} else {
|
|
10375
|
-
dragInfo = findDraggableVertices(e);
|
|
10376
|
-
}
|
|
10377
|
-
if (dragInfo) {
|
|
10378
|
-
setHoverVertex(dragInfo.ids[0]);
|
|
10379
|
-
}
|
|
10380
|
-
});
|
|
10381
|
-
|
|
10382
|
-
hit.on('drag', function(e) {
|
|
10383
|
-
if (!dragging()) return;
|
|
10384
|
-
var target = hit.getHitTarget();
|
|
10385
|
-
var p = ext.translatePixelCoords(e.x, e.y);
|
|
10386
|
-
if (gui.keyboard.shiftIsPressed()) {
|
|
10387
|
-
internal.snapPointToArcEndpoint(p, dragInfo.ids, target.arcs);
|
|
10388
|
-
}
|
|
10389
|
-
internal.snapVerticesToPoint(dragInfo.ids, p, target.arcs);
|
|
10390
|
-
setHoverVertex(dragInfo.ids[0]);
|
|
10391
|
-
// redrawing the whole map updates the data layer as well as the overlay layer
|
|
10392
|
-
// gui.dispatchEvent('map-needs-refresh');
|
|
10393
|
-
});
|
|
10394
|
-
|
|
10395
|
-
hit.on('dragend', function(e) {
|
|
10396
|
-
if (!dragging()) return;
|
|
10397
|
-
// kludge to get dataset to recalculate internal bounding boxes
|
|
10398
|
-
hit.getHitTarget().arcs.transformPoints(function() {});
|
|
10399
|
-
clearHoverVertex();
|
|
10400
|
-
updateVertexCoords(dragInfo.target, dragInfo.ids);
|
|
10401
|
-
gui.dispatchEvent('vertex_dragend', dragInfo);
|
|
10402
|
-
gui.dispatchEvent('map-needs-refresh');
|
|
10403
|
-
dragInfo = null;
|
|
10670
|
+
gui.on('interaction_mode_change', function(e) {
|
|
10671
|
+
gui.container.findChild('.map-layers').classed('add-points', e.mode === 'add-points');
|
|
10404
10672
|
});
|
|
10405
10673
|
|
|
10406
|
-
|
|
10674
|
+
function active() {
|
|
10675
|
+
return gui.interaction.getMode() == 'add-points';
|
|
10676
|
+
}
|
|
10677
|
+
|
|
10678
|
+
mouse.on('click', function(e) {
|
|
10407
10679
|
if (!active()) return;
|
|
10408
|
-
var
|
|
10409
|
-
if (!info) return;
|
|
10680
|
+
var p = pixToDataCoords(e.x, e.y);
|
|
10410
10681
|
var target = hit.getHitTarget();
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
internal.vertexIsArcEnd(vId, target.arcs)) {
|
|
10414
|
-
// TODO: support removing arc endpoints
|
|
10415
|
-
return;
|
|
10416
|
-
}
|
|
10417
|
-
gui.dispatchEvent('vertex_delete', {
|
|
10418
|
-
target: target,
|
|
10419
|
-
vertex_id: vId
|
|
10420
|
-
});
|
|
10421
|
-
deleteVertex$1(target, vId);
|
|
10422
|
-
clearHoverVertex();
|
|
10682
|
+
appendNewPoint(target, p);
|
|
10683
|
+
gui.dispatchEvent('point_add', {p, target});
|
|
10423
10684
|
gui.dispatchEvent('map-needs-refresh');
|
|
10424
10685
|
});
|
|
10425
10686
|
|
|
10426
|
-
// highlight hit vertex in path edit mode
|
|
10427
|
-
hit.on('hover', function(e) {
|
|
10428
|
-
insertionPoint = null;
|
|
10429
|
-
if (!active() || dragging()) return; // no hover effect while dragging
|
|
10430
|
-
var info = findDraggableVertices(e);
|
|
10431
|
-
if (info) {
|
|
10432
|
-
// hovering near a vertex: highlight the vertex
|
|
10433
|
-
setHoverVertex(info.ids[0]);
|
|
10434
|
-
return;
|
|
10435
|
-
}
|
|
10436
|
-
// if hovering near a segment midpoint: show the midpoint and save midpoint info
|
|
10437
|
-
insertionPoint = findVertexInsertionPoint(e);
|
|
10438
|
-
if (insertionPoint) {
|
|
10439
|
-
hit.setHoverVertex(insertionPoint.displayPoint);
|
|
10440
|
-
} else {
|
|
10441
|
-
// pointer is not over a vertex: clear any hover effect
|
|
10442
|
-
clearHoverVertex();
|
|
10443
|
-
}
|
|
10444
|
-
}, null, 100);
|
|
10445
|
-
}
|
|
10446
10687
|
|
|
10688
|
+
function pixToDataCoords(x, y) {
|
|
10689
|
+
var target = hit.getHitTarget();
|
|
10690
|
+
return translateDisplayPoint(target, ext.translatePixelCoords(x, y));
|
|
10691
|
+
}
|
|
10447
10692
|
|
|
10448
|
-
// Given a location @p (e.g. corresponding to the mouse pointer location),
|
|
10449
|
-
// find the midpoint of two vertices on @shp suitable for inserting a new vertex
|
|
10450
|
-
function findNearestMidpoint(p, fid, target) {
|
|
10451
|
-
var arcs = target.arcs;
|
|
10452
|
-
var shp = target.layer.shapes[fid];
|
|
10453
|
-
var minDist = Infinity, v;
|
|
10454
|
-
internal.forEachSegmentInShape(shp, arcs, function(i, j, xx, yy) {
|
|
10455
|
-
var x1 = xx[i],
|
|
10456
|
-
y1 = yy[i],
|
|
10457
|
-
x2 = xx[j],
|
|
10458
|
-
y2 = yy[j],
|
|
10459
|
-
cx = (x1 + x2) / 2,
|
|
10460
|
-
cy = (y1 + y2) / 2,
|
|
10461
|
-
midpoint = [cx, cy],
|
|
10462
|
-
dist = geom.distance2D(cx, cy, p[0], p[1]);
|
|
10463
|
-
if (dist < minDist) {
|
|
10464
|
-
minDist = dist;
|
|
10465
|
-
v = {
|
|
10466
|
-
i: (i < j ? i : j) + 1, // insertion point
|
|
10467
|
-
segment: [i, j],
|
|
10468
|
-
segmentLen: geom.distance2D(x1, y1, x2, y2),
|
|
10469
|
-
displayPoint: midpoint,
|
|
10470
|
-
point: translateDisplayPoint(target, midpoint),
|
|
10471
|
-
distance: dist
|
|
10472
|
-
};
|
|
10473
|
-
}
|
|
10474
|
-
});
|
|
10475
|
-
return v || null;
|
|
10476
10693
|
}
|
|
10477
10694
|
|
|
10478
10695
|
function initInteractiveEditing(gui, ext, hit) {
|
|
@@ -10480,7 +10697,6 @@
|
|
|
10480
10697
|
initPointDragging(gui, ext, hit);
|
|
10481
10698
|
initPointDrawing(gui, ext, hit);
|
|
10482
10699
|
initLineEditing(gui, ext, hit);
|
|
10483
|
-
initVertexDragging(gui, ext, hit);
|
|
10484
10700
|
}
|
|
10485
10701
|
|
|
10486
10702
|
function MapExtent(_position) {
|
|
@@ -10493,26 +10709,32 @@
|
|
|
10493
10709
|
|
|
10494
10710
|
_position.on('resize', function(e) {
|
|
10495
10711
|
if (ready()) {
|
|
10496
|
-
triggerChangeEvent({resize: true});
|
|
10712
|
+
// triggerChangeEvent({resize: true});
|
|
10713
|
+
triggerChangeEvent();
|
|
10497
10714
|
}
|
|
10498
10715
|
});
|
|
10499
10716
|
|
|
10500
10717
|
function ready() { return !!_fullBounds; }
|
|
10501
10718
|
|
|
10502
|
-
this.reset = function() {
|
|
10719
|
+
this.reset = function(fire) {
|
|
10503
10720
|
if (!ready()) return;
|
|
10504
|
-
recenter(_fullBounds.centerX(), _fullBounds.centerY(), 1
|
|
10721
|
+
recenter(_fullBounds.centerX(), _fullBounds.centerY(), 1);
|
|
10722
|
+
if (fire) {
|
|
10723
|
+
triggerChangeEvent();
|
|
10724
|
+
}
|
|
10505
10725
|
};
|
|
10506
10726
|
|
|
10507
10727
|
this.home = function() {
|
|
10508
10728
|
if (!ready()) return;
|
|
10509
10729
|
recenter(_fullBounds.centerX(), _fullBounds.centerY(), 1);
|
|
10730
|
+
triggerChangeEvent();
|
|
10510
10731
|
};
|
|
10511
10732
|
|
|
10512
10733
|
this.pan = function(xpix, ypix) {
|
|
10513
10734
|
if (!ready()) return;
|
|
10514
10735
|
var t = this.getTransform();
|
|
10515
10736
|
recenter(_cx - xpix / t.mx, _cy - ypix / t.my);
|
|
10737
|
+
triggerChangeEvent();
|
|
10516
10738
|
};
|
|
10517
10739
|
|
|
10518
10740
|
// Zoom to @w (width of the map viewport in coordinates)
|
|
@@ -10535,6 +10757,7 @@
|
|
|
10535
10757
|
cx = fx + dx2,
|
|
10536
10758
|
cy = fy + dy2;
|
|
10537
10759
|
recenter(cx, cy, scale);
|
|
10760
|
+
triggerChangeEvent();
|
|
10538
10761
|
};
|
|
10539
10762
|
|
|
10540
10763
|
this.zoomByPct = function(pct, xpct, ypct) {
|
|
@@ -10626,11 +10849,10 @@
|
|
|
10626
10849
|
return this.getTransform().invert().transform(x, y);
|
|
10627
10850
|
};
|
|
10628
10851
|
|
|
10629
|
-
function recenter(cx, cy, scale
|
|
10852
|
+
function recenter(cx, cy, scale) {
|
|
10630
10853
|
scale = scale ? limitScale(scale) : _scale;
|
|
10631
10854
|
if (cx == _cx && cy == _cy && scale == _scale) return;
|
|
10632
10855
|
navigate(cx, cy, scale);
|
|
10633
|
-
triggerChangeEvent(data);
|
|
10634
10856
|
}
|
|
10635
10857
|
|
|
10636
10858
|
function navigate(cx, cy, scale) {
|
|
@@ -10657,9 +10879,8 @@
|
|
|
10657
10879
|
_scale = scale;
|
|
10658
10880
|
}
|
|
10659
10881
|
|
|
10660
|
-
function triggerChangeEvent(
|
|
10661
|
-
|
|
10662
|
-
_self.dispatchEvent('change', data);
|
|
10882
|
+
function triggerChangeEvent() {
|
|
10883
|
+
_self.dispatchEvent('change');
|
|
10663
10884
|
}
|
|
10664
10885
|
|
|
10665
10886
|
// stop zooming before rounding errors become too obvious
|
|
@@ -10854,47 +11075,46 @@
|
|
|
10854
11075
|
}
|
|
10855
11076
|
|
|
10856
11077
|
// TODO: consider moving this upstream
|
|
10857
|
-
function getArcsForRendering(
|
|
10858
|
-
var dataset =
|
|
11078
|
+
function getArcsForRendering(lyr, ext) {
|
|
11079
|
+
var dataset = lyr.gui.source.dataset;
|
|
10859
11080
|
var sourceArcs = dataset.arcs;
|
|
10860
|
-
if (
|
|
11081
|
+
if (lyr.gui.geographic && dataset.gui?.displayArcs) {
|
|
10861
11082
|
return dataset.gui.displayArcs.getScaledArcs(ext);
|
|
10862
11083
|
}
|
|
10863
|
-
return
|
|
11084
|
+
return lyr.gui.displayArcs;
|
|
10864
11085
|
}
|
|
10865
11086
|
|
|
10866
|
-
function drawOutlineLayerToCanvas(
|
|
11087
|
+
function drawOutlineLayerToCanvas(lyr, canv, ext) {
|
|
10867
11088
|
var arcs;
|
|
10868
|
-
var style =
|
|
11089
|
+
var style = lyr.gui.style;
|
|
11090
|
+
var arcCounts = lyr.gui.arcCounts;
|
|
10869
11091
|
var darkStyle = {strokeWidth: style.strokeWidth, strokeColor: style.strokeColors[1]},
|
|
10870
11092
|
lightStyle = {strokeWidth: style.strokeWidth, strokeColor: style.strokeColors[0]};
|
|
10871
11093
|
var filter;
|
|
10872
|
-
if (internal.layerHasPaths(
|
|
10873
|
-
if (!
|
|
10874
|
-
|
|
10875
|
-
internal.countArcsInShapes(
|
|
11094
|
+
if (internal.layerHasPaths(lyr.gui.displayLayer)) {
|
|
11095
|
+
if (!arcCounts) {
|
|
11096
|
+
arcCounts = lyr.gui.arcCounts = new Uint8Array(lyr.gui.displayArcs.size());
|
|
11097
|
+
internal.countArcsInShapes(lyr.gui.displayLayer.shapes, arcCounts);
|
|
10876
11098
|
}
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
canv.drawArcs(arcs, darkStyle, filter);
|
|
10886
|
-
}
|
|
11099
|
+
arcs = getArcsForRendering(lyr, ext);
|
|
11100
|
+
if (lightStyle.strokeColor) {
|
|
11101
|
+
filter = getArcFilter(arcs, ext, false, arcCounts);
|
|
11102
|
+
canv.drawArcs(arcs, lightStyle, filter);
|
|
11103
|
+
}
|
|
11104
|
+
if (darkStyle.strokeColor && lyr.gui.displayLayer.geometry_type != 'point') {
|
|
11105
|
+
filter = getArcFilter(arcs, ext, true, arcCounts);
|
|
11106
|
+
canv.drawArcs(arcs, darkStyle, filter);
|
|
10887
11107
|
}
|
|
10888
11108
|
}
|
|
10889
|
-
if (
|
|
10890
|
-
canv.drawSquareDots(
|
|
11109
|
+
if (lyr.gui.displayLayer.geometry_type == 'point') {
|
|
11110
|
+
canv.drawSquareDots(lyr.gui.displayLayer.shapes, style);
|
|
10891
11111
|
}
|
|
10892
11112
|
}
|
|
10893
11113
|
|
|
10894
|
-
function drawStyledLayerToCanvas(
|
|
11114
|
+
function drawStyledLayerToCanvas(lyr, canv, ext) {
|
|
10895
11115
|
// TODO: add filter for out-of-view shapes
|
|
10896
|
-
var style =
|
|
10897
|
-
var layer =
|
|
11116
|
+
var style = lyr.gui.style;
|
|
11117
|
+
var layer = lyr.gui.displayLayer;
|
|
10898
11118
|
var arcs, filter;
|
|
10899
11119
|
if (layer.geometry_type == 'point') {
|
|
10900
11120
|
if (style.type == 'styled') {
|
|
@@ -10903,7 +11123,7 @@
|
|
|
10903
11123
|
canv.drawSquareDots(layer.shapes, style);
|
|
10904
11124
|
}
|
|
10905
11125
|
} else {
|
|
10906
|
-
arcs = getArcsForRendering(
|
|
11126
|
+
arcs = getArcsForRendering(lyr, ext);
|
|
10907
11127
|
filter = getShapeFilter(arcs, ext);
|
|
10908
11128
|
canv.drawStyledPaths(layer.shapes, arcs, style, filter);
|
|
10909
11129
|
if (style.vertices) {
|
|
@@ -11497,12 +11717,12 @@
|
|
|
11497
11717
|
}
|
|
11498
11718
|
};
|
|
11499
11719
|
|
|
11500
|
-
el.reposition = function(
|
|
11720
|
+
el.reposition = function(lyr, type) {
|
|
11501
11721
|
resize(ext);
|
|
11502
|
-
reposition(
|
|
11722
|
+
reposition(lyr, type, ext);
|
|
11503
11723
|
};
|
|
11504
11724
|
|
|
11505
|
-
el.drawLayer = function(
|
|
11725
|
+
el.drawLayer = function(lyr, type) {
|
|
11506
11726
|
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
11507
11727
|
var html = '';
|
|
11508
11728
|
// generate a unique id so layer can be identified when symbols are repositioned
|
|
@@ -11510,24 +11730,25 @@
|
|
|
11510
11730
|
var id = utils$1.getUniqueName();
|
|
11511
11731
|
var classNames = [id, 'mapshaper-svg-layer', 'mapshaper-' + type + '-layer'];
|
|
11512
11732
|
g.setAttribute('class', classNames.join(' '));
|
|
11513
|
-
|
|
11733
|
+
lyr.gui.svg_id = id;
|
|
11734
|
+
lyr.gui.svg_container = g;
|
|
11514
11735
|
resize(ext);
|
|
11515
11736
|
if (type == 'label' || type == 'symbol') {
|
|
11516
|
-
html = renderSymbols(
|
|
11737
|
+
html = renderSymbols(lyr.gui.displayLayer, ext);
|
|
11517
11738
|
} else if (type == 'furniture') {
|
|
11518
|
-
html = renderFurniture(
|
|
11739
|
+
html = renderFurniture(lyr.gui.displayLayer, ext);
|
|
11519
11740
|
}
|
|
11520
11741
|
g.innerHTML = html;
|
|
11521
11742
|
svg.append(g);
|
|
11522
11743
|
|
|
11523
11744
|
// prevent svg hit detection on inactive layers
|
|
11524
|
-
if (!
|
|
11745
|
+
if (!lyr.active) {
|
|
11525
11746
|
g.style.pointerEvents = 'none';
|
|
11526
11747
|
}
|
|
11527
11748
|
};
|
|
11528
11749
|
|
|
11529
|
-
function reposition(
|
|
11530
|
-
var container = el.findChild('.' +
|
|
11750
|
+
function reposition(lyr, type, ext) {
|
|
11751
|
+
var container = el.findChild('.' + lyr.gui.svg_id);
|
|
11531
11752
|
if (!container || !container.node()) {
|
|
11532
11753
|
console.error('[reposition] missing SVG container');
|
|
11533
11754
|
return;
|
|
@@ -11535,9 +11756,9 @@
|
|
|
11535
11756
|
var elements;
|
|
11536
11757
|
if (type == 'symbol') {
|
|
11537
11758
|
elements = El.findAll('.mapshaper-svg-symbol', container.node());
|
|
11538
|
-
repositionSymbols(elements,
|
|
11759
|
+
repositionSymbols(elements, lyr.gui.displayLayer, ext);
|
|
11539
11760
|
} else if (type == 'furniture') {
|
|
11540
|
-
repositionFurniture(container.node(),
|
|
11761
|
+
repositionFurniture(container.node(), lyr.gui.displayLayer, ext);
|
|
11541
11762
|
} else {
|
|
11542
11763
|
// container.getElementsByTagName('text')
|
|
11543
11764
|
error('Unsupported symbol type:', type);
|
|
@@ -11552,8 +11773,10 @@
|
|
|
11552
11773
|
return el;
|
|
11553
11774
|
}
|
|
11554
11775
|
|
|
11555
|
-
function LayerRenderer(gui, container
|
|
11776
|
+
function LayerRenderer(gui, container) {
|
|
11556
11777
|
var el = El(container),
|
|
11778
|
+
ext = gui.map.getExtent(),
|
|
11779
|
+
mouse = gui.map.getMouse(),
|
|
11557
11780
|
_mainCanv = new DisplayCanvas().appendTo(el),
|
|
11558
11781
|
_overlayCanv = new DisplayCanvas().appendTo(el),
|
|
11559
11782
|
_svg = new SvgDisplayLayer(gui, ext, mouse).appendTo(el),
|
|
@@ -11571,7 +11794,7 @@
|
|
|
11571
11794
|
_svg.clear();
|
|
11572
11795
|
}
|
|
11573
11796
|
layers.forEach(function(lyr) {
|
|
11574
|
-
var isSvgLayer = internal.layerHasSvgSymbols(lyr
|
|
11797
|
+
var isSvgLayer = internal.layerHasSvgSymbols(lyr) || internal.layerHasLabels(lyr);
|
|
11575
11798
|
if (isSvgLayer && !needSvgRedraw) {
|
|
11576
11799
|
_svg.reposition(lyr, 'symbol');
|
|
11577
11800
|
} else if (isSvgLayer) {
|
|
@@ -11620,7 +11843,7 @@
|
|
|
11620
11843
|
|
|
11621
11844
|
function drawCanvasLayer(lyr, canv) {
|
|
11622
11845
|
if (!lyr) return;
|
|
11623
|
-
if (lyr.style.type == 'outline') {
|
|
11846
|
+
if (lyr.gui.style.type == 'outline') {
|
|
11624
11847
|
drawOutlineLayerToCanvas(lyr, canv, ext);
|
|
11625
11848
|
} else {
|
|
11626
11849
|
drawStyledLayerToCanvas(lyr, canv, ext);
|
|
@@ -11688,13 +11911,12 @@
|
|
|
11688
11911
|
gui.on('interaction_mode_change', function(e) {
|
|
11689
11912
|
if (e.mode === 'box') {
|
|
11690
11913
|
gui.enterMode('box_tool');
|
|
11691
|
-
} else if (
|
|
11692
|
-
|
|
11914
|
+
} else if (_on) {
|
|
11915
|
+
turnOff();
|
|
11693
11916
|
}
|
|
11694
11917
|
});
|
|
11695
11918
|
|
|
11696
11919
|
gui.on('shift_drag_start', function() {
|
|
11697
|
-
// box.classed('zooming', inZoomMode());
|
|
11698
11920
|
hideCoords();
|
|
11699
11921
|
});
|
|
11700
11922
|
|
|
@@ -11760,358 +11982,101 @@
|
|
|
11760
11982
|
var el = popup.container();
|
|
11761
11983
|
el.addClass('option-menu');
|
|
11762
11984
|
var html = `<p>Enter a width in px, cm or inches to create a frame layer
|
|
11763
|
-
for setting the size of the map for symbol scaling in the
|
|
11764
|
-
GUI and setting the size and crop of SVG output.</p><div><input type="text" class="frame-width text-input" placeholder="examples: 600px 5in"></div>
|
|
11765
|
-
<div tabindex="0" class="btn dialog-btn">Create</div></span>`;
|
|
11766
|
-
el.html(html);
|
|
11767
|
-
var input = el.findChild('.frame-width');
|
|
11768
|
-
input.node().focus();
|
|
11769
|
-
var btn = el.findChild('.btn').on('click', function() {
|
|
11770
|
-
var widthStr = input.node().value.trim();
|
|
11771
|
-
if (parseFloat(widthStr) > 0 === false) {
|
|
11772
|
-
// invalid input
|
|
11773
|
-
input.node().value = '';
|
|
11774
|
-
return;
|
|
11775
|
-
}
|
|
11776
|
-
var cmd = `-rectangle + name=frame bbox='${bbox.join(',')}' width='${widthStr}'`;
|
|
11777
|
-
runCommand(cmd);
|
|
11778
|
-
popup.close();
|
|
11779
|
-
});
|
|
11780
|
-
}
|
|
11781
|
-
|
|
11782
|
-
return self;
|
|
11783
|
-
}
|
|
11784
|
-
|
|
11785
|
-
function RectangleControl(gui, hit) {
|
|
11786
|
-
var box = new HighlightBox(gui, {name: 'rectangle-tool', persistent: true, handles: true, classname: 'rectangles', draggable: false});
|
|
11787
|
-
var _on = false;
|
|
11788
|
-
var dragInfo;
|
|
11789
|
-
|
|
11790
|
-
gui.addMode('rectangle_tool', turnOn, turnOff);
|
|
11791
|
-
|
|
11792
|
-
gui.on('interaction_mode_change', function(e) {
|
|
11793
|
-
if (e.mode === 'rectangles') {
|
|
11794
|
-
gui.enterMode('rectangle_tool');
|
|
11795
|
-
} else if (gui.getMode() == 'rectangle_tool') {
|
|
11796
|
-
gui.clearMode();
|
|
11797
|
-
}
|
|
11798
|
-
});
|
|
11799
|
-
|
|
11800
|
-
hit.on('change', function(e) {
|
|
11801
|
-
if (!_on) return;
|
|
11802
|
-
// TODO: handle multiple hits (see gui-inspection-control)
|
|
11803
|
-
var id = e.id;
|
|
11804
|
-
if (e.id > -1 && e.pinned) {
|
|
11805
|
-
var target = hit.getHitTarget();
|
|
11806
|
-
var path = target.layer.shapes[e.id][0];
|
|
11807
|
-
var bbox = target.arcs.getSimpleShapeBounds(path).toArray();
|
|
11808
|
-
box.setDataCoords(bbox);
|
|
11809
|
-
dragInfo = {
|
|
11810
|
-
id: e.id,
|
|
11811
|
-
target: target,
|
|
11812
|
-
ids: [],
|
|
11813
|
-
points: []
|
|
11814
|
-
};
|
|
11815
|
-
var iter = target.arcs.getShapeIter(path);
|
|
11816
|
-
while (iter.hasNext()) {
|
|
11817
|
-
dragInfo.points.push([iter.x, iter.y]);
|
|
11818
|
-
dragInfo.ids.push(iter._arc.i);
|
|
11819
|
-
}
|
|
11820
|
-
gui.container.findChild('.map-layers').classed('dragging', true);
|
|
11821
|
-
|
|
11822
|
-
} else if (dragInfo) {
|
|
11823
|
-
gui.dispatchEvent('rectangle_dragend', dragInfo); // save undo state
|
|
11824
|
-
gui.container.findChild('.map-layers').classed('dragging', false);
|
|
11825
|
-
reset();
|
|
11826
|
-
} else {
|
|
11827
|
-
box.hide();
|
|
11828
|
-
}
|
|
11829
|
-
|
|
11830
|
-
});
|
|
11831
|
-
|
|
11832
|
-
box.on('handle_drag', function(e) {
|
|
11833
|
-
if (!_on || !dragInfo) return;
|
|
11834
|
-
var coords = internal.bboxToCoords(box.getDataCoords());
|
|
11835
|
-
setRectangleCoords(dragInfo.target, dragInfo.ids, coords);
|
|
11836
|
-
gui.dispatchEvent('map-needs-refresh');
|
|
11837
|
-
});
|
|
11838
|
-
|
|
11839
|
-
function turnOn() {
|
|
11840
|
-
box.turnOn();
|
|
11841
|
-
_on = true;
|
|
11842
|
-
}
|
|
11843
|
-
|
|
11844
|
-
function turnOff() {
|
|
11845
|
-
box.turnOff();
|
|
11846
|
-
if (gui.interaction.getMode() == 'rectangles') {
|
|
11847
|
-
// mode change was not initiated by interactive menu -- turn off interactivity
|
|
11848
|
-
gui.interaction.turnOff();
|
|
11849
|
-
}
|
|
11850
|
-
_on = false;
|
|
11851
|
-
reset();
|
|
11852
|
-
}
|
|
11853
|
-
|
|
11854
|
-
function reset() {
|
|
11855
|
-
box.hide();
|
|
11856
|
-
dragInfo = null;
|
|
11857
|
-
}
|
|
11858
|
-
}
|
|
11859
|
-
|
|
11860
|
-
// Create low-detail versions of large arc collections for faster rendering
|
|
11861
|
-
// at zoomed-out scales.
|
|
11862
|
-
function enhanceArcCollectionForDisplay(unfilteredArcs) {
|
|
11863
|
-
var size = unfilteredArcs.getPointCount(),
|
|
11864
|
-
filteredArcs, filteredSegLen;
|
|
11865
|
-
|
|
11866
|
-
// Only generate low-detail arcs for larger datasets
|
|
11867
|
-
if (size > 5e5) {
|
|
11868
|
-
update();
|
|
11869
|
-
}
|
|
11870
|
-
|
|
11871
|
-
function update() {
|
|
11872
|
-
if (unfilteredArcs.getVertexData().zz) {
|
|
11873
|
-
// Use precalculated simplification data for vertex filtering, if available
|
|
11874
|
-
filteredArcs = initFilteredArcs(unfilteredArcs);
|
|
11875
|
-
filteredSegLen = internal.getAvgSegment(filteredArcs);
|
|
11876
|
-
} else {
|
|
11877
|
-
// Use fast simplification as a fallback
|
|
11878
|
-
filteredSegLen = internal.getAvgSegment(unfilteredArcs) * 4;
|
|
11879
|
-
filteredArcs = internal.simplifyArcsFast(unfilteredArcs, filteredSegLen);
|
|
11880
|
-
}
|
|
11881
|
-
}
|
|
11882
|
-
|
|
11883
|
-
function initFilteredArcs(arcs) {
|
|
11884
|
-
var filterPct = 0.08;
|
|
11885
|
-
var nth = Math.ceil(arcs.getPointCount() / 5e5);
|
|
11886
|
-
var currInterval = arcs.getRetainedInterval();
|
|
11887
|
-
var filterZ = arcs.getThresholdByPct(filterPct, nth);
|
|
11888
|
-
var filteredArcs = arcs.setRetainedInterval(filterZ).getFilteredCopy();
|
|
11889
|
-
arcs.setRetainedInterval(currInterval); // reset current simplification
|
|
11890
|
-
return filteredArcs;
|
|
11891
|
-
}
|
|
11892
|
-
|
|
11893
|
-
// TODO: better job of detecting arc change... e.g. revision number
|
|
11894
|
-
unfilteredArcs.getScaledArcs = function(ext) {
|
|
11895
|
-
// check for changes in the number of arcs (probably due to editing)
|
|
11896
|
-
if (filteredArcs && filteredArcs.size() != unfilteredArcs.size()) {
|
|
11897
|
-
// arc count has changed... probably due to editing
|
|
11898
|
-
update();
|
|
11899
|
-
if (filteredArcs.size() != unfilteredArcs.size()) {
|
|
11900
|
-
throw Error('Internal error');
|
|
11901
|
-
}
|
|
11902
|
-
}
|
|
11903
|
-
if (filteredArcs) {
|
|
11904
|
-
// match simplification of unfiltered arcs
|
|
11905
|
-
filteredArcs.setRetainedInterval(unfilteredArcs.getRetainedInterval());
|
|
11906
|
-
}
|
|
11907
|
-
// switch to filtered version of arcs at small scales
|
|
11908
|
-
var unitsPerPixel = 1/ext.getTransform().mx,
|
|
11909
|
-
useFiltering = filteredArcs && unitsPerPixel > filteredSegLen * 1.5;
|
|
11910
|
-
return useFiltering ? filteredArcs : unfilteredArcs;
|
|
11911
|
-
};
|
|
11912
|
-
}
|
|
11913
|
-
|
|
11914
|
-
function getDisplayLayerForTable(table) {
|
|
11915
|
-
var n = table.size(),
|
|
11916
|
-
cellWidth = 12,
|
|
11917
|
-
cellHeight = 5,
|
|
11918
|
-
gutter = 6,
|
|
11919
|
-
arcs = [],
|
|
11920
|
-
shapes = [],
|
|
11921
|
-
aspectRatio = 1.1,
|
|
11922
|
-
x, y, col, row, blockSize;
|
|
11923
|
-
|
|
11924
|
-
if (n > 10000) {
|
|
11925
|
-
arcs = null;
|
|
11926
|
-
gutter = 0;
|
|
11927
|
-
cellWidth = 4;
|
|
11928
|
-
cellHeight = 4;
|
|
11929
|
-
aspectRatio = 1.45;
|
|
11930
|
-
} else if (n > 5000) {
|
|
11931
|
-
cellWidth = 5;
|
|
11932
|
-
gutter = 3;
|
|
11933
|
-
aspectRatio = 1.45;
|
|
11934
|
-
} else if (n > 1000) {
|
|
11935
|
-
gutter = 3;
|
|
11936
|
-
cellWidth = 8;
|
|
11937
|
-
aspectRatio = 1.3;
|
|
11938
|
-
}
|
|
11939
|
-
|
|
11940
|
-
if (n < 25) {
|
|
11941
|
-
blockSize = n;
|
|
11942
|
-
} else {
|
|
11943
|
-
blockSize = Math.sqrt(n * (cellWidth + gutter) / cellHeight / aspectRatio) | 0;
|
|
11944
|
-
}
|
|
11945
|
-
|
|
11946
|
-
for (var i=0; i<n; i++) {
|
|
11947
|
-
row = i % blockSize;
|
|
11948
|
-
col = Math.floor(i / blockSize);
|
|
11949
|
-
x = col * (cellWidth + gutter);
|
|
11950
|
-
y = cellHeight * (blockSize - row);
|
|
11951
|
-
if (arcs) {
|
|
11952
|
-
arcs.push(getArc(x, y, cellWidth, cellHeight));
|
|
11953
|
-
shapes.push([[i]]);
|
|
11954
|
-
} else {
|
|
11955
|
-
shapes.push([[x, y]]);
|
|
11956
|
-
}
|
|
11957
|
-
}
|
|
11958
|
-
|
|
11959
|
-
function getArc(x, y, w, h) {
|
|
11960
|
-
return [[x, y], [x + w, y], [x + w, y - h], [x, y - h], [x, y]];
|
|
11961
|
-
}
|
|
11962
|
-
|
|
11963
|
-
return {
|
|
11964
|
-
layer: {
|
|
11965
|
-
geometry_type: arcs ? 'polygon' : 'point',
|
|
11966
|
-
shapes: shapes,
|
|
11967
|
-
data: table
|
|
11968
|
-
},
|
|
11969
|
-
arcs: arcs ? new internal.ArcCollection(arcs) : null
|
|
11970
|
-
};
|
|
11971
|
-
}
|
|
11972
|
-
|
|
11973
|
-
// displayCRS: CRS to use for display, or null (which clears any current display CRS)
|
|
11974
|
-
function projectDisplayLayer(lyr, displayCRS) {
|
|
11975
|
-
var crsInfo = getDatasetCrsInfo(lyr.source.dataset);
|
|
11976
|
-
var sourceCRS = crsInfo.crs;
|
|
11977
|
-
var lyr2;
|
|
11978
|
-
//if (!lyr.geographic || !sourceCRS) {
|
|
11979
|
-
// let getDisplayLayer() handle case of unprojectable source
|
|
11980
|
-
if (!lyr.geographic) {
|
|
11981
|
-
return;
|
|
11982
|
-
}
|
|
11983
|
-
if (lyr.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.dynamic_crs)) {
|
|
11984
|
-
return;
|
|
11985
|
+
for setting the size of the map for symbol scaling in the
|
|
11986
|
+
GUI and setting the size and crop of SVG output.</p><div><input type="text" class="frame-width text-input" placeholder="examples: 600px 5in"></div>
|
|
11987
|
+
<div tabindex="0" class="btn dialog-btn">Create</div></span>`;
|
|
11988
|
+
el.html(html);
|
|
11989
|
+
var input = el.findChild('.frame-width');
|
|
11990
|
+
input.node().focus();
|
|
11991
|
+
var btn = el.findChild('.btn').on('click', function() {
|
|
11992
|
+
var widthStr = input.node().value.trim();
|
|
11993
|
+
if (parseFloat(widthStr) > 0 === false) {
|
|
11994
|
+
// invalid input
|
|
11995
|
+
input.node().value = '';
|
|
11996
|
+
return;
|
|
11997
|
+
}
|
|
11998
|
+
var cmd = `-rectangle + name=frame bbox='${bbox.join(',')}' width='${widthStr}'`;
|
|
11999
|
+
runCommand(cmd);
|
|
12000
|
+
popup.close();
|
|
12001
|
+
});
|
|
11985
12002
|
}
|
|
11986
|
-
lyr2 = getDisplayLayer(lyr.source.layer, lyr.source.dataset, {crs: displayCRS});
|
|
11987
|
-
// kludge: copy projection-related properties to original layer
|
|
11988
|
-
lyr.dynamic_crs = lyr2.dynamic_crs;
|
|
11989
|
-
lyr.layer = lyr2.layer;
|
|
11990
12003
|
|
|
11991
|
-
|
|
11992
|
-
// re-apply layer filter
|
|
11993
|
-
lyr.layer = filterLayerByIds(lyr.layer, lyr.style.ids);
|
|
11994
|
-
}
|
|
11995
|
-
lyr.invertPoint = lyr2.invertPoint;
|
|
11996
|
-
lyr.projectPoint = lyr2.projectPoint;
|
|
11997
|
-
lyr.bounds = lyr2.bounds;
|
|
11998
|
-
lyr.arcs = lyr2.arcs;
|
|
12004
|
+
return self;
|
|
11999
12005
|
}
|
|
12000
12006
|
|
|
12007
|
+
function RectangleControl(gui, hit) {
|
|
12008
|
+
var box = new HighlightBox(gui, {name: 'rectangle-tool', persistent: true, handles: true, classname: 'rectangles', draggable: false});
|
|
12009
|
+
var _on = false;
|
|
12010
|
+
var dragInfo;
|
|
12001
12011
|
|
|
12002
|
-
|
|
12003
|
-
function getDisplayLayer(layer, dataset, opts) {
|
|
12004
|
-
var obj = {
|
|
12005
|
-
layer: null,
|
|
12006
|
-
arcs: null,
|
|
12007
|
-
// display_arcs: null,
|
|
12008
|
-
style: null,
|
|
12009
|
-
invertPoint: null,
|
|
12010
|
-
projectPoint: null,
|
|
12011
|
-
source: {
|
|
12012
|
-
layer: layer,
|
|
12013
|
-
dataset: dataset
|
|
12014
|
-
},
|
|
12015
|
-
empty: internal.getFeatureCount(layer) === 0
|
|
12016
|
-
};
|
|
12017
|
-
|
|
12018
|
-
var displayCRS = opts.crs || null;
|
|
12019
|
-
// display arcs may have been generated when another layer in the dataset was converted for display... re-use if available
|
|
12020
|
-
var displayArcs = dataset.gui?.displayArcs || null;
|
|
12021
|
-
var sourceCRS;
|
|
12022
|
-
var emptyArcs;
|
|
12012
|
+
gui.addMode('rectangle_tool', turnOn, turnOff);
|
|
12023
12013
|
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
obj.geographic = true;
|
|
12030
|
-
} else {
|
|
12031
|
-
sourceCRS = crsInfo.crs;
|
|
12014
|
+
gui.on('interaction_mode_change', function(e) {
|
|
12015
|
+
if (e.mode === 'rectangles') {
|
|
12016
|
+
gui.enterMode('rectangle_tool');
|
|
12017
|
+
} else if (_on) {
|
|
12018
|
+
turnOff();
|
|
12032
12019
|
}
|
|
12033
|
-
}
|
|
12020
|
+
});
|
|
12034
12021
|
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12022
|
+
hit.on('change', function(e) {
|
|
12023
|
+
if (!_on) return;
|
|
12024
|
+
// TODO: handle multiple hits (see gui-inspection-control)
|
|
12025
|
+
var id = e.id;
|
|
12026
|
+
if (e.id > -1 && e.pinned) {
|
|
12027
|
+
var target = hit.getHitTarget();
|
|
12028
|
+
var path = target.shapes[e.id][0];
|
|
12029
|
+
var bbox = target.gui.displayArcs.getSimpleShapeBounds(path).toArray();
|
|
12030
|
+
box.setDataCoords(bbox);
|
|
12031
|
+
dragInfo = {
|
|
12032
|
+
id: e.id,
|
|
12033
|
+
target: target,
|
|
12034
|
+
ids: [],
|
|
12035
|
+
points: []
|
|
12036
|
+
};
|
|
12037
|
+
var iter = target.gui.displayArcs.getShapeIter(path);
|
|
12038
|
+
while (iter.hasNext()) {
|
|
12039
|
+
dragInfo.points.push([iter.x, iter.y]);
|
|
12040
|
+
dragInfo.ids.push(iter._arc.i);
|
|
12041
|
+
}
|
|
12042
|
+
gui.container.findChild('.map-layers').classed('dragging', true);
|
|
12043
|
+
|
|
12044
|
+
} else if (dragInfo) {
|
|
12045
|
+
gui.dispatchEvent('rectangle_dragend', dragInfo); // save undo state
|
|
12046
|
+
gui.container.findChild('.map-layers').classed('dragging', false);
|
|
12047
|
+
reset();
|
|
12042
12048
|
} else {
|
|
12043
|
-
|
|
12044
|
-
displayArcs = dataset.arcs;
|
|
12049
|
+
box.hide();
|
|
12045
12050
|
}
|
|
12046
12051
|
|
|
12047
|
-
|
|
12048
|
-
dataset.gui = {displayArcs}; // stash these in the dataset for other layers to use
|
|
12049
|
-
}
|
|
12052
|
+
});
|
|
12050
12053
|
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
// treating furniture layers (other than frame) as tabular for now,
|
|
12058
|
-
// so there is something to show if they are selected
|
|
12059
|
-
// obj.tabular = obj.furniture_type != 'frame';
|
|
12060
|
-
} else if (layer.geometry_type) {
|
|
12061
|
-
obj.geographic = true;
|
|
12062
|
-
obj.layer = layer;
|
|
12063
|
-
obj.arcs = displayArcs;
|
|
12064
|
-
} else if (!obj.empty) {
|
|
12065
|
-
obj.tabular = true;
|
|
12066
|
-
} else {
|
|
12067
|
-
obj.layer = {shapes: []}; // ideally we should avoid empty layers
|
|
12068
|
-
}
|
|
12054
|
+
box.on('handle_drag', function(e) {
|
|
12055
|
+
if (!_on || !dragInfo) return;
|
|
12056
|
+
var coords = internal.bboxToCoords(box.getDataCoords());
|
|
12057
|
+
setRectangleCoords(dragInfo.target, dragInfo.ids, coords);
|
|
12058
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
12059
|
+
});
|
|
12069
12060
|
|
|
12070
|
-
|
|
12071
|
-
|
|
12061
|
+
function turnOn() {
|
|
12062
|
+
box.turnOn();
|
|
12063
|
+
_on = true;
|
|
12072
12064
|
}
|
|
12073
12065
|
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
if (internal.layerHasPoints(layer)) {
|
|
12080
|
-
obj.layer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
|
|
12081
|
-
} else if (internal.layerHasPaths(layer)) {
|
|
12082
|
-
emptyArcs = findEmptyArcs(displayArcs);
|
|
12083
|
-
if (emptyArcs.length > 0) {
|
|
12084
|
-
// Don't try to draw paths containing coordinates that failed to project
|
|
12085
|
-
obj.layer = internal.filterPathLayerByArcIds(obj.layer, emptyArcs);
|
|
12086
|
-
}
|
|
12066
|
+
function turnOff() {
|
|
12067
|
+
box.turnOff();
|
|
12068
|
+
if (gui.interaction.getMode() == 'rectangles') {
|
|
12069
|
+
// mode change was not initiated by interactive menu -- turn off interactivity
|
|
12070
|
+
gui.interaction.turnOff();
|
|
12087
12071
|
}
|
|
12072
|
+
_on = false;
|
|
12073
|
+
reset();
|
|
12088
12074
|
}
|
|
12089
12075
|
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
function getDisplayBounds(lyr, arcs) {
|
|
12096
|
-
var bounds = internal.getLayerBounds(lyr, arcs) || new Bounds();
|
|
12097
|
-
if (lyr.geometry_type == 'point' && arcs && bounds.hasBounds() && bounds.area() > 0 === false) {
|
|
12098
|
-
// if a point layer has no extent (e.g. contains only a single point),
|
|
12099
|
-
// then merge with arc bounds, to place the point in context.
|
|
12100
|
-
bounds = bounds.mergeBounds(arcs.getBounds());
|
|
12101
|
-
}
|
|
12102
|
-
return bounds;
|
|
12103
|
-
}
|
|
12104
|
-
|
|
12105
|
-
// Returns an array of ids of empty arcs (arcs can be set to empty if errors occur while projecting them)
|
|
12106
|
-
function findEmptyArcs(arcs) {
|
|
12107
|
-
var nn = arcs.getVertexData().nn;
|
|
12108
|
-
var ids = [];
|
|
12109
|
-
for (var i=0, n=nn.length; i<n; i++) {
|
|
12110
|
-
if (nn[i] === 0) {
|
|
12111
|
-
ids.push(i);
|
|
12112
|
-
}
|
|
12076
|
+
function reset() {
|
|
12077
|
+
box.hide();
|
|
12078
|
+
dragInfo = null;
|
|
12113
12079
|
}
|
|
12114
|
-
return ids;
|
|
12115
12080
|
}
|
|
12116
12081
|
|
|
12117
12082
|
function loadScript(url, cb) {
|
|
@@ -12164,15 +12129,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12164
12129
|
turnOff();
|
|
12165
12130
|
});
|
|
12166
12131
|
|
|
12167
|
-
// hideBtn.on('mousedown', function() {
|
|
12168
|
-
// if (activeStyle) {
|
|
12169
|
-
// mapEl.css('visibility', 'hidden');
|
|
12170
|
-
// hidden = true;
|
|
12171
|
-
// }
|
|
12172
|
-
// })
|
|
12173
12132
|
clearBtn.on('click', function() {
|
|
12174
12133
|
if (activeStyle) {
|
|
12175
|
-
|
|
12134
|
+
turnOffBasemap();
|
|
12176
12135
|
updateButtons();
|
|
12177
12136
|
}
|
|
12178
12137
|
});
|
|
@@ -12196,28 +12155,34 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12196
12155
|
params.styles.forEach(function(style) {
|
|
12197
12156
|
var btn = El('div').html(`<div class="basemap-style-btn"><img src="${style.icon}"></img></div><div class="basemap-style-label">${style.name}</div>`);
|
|
12198
12157
|
btn.findChild('.basemap-style-btn').on('click', function() {
|
|
12199
|
-
|
|
12158
|
+
if (style == activeStyle) {
|
|
12159
|
+
turnOffBasemap();
|
|
12160
|
+
} else {
|
|
12161
|
+
showBasemap(style);
|
|
12162
|
+
}
|
|
12200
12163
|
updateButtons();
|
|
12201
12164
|
});
|
|
12202
12165
|
btn.appendTo(list);
|
|
12203
12166
|
});
|
|
12204
12167
|
}
|
|
12205
12168
|
|
|
12206
|
-
function
|
|
12207
|
-
activeStyle =
|
|
12208
|
-
|
|
12169
|
+
function turnOffBasemap() {
|
|
12170
|
+
activeStyle = null;
|
|
12171
|
+
gui.map.setDisplayCRS(null);
|
|
12172
|
+
refresh();
|
|
12173
|
+
}
|
|
12174
|
+
|
|
12175
|
+
function showBasemap(style) {
|
|
12176
|
+
activeStyle = style;
|
|
12177
|
+
// TODO: consider enabling dark basemap mode
|
|
12209
12178
|
// Make sure that the selected layer style gets updated in gui-map.js
|
|
12210
12179
|
// gui.state.dark_basemap = style && style.dark || false;
|
|
12211
|
-
if (
|
|
12212
|
-
gui.map.setDisplayCRS(null);
|
|
12213
|
-
refresh();
|
|
12214
|
-
} else if (map) {
|
|
12180
|
+
if (map) {
|
|
12215
12181
|
map.setStyle(style.url);
|
|
12216
12182
|
refresh();
|
|
12217
|
-
} else {
|
|
12183
|
+
} else if (prepareMapView()) {
|
|
12218
12184
|
initMap();
|
|
12219
12185
|
}
|
|
12220
|
-
|
|
12221
12186
|
}
|
|
12222
12187
|
|
|
12223
12188
|
function updateButtons() {
|
|
@@ -12227,8 +12192,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12227
12192
|
}
|
|
12228
12193
|
|
|
12229
12194
|
function turnOn() {
|
|
12230
|
-
|
|
12231
|
-
var
|
|
12195
|
+
// TODO: show basemap even if there is no data
|
|
12196
|
+
var activeLyr = gui.model.getActiveLayer(); // may be null
|
|
12197
|
+
var info = getDatasetCrsInfo(activeLyr?.dataset); // defaults to wgs84
|
|
12232
12198
|
var dataCRS = info.crs || null;
|
|
12233
12199
|
var displayCRS = gui.map.getDisplayCRS();
|
|
12234
12200
|
var warning;
|
|
@@ -12271,9 +12237,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12271
12237
|
|
|
12272
12238
|
function getLonLatBounds() {
|
|
12273
12239
|
var bbox = ext.getBounds().toArray();
|
|
12274
|
-
var
|
|
12275
|
-
|
|
12276
|
-
return
|
|
12240
|
+
var bbox2 = fromWebMercator(bbox[0], bbox[1])
|
|
12241
|
+
.concat(fromWebMercator(bbox[2], bbox[3]));
|
|
12242
|
+
return bbox2;
|
|
12277
12243
|
}
|
|
12278
12244
|
|
|
12279
12245
|
function initMap() {
|
|
@@ -12331,6 +12297,15 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12331
12297
|
return true;
|
|
12332
12298
|
}
|
|
12333
12299
|
|
|
12300
|
+
function prepareMapView() {
|
|
12301
|
+
var crs = gui.map.getDisplayCRS();
|
|
12302
|
+
if (!crs) return false;
|
|
12303
|
+
if (!internal.isWebMercator(crs)) {
|
|
12304
|
+
gui.map.setDisplayCRS(internal.parseCrsString('webmercator'));
|
|
12305
|
+
}
|
|
12306
|
+
return true;
|
|
12307
|
+
}
|
|
12308
|
+
|
|
12334
12309
|
function refresh() {
|
|
12335
12310
|
var crs = gui.map.getDisplayCRS();
|
|
12336
12311
|
var off = !crs || !enabled() || !map || loading || !activeStyle;
|
|
@@ -12342,9 +12317,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12342
12317
|
return;
|
|
12343
12318
|
}
|
|
12344
12319
|
|
|
12345
|
-
|
|
12346
|
-
gui.map.setDisplayCRS(internal.parseCrsString('webmercator'));
|
|
12347
|
-
}
|
|
12320
|
+
prepareMapView();
|
|
12348
12321
|
var bbox = getLonLatBounds();
|
|
12349
12322
|
if (!checkBounds(bbox)) {
|
|
12350
12323
|
// map does not display outside these bounds
|
|
@@ -12402,13 +12375,19 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12402
12375
|
|
|
12403
12376
|
model.on('update', onUpdate);
|
|
12404
12377
|
|
|
12378
|
+
document.addEventListener('visibilitychange', function(e) {
|
|
12379
|
+
// refresh map when browser tab is re-activated (Chrome on mac has been
|
|
12380
|
+
// blanking the canvas after several other tabs are visited)
|
|
12381
|
+
if (document.visibilityState == 'visible') drawLayers();
|
|
12382
|
+
});
|
|
12405
12383
|
|
|
12406
12384
|
// Update display of segment intersections
|
|
12407
12385
|
this.setIntersectionLayer = function(lyr, dataset) {
|
|
12408
12386
|
if (lyr == _intersectionLyr) return; // no change
|
|
12409
12387
|
if (lyr) {
|
|
12410
|
-
|
|
12411
|
-
_intersectionLyr
|
|
12388
|
+
enhanceLayerForDisplay(lyr, dataset, getDisplayOptions());
|
|
12389
|
+
_intersectionLyr = lyr;
|
|
12390
|
+
_intersectionLyr.gui.style = getIntersectionStyle(_intersectionLyr.gui.displayLayer, getGlobalStyleOptions());
|
|
12412
12391
|
} else {
|
|
12413
12392
|
_intersectionLyr = null;
|
|
12414
12393
|
}
|
|
@@ -12437,9 +12416,16 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12437
12416
|
};
|
|
12438
12417
|
|
|
12439
12418
|
this.getDisplayCRS = function() {
|
|
12440
|
-
if (!_activeLyr
|
|
12441
|
-
|
|
12442
|
-
|
|
12419
|
+
if (!_activeLyr) {
|
|
12420
|
+
return _dynamicCRS || internal.parseCrsString('wgs84');
|
|
12421
|
+
}
|
|
12422
|
+
if (!_activeLyr.gui.geographic) {
|
|
12423
|
+
return null;
|
|
12424
|
+
}
|
|
12425
|
+
if (_activeLyr.gui.dynamic_crs) {
|
|
12426
|
+
return _activeLyr.gui.dynamic_crs;
|
|
12427
|
+
}
|
|
12428
|
+
var info = getDatasetCrsInfo(_activeLyr.gui.source.dataset);
|
|
12443
12429
|
return info.crs || null;
|
|
12444
12430
|
};
|
|
12445
12431
|
|
|
@@ -12471,14 +12457,14 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12471
12457
|
var newCRS = utils$1.isString(crs) ? internal.parseCrsString(crs) : crs;
|
|
12472
12458
|
// TODO: handle case that old and new CRS are the same
|
|
12473
12459
|
_dynamicCRS = newCRS;
|
|
12474
|
-
if (!_activeLyr) return; // stop here if no layers have been selected
|
|
12460
|
+
// if (!_activeLyr) return; // stop here if no layers have been selected
|
|
12475
12461
|
|
|
12476
12462
|
// clear any stored FilteredArcs objects (so they will be recreated with the desired projection)
|
|
12477
12463
|
clearAllDisplayArcs();
|
|
12478
12464
|
|
|
12479
12465
|
// Reproject all visible map layers
|
|
12480
12466
|
getContentLayers().forEach(function(lyr) {
|
|
12481
|
-
|
|
12467
|
+
projectLayerForDisplay(lyr, newCRS);
|
|
12482
12468
|
});
|
|
12483
12469
|
|
|
12484
12470
|
// kludge to make sure all layers have styles
|
|
@@ -12489,6 +12475,34 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12489
12475
|
updateFullBounds();
|
|
12490
12476
|
};
|
|
12491
12477
|
|
|
12478
|
+
// Initialization just before displaying the map for the first time
|
|
12479
|
+
this.init = function() {
|
|
12480
|
+
if (_renderer) return;
|
|
12481
|
+
_ext.setFullBounds(calcFullBounds());
|
|
12482
|
+
_ext.resize();
|
|
12483
|
+
_renderer = new LayerRenderer(gui, el);
|
|
12484
|
+
gui.buttons.show();
|
|
12485
|
+
|
|
12486
|
+
if (opts.inspectorControl) {
|
|
12487
|
+
_hit = new HitControl(gui, _ext, _mouse),
|
|
12488
|
+
new InspectionControl2(gui, _hit);
|
|
12489
|
+
new SelectionTool(gui, _ext, _hit),
|
|
12490
|
+
new BoxTool(gui, _ext, _nav),
|
|
12491
|
+
new RectangleControl(gui, _hit),
|
|
12492
|
+
initInteractiveEditing(gui, _ext, _hit);
|
|
12493
|
+
_hit.on('change', updateOverlayLayer);
|
|
12494
|
+
}
|
|
12495
|
+
|
|
12496
|
+
_ext.on('change', function(e) {
|
|
12497
|
+
if (_basemap) _basemap.refresh(); // keep basemap synced up (if enabled)
|
|
12498
|
+
drawLayers(e.redraw ? '' : 'nav');
|
|
12499
|
+
});
|
|
12500
|
+
|
|
12501
|
+
gui.on('resize', function() {
|
|
12502
|
+
position.update(); // kludge to detect new map size after console toggle
|
|
12503
|
+
});
|
|
12504
|
+
};
|
|
12505
|
+
|
|
12492
12506
|
function getGlobalStyleOptions(opts) {
|
|
12493
12507
|
var mode = gui.state.interaction_mode;
|
|
12494
12508
|
return Object.assign({
|
|
@@ -12503,9 +12517,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12503
12517
|
var prevLyr = _activeLyr || null;
|
|
12504
12518
|
var fullBounds;
|
|
12505
12519
|
var needReset;
|
|
12506
|
-
|
|
12507
12520
|
if (!prevLyr) {
|
|
12508
|
-
initMap(); // first call
|
|
12521
|
+
// initMap(); // first call
|
|
12509
12522
|
}
|
|
12510
12523
|
|
|
12511
12524
|
if (arcsMayHaveChanged(e.flags)) {
|
|
@@ -12529,9 +12542,14 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12529
12542
|
return;
|
|
12530
12543
|
}
|
|
12531
12544
|
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12545
|
+
if (e.layer) {
|
|
12546
|
+
enhanceLayerForDisplay(e.layer, e.dataset, getDisplayOptions());
|
|
12547
|
+
_activeLyr = e.layer;
|
|
12548
|
+
_activeLyr.gui.style = getActiveLayerStyle(_activeLyr.gui.displayLayer, getGlobalStyleOptions());
|
|
12549
|
+
_activeLyr.active = true;
|
|
12550
|
+
} else {
|
|
12551
|
+
_activeLyr = null;
|
|
12552
|
+
}
|
|
12535
12553
|
|
|
12536
12554
|
if (popupCanStayOpen(e.flags)) {
|
|
12537
12555
|
// data may have changed; if popup is open, it needs to be refreshed
|
|
@@ -12544,7 +12562,11 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12544
12562
|
updateVisibleMapLayers();
|
|
12545
12563
|
fullBounds = calcFullBounds();
|
|
12546
12564
|
|
|
12547
|
-
if (
|
|
12565
|
+
if (prevLyr?.gui.tabular || _activeLyr?.gui.tabular) {
|
|
12566
|
+
needReset = true;
|
|
12567
|
+
} else if (_activeLyr && internal.layerIsEmpty(_activeLyr)) {
|
|
12568
|
+
needReset = false;
|
|
12569
|
+
} else if (!prevLyr) {
|
|
12548
12570
|
needReset = true;
|
|
12549
12571
|
} else {
|
|
12550
12572
|
needReset = mapNeedsReset(fullBounds, _ext.getFullBounds(), _ext.getBounds(), e.flags);
|
|
@@ -12554,45 +12576,19 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12554
12576
|
|
|
12555
12577
|
if (needReset) {
|
|
12556
12578
|
_ext.reset();
|
|
12579
|
+
if (_basemap) _basemap.refresh();
|
|
12557
12580
|
}
|
|
12558
12581
|
drawLayers();
|
|
12559
12582
|
map.dispatchEvent('updated');
|
|
12560
12583
|
}
|
|
12561
12584
|
|
|
12562
|
-
// Initialization just before displaying the map for the first time
|
|
12563
|
-
function initMap() {
|
|
12564
|
-
_ext.resize();
|
|
12565
|
-
_renderer = new LayerRenderer(gui, el, _ext, _mouse);
|
|
12566
|
-
gui.buttons.show();
|
|
12567
|
-
|
|
12568
|
-
if (opts.inspectorControl) {
|
|
12569
|
-
_hit = new HitControl(gui, _ext, _mouse),
|
|
12570
|
-
new InspectionControl2(gui, _hit);
|
|
12571
|
-
new SelectionTool(gui, _ext, _hit),
|
|
12572
|
-
new BoxTool(gui, _ext, _nav),
|
|
12573
|
-
new RectangleControl(gui, _hit),
|
|
12574
|
-
initInteractiveEditing(gui, _ext, _hit);
|
|
12575
|
-
_hit.on('change', updateOverlayLayer);
|
|
12576
|
-
}
|
|
12577
|
-
|
|
12578
|
-
_ext.on('change', function(e) {
|
|
12579
|
-
if (_basemap) _basemap.refresh(); // keep basemap synced up (if enabled)
|
|
12580
|
-
if (e.reset) return; // don't need to redraw map here if extent has been reset
|
|
12581
|
-
drawLayers('nav');
|
|
12582
|
-
});
|
|
12583
|
-
|
|
12584
|
-
gui.on('resize', function() {
|
|
12585
|
-
position.update(); // kludge to detect new map size after console toggle
|
|
12586
|
-
});
|
|
12587
|
-
}
|
|
12588
12585
|
|
|
12589
12586
|
function updateOverlayLayer(e) {
|
|
12590
|
-
var style = getOverlayStyle(_activeLyr.
|
|
12587
|
+
var style = getOverlayStyle(_activeLyr.gui.displayLayer, e, getGlobalStyleOptions());
|
|
12591
12588
|
if (style) {
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
}, _activeLyr);
|
|
12589
|
+
var displayLayer = filterLayerByIds(_activeLyr.gui.displayLayer, style.ids);
|
|
12590
|
+
var gui = Object.assign({}, _activeLyr.gui, {style, displayLayer});
|
|
12591
|
+
_overlayLyr = utils$1.defaults({gui}, _activeLyr);
|
|
12596
12592
|
} else {
|
|
12597
12593
|
_overlayLyr = null;
|
|
12598
12594
|
}
|
|
@@ -12623,12 +12619,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12623
12619
|
var b = new Bounds();
|
|
12624
12620
|
var layers = getContentLayers();
|
|
12625
12621
|
layers.forEach(function(lyr) {
|
|
12626
|
-
b.mergeBounds(lyr.bounds);
|
|
12622
|
+
b.mergeBounds(lyr.gui.bounds);
|
|
12627
12623
|
});
|
|
12628
12624
|
|
|
12629
12625
|
if (!b.hasBounds()) {
|
|
12630
12626
|
// assign bounds to empty layers, to prevent rendering errors downstream
|
|
12631
|
-
b.setBounds(0,0,0,0);
|
|
12627
|
+
// b.setBounds(0,0,0,0);
|
|
12628
|
+
b.setBounds(projectLatLonBBox([11.28,33.43,32.26,46.04], _dynamicCRS));
|
|
12632
12629
|
}
|
|
12633
12630
|
return b;
|
|
12634
12631
|
}
|
|
@@ -12646,19 +12643,18 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12646
12643
|
var widthPx = _ext.width();
|
|
12647
12644
|
var marginPct = widthPx < 700 && 3.5 || widthPx < 800 && 3 || 2.5;
|
|
12648
12645
|
if (isTableView()) {
|
|
12649
|
-
var n = internal.getFeatureCount(_activeLyr
|
|
12646
|
+
var n = internal.getFeatureCount(_activeLyr);
|
|
12650
12647
|
marginPct = n < 5 && 20 || n < 100 && 10 || 4;
|
|
12651
12648
|
}
|
|
12652
12649
|
b.scale(1 + marginPct / 100 * 2);
|
|
12653
12650
|
|
|
12654
12651
|
// Inflate display bounding box by a tiny amount (gives extent to single-point layers and collapsed shapes)
|
|
12655
12652
|
b.padBounds(1e-4, 1e-4, 1e-4, 1e-4);
|
|
12656
|
-
|
|
12657
12653
|
return b;
|
|
12658
12654
|
}
|
|
12659
12655
|
|
|
12660
12656
|
function isActiveLayer(lyr) {
|
|
12661
|
-
return _activeLyr && lyr == _activeLyr
|
|
12657
|
+
return _activeLyr && lyr == _activeLyr || false;
|
|
12662
12658
|
}
|
|
12663
12659
|
|
|
12664
12660
|
function isVisibleLayer(lyr) {
|
|
@@ -12666,12 +12662,12 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12666
12662
|
}
|
|
12667
12663
|
|
|
12668
12664
|
function isTableView() {
|
|
12669
|
-
return !!_activeLyr.tabular;
|
|
12665
|
+
return !!_activeLyr?.gui.tabular;
|
|
12670
12666
|
}
|
|
12671
12667
|
|
|
12672
12668
|
function findFrameLayer() {
|
|
12673
12669
|
return getVisibleMapLayers().find(function(lyr) {
|
|
12674
|
-
return internal.isFrameLayer(lyr.
|
|
12670
|
+
return internal.isFrameLayer(lyr.gui.displayLayer, lyr.gui.displayArcs);
|
|
12675
12671
|
});
|
|
12676
12672
|
}
|
|
12677
12673
|
|
|
@@ -12682,10 +12678,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12682
12678
|
|
|
12683
12679
|
function getFrameData() {
|
|
12684
12680
|
var lyr = findFrameLayer();
|
|
12685
|
-
return lyr && internal.getFrameLayerData(lyr
|
|
12681
|
+
return lyr && internal.getFrameLayerData(lyr, lyr.gui.displayArcs) || null;
|
|
12686
12682
|
}
|
|
12687
12683
|
|
|
12688
12684
|
function clearAllDisplayArcs() {
|
|
12685
|
+
model.forEachLayer(function(lyr) {
|
|
12686
|
+
if (lyr.gui) delete lyr.gui.arcCounts;
|
|
12687
|
+
});
|
|
12689
12688
|
model.getDatasets().forEach(function(o) {
|
|
12690
12689
|
delete o.gui;
|
|
12691
12690
|
});
|
|
@@ -12698,7 +12697,8 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12698
12697
|
if (isActiveLayer(o.layer)) {
|
|
12699
12698
|
layers.push(_activeLyr);
|
|
12700
12699
|
} else if (!isTableView()) {
|
|
12701
|
-
|
|
12700
|
+
enhanceLayerForDisplay(o.layer, o.dataset, getDisplayOptions());
|
|
12701
|
+
layers.push(o.layer);
|
|
12702
12702
|
}
|
|
12703
12703
|
});
|
|
12704
12704
|
_visibleLayers = layers;
|
|
@@ -12720,13 +12720,13 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12720
12720
|
return findActiveLayer(layers);
|
|
12721
12721
|
}
|
|
12722
12722
|
return layers.filter(function(o) {
|
|
12723
|
-
return !!o.geographic;
|
|
12723
|
+
return !!o.gui.geographic;
|
|
12724
12724
|
});
|
|
12725
12725
|
}
|
|
12726
12726
|
|
|
12727
12727
|
function getDrawableContentLayers() {
|
|
12728
12728
|
return getContentLayers().filter(function(lyr) {
|
|
12729
|
-
if (isActiveLayer(lyr
|
|
12729
|
+
if (isActiveLayer(lyr) && lyr.hidden) return false;
|
|
12730
12730
|
return true;
|
|
12731
12731
|
});
|
|
12732
12732
|
}
|
|
@@ -12740,31 +12740,33 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12740
12740
|
|
|
12741
12741
|
function updateLayerStyles(layers) {
|
|
12742
12742
|
layers.forEach(function(mapLayer, i) {
|
|
12743
|
+
var style;
|
|
12743
12744
|
if (mapLayer.active) {
|
|
12744
12745
|
// regenerating active style everytime, to support style change when
|
|
12745
12746
|
// switching between outline and preview modes.
|
|
12746
|
-
|
|
12747
|
-
if (
|
|
12747
|
+
style = getActiveLayerStyle(mapLayer.gui.displayLayer, getGlobalStyleOptions());
|
|
12748
|
+
if (style.type != 'styled' && layers.length > 1 && style.strokeColors) {
|
|
12748
12749
|
// kludge to hide ghosted layers when reference layers are present
|
|
12749
12750
|
// TODO: consider never showing ghosted layers (which appear after
|
|
12750
12751
|
// commands like dissolve and filter).
|
|
12751
|
-
|
|
12752
|
-
strokeColors: [null,
|
|
12753
|
-
},
|
|
12752
|
+
style = utils$1.defaults({
|
|
12753
|
+
strokeColors: [null, style.strokeColors[1]]
|
|
12754
|
+
}, style);
|
|
12754
12755
|
}
|
|
12755
12756
|
} else {
|
|
12756
|
-
if (mapLayer
|
|
12757
|
+
if (mapLayer == _activeLyr) {
|
|
12757
12758
|
console.error("Error: shared map layer");
|
|
12758
12759
|
}
|
|
12759
|
-
|
|
12760
|
+
style = getReferenceLayerStyle(mapLayer.gui.displayLayer, getGlobalStyleOptions());
|
|
12760
12761
|
}
|
|
12762
|
+
mapLayer.gui.style = style;
|
|
12761
12763
|
});
|
|
12762
12764
|
}
|
|
12763
12765
|
|
|
12764
12766
|
function sortMapLayers(layers) {
|
|
12765
12767
|
layers.sort(function(a, b) {
|
|
12766
12768
|
// assume that each layer has a menu_order (assigned by updateLayerStackOrder())
|
|
12767
|
-
return a.
|
|
12769
|
+
return a.menu_order - b.menu_order;
|
|
12768
12770
|
});
|
|
12769
12771
|
}
|
|
12770
12772
|
|
|
@@ -12844,6 +12846,102 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12844
12846
|
});
|
|
12845
12847
|
}
|
|
12846
12848
|
|
|
12849
|
+
function ContextMenu() {
|
|
12850
|
+
var body = document.querySelector('body');
|
|
12851
|
+
var menu = El('div').addClass('contextmenu rollover').appendTo(body);
|
|
12852
|
+
var _open = false;
|
|
12853
|
+
var _openCount = 0;
|
|
12854
|
+
document.addEventListener('mousedown', close);
|
|
12855
|
+
|
|
12856
|
+
this.isOpen = function() {
|
|
12857
|
+
return _open;
|
|
12858
|
+
};
|
|
12859
|
+
|
|
12860
|
+
function close() {
|
|
12861
|
+
var count = _openCount;
|
|
12862
|
+
if (!_open) return;
|
|
12863
|
+
setTimeout(function() {
|
|
12864
|
+
if (count == _openCount) {
|
|
12865
|
+
menu.hide();
|
|
12866
|
+
_open = false;
|
|
12867
|
+
}
|
|
12868
|
+
}, 300);
|
|
12869
|
+
}
|
|
12870
|
+
|
|
12871
|
+
function addMenuItem(label, func) {
|
|
12872
|
+
El('div')
|
|
12873
|
+
.appendTo(menu)
|
|
12874
|
+
.addClass('contextmenu-item')
|
|
12875
|
+
.html(label)
|
|
12876
|
+
.on('click', func)
|
|
12877
|
+
.show();
|
|
12878
|
+
}
|
|
12879
|
+
|
|
12880
|
+
this.open = function(e, lyr) {
|
|
12881
|
+
if (!lyr || !lyr.gui.geographic) return;
|
|
12882
|
+
_open = true;
|
|
12883
|
+
_openCount++;
|
|
12884
|
+
var rspace = body.clientWidth - e.pageX;
|
|
12885
|
+
var xoffs = 10;
|
|
12886
|
+
menu.empty().show();
|
|
12887
|
+
if (rspace > 150) {
|
|
12888
|
+
menu.css('left', e.pageX + xoffs + 'px');
|
|
12889
|
+
menu.css('right', null);
|
|
12890
|
+
} else {
|
|
12891
|
+
menu.css('right', (body.clientWidth - e.pageX + xoffs) + 'px');
|
|
12892
|
+
menu.css('left', null);
|
|
12893
|
+
}
|
|
12894
|
+
menu.css('top', (e.pageY - 15) + 'px');
|
|
12895
|
+
|
|
12896
|
+
// menu contents
|
|
12897
|
+
if (e.coordinates) {
|
|
12898
|
+
addCopyCoords();
|
|
12899
|
+
}
|
|
12900
|
+
if (e.deleteVertex) {
|
|
12901
|
+
addMenuItem('Delete vertex', e.deleteVertex);
|
|
12902
|
+
}
|
|
12903
|
+
if (e.ids?.length) {
|
|
12904
|
+
addMenuItem('Copy as GeoJSON', copyGeoJSON);
|
|
12905
|
+
}
|
|
12906
|
+
|
|
12907
|
+
function addCopyCoords() {
|
|
12908
|
+
var bbox = internal.getLayerBounds(lyr, lyr.gui.source.dataset.arcs).toArray();
|
|
12909
|
+
var coordStr = internal.getRoundedCoordString(e.coordinates, internal.getBoundsPrecisionForDisplay(bbox));
|
|
12910
|
+
var displayStr = '• ' + coordStr.replace(/-/g, '–').replace(',', ', ');
|
|
12911
|
+
addMenuItem(displayStr, function() {
|
|
12912
|
+
saveFileContentToClipboard(coordStr);
|
|
12913
|
+
});
|
|
12914
|
+
}
|
|
12915
|
+
|
|
12916
|
+
function copyGeoJSON() {
|
|
12917
|
+
var opts = {
|
|
12918
|
+
no_replace: true,
|
|
12919
|
+
ids: e.ids,
|
|
12920
|
+
quiet: true
|
|
12921
|
+
};
|
|
12922
|
+
var dataset = lyr.gui.source.dataset;
|
|
12923
|
+
var layer = mapshaper.cmd.filterFeatures(lyr, dataset.arcs, opts);
|
|
12924
|
+
// the drawing tool can send open paths with 'polygon' geometry type,
|
|
12925
|
+
// should be changed to 'polyline'
|
|
12926
|
+
if (layerHasOpenPaths(layer, dataset.arcs)) {
|
|
12927
|
+
layer.geometry_type = 'polyline';
|
|
12928
|
+
}
|
|
12929
|
+
var features = internal.exportLayerAsGeoJSON(layer, dataset, {rfc7946: true, prettify: true}, true, 'string');
|
|
12930
|
+
var str = internal.geojson.formatCollection({"type": "FeatureCollection"}, features);
|
|
12931
|
+
saveFileContentToClipboard(str);
|
|
12932
|
+
}
|
|
12933
|
+
};
|
|
12934
|
+
}
|
|
12935
|
+
|
|
12936
|
+
|
|
12937
|
+
function layerHasOpenPaths(layer, arcs) {
|
|
12938
|
+
var retn = false;
|
|
12939
|
+
internal.editShapes(layer.shapes, function(part) {
|
|
12940
|
+
if (!geom.pathIsClosed(part, arcs)) retn = true;
|
|
12941
|
+
});
|
|
12942
|
+
return retn;
|
|
12943
|
+
}
|
|
12944
|
+
|
|
12847
12945
|
function GuiInstance(container, opts) {
|
|
12848
12946
|
var gui = new ModeSwitcher();
|
|
12849
12947
|
opts = utils$1.extend({
|
|
@@ -12862,20 +12960,21 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12862
12960
|
gui.model = new Model(gui);
|
|
12863
12961
|
gui.keyboard = new KeyboardEvents(gui);
|
|
12864
12962
|
gui.buttons = new SidebarButtons(gui);
|
|
12865
|
-
gui.map = new MshpMap(gui);
|
|
12866
|
-
gui.interaction = new InteractionMode(gui);
|
|
12867
12963
|
gui.session = new SessionHistory(gui);
|
|
12964
|
+
gui.contextMenu = new ContextMenu();
|
|
12868
12965
|
gui.undo = new Undo(gui);
|
|
12869
|
-
gui.
|
|
12870
|
-
|
|
12966
|
+
gui.map = new MshpMap(gui);
|
|
12871
12967
|
if (opts.saveControl) {
|
|
12872
12968
|
new SessionSnapshots(gui);
|
|
12873
12969
|
}
|
|
12970
|
+
gui.interaction = new InteractionMode(gui);
|
|
12971
|
+
gui.state = {};
|
|
12874
12972
|
|
|
12875
12973
|
var msgCount = 0;
|
|
12876
12974
|
var clearMsg;
|
|
12877
12975
|
|
|
12878
12976
|
initModeRules(gui);
|
|
12977
|
+
gui.map.init();
|
|
12879
12978
|
|
|
12880
12979
|
gui.showProgressMessage = function(msg) {
|
|
12881
12980
|
if (!gui.progressMessage) {
|
|
@@ -12988,9 +13087,10 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
12988
13087
|
importOpts = getImportOpts(manifest),
|
|
12989
13088
|
gui = new GuiInstance('body');
|
|
12990
13089
|
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
13090
|
+
// TODO: re-enable the "blurb"
|
|
13091
|
+
// if (manifest.blurb) {
|
|
13092
|
+
// El('#splash-screen-blurb').text(manifest.blurb);
|
|
13093
|
+
// }
|
|
12994
13094
|
|
|
12995
13095
|
new AlertControl(gui);
|
|
12996
13096
|
new RepairControl(gui);
|
|
@@ -13021,17 +13121,19 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
13021
13121
|
});
|
|
13022
13122
|
|
|
13023
13123
|
// Initial display configuration
|
|
13024
|
-
gui.
|
|
13124
|
+
gui.on('mode', function(e) {
|
|
13025
13125
|
if (dataLoaded) return;
|
|
13026
13126
|
dataLoaded = true;
|
|
13127
|
+
gui.buttons.show();
|
|
13027
13128
|
El('#mode-buttons').show();
|
|
13129
|
+
El('#splash-buttons').hide();
|
|
13130
|
+
El('body').addClass('map-view');
|
|
13028
13131
|
if (importOpts.display_all) {
|
|
13029
13132
|
gui.model.getLayers().forEach(function(o) {
|
|
13030
13133
|
gui.map.setLayerPinning(o, true);
|
|
13031
13134
|
});
|
|
13032
13135
|
}
|
|
13033
13136
|
gui.console.runInitialCommands(getInitialConsoleCommands());
|
|
13034
|
-
|
|
13035
13137
|
});
|
|
13036
13138
|
};
|
|
13037
13139
|
|