diff --git a/mapshaper-gui.js b/mapshaper-gui.js index 08e913b7..9b505327 100644 --- a/mapshaper-gui.js +++ b/mapshaper-gui.js @@ -1089,6 +1089,103 @@ return _el; } + function filterLayerByIds(lyr, ids) { + var shapes; + if (lyr.shapes) { + shapes = ids.map(function(id) { + return lyr.shapes[id]; + }); + return utils$1.defaults({shapes: shapes, data: null}, lyr); + } + return lyr; + } + + function formatLayerNameForDisplay(name) { + return name || '[unnamed]'; + } + + function cleanLayerName(raw) { + return raw.replace(/[\n\t/\\]/g, '') + .replace(/^[.\s]+/, '').replace(/[.\s]+$/, ''); + } + + function updateLayerStackOrder(layers) { + // 1. assign ascending ids to unassigned layers above the range of other layers + layers.forEach(function(o, i) { + if (!o.layer.menu_order) o.layer.menu_order = 1e6 + i; + }); + // 2. sort in ascending order + layers.sort(function(a, b) { + return a.layer.menu_order - b.layer.menu_order; + }); + // 3. assign consecutve ids + layers.forEach(function(o, i) { + o.layer.menu_order = i + 1; + }); + return layers; + } + + function sortLayersForMenuDisplay(layers) { + layers = updateLayerStackOrder(layers); + return layers.reverse(); + } + + function setLayerPinning(lyr, pinned) { + lyr.pinned = !!pinned; + } + + + function adjustPointSymbolSizes(layers, overlayLyr, ext) { + var bbox = ext.getBounds().scale(1.5).toArray(); + var testInBounds = function(p) { + return p[0] > bbox[0] && p[0] < bbox[2] && p[1] > bbox[1] && p[1] < bbox[3]; + }; + var topTier = 50000; + var count = 0; + layers = layers.filter(function(lyr) { + return lyr.geometry_type == 'point' && lyr.gui.style.dotSize > 0; + }); + layers.forEach(function(lyr) { + // short-circuit point counting above top threshold + count += countPoints(lyr.gui.displayLayer.shapes, topTier, testInBounds); + }); + count = Math.min(topTier, count) || 1; + var k = Math.pow(6 - utils$1.clamp(Math.log10(count), 1, 5), 1.3); + + // zoom adjustments + var mapScale = ext.scale(); + if (mapScale < 0.5) { + k *= Math.pow(mapScale + 0.5, 0.35); + } else if (mapScale > 1) { + // scale faster at first + k *= Math.pow(Math.min(mapScale, 4), 0.15); + k *= Math.pow(mapScale, 0.05); + } + + // scale down when map is small + var smallSide = Math.min(ext.width(), ext.height()); + k *= utils$1.clamp(smallSide / 500, 0.5, 1); + + layers.forEach(function(lyr) { + lyr.gui.style.dotScale = k; + }); + if (overlayLyr && overlayLyr.geometry_type == 'point' && overlayLyr.gui.style.dotSize > 0) { + overlayLyr.gui.style.dotScale = k; + } + } + + function countPoints(shapes, max, filter) { + var count = 0; + var i, j, n, m, shp; + for (i=0, n=shapes.length; i bbox[0] && p[0] < bbox[2] && p[1] > bbox[1] && p[1] < bbox[3]; - }; - var topTier = 50000; - var count = 0; - layers = layers.filter(function(lyr) { - return lyr.geometry_type == 'point' && lyr.gui.style.dotSize > 0; - }); - layers.forEach(function(lyr) { - // short-circuit point counting above top threshold - count += countPoints(lyr.gui.displayLayer.shapes, topTier, testInBounds); - }); - count = Math.min(topTier, count) || 1; - var k = Math.pow(6 - utils$1.clamp(Math.log10(count), 1, 5), 1.3); - - // zoom adjustments - var mapScale = ext.scale(); - if (mapScale < 0.5) { - k *= Math.pow(mapScale + 0.5, 0.35); - } else if (mapScale > 1) { - // scale faster at first - k *= Math.pow(Math.min(mapScale, 4), 0.15); - k *= Math.pow(mapScale, 0.05); - } - - // scale down when map is small - var smallSide = Math.min(ext.width(), ext.height()); - k *= utils$1.clamp(smallSide / 500, 0.5, 1); - - layers.forEach(function(lyr) { - lyr.gui.style.dotScale = k; - }); - if (overlayLyr && overlayLyr.geometry_type == 'point' && overlayLyr.gui.style.dotSize > 0) { - overlayLyr.gui.style.dotScale = k; - } - } - - function countPoints(shapes, max, filter) { - var count = 0; - var i, j, n, m, shp; - for (i=0, n=shapes.length; i
'; html += rowHTML('name', '' + formatLayerNameForDisplay(lyr.name) + '', 'row1'); - html += rowHTML('contents', describeLyr(lyr)); + html += rowHTML('contents', describeLyr(lyr, dataset)); html += ''; if (opts.pinnable) { html += ''; @@ -5265,7 +5274,7 @@ e.stopPropagation(); if (map.isVisibleLayer(target.layer)) { // TODO: check for double map refresh after model.deleteLayer() below - map.setLayerPinning(target, false); + setLayerPinning(target.layer, false); } model.deleteLayer(target.layer, target.dataset); }); @@ -5289,7 +5298,7 @@ } lyr.hidden = hidden; lyr.unpinned = unpinned; - map.setLayerPinning(target, pinned); + setLayerPinning(lyr, pinned); entry.classed('pinned', pinned); entry.classed('invisible', hidden); updatePinAllButton(); @@ -5330,15 +5339,18 @@ }); } - function describeLyr(lyr) { + function describeLyr(lyr, dataset) { var n = internal.getFeatureCount(lyr), + isFrame = internal.isFrameLayer(lyr, dataset.arcs), str, type; if (lyr.data && !lyr.shapes) { type = 'data record'; } else if (lyr.geometry_type) { type = lyr.geometry_type + ' feature'; } - if (type) { + if (isFrame) { + str = 'map frame'; + } else if (type) { str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n)); } else { str = "[empty]"; @@ -12477,10 +12489,6 @@ GUI and setting the size and crop of SVG output.

0) features[i-1] = null; // garbage-collect old features - return features[i]; - }; - - api.set = function(feat, i) { - var arr; - - if (utils.isString(feat)) { - feat = JSON.parse(feat); - } - - if (!feat) return; - - if (feat.type == 'GeometryCollection') { - arr = feat.geometries.map(geom => GeoJSON.toFeature(geom)); - } else if (feat.type == 'FeatureCollection') { - arr = feat.features; - } else { - feat = GeoJSON.toFeature(feat); - } - - if (arr) { - features2 = features2.concat(arr); - } else { - features2.push(feat); - } - }; - - api.done = function() { - if (features2.length === 0) return; // read-only expression - var geojson = { - type: 'FeatureCollection', - features: features2 - }; - return importGeoJSON(geojson); - }; - return api; - } - cmd.dashlines = function(lyr, dataset, opts) { var crs = getDatasetCRS(dataset); var defs = getStashedVar('defs'); @@ -37780,6 +37746,76 @@ ${svg} } }; + function expressionUsesGeoJSON(exp) { + return exp.includes('this.geojson') || exp.includes('this.geometry') || exp.includes('this.feature'); + } + + function getFeatureEditor(lyr, dataset) { + var api = {}; + // need to copy attribute to avoid circular references if geojson is assigned + // to a data property. + var copy = copyLayer(lyr); + var features = exportLayerAsGeoJSON(copy, dataset, {rfc7946: true}, true); + var features2 = []; + + api.get = function(i) { + if (i > 0) features[i-1] = null; // garbage-collect old features + return features[i]; + }; + + api.getGeometry = function(i) { + if (i > 0) features[i-1] = null; // garbage-collect old features + return features[i] ? features[i].geometry : null; + }; + + api.setGeometry = function(geom, i) { + if (utils.isString(geom)) { + geom = JSON.parse(geom); + } + // TODO: validate? validate geometry in feature setter? + var feat = { + type: 'Feature', + properties: features[i] ? features[i].properties : null, + geometry: geom || null + }; + api.set(feat, i); + }; + + api.set = function(feat, i) { + var arr; + + if (utils.isString(feat)) { + feat = JSON.parse(feat); + } + + if (!feat) return; + + if (feat.type == 'GeometryCollection') { + arr = feat.geometries.map(geom => GeoJSON.toFeature(geom)); + } else if (feat.type == 'FeatureCollection') { + arr = feat.features; + } else { + feat = GeoJSON.toFeature(feat); + } + + if (arr) { + features2 = features2.concat(arr); + } else { + features2.push(feat); + } + }; + + api.done = function() { + if (features2.length === 0) return; // read-only expression + var geojson = { + type: 'FeatureCollection', + features: features2 + }; + return importGeoJSON(geojson); + }; + return api; + } + cmd.filterGeom = function(lyr, arcs, opts) { if (!layerHasGeometry(lyr)) { stop("Layer is missing geometry"); @@ -45690,7 +45726,7 @@ ${svg} }); } - var version = "0.6.98"; + var version = "0.6.99"; // Parse command line args into commands and run them // Function takes an optional Node-style callback. A Promise is returned if no callback is given.