mapshaper 0.7.49 → 0.7.51
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 +1996 -281
- package/package.json +3 -3
- package/www/mapshaper-gui.js +196 -30
- package/www/mapshaper.js +1996 -281
- package/www/modules.js +122 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapshaper",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.51",
|
|
4
4
|
"description": "A tool for editing geospatial data for mapping and GIS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shapefile",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"idb-keyval": "^6.2.0",
|
|
72
72
|
"jpeg-js": "^0.4.4",
|
|
73
73
|
"kdbush": "^3.0.0",
|
|
74
|
-
"mproj": "0.1.
|
|
74
|
+
"mproj": "0.1.6",
|
|
75
75
|
"msgpackr": "^1.11.12",
|
|
76
76
|
"open": "^11.0.0",
|
|
77
77
|
"pngjs": "^7.0.0",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
],
|
|
102
102
|
"check-leaks": true,
|
|
103
103
|
"parallel": true,
|
|
104
|
-
"jobs":
|
|
104
|
+
"jobs": 6
|
|
105
105
|
},
|
|
106
106
|
"bin": {
|
|
107
107
|
"mapshaper": "bin/mapshaper",
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -6876,31 +6876,37 @@
|
|
|
6876
6876
|
// instead.
|
|
6877
6877
|
|
|
6878
6878
|
|
|
6879
|
+
// Undo is on unless it has been turned off. Undoing is what a user expects by
|
|
6880
|
+
// default; the History menu's checkbox exists so that sessions working with
|
|
6881
|
+
// data too large to store restore payloads for can opt out.
|
|
6882
|
+
var APP_UNDO_DEFAULT = true;
|
|
6883
|
+
|
|
6879
6884
|
// Returns true if the manifest, URL query, gui-installed checker, or
|
|
6880
6885
|
// localStorage indicates that app-level undo should be active.
|
|
6881
6886
|
function appUndoIsEnabled(gui) {
|
|
6882
6887
|
var opt = gui && gui.options && (gui.options.undoCommands || gui.options.appUndo);
|
|
6883
6888
|
var query = getUndoQueryValue();
|
|
6889
|
+
if (query == 'off') return false;
|
|
6884
6890
|
if (opt === true || query == 'on' || query == 'commands') return true;
|
|
6885
6891
|
if (gui && gui.appUndoIsEnabled) return gui.appUndoIsEnabled();
|
|
6886
6892
|
return appUndoSettingIsOn();
|
|
6887
6893
|
}
|
|
6888
6894
|
|
|
6889
|
-
// True when the undo URL flag
|
|
6890
|
-
// Mirrors the toggle-disable behavior in HistoryMenu.
|
|
6895
|
+
// True when the undo URL flag pins app undo on or off regardless of UI
|
|
6896
|
+
// settings. Mirrors the toggle-disable behavior in HistoryMenu.
|
|
6891
6897
|
function appUndoForcedByUrl() {
|
|
6892
6898
|
var query = getUndoQueryValue();
|
|
6893
|
-
return query == 'on' || query == 'commands';
|
|
6899
|
+
return query == 'on' || query == 'commands' || query == 'off';
|
|
6894
6900
|
}
|
|
6895
6901
|
|
|
6896
|
-
// Read the persisted
|
|
6897
|
-
//
|
|
6902
|
+
// Read the persisted setting. Only an explicit opt-out turns undo off, so a
|
|
6903
|
+
// first-time session (and one where storage is unreadable) gets the default.
|
|
6898
6904
|
function appUndoSettingIsOn() {
|
|
6899
6905
|
try {
|
|
6900
|
-
|
|
6901
|
-
|
|
6906
|
+
if (typeof window == 'undefined' || !window.localStorage) return APP_UNDO_DEFAULT;
|
|
6907
|
+
return window.localStorage.getItem('mapshaper.undo') != 'off';
|
|
6902
6908
|
} catch(e) {
|
|
6903
|
-
return
|
|
6909
|
+
return APP_UNDO_DEFAULT;
|
|
6904
6910
|
}
|
|
6905
6911
|
}
|
|
6906
6912
|
|
|
@@ -7398,14 +7404,20 @@
|
|
|
7398
7404
|
}
|
|
7399
7405
|
}
|
|
7400
7406
|
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7407
|
+
// Draw and select what was imported. Called when import mode ends, and by
|
|
7408
|
+
// importQueuedFiles() if something else has already ended it.
|
|
7409
|
+
function finishImport() {
|
|
7404
7410
|
if (importCount > 0) {
|
|
7405
7411
|
onImportComplete();
|
|
7406
7412
|
importTotal += importCount;
|
|
7407
7413
|
importCount = 0;
|
|
7408
7414
|
}
|
|
7415
|
+
}
|
|
7416
|
+
|
|
7417
|
+
function turnOff() {
|
|
7418
|
+
var target;
|
|
7419
|
+
if (catalog) catalog.reset(); // re-enable clickable catalog
|
|
7420
|
+
finishImport();
|
|
7409
7421
|
gui.clearProgressMessage();
|
|
7410
7422
|
initialImport = false; // unset 'quick view' mode, if on
|
|
7411
7423
|
clearQueuedFiles();
|
|
@@ -7432,8 +7444,13 @@
|
|
|
7432
7444
|
gui.alert(e.message, 'Import error');
|
|
7433
7445
|
}
|
|
7434
7446
|
if (gui.getMode() == 'import') {
|
|
7435
|
-
//
|
|
7436
|
-
|
|
7447
|
+
gui.clearMode(); // leaving import mode finishes the import
|
|
7448
|
+
} else {
|
|
7449
|
+
// A popup shown while files were loading (an alert about one file's
|
|
7450
|
+
// metadata, say) takes the app out of import mode, so leaving it is no
|
|
7451
|
+
// longer what finishes the import. Without this, the files that did load
|
|
7452
|
+
// would sit in the catalog undrawn.
|
|
7453
|
+
finishImport();
|
|
7437
7454
|
}
|
|
7438
7455
|
if (undoTransaction && imported) {
|
|
7439
7456
|
addUndoTransactionToHistory(gui, undoTransaction, {
|
|
@@ -8056,6 +8073,15 @@
|
|
|
8056
8073
|
});
|
|
8057
8074
|
}
|
|
8058
8075
|
|
|
8076
|
+
// A .aux.xml sidecar is named after the whole raster file
|
|
8077
|
+
// (world.tif.aux.xml), so it joins that file's group rather than one of its
|
|
8078
|
+
// own. Returns null if the raster it names was not loaded with it.
|
|
8079
|
+
function auxSourceGroup(d) {
|
|
8080
|
+
var sourceName = internal.getAuxSourceFilename(d.name).toLowerCase();
|
|
8081
|
+
var source = data.find(f => f.name.toLowerCase() == sourceName);
|
|
8082
|
+
return source ? key(fileBase(source), fileType(source)) : null;
|
|
8083
|
+
}
|
|
8084
|
+
|
|
8059
8085
|
data.forEach(d => {
|
|
8060
8086
|
var basename = fileBase(d);
|
|
8061
8087
|
var type = fileType(d);
|
|
@@ -8067,6 +8093,8 @@
|
|
|
8067
8093
|
} else if (type == 'dbf') {
|
|
8068
8094
|
d.filename = d.name;
|
|
8069
8095
|
d.group = key(basename, 'dbf');
|
|
8096
|
+
} else if (type == 'aux' && auxSourceGroup(d)) {
|
|
8097
|
+
d.group = auxSourceGroup(d);
|
|
8070
8098
|
} else if ((type == 'png' || type == 'jpeg') || isRasterImagePart(d.name) && hasRasterImage(basename)) {
|
|
8071
8099
|
d.group = key(basename, type == 'png' || type == 'jpeg' ? type : getRasterImageGroupType(data, basename));
|
|
8072
8100
|
if (type == 'png' || type == 'jpeg') d.filename = d.name;
|
|
@@ -10630,6 +10658,8 @@
|
|
|
10630
10658
|
var unsupportedMsg = "Exporting is not supported in this browser";
|
|
10631
10659
|
var menu = gui.container.findChild('.export-options').on('click', GUI.handleDirectEvent(gui.clearMode));
|
|
10632
10660
|
var layersArr = [];
|
|
10661
|
+
var menuFormats = null; // formats the format menu was built with
|
|
10662
|
+
var formatPickedByUser = false;
|
|
10633
10663
|
var toggleBtn = null; // checkbox <input> for toggling layer selection
|
|
10634
10664
|
var exportBtn = gui.container.findChild('.export-btn').addClass('disabled');
|
|
10635
10665
|
var ofileName = gui.container.findChild('#ofile-name');
|
|
@@ -10698,6 +10728,7 @@
|
|
|
10698
10728
|
|
|
10699
10729
|
function turnOn() {
|
|
10700
10730
|
layersArr = initLayerMenu();
|
|
10731
|
+
formatPickedByUser = false;
|
|
10701
10732
|
// initZipOption();
|
|
10702
10733
|
initFormatMenu();
|
|
10703
10734
|
updateExportCheckboxes();
|
|
@@ -10879,6 +10910,7 @@
|
|
|
10879
10910
|
}
|
|
10880
10911
|
|
|
10881
10912
|
function updateToggleBtn() {
|
|
10913
|
+
refreshFormatMenu(); // the layer selection decides which formats apply
|
|
10882
10914
|
updateExportCheckboxes(); // checkbox visibility is affected by number of export layers
|
|
10883
10915
|
if (!toggleBtn) return;
|
|
10884
10916
|
var state = getSelectionState();
|
|
@@ -10901,19 +10933,44 @@
|
|
|
10901
10933
|
}
|
|
10902
10934
|
|
|
10903
10935
|
function getDefaultExportFormat() {
|
|
10936
|
+
var layers = getExportFormatLayers();
|
|
10904
10937
|
var active = model.getActiveLayer();
|
|
10905
10938
|
var dataset = active.dataset;
|
|
10906
10939
|
var inputFmt = dataset.info && dataset.info.input_formats &&
|
|
10907
10940
|
dataset.info.input_formats[0];
|
|
10908
|
-
|
|
10941
|
+
// Rasters and nothing else can only go to GeoTIFF, the one format that
|
|
10942
|
+
// takes their pixels.
|
|
10943
|
+
if (layers.length > 0 && layers.every(hasRaster)) return 'geotiff';
|
|
10944
|
+
// SVG is the default for a selection that mixes rasters with vector layers,
|
|
10945
|
+
// being the only format that accepts both.
|
|
10946
|
+
if (layers.some(hasRaster)) return 'svg';
|
|
10909
10947
|
return getExportFormats().includes(inputFmt) ? inputFmt : 'geojson';
|
|
10910
10948
|
}
|
|
10911
10949
|
|
|
10912
10950
|
function getExportFormats() {
|
|
10913
|
-
|
|
10951
|
+
var formats = ['shapefile', 'json', 'geojson', 'dsv', 'topojson', 'flatgeobuf', 'geopackage', 'geoparquet', 'kml', 'svg', internal.PACKAGE_EXT];
|
|
10952
|
+
// GeoTIFF is the one format here that only accepts raster layers, so it is
|
|
10953
|
+
// offered only when there is a raster to export.
|
|
10954
|
+
if (getExportFormatLayers().some(hasRaster)) formats.push('geotiff');
|
|
10955
|
+
return formats;
|
|
10956
|
+
}
|
|
10957
|
+
|
|
10958
|
+
// The layers the format menu describes: the ones checked for export, falling
|
|
10959
|
+
// back to the active layer when the menu is not up yet or nothing is checked.
|
|
10960
|
+
function getExportFormatLayers() {
|
|
10961
|
+
var selected = layersArr.length ? getSelectedLayerEntries() : [];
|
|
10962
|
+
var active = model.getActiveLayer();
|
|
10963
|
+
if (selected.length > 0) {
|
|
10964
|
+
return selected.map(function(o) { return o.layer; });
|
|
10965
|
+
}
|
|
10966
|
+
return active && active.layer ? [active.layer] : [];
|
|
10914
10967
|
}
|
|
10915
10968
|
|
|
10916
|
-
function
|
|
10969
|
+
function hasRaster(lyr) {
|
|
10970
|
+
return internal.layerHasRaster(lyr);
|
|
10971
|
+
}
|
|
10972
|
+
|
|
10973
|
+
function initFormatMenu(preferredFmt) {
|
|
10917
10974
|
var formats = getExportFormats();
|
|
10918
10975
|
var items = formats.map(function(fmt) {
|
|
10919
10976
|
return utils$1.format('<td><label><input type="radio" name="format" value="%s"' +
|
|
@@ -10925,13 +10982,39 @@
|
|
|
10925
10982
|
}
|
|
10926
10983
|
table += '</table>';
|
|
10927
10984
|
menu.findChild('.export-formats').html(table);
|
|
10928
|
-
|
|
10985
|
+
menuFormats = formats;
|
|
10986
|
+
setSelectedFormat(formats.includes(preferredFmt) ? preferredFmt :
|
|
10987
|
+
getDefaultExportFormat());
|
|
10929
10988
|
// update save-as settings when value changes
|
|
10930
10989
|
menu.findChildren('input[type="radio"]').forEach(el => {
|
|
10931
|
-
el.on('change',
|
|
10990
|
+
el.on('change', onFormatChange);
|
|
10932
10991
|
});
|
|
10933
10992
|
}
|
|
10934
10993
|
|
|
10994
|
+
function onFormatChange() {
|
|
10995
|
+
// A format the user picked is theirs to keep, however the layer selection
|
|
10996
|
+
// changes afterwards.
|
|
10997
|
+
formatPickedByUser = true;
|
|
10998
|
+
updateExportCheckboxes();
|
|
10999
|
+
}
|
|
11000
|
+
|
|
11001
|
+
function setSelectedFormat(fmt) {
|
|
11002
|
+
var el = menu.findChild('.export-formats input[value="' + fmt + '"]');
|
|
11003
|
+
if (el) el.node().checked = true;
|
|
11004
|
+
}
|
|
11005
|
+
|
|
11006
|
+
// Which formats apply depends on what is checked for export, so unchecking
|
|
11007
|
+
// the vector layers of a mixed session both offers GeoTIFF and makes it the
|
|
11008
|
+
// default -- unless the user has already chosen a format.
|
|
11009
|
+
function refreshFormatMenu() {
|
|
11010
|
+
var formats = getExportFormats();
|
|
11011
|
+
if (formats.join(',') != (menuFormats || []).join(',')) {
|
|
11012
|
+
initFormatMenu(formatPickedByUser ? getSelectedFormat() : null);
|
|
11013
|
+
} else if (!formatPickedByUser) {
|
|
11014
|
+
setSelectedFormat(getDefaultExportFormat());
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
|
|
10935
11018
|
|
|
10936
11019
|
// function getInputFormats() {
|
|
10937
11020
|
// return model.getDatasets().reduce(function(memo, d) {
|
|
@@ -12073,6 +12156,7 @@
|
|
|
12073
12156
|
}
|
|
12074
12157
|
|
|
12075
12158
|
function isAppUndoEnabled() {
|
|
12159
|
+
if (getUndoQueryValue() == 'off') return false;
|
|
12076
12160
|
return appUndoForcedByUrl() || appUndoSettingIsOn();
|
|
12077
12161
|
}
|
|
12078
12162
|
|
|
@@ -20962,6 +21046,31 @@
|
|
|
20962
21046
|
|
|
20963
21047
|
utils$1.inherit(MapExtent, EventDispatcher);
|
|
20964
21048
|
|
|
21049
|
+
// Rules for drawing a raster that is being displayed larger than its data.
|
|
21050
|
+
// Kept free of browser and mapshaper dependencies so they can be tested
|
|
21051
|
+
// directly.
|
|
21052
|
+
|
|
21053
|
+
// Screen pixels per raster pixel at which a magnified raster is drawn as
|
|
21054
|
+
// squares instead of being smoothed. Below this there is little detail to lose
|
|
21055
|
+
// and smoothing looks better; above it, the smoothing is what a user sees
|
|
21056
|
+
// instead of the data.
|
|
21057
|
+
var MIN_CRISP_MAGNIFICATION = 2;
|
|
21058
|
+
|
|
21059
|
+
// True if a preview image holds every raster pixel in view, and so is showing
|
|
21060
|
+
// the data itself rather than a scaled-down rendering of it. Only such an
|
|
21061
|
+
// image is worth drawing as squares: magnifying a coarser preview would show
|
|
21062
|
+
// crisp squares that are not pixels of the raster.
|
|
21063
|
+
function previewHasSourcePixels(width, height, sourceWidth, sourceHeight) {
|
|
21064
|
+
if (!(sourceWidth > 0) || !(sourceHeight > 0)) return false;
|
|
21065
|
+
return width >= sourceWidth && height >= sourceHeight;
|
|
21066
|
+
}
|
|
21067
|
+
|
|
21068
|
+
// sourcePixels: from previewHasSourcePixels()
|
|
21069
|
+
// magnification: screen pixels per pixel of the preview image
|
|
21070
|
+
function rasterPreviewIsSmoothed(sourcePixels, magnification) {
|
|
21071
|
+
return !sourcePixels || !(magnification >= MIN_CRISP_MAGNIFICATION);
|
|
21072
|
+
}
|
|
21073
|
+
|
|
20965
21074
|
var MAX_VIEWPORT_PREVIEW_PIXELS = 6e6;
|
|
20966
21075
|
var cache$1 = new WeakMap();
|
|
20967
21076
|
var requestId$1 = 0;
|
|
@@ -20988,6 +21097,7 @@
|
|
|
20988
21097
|
stats = getCachedRasterScalingStats(params, timing);
|
|
20989
21098
|
timing.renderStart = getTimer$1();
|
|
20990
21099
|
preview = internal.renderRasterViewportPreview(params.grid, params.recipe, params.bbox, params.width, params.height, stats);
|
|
21100
|
+
if (preview) preview.sourcePixels = params.sourcePixels;
|
|
20991
21101
|
timing.renderMs = getTimer$1() - timing.renderStart;
|
|
20992
21102
|
logRasterPreviewTiming(params, timing);
|
|
20993
21103
|
current = cache$1.get(layer);
|
|
@@ -21039,6 +21149,7 @@
|
|
|
21039
21149
|
return {
|
|
21040
21150
|
key: key,
|
|
21041
21151
|
needed: needed,
|
|
21152
|
+
sourcePixels: previewHasSourcePixels(width, height, crop.width, crop.height),
|
|
21042
21153
|
raster: raster,
|
|
21043
21154
|
grid: grid,
|
|
21044
21155
|
recipe: recipe,
|
|
@@ -21197,6 +21308,7 @@
|
|
|
21197
21308
|
}
|
|
21198
21309
|
timing.renderStart = getTimer();
|
|
21199
21310
|
preview = internal.renderRasterPreview(grid, params.recipe, grid.width, grid.height, params.stats);
|
|
21311
|
+
preview.sourcePixels = params.sourcePixels;
|
|
21200
21312
|
applyCoverageMask(preview, grid.coverage);
|
|
21201
21313
|
timing.renderMs = getTimer() - timing.renderStart;
|
|
21202
21314
|
logRasterReprojectionTiming(params, timing);
|
|
@@ -21220,12 +21332,13 @@
|
|
|
21220
21332
|
var viewBbox = ext && ext.getBounds().toArray();
|
|
21221
21333
|
var bbox = rasterBbox && viewBbox && internal.intersectBboxes(rasterBbox, viewBbox);
|
|
21222
21334
|
var size = bbox && getPreviewSize(ext, sourceGrid);
|
|
21223
|
-
var recipe, stats;
|
|
21335
|
+
var recipe, stats, sourcePixels;
|
|
21224
21336
|
if (!sourceCRS || !displayCRS || !sourceGrid || !sourceGrid.samples || !bbox || !size) return null;
|
|
21225
21337
|
recipe = internal.getRasterViewRecipe(sourceGrid, raster.view && raster.view.recipe);
|
|
21226
21338
|
stats = internal.getRasterViewScalingStats(raster, recipe);
|
|
21339
|
+
sourcePixels = outputHasSourcePixels(sourceGrid, bbox, sourceCRS, displayCRS, size);
|
|
21227
21340
|
return {
|
|
21228
|
-
key: getRasterReprojectedPreviewKey(layer, bbox, size, sourceCRS, displayCRS, recipe),
|
|
21341
|
+
key: getRasterReprojectedPreviewKey(layer, bbox, size, sourceCRS, displayCRS, recipe, sourcePixels),
|
|
21229
21342
|
grid: sourceGrid,
|
|
21230
21343
|
sourceCRS: sourceCRS,
|
|
21231
21344
|
displayCRS: displayCRS,
|
|
@@ -21234,11 +21347,49 @@
|
|
|
21234
21347
|
height: size.height,
|
|
21235
21348
|
recipe: recipe,
|
|
21236
21349
|
stats: stats,
|
|
21237
|
-
|
|
21350
|
+
sourcePixels: sourcePixels,
|
|
21351
|
+
sampleMethod: getRasterReprojectionSampleMethod(sourcePixels),
|
|
21238
21352
|
meshInterval: 32
|
|
21239
21353
|
};
|
|
21240
21354
|
}
|
|
21241
21355
|
|
|
21356
|
+
// True if the reprojected grid has room for every source pixel in view, so that
|
|
21357
|
+
// sampling it without interpolation gives back the raster's own pixels. This is
|
|
21358
|
+
// the zoomed-in case: reprojecting a raster to fewer pixels than it has needs
|
|
21359
|
+
// interpolation to avoid dropping data.
|
|
21360
|
+
function outputHasSourcePixels(grid, bbox, sourceCRS, displayCRS, size) {
|
|
21361
|
+
var sourceSize = getSourcePixelsInView(grid, bbox, sourceCRS, displayCRS);
|
|
21362
|
+
return !!sourceSize && previewHasSourcePixels(
|
|
21363
|
+
size.width, size.height, sourceSize.width, sourceSize.height);
|
|
21364
|
+
}
|
|
21365
|
+
|
|
21366
|
+
// Estimates how many of the raster's pixels a view covers, by taking the part
|
|
21367
|
+
// of the source grid that the view maps back onto. Returns null if the view
|
|
21368
|
+
// does not map back (e.g. it includes coordinates outside the projection).
|
|
21369
|
+
function getSourcePixelsInView(grid, bbox, sourceCRS, displayCRS) {
|
|
21370
|
+
var transform = internal.getProjTransform2(displayCRS, sourceCRS);
|
|
21371
|
+
var xx = [bbox[0], (bbox[0] + bbox[2]) / 2, bbox[2]];
|
|
21372
|
+
var yy = [bbox[1], (bbox[1] + bbox[3]) / 2, bbox[3]];
|
|
21373
|
+
var xmin = Infinity, ymin = Infinity, xmax = -Infinity, ymax = -Infinity;
|
|
21374
|
+
var p;
|
|
21375
|
+
if (!transform) return null;
|
|
21376
|
+
for (var i = 0; i < xx.length; i++) {
|
|
21377
|
+
for (var j = 0; j < yy.length; j++) {
|
|
21378
|
+
p = transform(xx[i], yy[j]);
|
|
21379
|
+
if (!p || !isFinite(p[0]) || !isFinite(p[1])) continue;
|
|
21380
|
+
if (p[0] < xmin) xmin = p[0];
|
|
21381
|
+
if (p[0] > xmax) xmax = p[0];
|
|
21382
|
+
if (p[1] < ymin) ymin = p[1];
|
|
21383
|
+
if (p[1] > ymax) ymax = p[1];
|
|
21384
|
+
}
|
|
21385
|
+
}
|
|
21386
|
+
if (!(xmax > xmin) || !(ymax > ymin)) return null;
|
|
21387
|
+
return {
|
|
21388
|
+
width: (xmax - xmin) / (grid.bbox[2] - grid.bbox[0]) * grid.width,
|
|
21389
|
+
height: (ymax - ymin) / (grid.bbox[3] - grid.bbox[1]) * grid.height
|
|
21390
|
+
};
|
|
21391
|
+
}
|
|
21392
|
+
|
|
21242
21393
|
function getPreviewSize(ext, grid) {
|
|
21243
21394
|
var pixelRatio = GUI.getPixelRatio();
|
|
21244
21395
|
var t = ext.getTransform(pixelRatio);
|
|
@@ -21253,10 +21404,11 @@
|
|
|
21253
21404
|
return {width: width, height: height};
|
|
21254
21405
|
}
|
|
21255
21406
|
|
|
21256
|
-
function getRasterReprojectedPreviewKey(layer, bbox, size, sourceCRS, displayCRS, recipe) {
|
|
21407
|
+
function getRasterReprojectedPreviewKey(layer, bbox, size, sourceCRS, displayCRS, recipe, sourcePixels) {
|
|
21257
21408
|
return [
|
|
21258
21409
|
size.width,
|
|
21259
21410
|
size.height,
|
|
21411
|
+
sourcePixels,
|
|
21260
21412
|
bbox.join(','),
|
|
21261
21413
|
internal.crsToProj4(sourceCRS),
|
|
21262
21414
|
internal.crsToProj4(displayCRS),
|
|
@@ -21264,7 +21416,7 @@
|
|
|
21264
21416
|
recipe.scaling,
|
|
21265
21417
|
recipe.scaleRange && recipe.scaleRange.join(','),
|
|
21266
21418
|
recipe.percentileRange && recipe.percentileRange.join(','),
|
|
21267
|
-
getRasterReprojectionSampleMethod(),
|
|
21419
|
+
getRasterReprojectionSampleMethod(sourcePixels),
|
|
21268
21420
|
layer.raster && layer.raster.grid && layer.raster.grid.samples && layer.raster.grid.samples.length
|
|
21269
21421
|
].join('|');
|
|
21270
21422
|
}
|
|
@@ -21292,10 +21444,13 @@
|
|
|
21292
21444
|
}
|
|
21293
21445
|
}
|
|
21294
21446
|
|
|
21295
|
-
function getRasterReprojectionSampleMethod() {
|
|
21447
|
+
function getRasterReprojectionSampleMethod(sourcePixels) {
|
|
21296
21448
|
var vars = GUI.getUrlVars();
|
|
21297
21449
|
var val = vars['raster-bilinear'] ?? vars.raster_bilinear;
|
|
21298
|
-
|
|
21450
|
+
if (val === false || val == '0') return 'nearest';
|
|
21451
|
+
// Interpolating between pixels when there is room for all of them would blur
|
|
21452
|
+
// the data away for nothing.
|
|
21453
|
+
return sourcePixels ? 'nearest' : 'bilinear';
|
|
21299
21454
|
}
|
|
21300
21455
|
|
|
21301
21456
|
function logRasterReprojectionTiming(params, timing) {
|
|
@@ -21584,7 +21739,7 @@
|
|
|
21584
21739
|
var raster = layer.raster;
|
|
21585
21740
|
var options = opts || {};
|
|
21586
21741
|
var preview = null;
|
|
21587
|
-
var bbox;
|
|
21742
|
+
var bbox, grid, sourcePixels;
|
|
21588
21743
|
if (layer.gui && layer.gui.dynamic_crs) {
|
|
21589
21744
|
preview = getCachedRasterReprojectedPreview(layer, _ext);
|
|
21590
21745
|
if (!preview) {
|
|
@@ -21595,7 +21750,7 @@
|
|
|
21595
21750
|
}
|
|
21596
21751
|
bbox = preview.bbox;
|
|
21597
21752
|
if (!preview.pixels || !bbox) return;
|
|
21598
|
-
drawRasterPreview(preview, bbox);
|
|
21753
|
+
drawRasterPreview(preview, bbox, preview.sourcePixels);
|
|
21599
21754
|
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
21600
21755
|
scheduleRasterReprojectedPreview(layer, _ext, options.onViewportPreviewReady);
|
|
21601
21756
|
}
|
|
@@ -21603,15 +21758,21 @@
|
|
|
21603
21758
|
}
|
|
21604
21759
|
preview = options.action == 'nav' ? null : getCachedRasterViewportPreview(layer, _ext);
|
|
21605
21760
|
bbox = preview && preview.bbox;
|
|
21761
|
+
sourcePixels = preview && preview.sourcePixels;
|
|
21606
21762
|
if (!preview) {
|
|
21763
|
+
// The whole-raster preview made at import time, which is scaled down if
|
|
21764
|
+
// the raster is large.
|
|
21607
21765
|
preview = raster && internal.getRasterPreview(raster);
|
|
21608
21766
|
bbox = raster && internal.getRasterBBox(raster);
|
|
21767
|
+
grid = raster && internal.getRasterGrid(raster);
|
|
21768
|
+
sourcePixels = !!(preview && grid) && previewHasSourcePixels(
|
|
21769
|
+
preview.width, preview.height, grid.width, grid.height);
|
|
21609
21770
|
if (options.action != 'nav' && options.onViewportPreviewReady) {
|
|
21610
21771
|
scheduleRasterViewportPreview(layer, _ext, options.onViewportPreviewReady);
|
|
21611
21772
|
}
|
|
21612
21773
|
}
|
|
21613
21774
|
if (!preview || !preview.pixels || !bbox) return;
|
|
21614
|
-
drawRasterPreview(preview, bbox);
|
|
21775
|
+
drawRasterPreview(preview, bbox, sourcePixels);
|
|
21615
21776
|
};
|
|
21616
21777
|
|
|
21617
21778
|
/*
|
|
@@ -21813,12 +21974,16 @@
|
|
|
21813
21974
|
}
|
|
21814
21975
|
}
|
|
21815
21976
|
|
|
21816
|
-
|
|
21977
|
+
// sourcePixels: true if one pixel of the preview is one pixel of the raster,
|
|
21978
|
+
// so that drawing it as squares shows the data rather than an artifact of
|
|
21979
|
+
// whatever it was scaled down to.
|
|
21980
|
+
function drawRasterPreview(preview, bbox, sourcePixels) {
|
|
21817
21981
|
var img = getRasterCanvas(preview);
|
|
21818
21982
|
var t = _ext.getTransform(GUI.getPixelRatio());
|
|
21819
21983
|
var p1 = t.transform(bbox[0], bbox[3]);
|
|
21820
21984
|
var p2 = t.transform(bbox[2], bbox[1]);
|
|
21821
|
-
|
|
21985
|
+
var magnification = Math.abs(p2[0] - p1[0]) / img.width / GUI.getPixelRatio();
|
|
21986
|
+
_ctx.imageSmoothingEnabled = rasterPreviewIsSmoothed(sourcePixels, magnification);
|
|
21822
21987
|
_ctx.drawImage(img, p1[0], p1[1], p2[0] - p1[0], p2[1] - p1[1]);
|
|
21823
21988
|
}
|
|
21824
21989
|
|
|
@@ -25327,6 +25492,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
25327
25492
|
return {
|
|
25328
25493
|
layerCount: dataset.layers ? dataset.layers.length : 0,
|
|
25329
25494
|
arcCount: dataset.arcs ? dataset.arcs.size() : 0,
|
|
25495
|
+
crs_string: dataset.info && dataset.info.crs_string || null,
|
|
25330
25496
|
layers: (dataset.layers || []).map(function(lyr) {
|
|
25331
25497
|
return {
|
|
25332
25498
|
name: getLayerName(lyr),
|