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.
Files changed (89) hide show
  1. package/README.md +5 -3
  2. package/mapshaper.js +363 -62
  3. package/package.json +6 -2
  4. package/www/.nojekyll +0 -0
  5. package/www/assets/jetbrains-mono-regular.woff2 +0 -0
  6. package/www/assets/static-page.css +179 -0
  7. package/www/docs/_assets/cmd-search.js +213 -0
  8. package/www/docs/_assets/docs.css +722 -0
  9. package/www/docs/_assets/docs.js +75 -0
  10. package/www/docs/_assets/highlight.css +10 -0
  11. package/www/docs/essentials/command-line.html +127 -0
  12. package/www/docs/essentials/command-line.html.md +112 -0
  13. package/www/docs/essentials/web-app.html +138 -0
  14. package/www/docs/essentials/web-app.html.md +106 -0
  15. package/www/docs/examples/basics.html +275 -0
  16. package/www/docs/examples/basics.html.md +370 -0
  17. package/www/docs/examples/data/Makefile +31 -0
  18. package/www/docs/examples/data/globe.msx +0 -0
  19. package/www/docs/examples/data/globe.svg +616 -0
  20. package/www/docs/examples/data/globe.txt +21 -0
  21. package/www/docs/examples/data/globe.zip +0 -0
  22. package/www/docs/examples/data/ne_50m_admin_0_countries.geojson +1 -0
  23. package/www/docs/examples/data/ne_50m_admin_1_states_provinces_lakes.geojson +1 -0
  24. package/www/docs/examples/data/us-states.msx +0 -0
  25. package/www/docs/examples/data/us-states.svg +56 -0
  26. package/www/docs/examples/data/us-states.txt +6 -0
  27. package/www/docs/examples/data/us-states.zip +0 -0
  28. package/www/docs/examples/globe.html +108 -0
  29. package/www/docs/examples/globe.html.md +64 -0
  30. package/www/docs/examples/us-states.html +88 -0
  31. package/www/docs/examples/us-states.html.md +44 -0
  32. package/www/docs/formats/csv.html +127 -0
  33. package/www/docs/formats/csv.html.md +97 -0
  34. package/www/docs/formats/dbf.html +87 -0
  35. package/www/docs/formats/dbf.html.md +39 -0
  36. package/www/docs/formats/flatgeobuf.html +85 -0
  37. package/www/docs/formats/flatgeobuf.html.md +41 -0
  38. package/www/docs/formats/geojson.html +107 -0
  39. package/www/docs/formats/geojson.html.md +65 -0
  40. package/www/docs/formats/geopackage.html +87 -0
  41. package/www/docs/formats/geopackage.html.md +42 -0
  42. package/www/docs/formats/json.html +83 -0
  43. package/www/docs/formats/json.html.md +35 -0
  44. package/www/docs/formats/kml.html +82 -0
  45. package/www/docs/formats/kml.html.md +39 -0
  46. package/www/docs/formats/overview.html +191 -0
  47. package/www/docs/formats/overview.html.md +34 -0
  48. package/www/docs/formats/shapefile.html +123 -0
  49. package/www/docs/formats/shapefile.html.md +84 -0
  50. package/www/docs/formats/snapshot.html +87 -0
  51. package/www/docs/formats/snapshot.html.md +39 -0
  52. package/www/docs/formats/svg.html +99 -0
  53. package/www/docs/formats/svg.html.md +51 -0
  54. package/www/docs/formats/topojson.html +102 -0
  55. package/www/docs/formats/topojson.html.md +54 -0
  56. package/www/docs/gallery/index.html +80 -0
  57. package/www/docs/gallery/index.html.md +29 -0
  58. package/www/docs/guides/combining-layers.html +109 -0
  59. package/www/docs/guides/combining-layers.html.md +89 -0
  60. package/www/docs/guides/expressions.html +600 -0
  61. package/www/docs/guides/expressions.html.md +376 -0
  62. package/www/docs/guides/programmatic.html +117 -0
  63. package/www/docs/guides/programmatic.html.md +91 -0
  64. package/www/docs/guides/projections.html +158 -0
  65. package/www/docs/guides/projections.html.md +118 -0
  66. package/www/docs/guides/simplification.html +110 -0
  67. package/www/docs/guides/simplification.html.md +96 -0
  68. package/www/docs/guides/topology.html +90 -0
  69. package/www/docs/guides/topology.html.md +63 -0
  70. package/www/docs/images/cal-counties.png +0 -0
  71. package/www/docs/images/simplification-detail.png +0 -0
  72. package/www/docs/images/simplification-dp.png +0 -0
  73. package/www/docs/images/simplification-mod2.png +0 -0
  74. package/www/docs/images/tiger-counties.png +0 -0
  75. package/www/docs/index.html +101 -0
  76. package/www/docs/index.html.md +59 -0
  77. package/www/docs/reference.html +1302 -0
  78. package/www/docs/reference.html.md +1817 -0
  79. package/www/docs/whats-new.html +77 -0
  80. package/www/docs/whats-new.html.md +59 -0
  81. package/www/index.html +47 -4
  82. package/www/llms-full.txt +4053 -0
  83. package/www/llms.txt +55 -0
  84. package/www/mapshaper-gui.js +299 -5
  85. package/www/mapshaper.js +363 -62
  86. package/www/page.css +189 -11
  87. package/www/privacy.html +1 -112
  88. package/www/sponsor.html +4 -164
  89. 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.
@@ -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: Click on the map to add points. Move points by dragging. Type ${symbol}Z/${symbol}Y to undo/redo.`;
10947
- alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '360px'});
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. Type ${undoKey}Z/${undoKey}Y to undo/redo.`;
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: '388px'});
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() {};