mapshaper 0.7.0 → 0.7.2
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/README.md +5 -3
- package/mapshaper.js +363 -62
- package/package.json +6 -2
- package/www/.nojekyll +0 -0
- package/www/assets/jetbrains-mono-regular.woff2 +0 -0
- package/www/assets/static-page.css +179 -0
- package/www/docs/_assets/cmd-search.js +213 -0
- package/www/docs/_assets/docs.css +722 -0
- package/www/docs/_assets/docs.js +75 -0
- package/www/docs/_assets/highlight.css +10 -0
- package/www/docs/essentials/command-line.html +127 -0
- package/www/docs/essentials/command-line.html.md +112 -0
- package/www/docs/essentials/web-app.html +138 -0
- package/www/docs/essentials/web-app.html.md +106 -0
- package/www/docs/examples/basics.html +275 -0
- package/www/docs/examples/basics.html.md +370 -0
- package/www/docs/examples/data/Makefile +31 -0
- package/www/docs/examples/data/globe.msx +0 -0
- package/www/docs/examples/data/globe.svg +616 -0
- package/www/docs/examples/data/globe.txt +21 -0
- package/www/docs/examples/data/globe.zip +0 -0
- package/www/docs/examples/data/ne_50m_admin_0_countries.geojson +1 -0
- package/www/docs/examples/data/ne_50m_admin_1_states_provinces_lakes.geojson +1 -0
- package/www/docs/examples/data/us-states.msx +0 -0
- package/www/docs/examples/data/us-states.svg +56 -0
- package/www/docs/examples/data/us-states.txt +6 -0
- package/www/docs/examples/data/us-states.zip +0 -0
- package/www/docs/examples/globe.html +108 -0
- package/www/docs/examples/globe.html.md +64 -0
- package/www/docs/examples/us-states.html +88 -0
- package/www/docs/examples/us-states.html.md +44 -0
- package/www/docs/formats/csv.html +127 -0
- package/www/docs/formats/csv.html.md +97 -0
- package/www/docs/formats/dbf.html +87 -0
- package/www/docs/formats/dbf.html.md +39 -0
- package/www/docs/formats/flatgeobuf.html +85 -0
- package/www/docs/formats/flatgeobuf.html.md +41 -0
- package/www/docs/formats/geojson.html +107 -0
- package/www/docs/formats/geojson.html.md +65 -0
- package/www/docs/formats/geopackage.html +87 -0
- package/www/docs/formats/geopackage.html.md +42 -0
- package/www/docs/formats/json.html +83 -0
- package/www/docs/formats/json.html.md +35 -0
- package/www/docs/formats/kml.html +82 -0
- package/www/docs/formats/kml.html.md +39 -0
- package/www/docs/formats/overview.html +191 -0
- package/www/docs/formats/overview.html.md +34 -0
- package/www/docs/formats/shapefile.html +123 -0
- package/www/docs/formats/shapefile.html.md +84 -0
- package/www/docs/formats/snapshot.html +87 -0
- package/www/docs/formats/snapshot.html.md +39 -0
- package/www/docs/formats/svg.html +99 -0
- package/www/docs/formats/svg.html.md +51 -0
- package/www/docs/formats/topojson.html +102 -0
- package/www/docs/formats/topojson.html.md +54 -0
- package/www/docs/gallery/index.html +80 -0
- package/www/docs/gallery/index.html.md +29 -0
- package/www/docs/guides/combining-layers.html +109 -0
- package/www/docs/guides/combining-layers.html.md +89 -0
- package/www/docs/guides/expressions.html +600 -0
- package/www/docs/guides/expressions.html.md +376 -0
- package/www/docs/guides/programmatic.html +117 -0
- package/www/docs/guides/programmatic.html.md +91 -0
- package/www/docs/guides/projections.html +158 -0
- package/www/docs/guides/projections.html.md +118 -0
- package/www/docs/guides/simplification.html +110 -0
- package/www/docs/guides/simplification.html.md +96 -0
- package/www/docs/guides/topology.html +90 -0
- package/www/docs/guides/topology.html.md +63 -0
- package/www/docs/images/cal-counties.png +0 -0
- package/www/docs/images/simplification-detail.png +0 -0
- package/www/docs/images/simplification-dp.png +0 -0
- package/www/docs/images/simplification-mod2.png +0 -0
- package/www/docs/images/tiger-counties.png +0 -0
- package/www/docs/index.html +101 -0
- package/www/docs/index.html.md +59 -0
- package/www/docs/reference.html +1302 -0
- package/www/docs/reference.html.md +1817 -0
- package/www/docs/whats-new.html +77 -0
- package/www/docs/whats-new.html.md +59 -0
- package/www/index.html +47 -4
- package/www/llms-full.txt +4053 -0
- package/www/llms.txt +55 -0
- package/www/mapshaper-gui.js +299 -5
- package/www/mapshaper.js +363 -62
- package/www/page.css +189 -11
- package/www/privacy.html +1 -112
- package/www/sponsor.html +4 -164
- package/www/terms.html +1 -112
package/www/llms.txt
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Mapshaper
|
|
2
|
+
|
|
3
|
+
> Mapshaper is a command-line tool and in-browser editor for geographic vector data. It reads and writes Shapefile, GeoJSON, TopoJSON, GeoPackage, FlatGeobuf, KML/KMZ, CSV/TSV, DBF, JSON records and SVG, and can simplify, clip, dissolve, join, project and otherwise transform layers. The CLI and web app share the same command language, so commands written interactively in the browser console run unchanged in scripts.
|
|
4
|
+
|
|
5
|
+
These docs cover the same material in two forms:
|
|
6
|
+
|
|
7
|
+
- **Markdown mirrors** of every page (this file links to them) for use by language models and other tools.
|
|
8
|
+
- **HTML pages** at the same paths without the trailing `.md`, with navigation, search and syntax highlighting, intended for humans.
|
|
9
|
+
|
|
10
|
+
When in doubt about a command's behavior, the [command reference](https://mapshaper.org/docs/reference.html.md) is the source of truth. The Node.js source code lives at <https://github.com/mbloch/mapshaper>.
|
|
11
|
+
|
|
12
|
+
## Getting started
|
|
13
|
+
|
|
14
|
+
- [The command-line tool](https://mapshaper.org/docs/essentials/command-line.html.md): Install the mapshaper CLI, run your first commands and learn how Mapshaper organizes data into layers.
|
|
15
|
+
- [The web app](https://mapshaper.org/docs/essentials/web-app.html.md): A tour of Mapshaper's web interface, including loading data, the console, the right-click menu, snapshots, browser support and running it locally.
|
|
16
|
+
|
|
17
|
+
## Guides
|
|
18
|
+
|
|
19
|
+
- [Simplification](https://mapshaper.org/docs/guides/simplification.html.md): How to choose between Visvalingam and Douglas-Peucker, and tips for getting good-looking results from polygon and polyline simplification.
|
|
20
|
+
- [JavaScript expressions](https://mapshaper.org/docs/guides/expressions.html.md): Reference for the JS expressions used in -each, -filter, -calc, -where, -sort, -if, -run and other Mapshaper commands.
|
|
21
|
+
- [Projections](https://mapshaper.org/docs/guides/projections.html.md): How to reproject geographic data with Mapshaper, including CRS notation, built-in aliases, and the albersusa composite projection.
|
|
22
|
+
- [Topology and cleaning](https://mapshaper.org/docs/guides/topology.html.md): How Mapshaper detects shared boundaries between features, and how to fix the common topology errors that creep into Shapefile and GeoJSON datasets.
|
|
23
|
+
- [Combining two layers](https://mapshaper.org/docs/guides/combining-layers.html.md): A step-by-step walkthrough of combining and pruning two boundary layers to produce a custom GeoJSON basemap.
|
|
24
|
+
- [Using Mapshaper from Node.js](https://mapshaper.org/docs/guides/programmatic.html.md): Integrating Mapshaper into JavaScript builds and applications via its Node.js API.
|
|
25
|
+
|
|
26
|
+
## Formats
|
|
27
|
+
|
|
28
|
+
- [Shapefile](https://mapshaper.org/docs/formats/shapefile.html.md): How Mapshaper reads and writes ESRI Shapefiles, including encoding and field-name notes.
|
|
29
|
+
- [GeoJSON](https://mapshaper.org/docs/formats/geojson.html.md): How Mapshaper reads and writes GeoJSON, including precision, ndjson and RFC 7946 options.
|
|
30
|
+
- [TopoJSON](https://mapshaper.org/docs/formats/topojson.html.md): How Mapshaper reads and writes TopoJSON, including quantization and multi-layer output.
|
|
31
|
+
- [GeoPackage](https://mapshaper.org/docs/formats/geopackage.html.md): How Mapshaper reads and writes GeoPackage (.gpkg), the OGC's SQLite-based GIS container.
|
|
32
|
+
- [FlatGeobuf](https://mapshaper.org/docs/formats/flatgeobuf.html.md): How Mapshaper reads and writes FlatGeobuf (.fgb), a streamable binary vector format.
|
|
33
|
+
- [KML / KMZ](https://mapshaper.org/docs/formats/kml.html.md): How Mapshaper reads and writes KML and KMZ files for use with Google Earth and similar tools.
|
|
34
|
+
- [CSV / TSV](https://mapshaper.org/docs/formats/csv.html.md): How Mapshaper reads and writes CSV/TSV, including encoding, type hints and large-file handling.
|
|
35
|
+
- [DBF](https://mapshaper.org/docs/formats/dbf.html.md): How Mapshaper reads and writes standalone DBF files, the tabular component of Shapefile.
|
|
36
|
+
- [JSON records](https://mapshaper.org/docs/formats/json.html.md): How Mapshaper reads and writes plain JSON arrays of objects (no geometry), useful for tabular data exchange.
|
|
37
|
+
- [SVG](https://mapshaper.org/docs/formats/svg.html.md): How Mapshaper exports vector data as SVG, including width, scale, bounding-box and per-feature data attributes.
|
|
38
|
+
- [Mapshaper snapshot](https://mapshaper.org/docs/formats/snapshot.html.md): A single-file binary snapshot of a Mapshaper session, used to save work in progress and share reproducible projects.
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
- [Basics](https://mapshaper.org/docs/examples/basics.html.md): A scannable collection of short Mapshaper recipes for common GIS tasks — format conversion, joins, simplification, dissolves, projection, classification, web export and more.
|
|
43
|
+
- [Globe](https://mapshaper.org/docs/examples/globe.html.md): A simple locator map in the shape of a globe.
|
|
44
|
+
- [U.S. state map](https://mapshaper.org/docs/examples/us-states.html.md): Simple U.S. state map with random, non-adjacent colors
|
|
45
|
+
|
|
46
|
+
## Command reference
|
|
47
|
+
|
|
48
|
+
- [Command reference](https://mapshaper.org/docs/reference.html.md): Exhaustive reference for every mapshaper command and its options, with short examples.
|
|
49
|
+
|
|
50
|
+
## Optional
|
|
51
|
+
|
|
52
|
+
- [Home](https://mapshaper.org/docs/index.html.md): Documentation for mapshaper, the command-line tool and web app for editing Shapefile, GeoJSON, TopoJSON, GeoPackage, FlatGeobuf, KML and CSV data.
|
|
53
|
+
- [What's new](https://mapshaper.org/docs/whats-new.html.md): A curated log of recently added user-visible features in Mapshaper, with links into the rest of the docs.
|
|
54
|
+
- [Gallery](https://mapshaper.org/docs/gallery/index.html.md): Example maps made with Mapshaper, with full source data and reproduction steps for each.
|
|
55
|
+
- [Formats overview](https://mapshaper.org/docs/formats/overview.html.md): A quick comparison of every file format Mapshaper can read and write, with links to per-format details.
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -4000,7 +4000,8 @@
|
|
|
4000
4000
|
var historyId = 0;
|
|
4001
4001
|
var _isOpen = false;
|
|
4002
4002
|
var btn = gui.container.findChild('.console-btn').on('click', toggle);
|
|
4003
|
-
var globals = {}; // share user-defined globals between runs
|
|
4003
|
+
var globals = {}; // share user-defined globals (job.defs) between runs
|
|
4004
|
+
var sharedVars = {}; // share -vars / -defaults templating scope between runs
|
|
4004
4005
|
|
|
4005
4006
|
// expose this function, so other components can run commands (e.g. box tool)
|
|
4006
4007
|
this.runMapshaperCommands = runMapshaperCommands;
|
|
@@ -4404,6 +4405,7 @@
|
|
|
4404
4405
|
job = new internal.Job(model);
|
|
4405
4406
|
|
|
4406
4407
|
job.defs = globals; // share globals between runs
|
|
4408
|
+
job.vars = sharedVars; // share templating scope between runs
|
|
4407
4409
|
internal.runParsedCommands(commands, job, function(err) {
|
|
4408
4410
|
var flags = getCommandFlags(commands),
|
|
4409
4411
|
active2 = model.getActiveLayer(),
|
|
@@ -5653,6 +5655,56 @@
|
|
|
5653
5655
|
}
|
|
5654
5656
|
}
|
|
5655
5657
|
|
|
5658
|
+
// Hamburger menu in the top-right of the header. After the user starts
|
|
5659
|
+
// editing, the splash bar (Docs / GitHub / Survey / Sponsor) is hidden by
|
|
5660
|
+
// gui.mjs; this menu is the post-edit replacement, exposing the same links
|
|
5661
|
+
// from a single button so the editing controls don't have to share the row.
|
|
5662
|
+
|
|
5663
|
+
|
|
5664
|
+
function HeaderMenu() {
|
|
5665
|
+
var btn = El('#header-menu-btn');
|
|
5666
|
+
var dropdown = El('#header-menu-dropdown');
|
|
5667
|
+
if (!btn.node() || !dropdown.node()) return; // markup missing; nothing to do
|
|
5668
|
+
|
|
5669
|
+
var open = false;
|
|
5670
|
+
|
|
5671
|
+
function setOpen(state) {
|
|
5672
|
+
if (state === open) return;
|
|
5673
|
+
open = state;
|
|
5674
|
+
dropdown.classed('hidden', !open);
|
|
5675
|
+
btn.attr('aria-expanded', open ? 'true' : 'false');
|
|
5676
|
+
}
|
|
5677
|
+
|
|
5678
|
+
btn.on('click', function(e) {
|
|
5679
|
+
e.stopPropagation();
|
|
5680
|
+
setOpen(!open);
|
|
5681
|
+
});
|
|
5682
|
+
|
|
5683
|
+
// Keyboard activation matches button semantics for the role="button" span.
|
|
5684
|
+
btn.on('keydown', function(e) {
|
|
5685
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
5686
|
+
e.preventDefault();
|
|
5687
|
+
setOpen(!open);
|
|
5688
|
+
}
|
|
5689
|
+
});
|
|
5690
|
+
|
|
5691
|
+
// Outside click closes the menu. Clicks on menu items also close it (they
|
|
5692
|
+
// navigate or open in a new tab; either way the menu shouldn't linger).
|
|
5693
|
+
document.addEventListener('click', function(e) {
|
|
5694
|
+
if (!open) return;
|
|
5695
|
+
var target = e.target;
|
|
5696
|
+
if (btn.node().contains(target)) return;
|
|
5697
|
+
setOpen(false);
|
|
5698
|
+
});
|
|
5699
|
+
|
|
5700
|
+
document.addEventListener('keydown', function(e) {
|
|
5701
|
+
if (open && e.key === 'Escape') {
|
|
5702
|
+
setOpen(false);
|
|
5703
|
+
btn.node().focus();
|
|
5704
|
+
}
|
|
5705
|
+
});
|
|
5706
|
+
}
|
|
5707
|
+
|
|
5656
5708
|
function SessionHistory(gui) {
|
|
5657
5709
|
var commands = [];
|
|
5658
5710
|
// index of first command after the last "save" boundary; commands at indices
|
|
@@ -5991,6 +6043,15 @@
|
|
|
5991
6043
|
|
|
5992
6044
|
this.clear = function() {
|
|
5993
6045
|
reset();
|
|
6046
|
+
fireHistoryChange();
|
|
6047
|
+
};
|
|
6048
|
+
|
|
6049
|
+
this.canUndo = function() {
|
|
6050
|
+
return history.length - offset > 0;
|
|
6051
|
+
};
|
|
6052
|
+
|
|
6053
|
+
this.canRedo = function() {
|
|
6054
|
+
return offset > 0;
|
|
5994
6055
|
};
|
|
5995
6056
|
|
|
5996
6057
|
function addHistoryState(undo, redo) {
|
|
@@ -5999,6 +6060,14 @@
|
|
|
5999
6060
|
offset = 0;
|
|
6000
6061
|
}
|
|
6001
6062
|
history.push({undo, redo});
|
|
6063
|
+
fireHistoryChange();
|
|
6064
|
+
}
|
|
6065
|
+
|
|
6066
|
+
function fireHistoryChange() {
|
|
6067
|
+
gui.dispatchEvent('history_change', {
|
|
6068
|
+
canUndo: history.length - offset > 0,
|
|
6069
|
+
canRedo: offset > 0
|
|
6070
|
+
});
|
|
6002
6071
|
}
|
|
6003
6072
|
|
|
6004
6073
|
this.undo = function() {
|
|
@@ -6011,6 +6080,7 @@
|
|
|
6011
6080
|
item.undo();
|
|
6012
6081
|
gui.dispatchEvent('undo_redo_post', {type: 'undo'});
|
|
6013
6082
|
gui.dispatchEvent('map-needs-refresh');
|
|
6083
|
+
fireHistoryChange();
|
|
6014
6084
|
}
|
|
6015
6085
|
};
|
|
6016
6086
|
|
|
@@ -6022,6 +6092,7 @@
|
|
|
6022
6092
|
item.redo();
|
|
6023
6093
|
gui.dispatchEvent('undo_redo_post', {type: 'redo'});
|
|
6024
6094
|
gui.dispatchEvent('map-needs-refresh');
|
|
6095
|
+
fireHistoryChange();
|
|
6025
6096
|
};
|
|
6026
6097
|
|
|
6027
6098
|
function getHistoryItem() {
|
|
@@ -6335,6 +6406,10 @@
|
|
|
6335
6406
|
return ['info', 'selection', 'data', 'box', 'labels', 'edit_points', 'rectangles'].includes(mode);
|
|
6336
6407
|
};
|
|
6337
6408
|
|
|
6409
|
+
this.modeSupportsUndo = function(mode) {
|
|
6410
|
+
return ['data', 'labels', 'edit_points', 'edit_lines', 'edit_polygons', 'vertices', 'rectangles'].includes(mode);
|
|
6411
|
+
};
|
|
6412
|
+
|
|
6338
6413
|
this.getMode = getInteractionMode;
|
|
6339
6414
|
|
|
6340
6415
|
this.setMode = function(mode) {
|
|
@@ -6477,6 +6552,214 @@
|
|
|
6477
6552
|
}
|
|
6478
6553
|
}
|
|
6479
6554
|
|
|
6555
|
+
// A reusable floating toolbar anchored at the bottom-center of the map area.
|
|
6556
|
+
//
|
|
6557
|
+
// Multiple toolbars stack vertically inside a shared container, so additional
|
|
6558
|
+
// per-mode toolbars (e.g. feature styling) can coexist with the edit toolbar.
|
|
6559
|
+
//
|
|
6560
|
+
// The DOM is structured to leave room for a future drag handle without
|
|
6561
|
+
// requiring rework: the toolbar element is a flex row with a content slot,
|
|
6562
|
+
// and a sibling drag-handle slot can be added later.
|
|
6563
|
+
//
|
|
6564
|
+
// Constructor options:
|
|
6565
|
+
// name: optional CSS class added to the toolbar element
|
|
6566
|
+
// transition: ms for the show/hide transition (default 150)
|
|
6567
|
+
//
|
|
6568
|
+
// API:
|
|
6569
|
+
// toolbar.addButton(iconRef, opts) -> ToolbarButton
|
|
6570
|
+
// toolbar.addSeparator()
|
|
6571
|
+
// toolbar.show()
|
|
6572
|
+
// toolbar.hide()
|
|
6573
|
+
// toolbar.visible()
|
|
6574
|
+
// toolbar.node()
|
|
6575
|
+
|
|
6576
|
+
function FloatingToolbar(gui, opts) {
|
|
6577
|
+
opts = opts || {};
|
|
6578
|
+
var transitionMs = opts.transition || 150;
|
|
6579
|
+
var root = gui.container.findChild('.mshp-main-map');
|
|
6580
|
+
var stack = root.findChild('.floating-toolbar-stack');
|
|
6581
|
+
if (!stack) {
|
|
6582
|
+
stack = El('div').addClass('floating-toolbar-stack').appendTo(root);
|
|
6583
|
+
}
|
|
6584
|
+
var el = El('div').addClass('floating-toolbar');
|
|
6585
|
+
if (opts.name) el.addClass(opts.name);
|
|
6586
|
+
var content = El('div').addClass('floating-toolbar-content').appendTo(el);
|
|
6587
|
+
var visible = false;
|
|
6588
|
+
var hideTimer = null;
|
|
6589
|
+
|
|
6590
|
+
el.appendTo(stack);
|
|
6591
|
+
el.css('display', 'none');
|
|
6592
|
+
|
|
6593
|
+
// Hide when this gui instance becomes inactive (e.g. multi-instance mode)
|
|
6594
|
+
gui.on('active', updateVisibility);
|
|
6595
|
+
gui.on('inactive', updateVisibility);
|
|
6596
|
+
|
|
6597
|
+
this.addButton = function(iconRef, btnOpts) {
|
|
6598
|
+
return new ToolbarButton(content, iconRef, btnOpts || {});
|
|
6599
|
+
};
|
|
6600
|
+
|
|
6601
|
+
this.addSeparator = function() {
|
|
6602
|
+
return El('div').addClass('floating-toolbar-separator').appendTo(content);
|
|
6603
|
+
};
|
|
6604
|
+
|
|
6605
|
+
this.show = function() {
|
|
6606
|
+
if (visible) return;
|
|
6607
|
+
visible = true;
|
|
6608
|
+
updateVisibility();
|
|
6609
|
+
};
|
|
6610
|
+
|
|
6611
|
+
this.hide = function() {
|
|
6612
|
+
if (!visible) return;
|
|
6613
|
+
visible = false;
|
|
6614
|
+
updateVisibility();
|
|
6615
|
+
};
|
|
6616
|
+
|
|
6617
|
+
this.visible = function() {
|
|
6618
|
+
return visible;
|
|
6619
|
+
};
|
|
6620
|
+
|
|
6621
|
+
this.node = function() {
|
|
6622
|
+
return el.node();
|
|
6623
|
+
};
|
|
6624
|
+
|
|
6625
|
+
function updateVisibility() {
|
|
6626
|
+
var shouldShow = visible && GUI.isActiveInstance(gui);
|
|
6627
|
+
if (shouldShow) {
|
|
6628
|
+
clearTimeout(hideTimer);
|
|
6629
|
+
hideTimer = null;
|
|
6630
|
+
el.css('display', 'flex');
|
|
6631
|
+
// wait one frame so the browser registers the initial state before
|
|
6632
|
+
// the transition kicks in
|
|
6633
|
+
requestAnimationFrame(function() {
|
|
6634
|
+
el.addClass('visible');
|
|
6635
|
+
});
|
|
6636
|
+
} else {
|
|
6637
|
+
el.removeClass('visible');
|
|
6638
|
+
// wait for the transition to finish before hiding completely
|
|
6639
|
+
clearTimeout(hideTimer);
|
|
6640
|
+
hideTimer = setTimeout(function() {
|
|
6641
|
+
if (!(visible && GUI.isActiveInstance(gui))) {
|
|
6642
|
+
el.css('display', 'none');
|
|
6643
|
+
}
|
|
6644
|
+
}, transitionMs);
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
6648
|
+
|
|
6649
|
+
function ToolbarButton(parent, iconRef, opts) {
|
|
6650
|
+
var btn = El('div').addClass('floating-toolbar-btn').appendTo(parent);
|
|
6651
|
+
if (iconRef) {
|
|
6652
|
+
var iconNode = El('body').findChild(iconRef);
|
|
6653
|
+
if (iconNode) {
|
|
6654
|
+
var icon = iconNode.node().cloneNode(true);
|
|
6655
|
+
if (icon.hasAttribute('id')) icon.removeAttribute('id');
|
|
6656
|
+
btn.node().appendChild(icon);
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
var enabled = true;
|
|
6660
|
+
var clickHandlers = [];
|
|
6661
|
+
|
|
6662
|
+
if (opts.tooltip) setTooltip(opts.tooltip);
|
|
6663
|
+
|
|
6664
|
+
// Block native dblclick to avoid the map's double-click zoom
|
|
6665
|
+
btn.on('dblclick', function(e) { e.stopPropagation(); });
|
|
6666
|
+
|
|
6667
|
+
btn.on('click', function(e) {
|
|
6668
|
+
if (!enabled) return;
|
|
6669
|
+
for (var i = 0; i < clickHandlers.length; i++) {
|
|
6670
|
+
clickHandlers[i](e);
|
|
6671
|
+
}
|
|
6672
|
+
});
|
|
6673
|
+
|
|
6674
|
+
this.on = function(event, fn) {
|
|
6675
|
+
if (event == 'click') {
|
|
6676
|
+
clickHandlers.push(fn);
|
|
6677
|
+
} else {
|
|
6678
|
+
btn.on(event, fn);
|
|
6679
|
+
}
|
|
6680
|
+
return this;
|
|
6681
|
+
};
|
|
6682
|
+
|
|
6683
|
+
this.enable = function() { return this.setEnabled(true); };
|
|
6684
|
+
this.disable = function() { return this.setEnabled(false); };
|
|
6685
|
+
|
|
6686
|
+
this.setEnabled = function(b) {
|
|
6687
|
+
enabled = !!b;
|
|
6688
|
+
btn.classed('disabled', !enabled);
|
|
6689
|
+
return this;
|
|
6690
|
+
};
|
|
6691
|
+
|
|
6692
|
+
this.setTooltip = setTooltip;
|
|
6693
|
+
|
|
6694
|
+
this.node = function() {
|
|
6695
|
+
return btn.node();
|
|
6696
|
+
};
|
|
6697
|
+
|
|
6698
|
+
function setTooltip(text) {
|
|
6699
|
+
btn.attr('data-tooltip', text);
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
|
|
6703
|
+
// Floating toolbar that exposes undo/redo while the user is in an editing
|
|
6704
|
+
// interaction mode. The toolbar is the first consumer of FloatingToolbar;
|
|
6705
|
+
// future per-mode toolbars (e.g. feature styling) can follow the same pattern.
|
|
6706
|
+
|
|
6707
|
+
function EditToolbar(gui) {
|
|
6708
|
+
var toolbar = new FloatingToolbar(gui, { name: 'edit-toolbar' });
|
|
6709
|
+
var isMac = navigator.userAgent.includes('Mac');
|
|
6710
|
+
var modKey = isMac ? '\u2318' : 'Ctrl+';
|
|
6711
|
+
var shiftKey = isMac ? '\u21e7' : 'Shift+';
|
|
6712
|
+
|
|
6713
|
+
var undoBtn = toolbar.addButton('#undo-icon', {
|
|
6714
|
+
tooltip: 'Undo (' + modKey + 'Z)'
|
|
6715
|
+
}).on('click', function() {
|
|
6716
|
+
gui.undo.undo();
|
|
6717
|
+
});
|
|
6718
|
+
|
|
6719
|
+
var redoBtn = toolbar.addButton('#redo-icon', {
|
|
6720
|
+
tooltip: 'Redo (' + shiftKey + modKey + 'Z)'
|
|
6721
|
+
}).on('click', function() {
|
|
6722
|
+
gui.undo.redo();
|
|
6723
|
+
});
|
|
6724
|
+
|
|
6725
|
+
updateButtons();
|
|
6726
|
+
|
|
6727
|
+
gui.on('interaction_mode_change', function() {
|
|
6728
|
+
updateVisibility();
|
|
6729
|
+
// history is cleared on mode change; refresh button states next tick
|
|
6730
|
+
updateButtons();
|
|
6731
|
+
});
|
|
6732
|
+
|
|
6733
|
+
gui.on('history_change', function(e) {
|
|
6734
|
+
undoBtn.setEnabled(!!e.canUndo);
|
|
6735
|
+
redoBtn.setEnabled(!!e.canRedo);
|
|
6736
|
+
// Visibility may also depend on history (e.g. attribute edits via popup
|
|
6737
|
+
// happen in modes that don't otherwise support undo).
|
|
6738
|
+
updateVisibility();
|
|
6739
|
+
});
|
|
6740
|
+
|
|
6741
|
+
updateVisibility();
|
|
6742
|
+
|
|
6743
|
+
function updateVisibility() {
|
|
6744
|
+
if (!gui.interaction) {
|
|
6745
|
+
toolbar.hide();
|
|
6746
|
+
return;
|
|
6747
|
+
}
|
|
6748
|
+
var mode = gui.interaction.getMode();
|
|
6749
|
+
var hasHistory = gui.undo.canUndo() || gui.undo.canRedo();
|
|
6750
|
+
if (gui.interaction.modeSupportsUndo(mode) || hasHistory) {
|
|
6751
|
+
toolbar.show();
|
|
6752
|
+
} else {
|
|
6753
|
+
toolbar.hide();
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6757
|
+
function updateButtons() {
|
|
6758
|
+
undoBtn.setEnabled(gui.undo.canUndo());
|
|
6759
|
+
redoBtn.setEnabled(gui.undo.canRedo());
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
|
|
6480
6763
|
function Model(gui) {
|
|
6481
6764
|
var self = new internal.Catalog();
|
|
6482
6765
|
var deleteLayer = self.deleteLayer;
|
|
@@ -6609,6 +6892,10 @@
|
|
|
6609
6892
|
LOGGING = true;
|
|
6610
6893
|
}
|
|
6611
6894
|
|
|
6895
|
+
function disableLogging() {
|
|
6896
|
+
LOGGING = false;
|
|
6897
|
+
}
|
|
6898
|
+
|
|
6612
6899
|
function loggingEnabled() {
|
|
6613
6900
|
return !!LOGGING;
|
|
6614
6901
|
}
|
|
@@ -9570,6 +9857,10 @@
|
|
|
9570
9857
|
|
|
9571
9858
|
box.setDataCoords = function(bbox) {
|
|
9572
9859
|
boxCoords = bbox;
|
|
9860
|
+
// Box is being placed programmatically (e.g. around a pinned rectangle);
|
|
9861
|
+
// enable pointer events on the handles so they can be dragged. Without
|
|
9862
|
+
// this, mousedowns fall through to the map and trigger pan.
|
|
9863
|
+
el.classed('hittable', true);
|
|
9573
9864
|
redraw();
|
|
9574
9865
|
};
|
|
9575
9866
|
|
|
@@ -9593,6 +9884,7 @@
|
|
|
9593
9884
|
// remove the current box (if any)
|
|
9594
9885
|
box.hide = function() {
|
|
9595
9886
|
el.hide();
|
|
9887
|
+
el.classed('hittable', false);
|
|
9596
9888
|
boxCoords = null;
|
|
9597
9889
|
_visible = false;
|
|
9598
9890
|
clickStartPoint = null;
|
|
@@ -10943,8 +11235,8 @@
|
|
|
10943
11235
|
function showInstructions() {
|
|
10944
11236
|
var isMac = navigator.userAgent.includes('Mac');
|
|
10945
11237
|
var symbol = isMac ? '⌘' : '^';
|
|
10946
|
-
var msg = `Instructions:
|
|
10947
|
-
alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '
|
|
11238
|
+
var msg = `Instructions: click on the map to add points. Move points by dragging.`;
|
|
11239
|
+
alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '290px'});
|
|
10948
11240
|
}
|
|
10949
11241
|
|
|
10950
11242
|
gui.on('interaction_mode_change', function(e) {
|
|
@@ -11161,9 +11453,9 @@
|
|
|
11161
11453
|
function showInstructions() {
|
|
11162
11454
|
var isMac = navigator.userAgent.includes('Mac');
|
|
11163
11455
|
var undoKey = isMac ? '⌘' : '^';
|
|
11164
|
-
var msg = `Instructions: click to start a path, click or drag to keep drawing. Drag vertices to reshape a path
|
|
11456
|
+
var msg = `Instructions: click to start a path, click or drag to keep drawing. Drag vertices to reshape a path.`;
|
|
11165
11457
|
alert = showPopupAlert(msg, null, {
|
|
11166
|
-
non_blocking: true, max_width: '
|
|
11458
|
+
non_blocking: true, max_width: '350px'});
|
|
11167
11459
|
}
|
|
11168
11460
|
|
|
11169
11461
|
function hideInstructions() {
|
|
@@ -14625,6 +14917,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14625
14917
|
new SessionSnapshots(gui);
|
|
14626
14918
|
}
|
|
14627
14919
|
gui.interaction = new InteractionMode(gui);
|
|
14920
|
+
gui.editToolbar = new EditToolbar(gui);
|
|
14628
14921
|
|
|
14629
14922
|
gui.showProgressMessage = function(msg) {
|
|
14630
14923
|
if (!gui.progressMessage) {
|
|
@@ -14749,6 +15042,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14749
15042
|
new ImportControl(gui, importOpts);
|
|
14750
15043
|
new ExportControl(gui);
|
|
14751
15044
|
new LayerControl(gui);
|
|
15045
|
+
HeaderMenu();
|
|
14752
15046
|
gui.console = new Console(gui);
|
|
14753
15047
|
|
|
14754
15048
|
startEditing = function() {};
|