mapshaper 0.7.1 → 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/mapshaper.js +1 -1
- package/package.json +2 -1
- package/www/.nojekyll +0 -0
- package/www/docs/_assets/docs.css +15 -5
- package/www/docs/examples/basics.html +1 -2
- package/www/docs/examples/basics.html.md +1 -2
- package/www/docs/examples/data/globe.msx +0 -0
- package/www/docs/examples/data/globe.zip +0 -0
- package/www/docs/examples/data/us-states.msx +0 -0
- package/www/docs/examples/data/us-states.zip +0 -0
- package/www/docs/formats/flatgeobuf.html +0 -1
- package/www/docs/formats/flatgeobuf.html.md +0 -1
- package/www/docs/formats/overview.html +0 -1
- package/www/docs/formats/overview.html.md +0 -1
- package/www/docs/guides/combining-layers.html +4 -0
- package/www/docs/guides/combining-layers.html.md +8 -0
- package/www/docs/guides/simplification.html +3 -3
- package/www/docs/guides/simplification.html.md +5 -3
- package/www/docs/images/cal-counties.png +0 -0
- package/www/docs/images/tiger-counties.png +0 -0
- package/www/docs/reference.html +2 -2
- package/www/docs/reference.html.md +1 -1
- package/www/docs/whats-new.html +2 -1
- package/www/docs/whats-new.html.md +6 -0
- package/www/index.html +22 -6
- package/www/llms-full.txt +21 -8
- package/www/mapshaper-gui.js +292 -4
- package/www/mapshaper.js +1 -1
- package/www/page.css +188 -10
package/mapshaper.js
CHANGED
|
@@ -52127,7 +52127,7 @@ ${svg}
|
|
|
52127
52127
|
});
|
|
52128
52128
|
}
|
|
52129
52129
|
|
|
52130
|
-
var version = "0.7.
|
|
52130
|
+
var version = "0.7.2";
|
|
52131
52131
|
|
|
52132
52132
|
// Parse command line args into commands and run them
|
|
52133
52133
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapshaper",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "A tool for editing vector datasets for mapping and GIS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shapefile",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"test": "mocha test",
|
|
27
27
|
"build": "rollup --config",
|
|
28
28
|
"docs": "node build-docs.mjs",
|
|
29
|
+
"roadmap": "node build-roadmap.mjs",
|
|
29
30
|
"lint": "eslint --ext mjs src/",
|
|
30
31
|
"prepublishOnly": "npm test; ./pre-publish",
|
|
31
32
|
"postpublish": "./release_web_ui; ./release_github_version",
|
package/www/.nojekyll
ADDED
|
File without changes
|
|
@@ -40,10 +40,15 @@ code, pre, .mono {
|
|
|
40
40
|
.page-header {
|
|
41
41
|
background-color: #1385B7;
|
|
42
42
|
color: white;
|
|
43
|
-
padding
|
|
44
|
-
|
|
43
|
+
/* Asymmetric vertical padding (4px top, 8px bottom) optically centers the
|
|
44
|
+
baseline-aligned row: ascenders are taller than descenders, so equal
|
|
45
|
+
padding makes the text look ~2px low. */
|
|
46
|
+
padding: 2px 14px 8px;
|
|
47
|
+
height: 32px;
|
|
45
48
|
display: flex;
|
|
46
|
-
align
|
|
49
|
+
/* baseline-align so the 18px logo and the 14px nav text share a baseline,
|
|
50
|
+
not just a vertical center */
|
|
51
|
+
align-items: baseline;
|
|
47
52
|
gap: 14px;
|
|
48
53
|
}
|
|
49
54
|
|
|
@@ -76,7 +81,10 @@ code, pre, .mono {
|
|
|
76
81
|
color: white;
|
|
77
82
|
text-decoration: none;
|
|
78
83
|
font-size: 14px;
|
|
79
|
-
|
|
84
|
+
/* line-height: 1 keeps each anchor's hover background tight enough to fit
|
|
85
|
+
inside the 36px header once the row is baseline-aligned. */
|
|
86
|
+
line-height: 1;
|
|
87
|
+
padding: 6px 6px;
|
|
80
88
|
border-radius: 3px;
|
|
81
89
|
}
|
|
82
90
|
|
|
@@ -165,7 +173,9 @@ code, pre, .mono {
|
|
|
165
173
|
|
|
166
174
|
.docs-nav a {
|
|
167
175
|
display: block;
|
|
168
|
-
padding
|
|
176
|
+
/* Asymmetric vertical padding (6px top, 2px bottom) optically centers
|
|
177
|
+
the text inside the active/hover background. */
|
|
178
|
+
padding: 5px 8px 2px;
|
|
169
179
|
border-radius: 3px;
|
|
170
180
|
color: #333;
|
|
171
181
|
font-size: 14px;
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<article class="docs-article">
|
|
34
34
|
<div class="docs-breadcrumbs"><a href="/docs/">Docs</a> › Examples › Basics</div>
|
|
35
35
|
<h1 id="basics">Basics</h1>
|
|
36
|
-
<p>Short Mapshaper
|
|
36
|
+
<p>Short Mapshaper examples for common tasks. The filenames and field names are illustrative — substitute your own. Most recipes also work in the <a href="/docs/essentials/web-app.html">web app's</a> console: drop the leading <code>mapshaper</code> and the input filename; the GUI should already your data loaded.</p>
|
|
37
37
|
<blockquote>
|
|
38
38
|
<p>Looking for the syntax of a particular command or option? See the <a href="/docs/reference.html">command reference</a>. For the JS expression context used by <code>-each</code>, <code>-filter</code>, <code>calc=</code> and other commands, see <a href="/docs/guides/expressions.html">JavaScript expressions</a>.</p>
|
|
39
39
|
</blockquote>
|
|
@@ -248,7 +248,6 @@ mapshaper
|
|
|
248
248
|
<ul>
|
|
249
249
|
<li><a href="/docs/reference.html">Command reference</a> — every command and option</li>
|
|
250
250
|
<li><a href="/docs/guides/expressions.html">JavaScript expressions</a> — the syntax and context used by <code>-each</code>, <code>-filter</code>, <code>calc=</code>, etc.</li>
|
|
251
|
-
<li><a href="/docs/guides/">Guides</a> — longer-form walk-throughs of simplification, topology cleaning, and more</li>
|
|
252
251
|
</ul>
|
|
253
252
|
|
|
254
253
|
<div class="edit-link">
|
|
@@ -5,7 +5,7 @@ description: A scannable collection of short Mapshaper recipes for common GIS ta
|
|
|
5
5
|
|
|
6
6
|
# Basics
|
|
7
7
|
|
|
8
|
-
Short Mapshaper
|
|
8
|
+
Short Mapshaper examples for common tasks. The filenames and field names are illustrative — substitute your own. Most recipes also work in the [web app's](/docs/essentials/web-app.html.md) console: drop the leading `mapshaper` and the input filename; the GUI should already your data loaded.
|
|
9
9
|
|
|
10
10
|
> Looking for the syntax of a particular command or option? See the [command reference](/docs/reference.html.md). For the JS expression context used by `-each`, `-filter`, `calc=` and other commands, see [JavaScript expressions](/docs/guides/expressions.html.md).
|
|
11
11
|
|
|
@@ -368,4 +368,3 @@ mapshaper-xl 16gb counties_5m.shp -simplify 10% -o counties_5m.topojson
|
|
|
368
368
|
|
|
369
369
|
- [Command reference](/docs/reference.html.md) — every command and option
|
|
370
370
|
- [JavaScript expressions](/docs/guides/expressions.html.md) — the syntax and context used by `-each`, `-filter`, `calc=`, etc.
|
|
371
|
-
- [Guides](/docs/guides/index.html.md) — longer-form walk-throughs of simplification, topology cleaning, and more
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -46,7 +46,6 @@ mapshaper provinces.shp -o provinces.fgb
|
|
|
46
46
|
<p>There are no FlatGeobuf-specific <code>-o</code> options. The format honors the general flags (<code>precision=</code>, <code>gzip</code>, <code>zip</code>, etc.) where they apply.</p>
|
|
47
47
|
<h3 id="practical-notes">Practical notes</h3>
|
|
48
48
|
<ul>
|
|
49
|
-
<li>FlatGeobuf is single-layer per file. Use <a href="/docs/formats/geopackage.html">GeoPackage</a> or <a href="/docs/formats/topojson.html">TopoJSON</a> when you need to package multiple layers in one file.</li>
|
|
50
49
|
<li>Mapshaper reads CRS metadata from the file header when it's encoded as an EPSG code. WKT2-only definitions can't be parsed and produce an "Unable to import WKT2 CRS from FlatGeobuf" warning — the layer comes in without a CRS.</li>
|
|
51
50
|
<li>On output, Mapshaper embeds an EPSG code in the FlatGeobuf header whenever it can derive one from the source: a round-tripped FlatGeobuf or GeoPackage CRS, an <code>epsg:NNNN</code> string passed to <code>-proj</code>, an <code>AUTHORITY["EPSG", N]</code> clause in a Shapefile <code>.prj</code>, or any encoding of WGS-84 or Web Mercator (which covers most GeoJSON, CSV-with-lat/lon and <code>-proj wgs84</code>/<code>-proj webmercator</code> outputs).</li>
|
|
52
51
|
<li>Mapshaper cannot yet convert an arbitrary proj4 definition (such as a custom Albers projection set with <code>-proj +proj=aea ...</code>) to an EPSG code. In that case the file is written with no CRS in the header and a warning is printed: <em>"Wrote <code>foo.fgb</code> without a CRS in the FlatGeobuf header..."</em>. Re-run the file through <code>ogr2ogr</code> if you need the CRS embedded for downstream tools.</li>
|
|
@@ -27,7 +27,6 @@ There are no FlatGeobuf-specific `-o` options. The format honors the general fla
|
|
|
27
27
|
|
|
28
28
|
### Practical notes
|
|
29
29
|
|
|
30
|
-
- FlatGeobuf is single-layer per file. Use [GeoPackage](/docs/formats/geopackage.html.md) or [TopoJSON](/docs/formats/topojson.html.md) when you need to package multiple layers in one file.
|
|
31
30
|
- Mapshaper reads CRS metadata from the file header when it's encoded as an EPSG code. WKT2-only definitions can't be parsed and produce an "Unable to import WKT2 CRS from FlatGeobuf" warning — the layer comes in without a CRS.
|
|
32
31
|
- On output, Mapshaper embeds an EPSG code in the FlatGeobuf header whenever it can derive one from the source: a round-tripped FlatGeobuf or GeoPackage CRS, an `epsg:NNNN` string passed to `-proj`, an `AUTHORITY["EPSG", N]` clause in a Shapefile `.prj`, or any encoding of WGS-84 or Web Mercator (which covers most GeoJSON, CSV-with-lat/lon and `-proj wgs84`/`-proj webmercator` outputs).
|
|
33
32
|
- Mapshaper cannot yet convert an arbitrary proj4 definition (such as a custom Albers projection set with `-proj +proj=aea ...`) to an EPSG code. In that case the file is written with no CRS in the header and a warning is printed: *"Wrote `foo.fgb` without a CRS in the FlatGeobuf header..."*. Re-run the file through `ogr2ogr` if you need the CRS embedded for downstream tools.
|
|
@@ -163,7 +163,6 @@
|
|
|
163
163
|
<ul>
|
|
164
164
|
<li><strong>Auto-detection by extension.</strong> You usually don't need to tell Mapshaper what format a file is — it picks the right reader from the file extension. Use <code>format=</code> on <code>-i</code> or <code>-o</code> to override.</li>
|
|
165
165
|
<li><strong>TopoJSON is the only interchange format that preserves topology</strong> in the file itself. Topology-aware operations like <a href="/docs/reference.html#-dissolve"><code>-dissolve</code></a>, <a href="/docs/reference.html#-clean"><code>-clean</code></a> and <a href="/docs/reference.html#-simplify"><code>-simplify</code></a> work correctly regardless of the input format, but only TopoJSON keeps shared boundaries between adjacent polygons from being duplicated on disk. (Mapshaper's own <a href="/docs/formats/snapshot.html"><code>.msx</code></a> snapshots also preserve topology, but they're not readable by other tools.)</li>
|
|
166
|
-
<li><strong>Most formats are single-layer per file.</strong> Multi-layer GeoJSON, Shapefile and FlatGeobuf datasets are conventionally split across multiple files (in the same directory on the CLI; selected together or zipped in the <a href="/docs/essentials/web-app.html">web app</a>); TopoJSON, GeoPackage and KML can hold many layers in one file.</li>
|
|
167
166
|
<li><strong>Encoding.</strong> The <code>encoding=</code> option on <code>-i</code> and <code>-o</code> applies to Shapefile, DBF and CSV/TSV i/o (UTF-8 is the default) — the other formats are UTF-8-only.</li>
|
|
168
167
|
</ul>
|
|
169
168
|
|
|
@@ -31,5 +31,4 @@ A few things worth knowing across all formats:
|
|
|
31
31
|
|
|
32
32
|
- **Auto-detection by extension.** You usually don't need to tell Mapshaper what format a file is — it picks the right reader from the file extension. Use `format=` on `-i` or `-o` to override.
|
|
33
33
|
- **TopoJSON is the only interchange format that preserves topology** in the file itself. Topology-aware operations like [`-dissolve`](/docs/reference.html.md#-dissolve), [`-clean`](/docs/reference.html.md#-clean) and [`-simplify`](/docs/reference.html.md#-simplify) work correctly regardless of the input format, but only TopoJSON keeps shared boundaries between adjacent polygons from being duplicated on disk. (Mapshaper's own [`.msx`](/docs/formats/snapshot.html.md) snapshots also preserve topology, but they're not readable by other tools.)
|
|
34
|
-
- **Most formats are single-layer per file.** Multi-layer GeoJSON, Shapefile and FlatGeobuf datasets are conventionally split across multiple files (in the same directory on the CLI; selected together or zipped in the [web app](/docs/essentials/web-app.html.md)); TopoJSON, GeoPackage and KML can hold many layers in one file.
|
|
35
34
|
- **Encoding.** The `encoding=` option on `-i` and `-o` applies to Shapefile, DBF and CSV/TSV i/o (UTF-8 is the default) — the other formats are UTF-8-only.
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
<li>A county boundary file from the California Open Data Portal — or, in this case, a <a href="https://geodata.lib.berkeley.edu/catalog/ark28722-s7hs4j">version clipped to the shoreline</a> from UC Berkeley's Geo Data Commons. The shoreline-clipped version reads more naturally on a map than the legal-boundary version, which extends into the bay.</li>
|
|
46
46
|
<li>A statewide <a href="https://geodata.lib.berkeley.edu/catalog/ark28722-s7bp4z">places boundary file</a> for city boundaries.</li>
|
|
47
47
|
</ul>
|
|
48
|
+
<p>The legal county boundaries from the Census TIGER files extend straight across the water — San Francisco even reaches out to include the Farallon Islands:</p>
|
|
49
|
+
<p><img src="/docs/images/tiger-counties.png" alt="Bay Area counties from the Census TIGER file, with boundaries cutting across the bay and extending offshore"></p>
|
|
50
|
+
<p>The Berkeley library's version is clipped to the shoreline, so the bay shows as empty space between the counties:</p>
|
|
51
|
+
<p><img src="/docs/images/cal-counties.png" alt="The same Bay Area counties, with each polygon clipped to the coastline so the bay is visible between them"></p>
|
|
48
52
|
<p>Download both as <code>.zip</code> files.</p>
|
|
49
53
|
<h2 id="open-them-in-mapshaper">Open them in Mapshaper</h2>
|
|
50
54
|
<p>Drag the two <code>.zip</code> files onto <a href="/">mapshaper.org</a>, or run the web app locally with <code>mapshaper-gui</code>. You'll end up with two layers loaded into the session.</p>
|
|
@@ -20,6 +20,14 @@ We'll use two source files:
|
|
|
20
20
|
- A county boundary file from the California Open Data Portal — or, in this case, a [version clipped to the shoreline](https://geodata.lib.berkeley.edu/catalog/ark28722-s7hs4j) from UC Berkeley's Geo Data Commons. The shoreline-clipped version reads more naturally on a map than the legal-boundary version, which extends into the bay.
|
|
21
21
|
- A statewide [places boundary file](https://geodata.lib.berkeley.edu/catalog/ark28722-s7bp4z) for city boundaries.
|
|
22
22
|
|
|
23
|
+
The legal county boundaries from the Census TIGER files extend straight across the water — San Francisco even reaches out to include the Farallon Islands:
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
The Berkeley library's version is clipped to the shoreline, so the bay shows as empty space between the counties:
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
23
31
|
Download both as `.zip` files.
|
|
24
32
|
|
|
25
33
|
## Open them in Mapshaper
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
<ul>
|
|
40
40
|
<li><strong><code>dp</code></strong> — Douglas-Peucker (also known as Ramer–Douglas–Peucker). Guarantees that simplified lines stay within a fixed distance of the original. Good for stripping excess vertices to reduce file size, but tends to grow visible spikes at high simplification.</li>
|
|
41
41
|
<li><strong><code>visvalingam</code></strong> — The Visvalingam algorithm. Iteratively removes the point that forms the smallest triangle with its two neighbors.</li>
|
|
42
|
-
<li><strong><code>weighted_visvalingam</code></strong> (Mapshaper's default) — Visvalingam's effective-area algorithm with a custom weighting that underweights points at sharp angles, so they are removed earlier than in standard Visvalingam. The result is visibly smoother lines and fewer jagged spikes at high simplification
|
|
42
|
+
<li><strong><code>weighted_visvalingam</code></strong> (Mapshaper's default) — Visvalingam's effective-area algorithm with a custom weighting that underweights points at sharp angles, so they are removed earlier than in standard Visvalingam. The result is visibly smoother lines and fewer jagged spikes at high simplification.</li>
|
|
43
43
|
</ul>
|
|
44
|
-
<p>Weighted Visvalingam is the default because it has proven to be versatile and effective at reducing detail in highly detailed source data.
|
|
45
|
-
<p>
|
|
44
|
+
<p>Weighted Visvalingam is the default because it has proven to be versatile and effective at reducing detail in highly detailed source data. This method can be effective at generalizing very detailed source files, but be careful that it doesn't remove long, thin geographic features that you want to keep.You can control the amount of weighting used by Weighted Visvalingam with the <code>weighting=</code> option (default is 0.7).</p>
|
|
45
|
+
<p>If you are only interested in minimizing file size, Douglas-Peucker is generally the better choice.</p>
|
|
46
46
|
<p><strong>Figures</strong></p>
|
|
47
47
|
<p>Natural Earth 10m coastlines, simplified with modified Visvalingam at 5% point retention.
|
|
48
48
|
<img src="/docs/images/simplification-mod2.png" alt="image"></p>
|
|
@@ -13,11 +13,13 @@ Mapshaper offers three simplification methods, selectable as flags to `-simplify
|
|
|
13
13
|
|
|
14
14
|
- **`dp`** — Douglas-Peucker (also known as Ramer–Douglas–Peucker). Guarantees that simplified lines stay within a fixed distance of the original. Good for stripping excess vertices to reduce file size, but tends to grow visible spikes at high simplification.
|
|
15
15
|
- **`visvalingam`** — The Visvalingam algorithm. Iteratively removes the point that forms the smallest triangle with its two neighbors.
|
|
16
|
-
- **`weighted_visvalingam`** (Mapshaper's default) — Visvalingam's effective-area algorithm with a custom weighting that underweights points at sharp angles, so they are removed earlier than in standard Visvalingam. The result is visibly smoother lines and fewer jagged spikes at high simplification.
|
|
16
|
+
- **`weighted_visvalingam`** (Mapshaper's default) — Visvalingam's effective-area algorithm with a custom weighting that underweights points at sharp angles, so they are removed earlier than in standard Visvalingam. The result is visibly smoother lines and fewer jagged spikes at high simplification.
|
|
17
|
+
|
|
18
|
+
Weighted Visvalingam is the default because it has proven to be versatile and effective at reducing detail in highly detailed source data. This method can be effective at generalizing very detailed source files, but be careful that it doesn't remove long, thin geographic features that you want to keep.You can control the amount of weighting used by Weighted Visvalingam with the `weighting=` option (default is 0.7).
|
|
19
|
+
|
|
20
|
+
If you are only interested in minimizing file size, Douglas-Peucker is generally the better choice.
|
|
17
21
|
|
|
18
|
-
Weighted Visvalingam is the default because it has proven to be versatile and effective at reducing detail in highly detailed source data. If you are only interested in minimizing file size, Douglas-Peucker is generally the better choice.
|
|
19
22
|
|
|
20
|
-
You can control the amount of weighting used by Weighted Visvalingam with the `weighting=` option (default is 0.7).
|
|
21
23
|
|
|
22
24
|
**Figures**
|
|
23
25
|
|
|
Binary file
|
|
Binary file
|
package/www/docs/reference.html
CHANGED
|
@@ -455,13 +455,13 @@ mapshaper counties.shp \
|
|
|
455
455
|
<p><code>holes</code> Delete any holes from a polygon layer.</p>
|
|
456
456
|
<p><code>target=</code> Layer(s) to target.</p>
|
|
457
457
|
</section>
|
|
458
|
-
<section class="cmd-section" data-id="-each" data-name="-each" data-options="this expression where target
|
|
458
|
+
<section class="cmd-section" data-id="-each" data-name="-each" data-options="this expression where target"><h3 id="-each">-each</h3>
|
|
459
459
|
<p>Apply a JavaScript expression to each feature in a layer. Data properties are available as local variables; the feature's geometry-derived properties are available on the <code>this</code> object (e.g. <code>this.area</code>, <code>this.centroidX</code>, <code>this.bbox</code>).</p>
|
|
460
460
|
<p><strong>Tip:</strong> Enclose JS expressions in single quotes when using the bash shell (Mac and Linux) to avoid shell expansion of <code>!</code> and other special characters. Using the Windows command interpreter, enclose JS expressions in double quotes.</p>
|
|
461
461
|
<p><code><expression></code> or <code>expression=</code> JavaScript expression to apply to each feature.</p>
|
|
462
462
|
<p><code>where=</code> Secondary boolean JS expression for targetting a subset of features.</p>
|
|
463
463
|
<p><code>target=</code> Layer to target.</p>
|
|
464
|
-
<p>The same expression syntax and execution context are used by
|
|
464
|
+
<p>The same expression syntax and execution context are used by all commands that support expressions. See <a href="/docs/guides/expressions.html">JavaScript expressions</a> for the full reference. The <a href="/docs/examples/basics.html">Basics</a> page has practical recipes that put expressions to work.</p>
|
|
465
465
|
<p><strong>Examples</strong></p>
|
|
466
466
|
<pre><code class="hljs language-bash"><span class="hljs-comment"># Create two fields</span>
|
|
467
467
|
mapshaper counties.shp \
|
|
@@ -681,7 +681,7 @@ Apply a JavaScript expression to each feature in a layer. Data properties are av
|
|
|
681
681
|
|
|
682
682
|
`target=` Layer to target.
|
|
683
683
|
|
|
684
|
-
The same expression syntax and execution context are used by
|
|
684
|
+
The same expression syntax and execution context are used by all commands that support expressions. See [JavaScript expressions](/docs/guides/expressions.html.md) for the full reference. The [Basics](/docs/examples/basics.html.md) page has practical recipes that put expressions to work.
|
|
685
685
|
|
|
686
686
|
**Examples**
|
|
687
687
|
|
package/www/docs/whats-new.html
CHANGED
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
<h1 id="whats-new">What's new</h1>
|
|
36
36
|
<p>This is a curated list of recently added features. For the full list of changes, including bug fixes and internal work, see the <a href="https://github.com/mbloch/mapshaper/blob/master/CHANGELOG.md">changelog</a> on GitHub.</p>
|
|
37
37
|
<h2 id="april-2026">April 2026</h2>
|
|
38
|
-
<div class="whats-new-entry"><p><strong>
|
|
38
|
+
<div class="whats-new-entry"><p><strong>Undo/redo buttons.</strong> The web UI now has an undo/redo toolbar. Undo and redo were already supported in the geometry and attribute editing modes; the toolbar makes them more discoverable for users who didn't know the keyboard shortcuts (<strong>⌘Z</strong> / <strong>⌘⇧Z</strong>) existed.</p>
|
|
39
|
+
</div><div class="whats-new-entry"><p><strong>Command files.</strong> A sequence of Mapshaper commands can be written to a <code>.txt</code> file with <code>#</code> comments and no shell quoting, and run with <code>-run <file></code> (or just <code>mapshaper commands.txt</code>). Command files can also be written in a shell-compatible way, if you want to be able to paste the commands into the terminal or add them to a shell script. In a future release, these files will also be runnable in the browser.</p>
|
|
39
40
|
<pre><code class="hljs language-bash">mapshaper build.txt
|
|
40
41
|
</code></pre>
|
|
41
42
|
<p>→ See <a href="/docs/reference.html#command-files">Command files</a> in the reference.</p>
|
|
@@ -11,6 +11,12 @@ This is a curated list of recently added features. For the full list of changes,
|
|
|
11
11
|
|
|
12
12
|
<div class="whats-new-entry">
|
|
13
13
|
|
|
14
|
+
**Undo/redo buttons.** The web UI now has an undo/redo toolbar. Undo and redo were already supported in the geometry and attribute editing modes; the toolbar makes them more discoverable for users who didn't know the keyboard shortcuts (**⌘Z** / **⌘⇧Z**) existed.
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="whats-new-entry">
|
|
19
|
+
|
|
14
20
|
**Command files.** A sequence of Mapshaper commands can be written to a `.txt` file with `#` comments and no shell quoting, and run with `-run <file>` (or just `mapshaper commands.txt`). Command files can also be written in a shell-compatible way, if you want to be able to paste the commands into the terminal or add them to a shell script. In a future release, these files will also be runnable in the browser.
|
|
15
21
|
|
|
16
22
|
```bash
|
package/www/index.html
CHANGED
|
@@ -43,6 +43,14 @@
|
|
|
43
43
|
<polygon points="8 14.5 13 19 13 4 3 4 3 19 8 14.5" fill="#30d4ef"/>
|
|
44
44
|
</svg>
|
|
45
45
|
|
|
46
|
+
<svg id="undo-icon" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
47
|
+
<path d="M1.5,8 L7,4.5 L7,7 C10,7 12,9 12,11 L12,13 L10,13 L10,11 C10,10 9,9 7,9 L7,11.5 Z"/>
|
|
48
|
+
</svg>
|
|
49
|
+
|
|
50
|
+
<svg id="redo-icon" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
51
|
+
<path d="M14.5,8 L9,4.5 L9,7 C6,7 4,9 4,11 L4,13 L6,13 L6,11 C6,10 7,9 9,9 L9,11.5 Z"/>
|
|
52
|
+
</svg>
|
|
53
|
+
|
|
46
54
|
</div>
|
|
47
55
|
|
|
48
56
|
<div class="page-header">
|
|
@@ -60,10 +68,17 @@
|
|
|
60
68
|
</div></div>
|
|
61
69
|
<div id="mode-buttons" class="page-header-buttons">
|
|
62
70
|
<!-- <span class="project-btn header-btn btn">Project</span><span class="separator"> -->
|
|
63
|
-
<span class="console-btn header-btn btn">Console</span><span class="display-btn header-btn btn">Display</span><span class="separator"></span><span class="separator"></span></span><span class="simplify-btn header-btn btn">Simplify</span><span class="separator"></span><span class="export-btn header-btn btn">Export</span>
|
|
71
|
+
<span class="console-btn header-btn btn">Console</span><span class="display-btn header-btn btn">Display</span><span class="separator"></span><span class="separator"></span></span><span class="simplify-btn header-btn btn">Simplify</span><span class="separator"></span><span class="export-btn header-btn btn">Export</span><span class="separator"></span><span id="header-menu-btn" class="header-btn btn" role="button" aria-label="More links" aria-haspopup="true" aria-expanded="false" tabindex="0"><svg viewBox="0 0 16 16" aria-hidden="true" width="16" height="14"><rect x="1" y="3" width="14" height="2"/><rect x="1" y="7" width="14" height="2"/><rect x="1" y="11" width="14" height="2"/></svg></span>
|
|
72
|
+
<div id="header-menu-dropdown" class="hidden" role="menu" aria-labelledby="header-menu-btn">
|
|
73
|
+
<a class="header-menu-item" href="/docs/" target="_blank" rel="noopener" role="menuitem">Docs</a>
|
|
74
|
+
<a class="header-menu-item" href="https://github.com/mbloch/mapshaper" target="_blank" rel="noopener" role="menuitem">GitHub</a>
|
|
75
|
+
<a class="header-menu-item survey-en-link" href="https://tally.so/r/44Njok" target="_blank" rel="noopener" role="menuitem">1-minute survey</a>
|
|
76
|
+
<a class="header-menu-item survey-zh-link" href="https://tally.so/r/vGx7KQ" target="_blank" rel="noopener" lang="zh-CN" style="display:none" role="menuitem">意见反馈</a>
|
|
77
|
+
<a class="header-menu-item header-menu-sponsor" href="sponsor.html" target="_blank" rel="noopener" role="menuitem"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>Become a sponsor</a>
|
|
78
|
+
</div>
|
|
64
79
|
</div>
|
|
65
80
|
<div id="splash-buttons" class="page-header-buttons">
|
|
66
|
-
<a href="/docs/"><span id="wiki-btn" class="header-btn btn">Docs</span></a><span class="separator"></span><a href="https://github.com/mbloch/mapshaper"><span id="github-btn" class="header-btn btn">GitHub</span></a><span class="separator"></span><a
|
|
81
|
+
<a href="/docs/"><span id="wiki-btn" class="header-btn btn">Docs</span></a><span class="separator"></span><a href="https://github.com/mbloch/mapshaper"><span id="github-btn" class="header-btn btn">GitHub</span></a><span class="separator"></span><a class="survey-en-link" href="https://tally.so/r/44Njok" target="_blank" rel="noopener"><span id="survey-en-btn" class="header-btn btn">1-minute survey</span></a><a class="survey-zh-link" href="https://tally.so/r/vGx7KQ" target="_blank" rel="noopener" style="display:none"><span id="survey-zh-btn" class="header-btn btn" lang="zh-CN">意见反馈</span></a><span class="separator"></span><a href="sponsor.html"><span id="sponsor-btn" class="header-btn btn"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg> Become a sponsor</span></a>
|
|
67
82
|
</div>
|
|
68
83
|
</div>
|
|
69
84
|
|
|
@@ -351,10 +366,11 @@ encoding=big5</span>. Click to see all options.</div></div></div>
|
|
|
351
366
|
if (/^zh(-Hans)?-CN\b/i.test(langs[i])) isChina = true;
|
|
352
367
|
}
|
|
353
368
|
if (isChina) {
|
|
354
|
-
|
|
355
|
-
var
|
|
356
|
-
|
|
357
|
-
|
|
369
|
+
// Swap every copy of the survey link (splash bar + hamburger menu)
|
|
370
|
+
var zhLinks = document.querySelectorAll('.survey-zh-link');
|
|
371
|
+
var enLinks = document.querySelectorAll('.survey-en-link');
|
|
372
|
+
for (var j = 0; j < zhLinks.length; j++) zhLinks[j].style.display = '';
|
|
373
|
+
for (var k = 0; k < enLinks.length; k++) enLinks[k].style.display = 'none';
|
|
358
374
|
}
|
|
359
375
|
})();
|
|
360
376
|
</script>
|
package/www/llms-full.txt
CHANGED
|
@@ -63,6 +63,12 @@ This is a curated list of recently added features. For the full list of changes,
|
|
|
63
63
|
|
|
64
64
|
<div class="whats-new-entry">
|
|
65
65
|
|
|
66
|
+
**Undo/redo buttons.** The web UI now has an undo/redo toolbar. Undo and redo were already supported in the geometry and attribute editing modes; the toolbar makes them more discoverable for users who didn't know the keyboard shortcuts (**⌘Z** / **⌘⇧Z**) existed.
|
|
67
|
+
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="whats-new-entry">
|
|
71
|
+
|
|
66
72
|
**Command files.** A sequence of Mapshaper commands can be written to a `.txt` file with `#` comments and no shell quoting, and run with `-run <file>` (or just `mapshaper commands.txt`). Command files can also be written in a shell-compatible way, if you want to be able to paste the commands into the terminal or add them to a shell script. In a future release, these files will also be runnable in the browser.
|
|
67
73
|
|
|
68
74
|
```bash
|
|
@@ -791,7 +797,7 @@ Apply a JavaScript expression to each feature in a layer. Data properties are av
|
|
|
791
797
|
|
|
792
798
|
`target=` Layer to target.
|
|
793
799
|
|
|
794
|
-
The same expression syntax and execution context are used by
|
|
800
|
+
The same expression syntax and execution context are used by all commands that support expressions. See [JavaScript expressions](/docs/guides/expressions.html.md) for the full reference. The [Basics](/docs/examples/basics.html.md) page has practical recipes that put expressions to work.
|
|
795
801
|
|
|
796
802
|
**Examples**
|
|
797
803
|
|
|
@@ -2168,11 +2174,13 @@ Mapshaper offers three simplification methods, selectable as flags to `-simplify
|
|
|
2168
2174
|
|
|
2169
2175
|
- **`dp`** — Douglas-Peucker (also known as Ramer–Douglas–Peucker). Guarantees that simplified lines stay within a fixed distance of the original. Good for stripping excess vertices to reduce file size, but tends to grow visible spikes at high simplification.
|
|
2170
2176
|
- **`visvalingam`** — The Visvalingam algorithm. Iteratively removes the point that forms the smallest triangle with its two neighbors.
|
|
2171
|
-
- **`weighted_visvalingam`** (Mapshaper's default) — Visvalingam's effective-area algorithm with a custom weighting that underweights points at sharp angles, so they are removed earlier than in standard Visvalingam. The result is visibly smoother lines and fewer jagged spikes at high simplification.
|
|
2177
|
+
- **`weighted_visvalingam`** (Mapshaper's default) — Visvalingam's effective-area algorithm with a custom weighting that underweights points at sharp angles, so they are removed earlier than in standard Visvalingam. The result is visibly smoother lines and fewer jagged spikes at high simplification.
|
|
2178
|
+
|
|
2179
|
+
Weighted Visvalingam is the default because it has proven to be versatile and effective at reducing detail in highly detailed source data. This method can be effective at generalizing very detailed source files, but be careful that it doesn't remove long, thin geographic features that you want to keep.You can control the amount of weighting used by Weighted Visvalingam with the `weighting=` option (default is 0.7).
|
|
2180
|
+
|
|
2181
|
+
If you are only interested in minimizing file size, Douglas-Peucker is generally the better choice.
|
|
2172
2182
|
|
|
2173
|
-
Weighted Visvalingam is the default because it has proven to be versatile and effective at reducing detail in highly detailed source data. If you are only interested in minimizing file size, Douglas-Peucker is generally the better choice.
|
|
2174
2183
|
|
|
2175
|
-
You can control the amount of weighting used by Weighted Visvalingam with the `weighting=` option (default is 0.7).
|
|
2176
2184
|
|
|
2177
2185
|
**Figures**
|
|
2178
2186
|
|
|
@@ -2826,6 +2834,14 @@ We'll use two source files:
|
|
|
2826
2834
|
- A county boundary file from the California Open Data Portal — or, in this case, a [version clipped to the shoreline](https://geodata.lib.berkeley.edu/catalog/ark28722-s7hs4j) from UC Berkeley's Geo Data Commons. The shoreline-clipped version reads more naturally on a map than the legal-boundary version, which extends into the bay.
|
|
2827
2835
|
- A statewide [places boundary file](https://geodata.lib.berkeley.edu/catalog/ark28722-s7bp4z) for city boundaries.
|
|
2828
2836
|
|
|
2837
|
+
The legal county boundaries from the Census TIGER files extend straight across the water — San Francisco even reaches out to include the Farallon Islands:
|
|
2838
|
+
|
|
2839
|
+

|
|
2840
|
+
|
|
2841
|
+
The Berkeley library's version is clipped to the shoreline, so the bay shows as empty space between the counties:
|
|
2842
|
+
|
|
2843
|
+

|
|
2844
|
+
|
|
2829
2845
|
Download both as `.zip` files.
|
|
2830
2846
|
|
|
2831
2847
|
## Open them in Mapshaper
|
|
@@ -3009,7 +3025,6 @@ A few things worth knowing across all formats:
|
|
|
3009
3025
|
|
|
3010
3026
|
- **Auto-detection by extension.** You usually don't need to tell Mapshaper what format a file is — it picks the right reader from the file extension. Use `format=` on `-i` or `-o` to override.
|
|
3011
3027
|
- **TopoJSON is the only interchange format that preserves topology** in the file itself. Topology-aware operations like [`-dissolve`](/docs/reference.html.md#-dissolve), [`-clean`](/docs/reference.html.md#-clean) and [`-simplify`](/docs/reference.html.md#-simplify) work correctly regardless of the input format, but only TopoJSON keeps shared boundaries between adjacent polygons from being duplicated on disk. (Mapshaper's own [`.msx`](/docs/formats/snapshot.html.md) snapshots also preserve topology, but they're not readable by other tools.)
|
|
3012
|
-
- **Most formats are single-layer per file.** Multi-layer GeoJSON, Shapefile and FlatGeobuf datasets are conventionally split across multiple files (in the same directory on the CLI; selected together or zipped in the [web app](/docs/essentials/web-app.html.md)); TopoJSON, GeoPackage and KML can hold many layers in one file.
|
|
3013
3028
|
- **Encoding.** The `encoding=` option on `-i` and `-o` applies to Shapefile, DBF and CSV/TSV i/o (UTF-8 is the default) — the other formats are UTF-8-only.
|
|
3014
3029
|
|
|
3015
3030
|
---
|
|
@@ -3285,7 +3300,6 @@ There are no FlatGeobuf-specific `-o` options. The format honors the general fla
|
|
|
3285
3300
|
|
|
3286
3301
|
### Practical notes
|
|
3287
3302
|
|
|
3288
|
-
- FlatGeobuf is single-layer per file. Use [GeoPackage](/docs/formats/geopackage.html.md) or [TopoJSON](/docs/formats/topojson.html.md) when you need to package multiple layers in one file.
|
|
3289
3303
|
- Mapshaper reads CRS metadata from the file header when it's encoded as an EPSG code. WKT2-only definitions can't be parsed and produce an "Unable to import WKT2 CRS from FlatGeobuf" warning — the layer comes in without a CRS.
|
|
3290
3304
|
- On output, Mapshaper embeds an EPSG code in the FlatGeobuf header whenever it can derive one from the source: a round-tripped FlatGeobuf or GeoPackage CRS, an `epsg:NNNN` string passed to `-proj`, an `AUTHORITY["EPSG", N]` clause in a Shapefile `.prj`, or any encoding of WGS-84 or Web Mercator (which covers most GeoJSON, CSV-with-lat/lon and `-proj wgs84`/`-proj webmercator` outputs).
|
|
3291
3305
|
- Mapshaper cannot yet convert an arbitrary proj4 definition (such as a custom Albers projection set with `-proj +proj=aea ...`) to an EPSG code. In that case the file is written with no CRS in the header and a warning is printed: *"Wrote `foo.fgb` without a CRS in the FlatGeobuf header..."*. Re-run the file through `ogr2ogr` if you need the CRS embedded for downstream tools.
|
|
@@ -3605,7 +3619,7 @@ The ribbon icon in the layer panel opens the **snapshot menu**. From there you c
|
|
|
3605
3619
|
|
|
3606
3620
|
# Basics
|
|
3607
3621
|
|
|
3608
|
-
Short Mapshaper
|
|
3622
|
+
Short Mapshaper examples for common tasks. The filenames and field names are illustrative — substitute your own. Most recipes also work in the [web app's](/docs/essentials/web-app.html.md) console: drop the leading `mapshaper` and the input filename; the GUI should already your data loaded.
|
|
3609
3623
|
|
|
3610
3624
|
> Looking for the syntax of a particular command or option? See the [command reference](/docs/reference.html.md). For the JS expression context used by `-each`, `-filter`, `calc=` and other commands, see [JavaScript expressions](/docs/guides/expressions.html.md).
|
|
3611
3625
|
|
|
@@ -3968,7 +3982,6 @@ mapshaper-xl 16gb counties_5m.shp -simplify 10% -o counties_5m.topojson
|
|
|
3968
3982
|
|
|
3969
3983
|
- [Command reference](/docs/reference.html.md) — every command and option
|
|
3970
3984
|
- [JavaScript expressions](/docs/guides/expressions.html.md) — the syntax and context used by `-each`, `-filter`, `calc=`, etc.
|
|
3971
|
-
- [Guides](/docs/guides/index.html.md) — longer-form walk-throughs of simplification, topology cleaning, and more
|
|
3972
3985
|
|
|
3973
3986
|
---
|
|
3974
3987
|
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -5655,6 +5655,56 @@
|
|
|
5655
5655
|
}
|
|
5656
5656
|
}
|
|
5657
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
|
+
|
|
5658
5708
|
function SessionHistory(gui) {
|
|
5659
5709
|
var commands = [];
|
|
5660
5710
|
// index of first command after the last "save" boundary; commands at indices
|
|
@@ -5993,6 +6043,15 @@
|
|
|
5993
6043
|
|
|
5994
6044
|
this.clear = function() {
|
|
5995
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;
|
|
5996
6055
|
};
|
|
5997
6056
|
|
|
5998
6057
|
function addHistoryState(undo, redo) {
|
|
@@ -6001,6 +6060,14 @@
|
|
|
6001
6060
|
offset = 0;
|
|
6002
6061
|
}
|
|
6003
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
|
+
});
|
|
6004
6071
|
}
|
|
6005
6072
|
|
|
6006
6073
|
this.undo = function() {
|
|
@@ -6013,6 +6080,7 @@
|
|
|
6013
6080
|
item.undo();
|
|
6014
6081
|
gui.dispatchEvent('undo_redo_post', {type: 'undo'});
|
|
6015
6082
|
gui.dispatchEvent('map-needs-refresh');
|
|
6083
|
+
fireHistoryChange();
|
|
6016
6084
|
}
|
|
6017
6085
|
};
|
|
6018
6086
|
|
|
@@ -6024,6 +6092,7 @@
|
|
|
6024
6092
|
item.redo();
|
|
6025
6093
|
gui.dispatchEvent('undo_redo_post', {type: 'redo'});
|
|
6026
6094
|
gui.dispatchEvent('map-needs-refresh');
|
|
6095
|
+
fireHistoryChange();
|
|
6027
6096
|
};
|
|
6028
6097
|
|
|
6029
6098
|
function getHistoryItem() {
|
|
@@ -6337,6 +6406,10 @@
|
|
|
6337
6406
|
return ['info', 'selection', 'data', 'box', 'labels', 'edit_points', 'rectangles'].includes(mode);
|
|
6338
6407
|
};
|
|
6339
6408
|
|
|
6409
|
+
this.modeSupportsUndo = function(mode) {
|
|
6410
|
+
return ['data', 'labels', 'edit_points', 'edit_lines', 'edit_polygons', 'vertices', 'rectangles'].includes(mode);
|
|
6411
|
+
};
|
|
6412
|
+
|
|
6340
6413
|
this.getMode = getInteractionMode;
|
|
6341
6414
|
|
|
6342
6415
|
this.setMode = function(mode) {
|
|
@@ -6479,6 +6552,214 @@
|
|
|
6479
6552
|
}
|
|
6480
6553
|
}
|
|
6481
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
|
+
|
|
6482
6763
|
function Model(gui) {
|
|
6483
6764
|
var self = new internal.Catalog();
|
|
6484
6765
|
var deleteLayer = self.deleteLayer;
|
|
@@ -9576,6 +9857,10 @@
|
|
|
9576
9857
|
|
|
9577
9858
|
box.setDataCoords = function(bbox) {
|
|
9578
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);
|
|
9579
9864
|
redraw();
|
|
9580
9865
|
};
|
|
9581
9866
|
|
|
@@ -9599,6 +9884,7 @@
|
|
|
9599
9884
|
// remove the current box (if any)
|
|
9600
9885
|
box.hide = function() {
|
|
9601
9886
|
el.hide();
|
|
9887
|
+
el.classed('hittable', false);
|
|
9602
9888
|
boxCoords = null;
|
|
9603
9889
|
_visible = false;
|
|
9604
9890
|
clickStartPoint = null;
|
|
@@ -10949,8 +11235,8 @@
|
|
|
10949
11235
|
function showInstructions() {
|
|
10950
11236
|
var isMac = navigator.userAgent.includes('Mac');
|
|
10951
11237
|
var symbol = isMac ? '⌘' : '^';
|
|
10952
|
-
var msg = `Instructions:
|
|
10953
|
-
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'});
|
|
10954
11240
|
}
|
|
10955
11241
|
|
|
10956
11242
|
gui.on('interaction_mode_change', function(e) {
|
|
@@ -11167,9 +11453,9 @@
|
|
|
11167
11453
|
function showInstructions() {
|
|
11168
11454
|
var isMac = navigator.userAgent.includes('Mac');
|
|
11169
11455
|
var undoKey = isMac ? '⌘' : '^';
|
|
11170
|
-
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.`;
|
|
11171
11457
|
alert = showPopupAlert(msg, null, {
|
|
11172
|
-
non_blocking: true, max_width: '
|
|
11458
|
+
non_blocking: true, max_width: '350px'});
|
|
11173
11459
|
}
|
|
11174
11460
|
|
|
11175
11461
|
function hideInstructions() {
|
|
@@ -14631,6 +14917,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14631
14917
|
new SessionSnapshots(gui);
|
|
14632
14918
|
}
|
|
14633
14919
|
gui.interaction = new InteractionMode(gui);
|
|
14920
|
+
gui.editToolbar = new EditToolbar(gui);
|
|
14634
14921
|
|
|
14635
14922
|
gui.showProgressMessage = function(msg) {
|
|
14636
14923
|
if (!gui.progressMessage) {
|
|
@@ -14755,6 +15042,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14755
15042
|
new ImportControl(gui, importOpts);
|
|
14756
15043
|
new ExportControl(gui);
|
|
14757
15044
|
new LayerControl(gui);
|
|
15045
|
+
HeaderMenu();
|
|
14758
15046
|
gui.console = new Console(gui);
|
|
14759
15047
|
|
|
14760
15048
|
startEditing = function() {};
|
package/www/mapshaper.js
CHANGED
|
@@ -52127,7 +52127,7 @@ ${svg}
|
|
|
52127
52127
|
});
|
|
52128
52128
|
}
|
|
52129
52129
|
|
|
52130
|
-
var version = "0.7.
|
|
52130
|
+
var version = "0.7.2";
|
|
52131
52131
|
|
|
52132
52132
|
// Parse command line args into commands and run them
|
|
52133
52133
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/www/page.css
CHANGED
|
@@ -170,7 +170,7 @@ body.map-view {
|
|
|
170
170
|
position: absolute;
|
|
171
171
|
top: 0px;
|
|
172
172
|
right: 0px;
|
|
173
|
-
margin: 0
|
|
173
|
+
margin: 0 3px 3px 0;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
.btn.header-btn {
|
|
@@ -207,6 +207,63 @@ body.map-view {
|
|
|
207
207
|
vertical-align: -1px;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/* --- Hamburger menu (post-edit overflow links) --- */
|
|
211
|
+
|
|
212
|
+
#header-menu-btn {
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
padding: 8px 7px 4px 7px;
|
|
215
|
+
user-select: none;
|
|
216
|
+
/* All header buttons are 29px tall, so top-align the hamburger to keep its
|
|
217
|
+
row baseline-neutral. Without this, the SVG-only button (no text) makes
|
|
218
|
+
its inline-block baseline = bottom edge, which drags the surrounding
|
|
219
|
+
text-containing buttons down to match. */
|
|
220
|
+
vertical-align: top;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#header-menu-btn svg {
|
|
224
|
+
display: block;
|
|
225
|
+
fill: currentColor;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#header-menu-dropdown {
|
|
229
|
+
position: absolute;
|
|
230
|
+
top: 30px;
|
|
231
|
+
right: 0;
|
|
232
|
+
min-width: 180px;
|
|
233
|
+
background-color: #fff;
|
|
234
|
+
color: #333;
|
|
235
|
+
border: 1px solid #ccc;
|
|
236
|
+
border-radius: 3px;
|
|
237
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
238
|
+
padding: 4px 0;
|
|
239
|
+
z-index: 50;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.header-menu-item {
|
|
243
|
+
display: block;
|
|
244
|
+
padding: 4px 14px;
|
|
245
|
+
color: #333;
|
|
246
|
+
font-size: 14px;
|
|
247
|
+
line-height: 1.3;
|
|
248
|
+
text-decoration: none;
|
|
249
|
+
white-space: nowrap;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.header-menu-item:hover,
|
|
253
|
+
.header-menu-item:focus {
|
|
254
|
+
background-color: #e6f7ff;
|
|
255
|
+
color: #1A6A96;
|
|
256
|
+
outline: none;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.header-menu-item.header-menu-sponsor svg {
|
|
260
|
+
width: 12px;
|
|
261
|
+
height: 12px;
|
|
262
|
+
fill: #d63384;
|
|
263
|
+
vertical-align: -1px;
|
|
264
|
+
margin-right: 6px;
|
|
265
|
+
}
|
|
266
|
+
|
|
210
267
|
.separator {
|
|
211
268
|
border-left: 1px solid white;
|
|
212
269
|
height: 10px;
|
|
@@ -427,6 +484,14 @@ div.alert-box {
|
|
|
427
484
|
margin: 0 0 5px 0;
|
|
428
485
|
}
|
|
429
486
|
|
|
487
|
+
input[type=text]:focus,
|
|
488
|
+
input[type=number]:focus,
|
|
489
|
+
textarea:focus {
|
|
490
|
+
outline: none;
|
|
491
|
+
border-color: #d3aa00;
|
|
492
|
+
box-shadow: 0 0 0 2px rgba(212, 168, 0, 0.4);
|
|
493
|
+
}
|
|
494
|
+
|
|
430
495
|
::placeholder {
|
|
431
496
|
color: #aaa;
|
|
432
497
|
opacity: 1;
|
|
@@ -495,13 +560,6 @@ div.alert-box {
|
|
|
495
560
|
padding-left: 12px;
|
|
496
561
|
}
|
|
497
562
|
|
|
498
|
-
.info-box,.popup {
|
|
499
|
-
border-radius: 9px;
|
|
500
|
-
border: 1px solid #d5d5d5;
|
|
501
|
-
box-shadow: 0 1px 7px rgba(0,0,0,0.18);
|
|
502
|
-
background-color: #fff;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
563
|
.contextmenu {
|
|
506
564
|
position: absolute;
|
|
507
565
|
border-radius: 6px;
|
|
@@ -518,6 +576,12 @@ div.alert-box {
|
|
|
518
576
|
padding: 6px;
|
|
519
577
|
}
|
|
520
578
|
|
|
579
|
+
.info-box,.popup {
|
|
580
|
+
border-radius: 11px;
|
|
581
|
+
border: 1px solid #d5d5d5;
|
|
582
|
+
box-shadow: 0 1px 7px rgba(0,0,0,0.18);
|
|
583
|
+
background-color: #fff;
|
|
584
|
+
}
|
|
521
585
|
|
|
522
586
|
.info-box {
|
|
523
587
|
min-width: 230px;
|
|
@@ -525,7 +589,8 @@ div.alert-box {
|
|
|
525
589
|
text-align: left;
|
|
526
590
|
margin-top: 12px;
|
|
527
591
|
margin-right: 20px;
|
|
528
|
-
padding: 12px 16px 12px 18px;
|
|
592
|
+
/* padding: 12px 16px 12px 18px; */
|
|
593
|
+
padding: 15px 17px 13px 19px;
|
|
529
594
|
vertical-align: top;
|
|
530
595
|
display: inline-block;
|
|
531
596
|
/* border: 1px solid #aaa; */
|
|
@@ -575,7 +640,7 @@ div.alert-box {
|
|
|
575
640
|
.info-box p {
|
|
576
641
|
white-space: pre-line;
|
|
577
642
|
margin: 0 0 6px 0;
|
|
578
|
-
line-height: 1.
|
|
643
|
+
line-height: 1.25;
|
|
579
644
|
/* font-size: 90%; */
|
|
580
645
|
}
|
|
581
646
|
|
|
@@ -1552,6 +1617,119 @@ body.pan.panning .map-layers:not(.drawing) {
|
|
|
1552
1617
|
cursor: default;
|
|
1553
1618
|
}
|
|
1554
1619
|
|
|
1620
|
+
/* --- FLOATING TOOLBAR --- */
|
|
1621
|
+
|
|
1622
|
+
/*
|
|
1623
|
+
* Anchored at bottom-center of the map area. Multiple toolbars stack
|
|
1624
|
+
* vertically (newest on top). Designed to host buttons and other small
|
|
1625
|
+
* controls; the inner content slot leaves room for a future drag handle.
|
|
1626
|
+
*/
|
|
1627
|
+
|
|
1628
|
+
.floating-toolbar-stack {
|
|
1629
|
+
position: absolute;
|
|
1630
|
+
z-index: 25;
|
|
1631
|
+
bottom: 24px;
|
|
1632
|
+
left: 50%;
|
|
1633
|
+
transform: translateX(-50%);
|
|
1634
|
+
display: flex;
|
|
1635
|
+
flex-direction: column-reverse;
|
|
1636
|
+
align-items: center;
|
|
1637
|
+
gap: 8px;
|
|
1638
|
+
pointer-events: none;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
.floating-toolbar {
|
|
1642
|
+
pointer-events: auto;
|
|
1643
|
+
display: flex;
|
|
1644
|
+
align-items: center;
|
|
1645
|
+
background-color: rgba(255, 255, 255, 0.96);
|
|
1646
|
+
border: 1px solid #ddd;
|
|
1647
|
+
border-radius: 8px;
|
|
1648
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
|
1649
|
+
padding: 4px;
|
|
1650
|
+
opacity: 0;
|
|
1651
|
+
transform: translateY(6px);
|
|
1652
|
+
transition: opacity 150ms ease, transform 150ms ease;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
.floating-toolbar.visible {
|
|
1656
|
+
opacity: 1;
|
|
1657
|
+
transform: translateY(0);
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
.floating-toolbar-content {
|
|
1661
|
+
display: flex;
|
|
1662
|
+
align-items: center;
|
|
1663
|
+
gap: 0;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
.floating-toolbar-separator {
|
|
1667
|
+
width: 1px;
|
|
1668
|
+
height: 18px;
|
|
1669
|
+
margin: 0 4px;
|
|
1670
|
+
background-color: #ddd;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
.floating-toolbar-btn {
|
|
1674
|
+
position: relative;
|
|
1675
|
+
display: flex;
|
|
1676
|
+
align-items: center;
|
|
1677
|
+
justify-content: center;
|
|
1678
|
+
width: 28px;
|
|
1679
|
+
height: 28px;
|
|
1680
|
+
border-radius: 5px;
|
|
1681
|
+
cursor: pointer;
|
|
1682
|
+
user-select: none;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.floating-toolbar-btn svg {
|
|
1686
|
+
width: 24px;
|
|
1687
|
+
height: 24px;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
.floating-toolbar-btn svg * {
|
|
1691
|
+
fill: #1385B7;
|
|
1692
|
+
transition: fill 100ms ease;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
.floating-toolbar-btn:hover:not(.disabled) {
|
|
1696
|
+
background-color: #e6f7ff;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
.floating-toolbar-btn:hover:not(.disabled) svg * {
|
|
1700
|
+
fill: #166689;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.floating-toolbar-btn:active:not(.disabled) {
|
|
1704
|
+
background-color: #cdebf9;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
.floating-toolbar-btn.disabled {
|
|
1708
|
+
cursor: default;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
.floating-toolbar-btn.disabled svg * {
|
|
1712
|
+
fill: #b8c4ca;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/* Tooltip shown above the button on hover */
|
|
1716
|
+
.floating-toolbar-btn[data-tooltip]:hover:not(.disabled)::after {
|
|
1717
|
+
content: attr(data-tooltip);
|
|
1718
|
+
position: absolute;
|
|
1719
|
+
bottom: calc(100% + 8px);
|
|
1720
|
+
left: 50%;
|
|
1721
|
+
transform: translateX(-50%);
|
|
1722
|
+
background-color: rgba(40, 40, 40, 0.92);
|
|
1723
|
+
color: #fff;
|
|
1724
|
+
font-size: 11px;
|
|
1725
|
+
line-height: 1;
|
|
1726
|
+
white-space: nowrap;
|
|
1727
|
+
padding: 5px 7px;
|
|
1728
|
+
border-radius: 4px;
|
|
1729
|
+
pointer-events: none;
|
|
1730
|
+
z-index: 1;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1555
1733
|
.zoom-box {
|
|
1556
1734
|
position: absolute;
|
|
1557
1735
|
border: 1px solid #cc6acc;
|