mapshaper 0.6.119 → 0.6.121
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 +1 -1
- package/mapshaper.js +10 -2
- package/package.json +1 -1
- package/www/CNAME +1 -0
- package/www/index.html +1 -1
- package/www/mapshaper-gui.js +310 -31
- package/www/mapshaper.js +10 -2
- package/www/page.css +8 -1
- package/www/privacy.html +203 -0
- package/www/{donate.html → sponsor.html} +16 -10
- package/www/terms.html +188 -0
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ Run `npm test` to run mapshaper's tests.
|
|
|
98
98
|
|
|
99
99
|
Mapshaper is free and open source. If it's useful to you or your organization, please consider supporting its continued development:
|
|
100
100
|
|
|
101
|
-
* [Ko-fi](https://ko-fi.com/mapshaper) — one-time or recurring
|
|
101
|
+
* [Ko-fi](https://ko-fi.com/mapshaper) — one-time or recurring contributions, no account required.
|
|
102
102
|
* [GitHub Sponsors](https://github.com/sponsors/mbloch) — recurring sponsorship.
|
|
103
103
|
|
|
104
104
|
You can also help by [reporting bugs](https://github.com/mbloch/mapshaper/issues) or [telling me what you'd like to see improved](https://tally.so/r/44Njok).
|
package/mapshaper.js
CHANGED
|
@@ -11723,7 +11723,11 @@
|
|
|
11723
11723
|
version: 1,
|
|
11724
11724
|
created: 'YYYY-MM-DDTHH:mm:ss.sssZ', // ISO string
|
|
11725
11725
|
datasets: [],
|
|
11726
|
-
gui: {} // see gui-session-snapshot-control.mjs
|
|
11726
|
+
gui: {}, // see gui-session-snapshot-control.mjs
|
|
11727
|
+
history: { // optional; only present in snapshots created by the GUI
|
|
11728
|
+
commands: ['-i foo.shp', '-simplify 10%', ...],
|
|
11729
|
+
savedAtIndex: 0 // index of the first command after the last save boundary
|
|
11730
|
+
}
|
|
11727
11731
|
}
|
|
11728
11732
|
*/
|
|
11729
11733
|
|
|
@@ -11747,12 +11751,16 @@
|
|
|
11747
11751
|
// exporting from command line: { compact: true, file: 'tmp.msx', final: true }
|
|
11748
11752
|
// exporting from gui export menu: {compact: true, format: 'msx'}
|
|
11749
11753
|
// saving gui temp snapshot: {compact: false}
|
|
11754
|
+
// opts.history: optional GUI session history captured by SessionHistory#getHistorySnapshot
|
|
11750
11755
|
async function exportDatasetsToPack(datasets, opts) {
|
|
11751
11756
|
var obj = {
|
|
11752
11757
|
version: 1,
|
|
11753
11758
|
created: (new Date).toISOString(),
|
|
11754
11759
|
datasets: await Promise.all(datasets.map(dataset => exportDataset(dataset, opts)))
|
|
11755
11760
|
};
|
|
11761
|
+
if (opts.history) {
|
|
11762
|
+
obj.history = opts.history;
|
|
11763
|
+
}
|
|
11756
11764
|
return obj;
|
|
11757
11765
|
}
|
|
11758
11766
|
|
|
@@ -51131,7 +51139,7 @@ ${svg}
|
|
|
51131
51139
|
});
|
|
51132
51140
|
}
|
|
51133
51141
|
|
|
51134
|
-
var version = "0.6.
|
|
51142
|
+
var version = "0.6.121";
|
|
51135
51143
|
|
|
51136
51144
|
// Parse command line args into commands and run them
|
|
51137
51145
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/package.json
CHANGED
package/www/CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mapshaper.org
|
package/www/index.html
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
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>
|
|
64
64
|
</div>
|
|
65
65
|
<div id="splash-buttons" class="page-header-buttons">
|
|
66
|
-
<a href="https://github.com/mbloch/mapshaper/wiki"><span id="wiki-btn" class="header-btn btn">Wiki</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 href="
|
|
66
|
+
<a href="https://github.com/mbloch/mapshaper/wiki"><span id="wiki-btn" class="header-btn btn">Wiki</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 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
67
|
</div>
|
|
68
68
|
</div>
|
|
69
69
|
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -1580,6 +1580,19 @@
|
|
|
1580
1580
|
action: saveSnapshot
|
|
1581
1581
|
});
|
|
1582
1582
|
|
|
1583
|
+
if (!gui.session.isEmpty()) {
|
|
1584
|
+
// Surface the console "history" command via the snapshot menu so users
|
|
1585
|
+
// can browse the session's command history without knowing about the
|
|
1586
|
+
// console keyword. Hidden when there's nothing to show.
|
|
1587
|
+
addMenuLink({
|
|
1588
|
+
slug: 'history',
|
|
1589
|
+
label: 'view session history',
|
|
1590
|
+
action: function(gui) {
|
|
1591
|
+
gui.console.runCommand('history');
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1583
1596
|
// var available = await getAvailableStorage();
|
|
1584
1597
|
// if (available) {
|
|
1585
1598
|
// El('div').addClass('save-menu-entry').text(available + ' available').appendTo(menu);
|
|
@@ -1703,10 +1716,24 @@
|
|
|
1703
1716
|
}
|
|
1704
1717
|
gui.model.clear();
|
|
1705
1718
|
importDatasets(data.datasets, gui);
|
|
1719
|
+
// Reinstate the session history (including its saved/unsaved boundary) that
|
|
1720
|
+
// was in effect when the snapshot was taken. If the snapshot has no history
|
|
1721
|
+
// field (e.g. older snapshots), this resets to a clean state.
|
|
1722
|
+
gui.session.restoreHistorySnapshot(data.history);
|
|
1706
1723
|
gui.clearMode();
|
|
1707
1724
|
}
|
|
1708
1725
|
|
|
1709
|
-
//
|
|
1726
|
+
// Import datasets from a packed .msx buffer.
|
|
1727
|
+
// Behavior depends on whether the current session contains data:
|
|
1728
|
+
// - empty session: full project restore -- datasets and any embedded session
|
|
1729
|
+
// history are loaded as if continuing the original session.
|
|
1730
|
+
// - non-empty session: merge -- datasets are added to the current project,
|
|
1731
|
+
// but any embedded session history is discarded (the imported commands
|
|
1732
|
+
// assume different layer indices and a different starting state, so
|
|
1733
|
+
// merging them into the current session would produce a misleading history).
|
|
1734
|
+
// Returns true if a full restore occurred, false if a merge occurred. The
|
|
1735
|
+
// caller uses this to decide whether to record an additional -i command in
|
|
1736
|
+
// the current session's history (see gui-import-control.mjs).
|
|
1710
1737
|
// TODO: figure out if interface data should be imported (e.g. should
|
|
1711
1738
|
// visibility flag of imported layers be imported)
|
|
1712
1739
|
async function importSessionData(buf, gui) {
|
|
@@ -1714,7 +1741,12 @@
|
|
|
1714
1741
|
buf = new Uint8Array(buf);
|
|
1715
1742
|
}
|
|
1716
1743
|
var data = await internal.unpackSessionData(buf);
|
|
1744
|
+
var fullRestore = gui.model.isEmpty();
|
|
1717
1745
|
importDatasets(data.datasets, gui);
|
|
1746
|
+
if (fullRestore) {
|
|
1747
|
+
gui.session.restoreHistorySnapshot(data.history);
|
|
1748
|
+
}
|
|
1749
|
+
return fullRestore;
|
|
1718
1750
|
}
|
|
1719
1751
|
|
|
1720
1752
|
function importDatasets(datasets, gui) {
|
|
@@ -1730,7 +1762,13 @@
|
|
|
1730
1762
|
if (!lyr) return null; // no data -- no snapshot
|
|
1731
1763
|
// compact: true applies compression to vector coordinates, for ~30% reduction
|
|
1732
1764
|
// in file size in a typical polygon or polyline file, but longer processing time
|
|
1733
|
-
|
|
1765
|
+
// history: capture session commands + saved/unsaved boundary so the history
|
|
1766
|
+
// can be reinstated if this snapshot is restored or re-imported later.
|
|
1767
|
+
var opts = {
|
|
1768
|
+
compact: false,
|
|
1769
|
+
active_layer: lyr,
|
|
1770
|
+
history: gui.session.getHistorySnapshot()
|
|
1771
|
+
};
|
|
1734
1772
|
var datasets = gui.model.getDatasets();
|
|
1735
1773
|
var obj = await internal.exportDatasetsToPack(datasets, opts);
|
|
1736
1774
|
obj.gui = getGuiState(gui);
|
|
@@ -2348,7 +2386,16 @@
|
|
|
2348
2386
|
await wait(35);
|
|
2349
2387
|
}
|
|
2350
2388
|
if (group[internal.PACKAGE_EXT]) {
|
|
2351
|
-
await importSessionData(group[internal.PACKAGE_EXT].content, gui);
|
|
2389
|
+
var fullRestore = await importSessionData(group[internal.PACKAGE_EXT].content, gui);
|
|
2390
|
+
importCount++;
|
|
2391
|
+
// Skip recording an -i command if the .msx import was a full project
|
|
2392
|
+
// restore: the previous session's history (including the original -i)
|
|
2393
|
+
// has already been reinstated, so adding another entry here would be
|
|
2394
|
+
// misleading. For the merge case, record the import as a regular -i
|
|
2395
|
+
// so the snapshot contributes a CLI-replayable entry to the session.
|
|
2396
|
+
if (!fullRestore) {
|
|
2397
|
+
gui.session.fileImported(group[internal.PACKAGE_EXT].filename, optStr);
|
|
2398
|
+
}
|
|
2352
2399
|
} else if (await importDataset(group, groupImportOpts)) {
|
|
2353
2400
|
importCount++;
|
|
2354
2401
|
gui.session.fileImported(group.filename, optStr);
|
|
@@ -3079,13 +3126,12 @@
|
|
|
3079
3126
|
throw Error('Internal error');
|
|
3080
3127
|
}
|
|
3081
3128
|
}
|
|
3129
|
+
// switch to filtered version of arcs at small scales
|
|
3130
|
+
var useFiltering = filteredArcs && 1/ext.getTransform().mx > filteredSegLen * 1.5;
|
|
3082
3131
|
if (filteredArcs) {
|
|
3083
3132
|
// match simplification of unfiltered arcs
|
|
3084
3133
|
filteredArcs.setRetainedInterval(unfilteredArcs.getRetainedInterval());
|
|
3085
3134
|
}
|
|
3086
|
-
// switch to filtered version of arcs at small scales
|
|
3087
|
-
var unitsPerPixel = 1/ext.getTransform().mx,
|
|
3088
|
-
useFiltering = filteredArcs && unitsPerPixel > filteredSegLen * 1.5;
|
|
3089
3135
|
return useFiltering ? filteredArcs : unfilteredArcs;
|
|
3090
3136
|
};
|
|
3091
3137
|
}
|
|
@@ -3959,13 +4005,18 @@
|
|
|
3959
4005
|
// expose this function, so other components can run commands (e.g. box tool)
|
|
3960
4006
|
this.runMapshaperCommands = runMapshaperCommands;
|
|
3961
4007
|
|
|
3962
|
-
|
|
4008
|
+
// Open the console (if closed) and run a command, as if the user had
|
|
4009
|
+
// typed it. Used by UI controls that surface console functionality, e.g.
|
|
4010
|
+
// the "view command history" link in the snapshot menu.
|
|
4011
|
+
this.runCommand = function(str) {
|
|
3963
4012
|
str = str.trim();
|
|
3964
4013
|
if (!str) return;
|
|
3965
4014
|
turnOn();
|
|
3966
4015
|
submit(str);
|
|
3967
4016
|
};
|
|
3968
4017
|
|
|
4018
|
+
this.runInitialCommands = this.runCommand;
|
|
4019
|
+
|
|
3969
4020
|
consoleMessage(PROMPT);
|
|
3970
4021
|
gui.keyboard.on('keydown', onKeyDown);
|
|
3971
4022
|
window.addEventListener('beforeunload', saveHistory); // save history if console is open on refresh
|
|
@@ -4784,6 +4835,17 @@
|
|
|
4784
4835
|
await loadGeopackageLib();
|
|
4785
4836
|
}
|
|
4786
4837
|
opts.active_layer = gui.model.getActiveLayer().layer; // kludge to support restoring active layer in gui
|
|
4838
|
+
if (opts.format == internal.PACKAGE_EXT) {
|
|
4839
|
+
// Embed the session history in .msx exports so that re-importing the
|
|
4840
|
+
// file into a fresh session restores the original command history.
|
|
4841
|
+
// The .msx file itself is a durable artifact, so mark every captured
|
|
4842
|
+
// command as "saved" -- a user reloading the file shouldn't see an
|
|
4843
|
+
// unsaved-changes warning for work that lives in the file they just
|
|
4844
|
+
// opened.
|
|
4845
|
+
var snapshot = gui.session.getHistorySnapshot();
|
|
4846
|
+
snapshot.savedAtIndex = snapshot.commands.length;
|
|
4847
|
+
opts.history = snapshot;
|
|
4848
|
+
}
|
|
4787
4849
|
try {
|
|
4788
4850
|
var files = await internal.exportTargetLayers(model, targets, opts);
|
|
4789
4851
|
} catch(e) {
|
|
@@ -5593,20 +5655,54 @@
|
|
|
5593
5655
|
|
|
5594
5656
|
function SessionHistory(gui) {
|
|
5595
5657
|
var commands = [];
|
|
5658
|
+
// index of first command after the last "save" boundary; commands at indices
|
|
5659
|
+
// [savedAtIndex .. commands.length) are considered unsaved
|
|
5660
|
+
var savedAtIndex = 0;
|
|
5596
5661
|
// commands that can be ignored when checking for unsaved changes
|
|
5597
|
-
var nonEditingCommands = 'i,target,info,version,verbose,projections,inspect,help,h,encodings,calc'.split(',');
|
|
5662
|
+
var nonEditingCommands = 'i,target,info,version,verbose,projections,inspect,help,h,encodings,calc,comment'.split(',');
|
|
5598
5663
|
|
|
5599
5664
|
this.unsavedChanges = function() {
|
|
5600
|
-
var
|
|
5601
|
-
|
|
5602
|
-
cmdName = getCommandName(commands[i]);
|
|
5603
|
-
if (cmdName == 'o') break;
|
|
5665
|
+
for (var i = commands.length - 1; i >= savedAtIndex; i--) {
|
|
5666
|
+
var cmdName = getCommandName(commands[i]);
|
|
5604
5667
|
if (nonEditingCommands.includes(cmdName)) continue;
|
|
5605
5668
|
return true;
|
|
5606
5669
|
}
|
|
5607
5670
|
return false;
|
|
5608
5671
|
};
|
|
5609
5672
|
|
|
5673
|
+
this.isEmpty = function() {
|
|
5674
|
+
return commands.length === 0;
|
|
5675
|
+
};
|
|
5676
|
+
|
|
5677
|
+
// Mark the current end of the history as a "saved" boundary -- called after
|
|
5678
|
+
// data has been written somewhere durable (e.g. an -o export). Snapshots
|
|
5679
|
+
// are session-scoped and are NOT durable, so creating one does not mark saved.
|
|
5680
|
+
this.markSaved = function() {
|
|
5681
|
+
savedAtIndex = commands.length;
|
|
5682
|
+
};
|
|
5683
|
+
|
|
5684
|
+
// Capture a serializable copy of the history for inclusion in a snapshot.
|
|
5685
|
+
this.getHistorySnapshot = function() {
|
|
5686
|
+
return {
|
|
5687
|
+
commands: commands.slice(),
|
|
5688
|
+
savedAtIndex: savedAtIndex
|
|
5689
|
+
};
|
|
5690
|
+
};
|
|
5691
|
+
|
|
5692
|
+
// Replace the current history with one captured by getHistorySnapshot().
|
|
5693
|
+
// Used when restoring an in-session snapshot. If the snapshot has no history
|
|
5694
|
+
// (e.g. older snapshots, or external .msx files), starts from a clean state.
|
|
5695
|
+
this.restoreHistorySnapshot = function(obj) {
|
|
5696
|
+
if (obj && Array.isArray(obj.commands)) {
|
|
5697
|
+
commands = obj.commands.slice();
|
|
5698
|
+
savedAtIndex = typeof obj.savedAtIndex == 'number' ?
|
|
5699
|
+
Math.min(obj.savedAtIndex, commands.length) : commands.length;
|
|
5700
|
+
} else {
|
|
5701
|
+
commands = [];
|
|
5702
|
+
savedAtIndex = 0;
|
|
5703
|
+
}
|
|
5704
|
+
};
|
|
5705
|
+
|
|
5610
5706
|
this.fileImported = function(file, optStr) {
|
|
5611
5707
|
var cmd = '-i ' + file;
|
|
5612
5708
|
if (optStr) {
|
|
@@ -5666,6 +5762,8 @@
|
|
|
5666
5762
|
cmd += ' ' + optStr;
|
|
5667
5763
|
}
|
|
5668
5764
|
commands.push(cmd);
|
|
5765
|
+
// -o writes data to a durable location, so treat this as a save boundary
|
|
5766
|
+
savedAtIndex = commands.length;
|
|
5669
5767
|
};
|
|
5670
5768
|
|
|
5671
5769
|
this.setTargetLayer = function(lyr) {
|
|
@@ -8931,6 +9029,9 @@
|
|
|
8931
9029
|
var pct = ease ? ease(e.pct) : e.pct,
|
|
8932
9030
|
val = end * pct + start * (1 - pct);
|
|
8933
9031
|
self.dispatchEvent('change', {value: val});
|
|
9032
|
+
if (e.done) {
|
|
9033
|
+
self.dispatchEvent('done');
|
|
9034
|
+
}
|
|
8934
9035
|
}
|
|
8935
9036
|
}
|
|
8936
9037
|
|
|
@@ -9095,6 +9196,7 @@
|
|
|
9095
9196
|
}
|
|
9096
9197
|
if (evt.done) {
|
|
9097
9198
|
active = false;
|
|
9199
|
+
self.dispatchEvent('mousewheelend');
|
|
9098
9200
|
} else {
|
|
9099
9201
|
if (fadeFactor > 0) {
|
|
9100
9202
|
// Decelerate towards the end of the sustain interval (for smoother zooming)
|
|
@@ -9688,12 +9790,26 @@
|
|
|
9688
9790
|
|
|
9689
9791
|
gui.on('map_reset', function() {
|
|
9690
9792
|
ext.reset(true);
|
|
9793
|
+
// ext.reset() synchronously triggers a 'nav' draw; signal end-of-
|
|
9794
|
+
// interaction so the LayerRenderer settles on a sharp frame
|
|
9795
|
+
// immediately instead of waiting on the fallback timer.
|
|
9796
|
+
gui.dispatchEvent('map_interaction_end');
|
|
9691
9797
|
});
|
|
9692
9798
|
|
|
9693
9799
|
zoomTween.on('change', function(e) {
|
|
9694
9800
|
ext.zoomToExtent(e.value, _fx, _fy);
|
|
9695
9801
|
});
|
|
9696
9802
|
|
|
9803
|
+
// Signal end-of-interaction so downstream modules (e.g. LayerRenderer) can
|
|
9804
|
+
// trigger a settle/redraw immediately, instead of waiting on a debounce.
|
|
9805
|
+
zoomTween.on('done', function() {
|
|
9806
|
+
gui.dispatchEvent('map_interaction_end');
|
|
9807
|
+
});
|
|
9808
|
+
|
|
9809
|
+
wheel.on('mousewheelend', function() {
|
|
9810
|
+
gui.dispatchEvent('map_interaction_end');
|
|
9811
|
+
});
|
|
9812
|
+
|
|
9697
9813
|
mouse.on('click', function(e) {
|
|
9698
9814
|
gui.dispatchEvent('map_click', e);
|
|
9699
9815
|
});
|
|
@@ -9744,6 +9860,7 @@
|
|
|
9744
9860
|
zoomBox.turnOff();
|
|
9745
9861
|
} else {
|
|
9746
9862
|
El('body').removeClass('panning').removeClass('pan');
|
|
9863
|
+
gui.dispatchEvent('map_interaction_end');
|
|
9747
9864
|
}
|
|
9748
9865
|
});
|
|
9749
9866
|
|
|
@@ -12279,7 +12396,6 @@
|
|
|
12279
12396
|
}
|
|
12280
12397
|
|
|
12281
12398
|
function drawStyledLayerToCanvas(lyr, canv, ext) {
|
|
12282
|
-
// TODO: add filter for out-of-view shapes
|
|
12283
12399
|
var style = lyr.gui.style;
|
|
12284
12400
|
var layer = lyr.gui.displayLayer;
|
|
12285
12401
|
var arcs, filter;
|
|
@@ -12291,7 +12407,7 @@
|
|
|
12291
12407
|
}
|
|
12292
12408
|
} else {
|
|
12293
12409
|
arcs = getArcsForRendering(lyr, ext);
|
|
12294
|
-
filter = getShapeFilter(arcs, ext);
|
|
12410
|
+
filter = getShapeFilter(arcs, layer.shapes, ext);
|
|
12295
12411
|
canv.drawStyledPaths(layer.shapes, arcs, style, filter);
|
|
12296
12412
|
if (style.vertices) {
|
|
12297
12413
|
canv.drawVertices(layer.shapes, arcs, style, filter);
|
|
@@ -12300,11 +12416,12 @@
|
|
|
12300
12416
|
canv.clearStyles();
|
|
12301
12417
|
}
|
|
12302
12418
|
|
|
12303
|
-
|
|
12304
12419
|
// Return a function for testing if an arc should be drawn in the current view
|
|
12305
12420
|
function getArcFilter(arcs, ext, usedFlag, arcCounts) {
|
|
12306
|
-
|
|
12307
|
-
|
|
12421
|
+
// Arcs whose bbox is smaller than this pixel threshold collapse to a dot
|
|
12422
|
+
// under roundToPix anyway; skipping them saves per-arc iteration.
|
|
12423
|
+
var MIN_PATH_LEN = 0.35;
|
|
12424
|
+
var minPathLen = ext.getPixelSize() * MIN_PATH_LEN,
|
|
12308
12425
|
geoBounds = ext.getBounds(),
|
|
12309
12426
|
geoBBox = geoBounds.toArray(),
|
|
12310
12427
|
allIn = geoBounds.contains(arcs.getBounds()),
|
|
@@ -12324,15 +12441,20 @@
|
|
|
12324
12441
|
};
|
|
12325
12442
|
}
|
|
12326
12443
|
|
|
12327
|
-
// Return a function for testing if a shape should be drawn in the current
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12444
|
+
// Return a function for testing if a shape should be drawn in the current
|
|
12445
|
+
// view. The filter takes a shape index and tests the shape's bbox against
|
|
12446
|
+
// the viewport. At nearly full extent the test is a no-op, so we return
|
|
12447
|
+
// null to let the caller skip it entirely.
|
|
12448
|
+
function getShapeFilter(arcs, shapes, ext) {
|
|
12449
|
+
if (ext.scale() < 1.1) return null;
|
|
12450
|
+
var view = ext.getBounds();
|
|
12451
|
+
var b = new Bounds();
|
|
12452
|
+
return function(i) {
|
|
12453
|
+
var shp = shapes[i];
|
|
12454
|
+
if (!shp) return false;
|
|
12455
|
+
b.empty();
|
|
12456
|
+
arcs.getMultiShapeBounds(shp, b);
|
|
12457
|
+
return view.intersects(b);
|
|
12336
12458
|
};
|
|
12337
12459
|
}
|
|
12338
12460
|
|
|
@@ -12405,7 +12527,7 @@
|
|
|
12405
12527
|
_ctx.fillStyle = color;
|
|
12406
12528
|
for (i=0; i<shapes.length; i++) {
|
|
12407
12529
|
var shp = shapes[i];
|
|
12408
|
-
if (!shp || filter && !filter(
|
|
12530
|
+
if (!shp || filter && !filter(i)) continue;
|
|
12409
12531
|
for (j=0; j<shp.length; j++) {
|
|
12410
12532
|
iter.init(shp[j]);
|
|
12411
12533
|
while (iter.hasNext()) {
|
|
@@ -12431,14 +12553,14 @@
|
|
|
12431
12553
|
// Optimized to draw paths in same-style batches (faster Canvas drawing)
|
|
12432
12554
|
_self.drawStyledPaths = function(shapes, arcs, style, filter) {
|
|
12433
12555
|
var styleIndex = {};
|
|
12434
|
-
var batchSize =
|
|
12556
|
+
var batchSize = 100;
|
|
12435
12557
|
var startPath = getPathStart(_ext, getScaledLineScale(_ext, style));
|
|
12436
12558
|
var draw = getShapePencil(arcs, _ext);
|
|
12437
12559
|
var key, item, shp;
|
|
12438
12560
|
var styler = style.styler || null;
|
|
12439
12561
|
for (var i=0; i<shapes.length; i++) {
|
|
12440
12562
|
shp = shapes[i];
|
|
12441
|
-
if (!shp || filter && !filter(
|
|
12563
|
+
if (!shp || filter && !filter(i)) continue;
|
|
12442
12564
|
if (styler) {
|
|
12443
12565
|
styler(style, i);
|
|
12444
12566
|
}
|
|
@@ -12606,7 +12728,8 @@
|
|
|
12606
12728
|
var startPath = getPathStart(_ext, getLineScale(_ext)),
|
|
12607
12729
|
t = getScaledTransform(_ext),
|
|
12608
12730
|
ctx = _ctx,
|
|
12609
|
-
|
|
12731
|
+
// Larger batches reduce the number of stroke() flushes.
|
|
12732
|
+
batch = 100,
|
|
12610
12733
|
count = 0,
|
|
12611
12734
|
n = arcs.size(),
|
|
12612
12735
|
i, iter;
|
|
@@ -12963,12 +13086,68 @@
|
|
|
12963
13086
|
_furniture = new SvgDisplayLayer(gui, ext, null).appendTo(el),
|
|
12964
13087
|
_ext = ext;
|
|
12965
13088
|
|
|
13089
|
+
// Fast-nav config: when the most recent render cycle exceeded SLOW_FRAME_MS,
|
|
13090
|
+
// subsequent 'nav' actions transform the previously rendered bitmap via CSS
|
|
13091
|
+
// instead of re-drawing vector content. This trades visible scaling/edge
|
|
13092
|
+
// artifacts for smoother panning and zooming on large datasets.
|
|
13093
|
+
//
|
|
13094
|
+
// Canvas 2D paint ops are normally rasterized asynchronously, so JS usually
|
|
13095
|
+
// returns long before the pixels are committed to the compositor. We force
|
|
13096
|
+
// a synchronous flush at the end of a full render (see flushCanvas) so that
|
|
13097
|
+
// (a) the elapsed time reflects the *true* frame cost (JS + rasterization)
|
|
13098
|
+
// rather than just the JS portion, and
|
|
13099
|
+
// (b) subsequent fast-nav CSS transforms composite cleanly rather than on
|
|
13100
|
+
// top of a still-committing bitmap.
|
|
13101
|
+
// We use only the most recent sample because previous renders may have
|
|
13102
|
+
// drawn a different scene (e.g. a simpler layer that is no longer active).
|
|
13103
|
+
var SLOW_FRAME_MS = 100;
|
|
13104
|
+
// Fallback delay before triggering a redraw if no explicit end-of-interaction
|
|
13105
|
+
// event arrives. The primary trigger is gui 'map_interaction_end' (mouse
|
|
13106
|
+
// release, wheel timeout, zoom tween done); this timer only fires if that
|
|
13107
|
+
// signal is somehow missed, so it's set generously.
|
|
13108
|
+
var FAST_SETTLE_FALLBACK_MS = 2000;
|
|
13109
|
+
var _lastFrameMs = 0; // duration of the most recent full render cycle
|
|
13110
|
+
var _snapshot = null; // {bounds, width, height, pixRatio}
|
|
13111
|
+
var _fastActive = false;
|
|
13112
|
+
var _settleTimer = null;
|
|
13113
|
+
var _redrawPending = false;
|
|
13114
|
+
var _settleRequested = false;
|
|
13115
|
+
|
|
13116
|
+
// 'map_interaction_end' may arrive before the in-flight 'nav' draw runs,
|
|
13117
|
+
// because drawLayers() schedules its work via requestAnimationFrame.
|
|
13118
|
+
// We latch the intent so that whichever order things happen in, the
|
|
13119
|
+
// settle fires immediately rather than waiting on the fallback timer.
|
|
13120
|
+
gui.on('map_interaction_end', function() {
|
|
13121
|
+
if (_redrawPending) {
|
|
13122
|
+
settleNow();
|
|
13123
|
+
} else {
|
|
13124
|
+
_settleRequested = true;
|
|
13125
|
+
}
|
|
13126
|
+
});
|
|
13127
|
+
|
|
12966
13128
|
// don't let furniture container block events to symbol layers
|
|
12967
13129
|
_furniture.css('pointer-events', 'none');
|
|
12968
13130
|
|
|
12969
13131
|
this.drawMainLayers = function(layers, action) {
|
|
12970
|
-
var needSvgRedraw = action != 'nav' && action != 'hover';
|
|
12971
13132
|
if (skipMainLayerRedraw(action)) return;
|
|
13133
|
+
if (action == 'nav' && shouldUseFastNav()) {
|
|
13134
|
+
applyFastTransform();
|
|
13135
|
+
// SVG symbol reposition is already cheap; keep labels/symbols accurate
|
|
13136
|
+
// while the canvas is being transformed.
|
|
13137
|
+
layers.forEach(function(lyr) {
|
|
13138
|
+
if (internal.layerHasSvgSymbols(lyr) || internal.layerHasLabels(lyr)) {
|
|
13139
|
+
_svg.reposition(lyr, 'symbol');
|
|
13140
|
+
}
|
|
13141
|
+
});
|
|
13142
|
+
markRedrawPending();
|
|
13143
|
+
return;
|
|
13144
|
+
}
|
|
13145
|
+
var startTime = performance.now();
|
|
13146
|
+
var needSvgRedraw = action != 'nav' && action != 'hover';
|
|
13147
|
+
cancelSettle();
|
|
13148
|
+
_redrawPending = false;
|
|
13149
|
+
_settleRequested = false; // a full render satisfies any pending settle
|
|
13150
|
+
clearFastTransform();
|
|
12972
13151
|
_mainCanv.prep(_ext);
|
|
12973
13152
|
if (needSvgRedraw) {
|
|
12974
13153
|
_svg.clear();
|
|
@@ -12983,6 +13162,12 @@
|
|
|
12983
13162
|
drawCanvasLayer(lyr, _mainCanv);
|
|
12984
13163
|
}
|
|
12985
13164
|
});
|
|
13165
|
+
// Force synchronous rasterization so performance.now() reflects the true
|
|
13166
|
+
// frame cost (including GPU paint-op commit), and so subsequent fast-nav
|
|
13167
|
+
// CSS transforms don't composite over a still-pending canvas commit.
|
|
13168
|
+
flushCanvas(_mainCanv);
|
|
13169
|
+
_lastFrameMs = performance.now() - startTime;
|
|
13170
|
+
captureSnapshot();
|
|
12986
13171
|
};
|
|
12987
13172
|
|
|
12988
13173
|
// Draw highlight effect for hover and selection
|
|
@@ -13034,6 +13219,18 @@
|
|
|
13034
13219
|
}
|
|
13035
13220
|
}
|
|
13036
13221
|
|
|
13222
|
+
// Reading back a single pixel forces Chrome/Firefox to synchronously complete
|
|
13223
|
+
// any queued paint operations before returning. This converts an unbounded
|
|
13224
|
+
// deferred "Commit" phase into in-line wall time we can measure, and ensures
|
|
13225
|
+
// the canvas pixels are actually on screen before the caller returns.
|
|
13226
|
+
function flushCanvas(canv) {
|
|
13227
|
+
try {
|
|
13228
|
+
canv.node().getContext('2d').getImageData(0, 0, 1, 1);
|
|
13229
|
+
} catch (e) {
|
|
13230
|
+
// e.g. cross-origin-tainted canvas (shouldn't happen here, but safe)
|
|
13231
|
+
}
|
|
13232
|
+
}
|
|
13233
|
+
|
|
13037
13234
|
function getSvgLayerType(layer) {
|
|
13038
13235
|
var type = null;
|
|
13039
13236
|
if (internal.layerHasSvgSymbols(layer)) {
|
|
@@ -13043,6 +13240,87 @@
|
|
|
13043
13240
|
}
|
|
13044
13241
|
return type;
|
|
13045
13242
|
}
|
|
13243
|
+
|
|
13244
|
+
function captureSnapshot() {
|
|
13245
|
+
_snapshot = {
|
|
13246
|
+
bounds: _ext.getBounds(),
|
|
13247
|
+
width: _ext.width(),
|
|
13248
|
+
height: _ext.height(),
|
|
13249
|
+
pixRatio: GUI.getPixelRatio()
|
|
13250
|
+
};
|
|
13251
|
+
}
|
|
13252
|
+
|
|
13253
|
+
function shouldUseFastNav() {
|
|
13254
|
+
if (!_snapshot) return false;
|
|
13255
|
+
if (_lastFrameMs <= SLOW_FRAME_MS) return false;
|
|
13256
|
+
if (_snapshot.width != _ext.width() || _snapshot.height != _ext.height()) return false;
|
|
13257
|
+
if (_snapshot.pixRatio != GUI.getPixelRatio()) return false;
|
|
13258
|
+
return true;
|
|
13259
|
+
}
|
|
13260
|
+
|
|
13261
|
+
// Apply a CSS transform to the main canvas so its previously rendered
|
|
13262
|
+
// contents line up with the current map extent. The canvas bitmap is not
|
|
13263
|
+
// redrawn.
|
|
13264
|
+
function applyFastTransform() {
|
|
13265
|
+
var t = _ext.getTransform();
|
|
13266
|
+
var b = _snapshot.bounds;
|
|
13267
|
+
var tl = t.transform(b.xmin, b.ymax);
|
|
13268
|
+
var br = t.transform(b.xmax, b.ymin);
|
|
13269
|
+
var sx = (br[0] - tl[0]) / _snapshot.width;
|
|
13270
|
+
var sy = (br[1] - tl[1]) / _snapshot.height;
|
|
13271
|
+
// On retina the canvas bitmap is already scaled down to CSS pixels via a
|
|
13272
|
+
// CSS transform from the .retina class; overriding `transform` here
|
|
13273
|
+
// replaces that rule so we must bake the pixRatio scale back in.
|
|
13274
|
+
var k = 1 / _snapshot.pixRatio;
|
|
13275
|
+
setFastTransform(_mainCanv.node(), tl[0], tl[1], sx * k, sy * k);
|
|
13276
|
+
_fastActive = true;
|
|
13277
|
+
}
|
|
13278
|
+
|
|
13279
|
+
function clearFastTransform() {
|
|
13280
|
+
if (!_fastActive) return;
|
|
13281
|
+
var node = _mainCanv.node();
|
|
13282
|
+
node.style.transform = '';
|
|
13283
|
+
node.style.transformOrigin = '';
|
|
13284
|
+
_fastActive = false;
|
|
13285
|
+
}
|
|
13286
|
+
|
|
13287
|
+
function setFastTransform(node, tx, ty, sx, sy) {
|
|
13288
|
+
node.style.transformOrigin = 'top left';
|
|
13289
|
+
node.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(' + sx + ',' + sy + ')';
|
|
13290
|
+
}
|
|
13291
|
+
|
|
13292
|
+
// Record that a fast-nav frame is showing so that the next end-of-interaction
|
|
13293
|
+
// event (or the fallback timer) can trigger a real redraw. Each fast-nav
|
|
13294
|
+
// frame resets the fallback timer so it only fires after the user truly
|
|
13295
|
+
// stops interacting, as a backstop for any interaction path that doesn't
|
|
13296
|
+
// emit 'map_interaction_end'. If 'map_interaction_end' was already
|
|
13297
|
+
// received before this frame ran (e.g. the home button reset, which
|
|
13298
|
+
// dispatches synchronously while the nav draw is still queued in rAF),
|
|
13299
|
+
// settle right now instead of waiting.
|
|
13300
|
+
function markRedrawPending() {
|
|
13301
|
+
_redrawPending = true;
|
|
13302
|
+
cancelSettle();
|
|
13303
|
+
if (_settleRequested) {
|
|
13304
|
+
_settleRequested = false;
|
|
13305
|
+
settleNow();
|
|
13306
|
+
} else {
|
|
13307
|
+
_settleTimer = setTimeout(settleNow, FAST_SETTLE_FALLBACK_MS);
|
|
13308
|
+
}
|
|
13309
|
+
}
|
|
13310
|
+
|
|
13311
|
+
function settleNow() {
|
|
13312
|
+
cancelSettle();
|
|
13313
|
+
if (!_redrawPending) return;
|
|
13314
|
+
_redrawPending = false;
|
|
13315
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
13316
|
+
}
|
|
13317
|
+
|
|
13318
|
+
function cancelSettle() {
|
|
13319
|
+
if (_settleTimer) {
|
|
13320
|
+
clearTimeout(_settleTimer);
|
|
13321
|
+
_settleTimer = null;
|
|
13322
|
+
}
|
|
13323
|
+
}
|
|
13046
13324
|
}
|
|
13047
13325
|
|
|
13048
13326
|
// Controls the shift-drag box editing tool
|
|
@@ -13778,6 +14056,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
13778
14056
|
|
|
13779
14057
|
// RENDERING
|
|
13780
14058
|
// draw main content layers
|
|
14059
|
+
|
|
13781
14060
|
_renderer.drawMainLayers(contentLayers, action);
|
|
13782
14061
|
|
|
13783
14062
|
// draw hover & selection overlay
|
package/www/mapshaper.js
CHANGED
|
@@ -11723,7 +11723,11 @@
|
|
|
11723
11723
|
version: 1,
|
|
11724
11724
|
created: 'YYYY-MM-DDTHH:mm:ss.sssZ', // ISO string
|
|
11725
11725
|
datasets: [],
|
|
11726
|
-
gui: {} // see gui-session-snapshot-control.mjs
|
|
11726
|
+
gui: {}, // see gui-session-snapshot-control.mjs
|
|
11727
|
+
history: { // optional; only present in snapshots created by the GUI
|
|
11728
|
+
commands: ['-i foo.shp', '-simplify 10%', ...],
|
|
11729
|
+
savedAtIndex: 0 // index of the first command after the last save boundary
|
|
11730
|
+
}
|
|
11727
11731
|
}
|
|
11728
11732
|
*/
|
|
11729
11733
|
|
|
@@ -11747,12 +11751,16 @@
|
|
|
11747
11751
|
// exporting from command line: { compact: true, file: 'tmp.msx', final: true }
|
|
11748
11752
|
// exporting from gui export menu: {compact: true, format: 'msx'}
|
|
11749
11753
|
// saving gui temp snapshot: {compact: false}
|
|
11754
|
+
// opts.history: optional GUI session history captured by SessionHistory#getHistorySnapshot
|
|
11750
11755
|
async function exportDatasetsToPack(datasets, opts) {
|
|
11751
11756
|
var obj = {
|
|
11752
11757
|
version: 1,
|
|
11753
11758
|
created: (new Date).toISOString(),
|
|
11754
11759
|
datasets: await Promise.all(datasets.map(dataset => exportDataset(dataset, opts)))
|
|
11755
11760
|
};
|
|
11761
|
+
if (opts.history) {
|
|
11762
|
+
obj.history = opts.history;
|
|
11763
|
+
}
|
|
11756
11764
|
return obj;
|
|
11757
11765
|
}
|
|
11758
11766
|
|
|
@@ -51131,7 +51139,7 @@ ${svg}
|
|
|
51131
51139
|
});
|
|
51132
51140
|
}
|
|
51133
51141
|
|
|
51134
|
-
var version = "0.6.
|
|
51142
|
+
var version = "0.6.121";
|
|
51135
51143
|
|
|
51136
51144
|
// Parse command line args into commands and run them
|
|
51137
51145
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/www/page.css
CHANGED
|
@@ -196,10 +196,17 @@ body.map-view {
|
|
|
196
196
|
background-color: black;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
#
|
|
199
|
+
#sponsor-btn {
|
|
200
200
|
color: #ffa;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
#sponsor-btn svg {
|
|
204
|
+
width: 12px;
|
|
205
|
+
height: 12px;
|
|
206
|
+
fill: currentColor;
|
|
207
|
+
vertical-align: -1px;
|
|
208
|
+
}
|
|
209
|
+
|
|
203
210
|
.separator {
|
|
204
211
|
border-left: 1px solid white;
|
|
205
212
|
height: 10px;
|
package/www/privacy.html
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Privacy Policy — mapshaper</title>
|
|
7
|
+
<meta name="description" content="Privacy policy for mapshaper, free open-source software for editing geographic data.">
|
|
8
|
+
<link rel="icon" type="image/png" href="images/icon.png">
|
|
9
|
+
|
|
10
|
+
<style>
|
|
11
|
+
@font-face {
|
|
12
|
+
font-family: 'SourceSans3';
|
|
13
|
+
src: url('assets/SourceSans3-VariableFont_wght.ttf') format('truetype');
|
|
14
|
+
font-display: swap;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
html, body {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
background-color: #f8fdff;
|
|
21
|
+
font: 16px/1.55 'SourceSans3', Arial, sans-serif;
|
|
22
|
+
color: #333;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
a { color: #10699b; }
|
|
26
|
+
a:hover { color: #1A6A96; }
|
|
27
|
+
|
|
28
|
+
.page-header {
|
|
29
|
+
background-color: #1385B7;
|
|
30
|
+
color: white;
|
|
31
|
+
padding: 0 11px;
|
|
32
|
+
height: 29px;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.mapshaper-logo,
|
|
39
|
+
.mapshaper-logo:hover {
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
font-size: 17px;
|
|
42
|
+
color: white;
|
|
43
|
+
text-decoration: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.mapshaper-logo .logo-highlight {
|
|
47
|
+
color: #ffa;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.header-back {
|
|
51
|
+
color: white;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
font-size: 14px;
|
|
54
|
+
height: 29px;
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
padding: 0 10px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.header-back:hover {
|
|
61
|
+
background-color: #1A6A96;
|
|
62
|
+
color: white;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main {
|
|
66
|
+
max-width: 640px;
|
|
67
|
+
margin: 0 auto;
|
|
68
|
+
padding: 40px 24px 80px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
h1 {
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
font-size: 32px;
|
|
74
|
+
margin: 0 0 8px;
|
|
75
|
+
color: #222;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.updated {
|
|
79
|
+
color: #888;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
margin: 0 0 28px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
h2 {
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
font-size: 20px;
|
|
87
|
+
margin: 32px 0 10px;
|
|
88
|
+
color: #222;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
p {
|
|
92
|
+
margin: 0 0 14px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
ul {
|
|
96
|
+
padding-left: 20px;
|
|
97
|
+
margin: 8px 0 16px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ul li {
|
|
101
|
+
margin-bottom: 6px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.footer-note {
|
|
105
|
+
margin-top: 48px;
|
|
106
|
+
padding-top: 24px;
|
|
107
|
+
border-top: 1px solid #e0e8ec;
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
color: #777;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.footer-note a {
|
|
113
|
+
color: #777;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@media (max-width: 480px) {
|
|
117
|
+
main { padding: 24px 18px 60px; }
|
|
118
|
+
h1 { font-size: 26px; }
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
121
|
+
</head>
|
|
122
|
+
<body>
|
|
123
|
+
|
|
124
|
+
<header class="page-header">
|
|
125
|
+
<a href="." class="mapshaper-logo">map<span class="logo-highlight">shaper</span></a>
|
|
126
|
+
<a href="." class="header-back">← Back to mapshaper</a>
|
|
127
|
+
</header>
|
|
128
|
+
|
|
129
|
+
<main>
|
|
130
|
+
|
|
131
|
+
<h1>Privacy Policy</h1>
|
|
132
|
+
<p class="updated">Last updated: April 2026</p>
|
|
133
|
+
|
|
134
|
+
<h2>Overview</h2>
|
|
135
|
+
<p>Mapshaper is free, open-source software for editing geographic data, hosted at mapshaper.org. This policy explains what information is collected when you use the site, how it is used, and what choices you have.</p>
|
|
136
|
+
<p>We collect as little information as possible. We do not sell or share your information with advertisers or data brokers.</p>
|
|
137
|
+
|
|
138
|
+
<h2>Information collected</h2>
|
|
139
|
+
<p><strong>On mapshaper.org:</strong></p>
|
|
140
|
+
<ul>
|
|
141
|
+
<li><strong>Web analytics</strong> via Google Analytics. This includes pages visited, browser and device type, approximate location (typically city-level, derived from your IP address), and timestamps. The data helps us understand how the site is used.</li>
|
|
142
|
+
<li><strong>Basemap requests.</strong> When the in-app basemap loads, the application requests assets from Mapbox servers. As with any web request, this transmits your IP address to Mapbox to facilitate data delivery and session accounting.</li>
|
|
143
|
+
<li><strong>Standard server logs</strong> are kept by GitHub Pages, the hosting provider. As with any web request, this records your IP address, browser user-agent string, and the URL requested. GitHub uses these for traffic analysis, security monitoring, and abuse prevention.</li>
|
|
144
|
+
</ul>
|
|
145
|
+
<p><strong>On the support page:</strong></p>
|
|
146
|
+
<ul>
|
|
147
|
+
<li><strong>Email address</strong>, if you provide one for a payment receipt. The address is handled by the payment processor (Stripe via Ko-fi, or GitHub Sponsors), not stored by mapshaper.</li>
|
|
148
|
+
<li><strong>Name and payment information</strong> are handled directly by the payment processor.</li>
|
|
149
|
+
</ul>
|
|
150
|
+
|
|
151
|
+
<h2>Information not collected</h2>
|
|
152
|
+
<ul>
|
|
153
|
+
<li>Files you load into mapshaper for editing remain in your browser. They are not uploaded to our servers or to any third party.</li>
|
|
154
|
+
<li>We do not maintain user accounts or store any user data on our own servers.</li>
|
|
155
|
+
<li>We do not sell, rent, or share your information with advertisers or data brokers.</li>
|
|
156
|
+
</ul>
|
|
157
|
+
|
|
158
|
+
<h2>Third parties</h2>
|
|
159
|
+
<p>The following services receive data when you use mapshaper:</p>
|
|
160
|
+
<ul>
|
|
161
|
+
<li><strong>Google Analytics</strong> — site usage analytics. <a href="https://policies.google.com/privacy">Privacy Policy</a></li>
|
|
162
|
+
<li><strong>Mapbox</strong> — basemap tiles. <a href="https://www.mapbox.com/legal/privacy">Privacy Policy</a></li>
|
|
163
|
+
<li><strong>GitHub</strong> — site hosting (GitHub Pages) and sponsorship platform. <a href="https://docs.github.com/site-policy/privacy-policies/github-general-privacy-statement">Privacy Statement</a></li>
|
|
164
|
+
<li><strong>Ko-fi</strong> — payment processing. <a href="https://ko-fi.com/manage/privacy">Privacy Policy</a></li>
|
|
165
|
+
<li><strong>Stripe</strong> — payment processing for Ko-fi contributions. <a href="https://stripe.com/privacy">Privacy Policy</a></li>
|
|
166
|
+
</ul>
|
|
167
|
+
|
|
168
|
+
<h2>Cookies</h2>
|
|
169
|
+
<p>Google Analytics sets cookies to track usage. You can opt out by:</p>
|
|
170
|
+
<ul>
|
|
171
|
+
<li>Adjusting your browser settings to block third-party cookies</li>
|
|
172
|
+
<li>Sending a Do Not Track signal</li>
|
|
173
|
+
<li>Installing the Google Analytics opt-out browser extension</li>
|
|
174
|
+
<li>Using a privacy-focused browser or extension that blocks analytics</li>
|
|
175
|
+
</ul>
|
|
176
|
+
|
|
177
|
+
<h2>EU and UK users (GDPR)</h2>
|
|
178
|
+
<p>If you are in the European Union or United Kingdom, the General Data Protection Regulation gives you the right to access, correct, delete, or export the personal data we hold about you, and to object to processing. Because we do not maintain a user database, the data we hold about any individual is limited to analytics events and (if you have contributed) records held by the payment processors.</p>
|
|
179
|
+
<p>To exercise these rights, contact us using the information below. The legal basis for processing is legitimate interest (analytics) and contract (payment processing).</p>
|
|
180
|
+
|
|
181
|
+
<h2>California residents (CCPA)</h2>
|
|
182
|
+
<p>California residents have the right to know what personal information we collect, to request its deletion, and to opt out of any sale of personal information. We do not sell personal information.</p>
|
|
183
|
+
|
|
184
|
+
<h2>Security</h2>
|
|
185
|
+
<p>All connections to mapshaper.org use TLS encryption (HTTPS). Payment information is handled by PCI-compliant processors (Stripe). We do not store payment information on our own systems.</p>
|
|
186
|
+
|
|
187
|
+
<h2>Children</h2>
|
|
188
|
+
<p>Mapshaper is not directed at children under 13. We do not knowingly collect personal information from children.</p>
|
|
189
|
+
|
|
190
|
+
<h2>Changes to this policy</h2>
|
|
191
|
+
<p>This policy may be updated from time to time. The “Last updated” date at the top reflects the most recent change. We encourage you to review the policy periodically.</p>
|
|
192
|
+
|
|
193
|
+
<h2>Contact</h2>
|
|
194
|
+
<p>Questions about privacy can be sent to the maintainer via <a href="https://github.com/mbloch/mapshaper/issues">GitHub</a> or <a href="https://ko-fi.com/mapshaper">Ko-fi</a>.</p>
|
|
195
|
+
|
|
196
|
+
<div class="footer-note">
|
|
197
|
+
<a href="terms.html">Terms of Service</a> · <a href="sponsor.html">Support mapshaper</a> · <a href=".">Back to mapshaper</a>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
</main>
|
|
201
|
+
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
margin: 0 0 32px;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
.
|
|
84
|
+
.sponsor-buttons {
|
|
85
85
|
display: flex;
|
|
86
86
|
gap: 12px;
|
|
87
87
|
flex-wrap: wrap;
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
background-color: #222;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
.
|
|
116
|
+
.sponsor-note {
|
|
117
117
|
font-size: 14px;
|
|
118
118
|
color: #666;
|
|
119
119
|
margin: 0 0 32px;
|
|
@@ -156,10 +156,14 @@
|
|
|
156
156
|
color: #777;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
.footer-note a {
|
|
160
|
+
color: #777;
|
|
161
|
+
}
|
|
162
|
+
|
|
159
163
|
@media (max-width: 480px) {
|
|
160
164
|
main { padding: 24px 18px 60px; }
|
|
161
165
|
h1 { font-size: 26px; }
|
|
162
|
-
.
|
|
166
|
+
.sponsor-buttons { flex-direction: column; align-items: stretch; }
|
|
163
167
|
.btn { text-align: center; }
|
|
164
168
|
}
|
|
165
169
|
</style>
|
|
@@ -167,8 +171,8 @@
|
|
|
167
171
|
<body>
|
|
168
172
|
|
|
169
173
|
<header class="page-header">
|
|
170
|
-
<a href="
|
|
171
|
-
<a href="
|
|
174
|
+
<a href="." class="mapshaper-logo">map<span class="logo-highlight">shaper</span></a>
|
|
175
|
+
<a href="." class="header-back">← Back to mapshaper</a>
|
|
172
176
|
</header>
|
|
173
177
|
|
|
174
178
|
<main>
|
|
@@ -177,20 +181,20 @@
|
|
|
177
181
|
|
|
178
182
|
<p class="tagline">Mapshaper is free, open-source software for editing geographic data — used worldwide for everything from classroom exercises to professional map work.</p>
|
|
179
183
|
|
|
180
|
-
<div class="
|
|
181
|
-
<a href="https://ko-fi.com/mapshaper" class="btn">
|
|
184
|
+
<div class="sponsor-buttons">
|
|
185
|
+
<a href="https://ko-fi.com/mapshaper" class="btn">Support via Ko-fi</a>
|
|
182
186
|
<a href="https://github.com/sponsors/mbloch" class="btn btn-secondary">Sponsor on GitHub</a>
|
|
183
187
|
</div>
|
|
184
188
|
|
|
185
|
-
<p class="
|
|
189
|
+
<p class="sponsor-note">Ko-fi accepts one-time or recurring contributions — no account required. GitHub Sponsors offers recurring sponsorship.</p>
|
|
186
190
|
|
|
187
|
-
<h2>What your
|
|
191
|
+
<h2>What your contribution supports</h2>
|
|
188
192
|
<ul class="support-list">
|
|
189
193
|
<li>Development time — bug fixes, new features, performance improvements</li>
|
|
190
194
|
<li>Responding to user issues and requests</li>
|
|
191
195
|
</ul>
|
|
192
196
|
|
|
193
|
-
<h2>Recent
|
|
197
|
+
<h2>Recent work</h2>
|
|
194
198
|
<ul class="support-list">
|
|
195
199
|
<li><strong>GeoPackage</strong> import and export</li>
|
|
196
200
|
<li><strong>FlatGeobuf</strong> import and export</li>
|
|
@@ -207,6 +211,8 @@
|
|
|
207
211
|
|
|
208
212
|
<div class="footer-note">
|
|
209
213
|
Mapshaper’s basemap services are generously provided by <a href="https://www.mapbox.com">Mapbox</a>.
|
|
214
|
+
<br>
|
|
215
|
+
<a href="privacy.html">Privacy Policy</a> · <a href="terms.html">Terms of Service</a>
|
|
210
216
|
</div>
|
|
211
217
|
|
|
212
218
|
</main>
|
package/www/terms.html
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Terms of Service — mapshaper</title>
|
|
7
|
+
<meta name="description" content="Terms of service for mapshaper, free open-source software for editing geographic data.">
|
|
8
|
+
<link rel="icon" type="image/png" href="images/icon.png">
|
|
9
|
+
|
|
10
|
+
<style>
|
|
11
|
+
@font-face {
|
|
12
|
+
font-family: 'SourceSans3';
|
|
13
|
+
src: url('assets/SourceSans3-VariableFont_wght.ttf') format('truetype');
|
|
14
|
+
font-display: swap;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
html, body {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
background-color: #f8fdff;
|
|
21
|
+
font: 16px/1.55 'SourceSans3', Arial, sans-serif;
|
|
22
|
+
color: #333;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
a { color: #10699b; }
|
|
26
|
+
a:hover { color: #1A6A96; }
|
|
27
|
+
|
|
28
|
+
.page-header {
|
|
29
|
+
background-color: #1385B7;
|
|
30
|
+
color: white;
|
|
31
|
+
padding: 0 11px;
|
|
32
|
+
height: 29px;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.mapshaper-logo,
|
|
39
|
+
.mapshaper-logo:hover {
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
font-size: 17px;
|
|
42
|
+
color: white;
|
|
43
|
+
text-decoration: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.mapshaper-logo .logo-highlight {
|
|
47
|
+
color: #ffa;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.header-back {
|
|
51
|
+
color: white;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
font-size: 14px;
|
|
54
|
+
height: 29px;
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
padding: 0 10px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.header-back:hover {
|
|
61
|
+
background-color: #1A6A96;
|
|
62
|
+
color: white;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main {
|
|
66
|
+
max-width: 640px;
|
|
67
|
+
margin: 0 auto;
|
|
68
|
+
padding: 40px 24px 80px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
h1 {
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
font-size: 32px;
|
|
74
|
+
margin: 0 0 8px;
|
|
75
|
+
color: #222;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.updated {
|
|
79
|
+
color: #888;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
margin: 0 0 28px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
h2 {
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
font-size: 20px;
|
|
87
|
+
margin: 32px 0 10px;
|
|
88
|
+
color: #222;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
p {
|
|
92
|
+
margin: 0 0 14px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
ul {
|
|
96
|
+
padding-left: 20px;
|
|
97
|
+
margin: 8px 0 16px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ul li {
|
|
101
|
+
margin-bottom: 6px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.footer-note {
|
|
105
|
+
margin-top: 48px;
|
|
106
|
+
padding-top: 24px;
|
|
107
|
+
border-top: 1px solid #e0e8ec;
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
color: #777;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.footer-note a {
|
|
113
|
+
color: #777;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@media (max-width: 480px) {
|
|
117
|
+
main { padding: 24px 18px 60px; }
|
|
118
|
+
h1 { font-size: 26px; }
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
121
|
+
</head>
|
|
122
|
+
<body>
|
|
123
|
+
|
|
124
|
+
<header class="page-header">
|
|
125
|
+
<a href="." class="mapshaper-logo">map<span class="logo-highlight">shaper</span></a>
|
|
126
|
+
<a href="." class="header-back">← Back to mapshaper</a>
|
|
127
|
+
</header>
|
|
128
|
+
|
|
129
|
+
<main>
|
|
130
|
+
|
|
131
|
+
<h1>Terms of Service</h1>
|
|
132
|
+
<p class="updated">Last updated: April 2026</p>
|
|
133
|
+
|
|
134
|
+
<h2>Acceptance</h2>
|
|
135
|
+
<p>By using mapshaper.org or related services (collectively, “the service”), you agree to these terms. If you do not agree, please do not use the service.</p>
|
|
136
|
+
|
|
137
|
+
<h2>About mapshaper</h2>
|
|
138
|
+
<p>Mapshaper is free, open-source software for editing geographic data, hosted at mapshaper.org and maintained by Matthew Bloch. The source code is licensed under the <a href="https://github.com/mbloch/mapshaper/blob/master/LICENSE.txt">Mozilla Public License 2.0</a>.</p>
|
|
139
|
+
|
|
140
|
+
<h2>Use of the software</h2>
|
|
141
|
+
<p>The software is provided under the terms of the MPL 2.0. You may use, modify, and redistribute the source code in accordance with that license. These Terms of Service govern your use of mapshaper.org as a hosted service; the MPL 2.0 governs your rights to the source code.</p>
|
|
142
|
+
|
|
143
|
+
<h2>Your responsibilities</h2>
|
|
144
|
+
<p>You are responsible for:</p>
|
|
145
|
+
<ul>
|
|
146
|
+
<li>The accuracy and legality of any data you load into or process with mapshaper</li>
|
|
147
|
+
<li>Ensuring you have the right to use any data you load (copyright, licensing, terms of source data)</li>
|
|
148
|
+
<li>Compliance with all applicable laws in your use of the service</li>
|
|
149
|
+
<li>Not using the service to violate the rights of others, transmit illegal content, or attempt to disrupt the service or its third-party providers</li>
|
|
150
|
+
</ul>
|
|
151
|
+
|
|
152
|
+
<h2>No warranty</h2>
|
|
153
|
+
<p>The service is provided “as is” and “as available,” without warranty of any kind, express or implied. We make no warranty that the service will be uninterrupted, secure, error-free, or that any output will be accurate, complete, or fit for any particular purpose.</p>
|
|
154
|
+
<p>Map data, basemaps, and conversion results may contain errors. Do not rely on mapshaper output for navigation, safety-critical decisions, legal boundaries, or other applications where accuracy is essential.</p>
|
|
155
|
+
|
|
156
|
+
<h2>Limitation of liability</h2>
|
|
157
|
+
<p>To the maximum extent permitted by law, mapshaper, its maintainer, and contributors are not liable for any direct, indirect, incidental, consequential, special, or punitive damages arising from your use of (or inability to use) the service, including but not limited to loss of data, business interruption, lost profits, or any other commercial damages.</p>
|
|
158
|
+
|
|
159
|
+
<h2>Contributions</h2>
|
|
160
|
+
<p>Contributions made via Ko-fi or GitHub Sponsors are voluntary. They are not tax-deductible (mapshaper is not a registered charity). Contributions are non-refundable except in cases of fraud or technical error. Refund requests should be directed to the payment processor (Ko-fi, Stripe, or GitHub).</p>
|
|
161
|
+
|
|
162
|
+
<h2>Third-party services</h2>
|
|
163
|
+
<p>The service relies on third-party providers including:</p>
|
|
164
|
+
<ul>
|
|
165
|
+
<li><strong>GitHub Pages</strong> for site hosting</li>
|
|
166
|
+
<li><strong>Mapbox</strong> for basemap tiles</li>
|
|
167
|
+
<li><strong>Google Analytics</strong> for usage analytics</li>
|
|
168
|
+
<li><strong>Stripe</strong>, <strong>Ko-fi</strong>, and <strong>GitHub Sponsors</strong> for payment processing</li>
|
|
169
|
+
</ul>
|
|
170
|
+
<p>When you interact with these services through mapshaper, their terms of service and privacy policies also apply. We are not responsible for the availability, behavior, or content of third-party services.</p>
|
|
171
|
+
|
|
172
|
+
<h2>Changes to these terms</h2>
|
|
173
|
+
<p>These terms may be updated. The “Last updated” date at the top reflects the most recent change. Continued use of the service after changes constitutes acceptance of the updated terms.</p>
|
|
174
|
+
|
|
175
|
+
<h2>Governing law</h2>
|
|
176
|
+
<p>These terms are governed by the laws of the State of New York, United States, without regard to conflict-of-law principles.</p>
|
|
177
|
+
|
|
178
|
+
<h2>Contact</h2>
|
|
179
|
+
<p>Questions about these terms can be sent to the maintainer via <a href="https://github.com/mbloch/mapshaper/issues">GitHub</a> or <a href="https://ko-fi.com/mapshaper">Ko-fi</a>.</p>
|
|
180
|
+
|
|
181
|
+
<div class="footer-note">
|
|
182
|
+
<a href="privacy.html">Privacy Policy</a> · <a href="sponsor.html">Support mapshaper</a> · <a href=".">Back to mapshaper</a>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
</main>
|
|
186
|
+
|
|
187
|
+
</body>
|
|
188
|
+
</html>
|