mapshaper 0.5.98 → 0.5.101

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.
@@ -1736,6 +1736,516 @@
1736
1736
 
1737
1737
  utils$1.inherit(Slider, EventDispatcher);
1738
1738
 
1739
+ // Create low-detail versions of large arc collections for faster rendering
1740
+ // at zoomed-out scales.
1741
+ function enhanceArcCollectionForDisplay(unfilteredArcs) {
1742
+ var size = unfilteredArcs.getPointCount(),
1743
+ filteredArcs, filteredSegLen;
1744
+
1745
+ // Only generate low-detail arcs for larger datasets
1746
+ if (size > 5e5) {
1747
+ if (!!unfilteredArcs.getVertexData().zz) {
1748
+ // Use precalculated simplification data for vertex filtering, if available
1749
+ filteredArcs = initFilteredArcs(unfilteredArcs);
1750
+ filteredSegLen = internal.getAvgSegment(filteredArcs);
1751
+ } else {
1752
+ // Use fast simplification as a fallback
1753
+ filteredSegLen = internal.getAvgSegment(unfilteredArcs) * 4;
1754
+ filteredArcs = internal.simplifyArcsFast(unfilteredArcs, filteredSegLen);
1755
+ }
1756
+ }
1757
+
1758
+ function initFilteredArcs(arcs) {
1759
+ var filterPct = 0.08;
1760
+ var nth = Math.ceil(arcs.getPointCount() / 5e5);
1761
+ var currInterval = arcs.getRetainedInterval();
1762
+ var filterZ = arcs.getThresholdByPct(filterPct, nth);
1763
+ var filteredArcs = arcs.setRetainedInterval(filterZ).getFilteredCopy();
1764
+ arcs.setRetainedInterval(currInterval); // reset current simplification
1765
+ return filteredArcs;
1766
+ }
1767
+
1768
+ unfilteredArcs.getScaledArcs = function(ext) {
1769
+ if (filteredArcs) {
1770
+ // match simplification of unfiltered arcs
1771
+ filteredArcs.setRetainedInterval(unfilteredArcs.getRetainedInterval());
1772
+ }
1773
+ // switch to filtered version of arcs at small scales
1774
+ var unitsPerPixel = 1/ext.getTransform().mx,
1775
+ useFiltering = filteredArcs && unitsPerPixel > filteredSegLen * 1.5;
1776
+ return useFiltering ? filteredArcs : unfilteredArcs;
1777
+ };
1778
+ }
1779
+
1780
+ function getDisplayLayerForTable(table) {
1781
+ var n = table.size(),
1782
+ cellWidth = 12,
1783
+ cellHeight = 5,
1784
+ gutter = 6,
1785
+ arcs = [],
1786
+ shapes = [],
1787
+ aspectRatio = 1.1,
1788
+ x, y, col, row, blockSize;
1789
+
1790
+ if (n > 10000) {
1791
+ arcs = null;
1792
+ gutter = 0;
1793
+ cellWidth = 4;
1794
+ cellHeight = 4;
1795
+ aspectRatio = 1.45;
1796
+ } else if (n > 5000) {
1797
+ cellWidth = 5;
1798
+ gutter = 3;
1799
+ aspectRatio = 1.45;
1800
+ } else if (n > 1000) {
1801
+ gutter = 3;
1802
+ cellWidth = 8;
1803
+ aspectRatio = 1.3;
1804
+ }
1805
+
1806
+ if (n < 25) {
1807
+ blockSize = n;
1808
+ } else {
1809
+ blockSize = Math.sqrt(n * (cellWidth + gutter) / cellHeight / aspectRatio) | 0;
1810
+ }
1811
+
1812
+ for (var i=0; i<n; i++) {
1813
+ row = i % blockSize;
1814
+ col = Math.floor(i / blockSize);
1815
+ x = col * (cellWidth + gutter);
1816
+ y = cellHeight * (blockSize - row);
1817
+ if (arcs) {
1818
+ arcs.push(getArc(x, y, cellWidth, cellHeight));
1819
+ shapes.push([[i]]);
1820
+ } else {
1821
+ shapes.push([[x, y]]);
1822
+ }
1823
+ }
1824
+
1825
+ function getArc(x, y, w, h) {
1826
+ return [[x, y], [x + w, y], [x + w, y - h], [x, y - h], [x, y]];
1827
+ }
1828
+
1829
+ return {
1830
+ layer: {
1831
+ geometry_type: arcs ? 'polygon' : 'point',
1832
+ shapes: shapes,
1833
+ data: table
1834
+ },
1835
+ arcs: arcs ? new internal.ArcCollection(arcs) : null
1836
+ };
1837
+ }
1838
+
1839
+ // Assumes projections are available
1840
+
1841
+ function needReprojectionForDisplay(sourceCRS, displayCRS) {
1842
+ if (!sourceCRS || !displayCRS) {
1843
+ return false;
1844
+ }
1845
+ if (internal.crsAreEqual(sourceCRS, displayCRS)) {
1846
+ return false;
1847
+ }
1848
+ return true;
1849
+ }
1850
+
1851
+ function projectArcsForDisplay(arcs, src, dest) {
1852
+ var copy = arcs.getCopy(); // need to flatten first?
1853
+ var destIsWebMerc = internal.isWebMercator(dest);
1854
+ if (destIsWebMerc && internal.isWebMercator(src)) {
1855
+ return copy;
1856
+ }
1857
+
1858
+ var wgs84 = internal.getCRS('wgs84');
1859
+ var toWGS84 = internal.getProjTransform2(src, wgs84);
1860
+ var fromWGS84 = internal.getProjTransform2(wgs84, dest);
1861
+
1862
+ try {
1863
+ // first try projectArcs() -- it's fast and preserves arc ids
1864
+ // (so vertex editing doesn't break)
1865
+ if (!internal.isWGS84(src)) {
1866
+ // use wgs84 as a pivot CRS, so we can handle polar coordinates
1867
+ // that can't be projected to Mercator
1868
+ internal.projectArcs(copy, toWGS84);
1869
+ }
1870
+ if (destIsWebMerc) {
1871
+ // handle polar points by clamping them to they will project
1872
+ // (downside: may cause unexpected behavior when editing vertices interactively)
1873
+ clampY(copy);
1874
+ }
1875
+ internal.projectArcs(copy, fromWGS84);
1876
+ } catch(e) {
1877
+ console.error(e);
1878
+ // use the more robust projectArcs2 if projectArcs throws an error
1879
+ // downside: projectArcs2 discards Z values and changes arc indexing,
1880
+ // which will break vertex editing.
1881
+ var reproject = internal.getProjTransform2(src, dest);
1882
+ copy = arcs.getCopy();
1883
+ internal.projectArcs2(copy, reproject);
1884
+ }
1885
+ return copy;
1886
+ }
1887
+
1888
+ function clampY(arcs) {
1889
+ var max = 89.9,
1890
+ min = -89.9,
1891
+ bbox = arcs.getBounds().toArray();
1892
+ if (bbox[1] >= min && bbox[3] <= max) return;
1893
+ arcs.transformPoints(function(x, y) {
1894
+ if (y > max) return [x, max];
1895
+ if (y < min) return [x, min];
1896
+ });
1897
+ }
1898
+
1899
+ function projectPointsForDisplay(lyr, src, dest) {
1900
+ var copy = utils$1.extend({}, lyr);
1901
+ var proj = internal.getProjTransform2(src, dest);
1902
+ copy.shapes = internal.cloneShapes(lyr.shapes);
1903
+ internal.projectPointLayer(copy, proj);
1904
+ return copy;
1905
+ }
1906
+
1907
+ function toWebMercator(lng, lat) {
1908
+ var R = 6378137;
1909
+ var D2R = Math.PI / 180;
1910
+ var k = Math.cos(lat * D2R);
1911
+ var x = R * lng * D2R;
1912
+ var y = R * Math.log(Math.tan(Math.PI * 0.25 + lat * D2R * 0.5));
1913
+ return [x, y];
1914
+ }
1915
+
1916
+ function fromWebMercator(x, y) {
1917
+ var R = 6378137;
1918
+ var R2D = 180 / Math.PI;
1919
+ var lon = x / R * R2D;
1920
+ var lat = R2D * (Math.PI * 0.5 - 2 * Math.atan(Math.exp(-y / R)));
1921
+ return [lon, lat];
1922
+ }
1923
+
1924
+ function clampToMapboxBounds(bounds) {
1925
+ var ymax = toWebMercator(0, 84)[1];
1926
+ var ymin = toWebMercator(0, -84)[1];
1927
+ var hmin = 1000;
1928
+ bounds.ymin = Math.max(bounds.ymin, ymin);
1929
+ bounds.ymax = Math.max(bounds.ymax, ymin + hmin);
1930
+ bounds.ymin = Math.min(bounds.ymin, ymax - hmin);
1931
+ bounds.ymax = Math.min(bounds.ymax, ymax);
1932
+ }
1933
+
1934
+
1935
+ // Update map extent and trigger redraw, after a new display CRS has been applied
1936
+ function projectMapExtent(ext, src, dest, newBounds) {
1937
+ var oldBounds = ext.getBounds();
1938
+ var oldScale = ext.scale();
1939
+ var newCP, proj;
1940
+
1941
+ if (dest && internal.isWebMercator(dest)) {
1942
+ clampToMapboxBounds(newBounds);
1943
+ }
1944
+
1945
+ // if source or destination CRS is unknown, show full extent
1946
+ // if map is at full extent, show full extent
1947
+ // TODO: handle case that scale is 1 and map is panned away from center
1948
+ if (ext.scale() == 1 || !dest) {
1949
+ ext.setBounds(newBounds);
1950
+ ext.home(); // sets full extent and triggers redraw
1951
+ } else {
1952
+ // if map is zoomed, stay centered on the same geographic location, at the same relative scale
1953
+ proj = internal.getProjTransform2(src, dest);
1954
+ newCP = proj(oldBounds.centerX(), oldBounds.centerY());
1955
+ ext.setBounds(newBounds);
1956
+ if (!newCP) {
1957
+ // projection of center point failed; use center of bounds
1958
+ // (also consider just resetting the view using ext.home())
1959
+ newCP = [newBounds.centerX(), newBounds.centerY()];
1960
+ }
1961
+ ext.recenter(newCP[0], newCP[1], oldScale);
1962
+ }
1963
+ }
1964
+
1965
+ // Called from console; for testing dynamic crs
1966
+ function setDisplayProjection(gui, cmd) {
1967
+ var arg = cmd.replace(/^projd[ ]*/, '');
1968
+ if (arg) {
1969
+ gui.map.setDisplayCRS(internal.getCRS(arg));
1970
+ } else {
1971
+ gui.map.setDisplayCRS(null);
1972
+ }
1973
+ }
1974
+
1975
+ function filterLayerByIds(lyr, ids) {
1976
+ var shapes;
1977
+ if (lyr.shapes) {
1978
+ shapes = ids.map(function(id) {
1979
+ return lyr.shapes[id];
1980
+ });
1981
+ return utils$1.defaults({shapes: shapes, data: null}, lyr);
1982
+ }
1983
+ return lyr;
1984
+ }
1985
+
1986
+ function formatLayerNameForDisplay(name) {
1987
+ return name || '[unnamed]';
1988
+ }
1989
+
1990
+ function cleanLayerName(raw) {
1991
+ return raw.replace(/[\n\t/\\]/g, '')
1992
+ .replace(/^[\.\s]+/, '').replace(/[\.\s]+$/, '');
1993
+ }
1994
+
1995
+ function updateLayerStackOrder(layers) {
1996
+ // 1. assign ascending ids to unassigned layers above the range of other layers
1997
+ layers.forEach(function(o, i) {
1998
+ if (!o.layer.stack_id) o.layer.stack_id = 1e6 + i;
1999
+ });
2000
+ // 2. sort in ascending order
2001
+ layers.sort(function(a, b) {
2002
+ return a.layer.stack_id - b.layer.stack_id;
2003
+ });
2004
+ // 3. assign consecutve ids
2005
+ layers.forEach(function(o, i) {
2006
+ o.layer.stack_id = i + 1;
2007
+ });
2008
+ return layers;
2009
+ }
2010
+
2011
+ function sortLayersForMenuDisplay(layers) {
2012
+ layers = updateLayerStackOrder(layers);
2013
+ return layers.reverse();
2014
+ }
2015
+
2016
+ function setZ(lyr, z) {
2017
+ lyr.source.dataset.arcs.setRetainedInterval(z);
2018
+ if (isProjectedLayer(lyr)) {
2019
+ lyr.arcs.setRetainedInterval(z);
2020
+ }
2021
+ }
2022
+
2023
+ function updateZ(lyr) {
2024
+ if (isProjectedLayer(lyr) && !lyr.source.dataset.arcs.isFlat()) {
2025
+ lyr.arcs.setThresholds(lyr.source.dataset.arcs.getVertexData().zz);
2026
+ }
2027
+ }
2028
+
2029
+ function insertVertex$1(lyr, id, dataPoint) {
2030
+ internal.insertVertex(lyr.source.dataset.arcs, id, dataPoint);
2031
+ if (isProjectedLayer(lyr)) {
2032
+ internal.insertVertex(lyr.arcs, id, lyr.projectPoint(dataPoint[0], dataPoint[1]));
2033
+ }
2034
+ }
2035
+
2036
+ function deleteVertex$1(lyr, id) {
2037
+ internal.deleteVertex(lyr.arcs, id);
2038
+ if (isProjectedLayer(lyr)) {
2039
+ internal.deleteVertex(lyr.source.dataset.arcs, id);
2040
+ }
2041
+ }
2042
+
2043
+ function translateDisplayPoint(lyr, p) {
2044
+ return isProjectedLayer(lyr) ? lyr.invertPoint(p[0], p[1]) : p;
2045
+ }
2046
+
2047
+ function getPointCoords(lyr, fid) {
2048
+ return internal.cloneShape(lyr.source.layer.shapes[fid]);
2049
+ }
2050
+
2051
+ // bbox: display coords
2052
+ // intended to work with rectangular projections like Mercator
2053
+ function getBBoxCoords(lyr, bbox) {
2054
+ if (!isProjectedLayer(lyr)) return bbox;
2055
+ var a = translateDisplayPoint(lyr, [bbox[0], bbox[1]]);
2056
+ var b = translateDisplayPoint(lyr, [bbox[2], bbox[3]]);
2057
+ var bounds = new internal.Bounds();
2058
+ bounds.mergePoint(a[0], a[1]);
2059
+ bounds.mergePoint(b[0], b[1]);
2060
+ return bounds.toArray();
2061
+ }
2062
+
2063
+ function getVertexCoords(lyr, id) {
2064
+ return lyr.source.dataset.arcs.getVertex2(id);
2065
+ }
2066
+
2067
+ function setVertexCoords(lyr, ids, dataPoint) {
2068
+ internal.snapVerticesToPoint(ids, dataPoint, lyr.source.dataset.arcs, true);
2069
+ if (isProjectedLayer(lyr)) {
2070
+ var p = lyr.projectPoint(dataPoint[0], dataPoint[1]);
2071
+ internal.snapVerticesToPoint(ids, p, lyr.arcs, true);
2072
+ }
2073
+ }
2074
+
2075
+ function setPointCoords(lyr, fid, coords) {
2076
+ lyr.source.layer.shapes[fid] = coords;
2077
+ if (isProjectedLayer(lyr)) {
2078
+ lyr.layer.shapes[fid] = projectPointCoords(coords, lyr.projectPoint);
2079
+ }
2080
+ }
2081
+
2082
+ function updateVertexCoords(lyr, ids) {
2083
+ if (!isProjectedLayer(lyr)) return;
2084
+ var p = lyr.arcs.getVertex2(ids[0]);
2085
+ internal.snapVerticesToPoint(ids, lyr.invertPoint(p[0], p[1]), lyr.source.dataset.arcs, true);
2086
+ }
2087
+
2088
+ function isProjectedLayer(lyr) {
2089
+ // TODO: could do some validation on the layer's contents
2090
+ return !!(lyr.source && lyr.invertPoint);
2091
+ }
2092
+
2093
+ // Update data coordinates by projecting display coordinates
2094
+ function updatePointCoords(lyr, fid) {
2095
+ if (!isProjectedLayer(lyr)) return;
2096
+ var displayShp = lyr.layer.shapes[fid];
2097
+ lyr.source.layer.shapes[fid] = projectPointCoords(displayShp, lyr.invertPoint);
2098
+ }
2099
+
2100
+ function projectPointCoords(src, proj) {
2101
+ var dest = [], p;
2102
+ for (var i=0; i<src.length; i++) {
2103
+ p = proj(src[i][0], src[i][1]);
2104
+ if (p) dest.push(p);
2105
+ }
2106
+ return dest.length ? dest : null;
2107
+ }
2108
+
2109
+ // displayCRS: CRS to use for display, or null (which clears any current display CRS)
2110
+ function projectDisplayLayer(lyr, displayCRS) {
2111
+ var sourceCRS = internal.getDatasetCRS(lyr.source.dataset);
2112
+ var lyr2;
2113
+ if (!lyr.geographic || !sourceCRS) {
2114
+ return lyr;
2115
+ }
2116
+ if (lyr.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.dynamic_crs)) {
2117
+ return lyr;
2118
+ }
2119
+ lyr2 = getDisplayLayer(lyr.source.layer, lyr.source.dataset, {crs: displayCRS});
2120
+ // kludge: copy projection-related properties to original layer
2121
+ lyr.dynamic_crs = lyr2.dynamic_crs;
2122
+ lyr.layer = lyr2.layer;
2123
+ if (lyr.style && lyr.style.ids) {
2124
+ // re-apply layer filter
2125
+ lyr.layer = filterLayerByIds(lyr.layer, lyr.style.ids);
2126
+ }
2127
+ lyr.invertPoint = lyr2.invertPoint;
2128
+ lyr.projectPoint = lyr2.projectPoint;
2129
+ lyr.bounds = lyr2.bounds;
2130
+ lyr.arcs = lyr2.arcs;
2131
+ }
2132
+
2133
+
2134
+ // Wrap a layer in an object along with information needed for rendering
2135
+ function getDisplayLayer(layer, dataset, opts) {
2136
+ var obj = {
2137
+ layer: null,
2138
+ arcs: null,
2139
+ // display_arcs: null,
2140
+ style: null,
2141
+ invertPoint: null,
2142
+ projectPoint: null,
2143
+ source: {
2144
+ layer: layer,
2145
+ dataset: dataset
2146
+ },
2147
+ empty: internal.getFeatureCount(layer) === 0
2148
+ };
2149
+
2150
+ var sourceCRS = opts.crs && internal.getDatasetCRS(dataset); // get src iff display CRS is given
2151
+ var displayCRS = opts.crs || null;
2152
+ // display arcs may have been generated when another layer in the dataset was converted for display... re-use if available
2153
+ var displayArcs = dataset.displayArcs || null;
2154
+ var emptyArcs;
2155
+
2156
+ // Assume that dataset.displayArcs is in the display CRS
2157
+ // (it must be deleted upstream if reprojection is needed)
2158
+ if (dataset.arcs && !displayArcs) {
2159
+ // project arcs, if needed
2160
+ if (needReprojectionForDisplay(sourceCRS, displayCRS)) {
2161
+ displayArcs = projectArcsForDisplay(dataset.arcs, sourceCRS, displayCRS);
2162
+ } else {
2163
+ // Use original arcs for display if there is no dynamic reprojection
2164
+ displayArcs = dataset.arcs;
2165
+ }
2166
+
2167
+ enhanceArcCollectionForDisplay(displayArcs);
2168
+ dataset.displayArcs = displayArcs; // stash these in the dataset for other layers to use
2169
+ }
2170
+
2171
+ if (internal.layerHasFurniture(layer)) {
2172
+ obj.furniture = true;
2173
+ obj.furniture_type = internal.getFurnitureLayerType(layer);
2174
+ obj.layer = layer;
2175
+ // treating furniture layers (other than frame) as tabular for now,
2176
+ // so there is something to show if they are selected
2177
+ obj.tabular = obj.furniture_type != 'frame';
2178
+ } else if (obj.empty) {
2179
+ obj.layer = {shapes: []}; // ideally we should avoid empty layers
2180
+ } else if (!layer.geometry_type) {
2181
+ obj.tabular = true;
2182
+ } else {
2183
+ obj.geographic = true;
2184
+ obj.layer = layer;
2185
+ obj.arcs = displayArcs;
2186
+ }
2187
+
2188
+ if (obj.tabular) {
2189
+ utils$1.extend(obj, getDisplayLayerForTable(layer.data));
2190
+ }
2191
+
2192
+ // dynamic reprojection (arcs were already reprojected above)
2193
+ if (obj.geographic && needReprojectionForDisplay(sourceCRS, displayCRS)) {
2194
+ obj.dynamic_crs = displayCRS;
2195
+ obj.invertPoint = internal.getProjTransform2(displayCRS, sourceCRS);
2196
+ obj.projectPoint = internal.getProjTransform2(sourceCRS, displayCRS);
2197
+ if (internal.layerHasPoints(layer)) {
2198
+ obj.layer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
2199
+ } else if (internal.layerHasPaths(layer)) {
2200
+ emptyArcs = findEmptyArcs(displayArcs);
2201
+ if (emptyArcs.length > 0) {
2202
+ // Don't try to draw paths containing coordinates that failed to project
2203
+ obj.layer = internal.filterPathLayerByArcIds(obj.layer, emptyArcs);
2204
+ }
2205
+ }
2206
+ }
2207
+
2208
+ obj.bounds = getDisplayBounds(obj.layer, obj.arcs);
2209
+ return obj;
2210
+ }
2211
+
2212
+
2213
+ function getDisplayBounds(lyr, arcs) {
2214
+ var arcBounds = arcs ? arcs.getBounds() : new Bounds(),
2215
+ bounds = arcBounds, // default display extent: all arcs in the dataset
2216
+ lyrBounds;
2217
+
2218
+ if (lyr.geometry_type == 'point') {
2219
+ lyrBounds = internal.getLayerBounds(lyr);
2220
+ if (lyrBounds && lyrBounds.hasBounds()) {
2221
+ if (lyrBounds.area() > 0 || !arcBounds.hasBounds()) {
2222
+ bounds = lyrBounds;
2223
+ } else {
2224
+ // if a point layer has no extent (e.g. contains only a single point),
2225
+ // then merge with arc bounds, to place the point in context.
2226
+ bounds = arcBounds.mergeBounds(lyrBounds);
2227
+ }
2228
+ }
2229
+ }
2230
+
2231
+ if (!bounds || !bounds.hasBounds()) { // empty layer
2232
+ bounds = new Bounds();
2233
+ }
2234
+ return bounds;
2235
+ }
2236
+
2237
+ // Returns an array of ids of empty arcs (arcs can be set to empty if errors occur while projecting them)
2238
+ function findEmptyArcs(arcs) {
2239
+ var nn = arcs.getVertexData().nn;
2240
+ var ids = [];
2241
+ for (var i=0, n=nn.length; i<n; i++) {
2242
+ if (nn[i] === 0) {
2243
+ ids.push(i);
2244
+ }
2245
+ }
2246
+ return ids;
2247
+ }
2248
+
1739
2249
  /*
1740
2250
  How changes in the simplify control should affect other components
1741
2251
 
@@ -1904,6 +2414,7 @@
1904
2414
  var opts = getSimplifyOptions();
1905
2415
  mapshaper.simplify(dataset, opts);
1906
2416
  gui.session.simplificationApplied(getSimplifyOptionsAsString());
2417
+ updateZ(gui.map.getActiveLayer()); // question: does this update all display layers?
1907
2418
  model.updated({
1908
2419
  // trigger filtered arc rebuild without redraw if pct is 1
1909
2420
  simplify_method: opts.percentage == 1,
@@ -1955,7 +2466,8 @@
1955
2466
  function onChange(pct) {
1956
2467
  if (_value != pct) {
1957
2468
  _value = pct;
1958
- model.getActiveLayer().dataset.arcs.setRetainedInterval(fromPct(pct));
2469
+ // model.getActiveLayer().dataset.arcs.setRetainedInterval(fromPct(pct));
2470
+ setZ(gui.map.getActiveLayer(), fromPct(pct));
1959
2471
  gui.session.updateSimplificationPct(pct);
1960
2472
  model.updated({'simplify_amount': true});
1961
2473
  updateSliderDisplay();
@@ -2191,77 +2703,6 @@
2191
2703
  }
2192
2704
  }
2193
2705
 
2194
- // Assumes projections are available
2195
-
2196
- function needReprojectionForDisplay(sourceCRS, displayCRS) {
2197
- if (!sourceCRS || !displayCRS) {
2198
- return false;
2199
- }
2200
- if (internal.crsAreEqual(sourceCRS, displayCRS)) {
2201
- return false;
2202
- }
2203
- return true;
2204
- }
2205
-
2206
- function projectArcsForDisplay_v1(arcs, src, dest) {
2207
- var copy = arcs.getCopy(); // need to flatten first?
2208
- var proj = internal.getProjTransform(src, dest);
2209
- internal.projectArcs(copy, proj); // need to densify arcs?
2210
- return copy;
2211
- }
2212
-
2213
- function projectArcsForDisplay(arcs, src, dest) {
2214
- var copy = arcs.getCopy(); // need to flatten first?
2215
- var proj = internal.getProjTransform2(src, dest);
2216
- internal.projectArcs2(copy, proj); // need to densify arcs?
2217
- return copy;
2218
- }
2219
-
2220
- function projectPointsForDisplay(lyr, src, dest) {
2221
- var copy = utils$1.extend({}, lyr);
2222
- var proj = internal.getProjTransform2(src, dest);
2223
- copy.shapes = internal.cloneShapes(lyr.shapes);
2224
- internal.projectPointLayer(copy, proj);
2225
- return copy;
2226
- }
2227
-
2228
-
2229
- // Update map extent and trigger redraw, after a new display CRS has been applied
2230
- function projectMapExtent(ext, src, dest, newBounds) {
2231
- var oldBounds = ext.getBounds();
2232
- var oldScale = ext.scale();
2233
- var newCP, proj;
2234
-
2235
- // if source or destination CRS is unknown, show full extent
2236
- // if map is at full extent, show full extent
2237
- // TODO: handle case that scale is 1 and map is panned away from center
2238
- if (ext.scale() == 1 || !dest) {
2239
- ext.setBounds(newBounds);
2240
- ext.home(); // sets full extent and triggers redraw
2241
- } else {
2242
- // if map is zoomed, stay centered on the same geographic location, at the same relative scale
2243
- proj = internal.getProjTransform2(src, dest);
2244
- newCP = proj(oldBounds.centerX(), oldBounds.centerY());
2245
- ext.setBounds(newBounds);
2246
- if (!newCP) {
2247
- // projection of center point failed; use center of bounds
2248
- // (also consider just resetting the view using ext.home())
2249
- newCP = [newBounds.centerX(), newBounds.centerY()];
2250
- }
2251
- ext.recenter(newCP[0], newCP[1], oldScale);
2252
- }
2253
- }
2254
-
2255
- // Called from console; for testing dynamic crs
2256
- function setDisplayProjection(gui, cmd) {
2257
- var arg = cmd.replace(/^projd[ ]*/, '');
2258
- if (arg) {
2259
- gui.map.setDisplayCRS(internal.getCRS(arg));
2260
- } else {
2261
- gui.map.setDisplayCRS(null);
2262
- }
2263
- }
2264
-
2265
2706
  function Console(gui) {
2266
2707
  var model = gui.model;
2267
2708
  var CURSOR = '$ ';
@@ -2346,8 +2787,8 @@
2346
2787
  // when an instance loses focus.
2347
2788
  internal.setLoggingFunctions(consoleMessage, consoleError, consoleStop);
2348
2789
  gui.container.addClass('console-open');
2349
- gui.dispatchEvent('resize');
2350
2790
  el.show();
2791
+ gui.dispatchEvent('resize');
2351
2792
  input.node().focus();
2352
2793
  history = getHistory();
2353
2794
  }
@@ -2902,47 +3343,6 @@
2902
3343
 
2903
3344
  utils$1.inherit(RepairControl, EventDispatcher);
2904
3345
 
2905
- function filterLayerByIds(lyr, ids) {
2906
- var shapes;
2907
- if (lyr.shapes) {
2908
- shapes = ids.map(function(id) {
2909
- return lyr.shapes[id];
2910
- });
2911
- return utils$1.defaults({shapes: shapes, data: null}, lyr);
2912
- }
2913
- return lyr;
2914
- }
2915
-
2916
- function formatLayerNameForDisplay(name) {
2917
- return name || '[unnamed]';
2918
- }
2919
-
2920
- function cleanLayerName(raw) {
2921
- return raw.replace(/[\n\t/\\]/g, '')
2922
- .replace(/^[\.\s]+/, '').replace(/[\.\s]+$/, '');
2923
- }
2924
-
2925
- function updateLayerStackOrder(layers) {
2926
- // 1. assign ascending ids to unassigned layers above the range of other layers
2927
- layers.forEach(function(o, i) {
2928
- if (!o.layer.stack_id) o.layer.stack_id = 1e6 + i;
2929
- });
2930
- // 2. sort in ascending order
2931
- layers.sort(function(a, b) {
2932
- return a.layer.stack_id - b.layer.stack_id;
2933
- });
2934
- // 3. assign consecutve ids
2935
- layers.forEach(function(o, i) {
2936
- o.layer.stack_id = i + 1;
2937
- });
2938
- return layers;
2939
- }
2940
-
2941
- function sortLayersForMenuDisplay(layers) {
2942
- layers = updateLayerStackOrder(layers);
2943
- return layers.reverse();
2944
- }
2945
-
2946
3346
  // import { groupLayersByDataset } from '../dataset/mapshaper-target-utils';
2947
3347
 
2948
3348
  // Export buttons and their behavior
@@ -3669,7 +4069,6 @@
3669
4069
  offset = 0;
3670
4070
  }
3671
4071
 
3672
-
3673
4072
  function isUndoEvt(e) {
3674
4073
  return (e.ctrlKey || e.metaKey) && !e.shiftKey && e.key == 'z';
3675
4074
  }
@@ -3691,18 +4090,17 @@
3691
4090
  e.stopPropagation();
3692
4091
  e.preventDefault();
3693
4092
  }
3694
-
3695
4093
  }, this, 10);
3696
4094
 
3697
- // undo/redo point/symbol dragging
3698
- //
3699
- gui.on('symbol_dragstart', function(e) {
3700
- stashedUndo = this.makePointSetter(e.FID);
3701
- }, this);
3702
-
3703
4095
  gui.on('symbol_dragend', function(e) {
3704
- var redo = this.makePointSetter(e.FID);
3705
- this.addHistoryState(stashedUndo, redo);
4096
+ var target = e.data.target;
4097
+ var undo = function() {
4098
+ setPointCoords(target, e.FID, e.startCoords);
4099
+ };
4100
+ var redo = function() {
4101
+ setPointCoords(target, e.FID, e.endCoords);
4102
+ };
4103
+ this.addHistoryState(undo, redo);
3706
4104
  }, this);
3707
4105
 
3708
4106
  // undo/redo label dragging
@@ -3729,35 +4127,33 @@
3729
4127
  }, this);
3730
4128
 
3731
4129
  gui.on('vertex_dragend', function(e) {
3732
- var target = gui.model.getActiveLayer();
3733
- var arcs = target.dataset.arcs;
3734
- var startPoint = e.points[0];
3735
- var endPoint = internal.getVertexCoords(e.ids[0], arcs);
4130
+ var target = e.data.target;
4131
+ var startPoint = e.points[0]; // in data coords
4132
+ var endPoint = getVertexCoords(target, e.ids[0]);
3736
4133
  var undo = function() {
3737
4134
  if (e.insertion) {
3738
- internal.deleteVertex(arcs, e.ids[0]);
4135
+ deleteVertex$1(target, e.ids[0]);
3739
4136
  } else {
3740
- snapVerticesToPoint(e.ids, startPoint, arcs, true);
4137
+ setVertexCoords(target, e.ids, startPoint);
3741
4138
  }
3742
4139
  };
3743
4140
  var redo = function() {
3744
4141
  if (e.insertion) {
3745
- internal.insertVertex(arcs, e.ids[0], e.points[0]);
4142
+ insertVertex$1(target, e.ids[0], endPoint);
3746
4143
  }
3747
- snapVerticesToPoint(e.ids, endPoint, arcs, true);
4144
+ setVertexCoords(target, e.ids, endPoint);
3748
4145
  };
3749
4146
  this.addHistoryState(undo, redo);
3750
4147
  }, this);
3751
4148
 
3752
4149
  gui.on('vertex_delete', function(e) {
3753
- var target = gui.model.getActiveLayer();
3754
- var arcs = target.dataset.arcs;
3755
- var p = arcs.getVertex2(e.vertex_id);
4150
+ // get vertex coords in data coordinates (not display coordinates);
4151
+ var p = getVertexCoords(e.data.target, e.vertex_id);
3756
4152
  var redo = function() {
3757
- internal.deleteVertex(arcs, e.vertex_id);
4153
+ deleteVertex$1(e.data.target, e.vertex_id);
3758
4154
  };
3759
4155
  var undo = function() {
3760
- internal.insertVertex(arcs, e.vertex_id, p);
4156
+ insertVertex$1(e.data.target, e.vertex_id, p);
3761
4157
  };
3762
4158
  this.addHistoryState(undo, redo);
3763
4159
  }, this);
@@ -3766,14 +4162,6 @@
3766
4162
  reset();
3767
4163
  };
3768
4164
 
3769
- this.makePointSetter = function(i) {
3770
- var target = gui.model.getActiveLayer();
3771
- var shp = cloneShape(target.layer.shapes[i]);
3772
- return function() {
3773
- target.layer.shapes[i] = shp;
3774
- };
3775
- };
3776
-
3777
4165
  this.makeDataSetter = function(id) {
3778
4166
  var target = gui.model.getActiveLayer();
3779
4167
  var rec = copyRecord(target.layer.data.getRecordAt(id));
@@ -3783,15 +4171,6 @@
3783
4171
  };
3784
4172
  };
3785
4173
 
3786
- this.makeVertexSetter = function(ids) {
3787
- var target = gui.model.getActiveLayer();
3788
- var arcs = target.dataset.arcs;
3789
- var p = internal.getVertexCoords(ids[0], arcs);
3790
- return function() {
3791
- snapVerticesToPoint(ids, p, arcs, true);
3792
- };
3793
- };
3794
-
3795
4174
  this.addHistoryState = function(undo, redo) {
3796
4175
  if (offset > 0) {
3797
4176
  history.splice(-offset);
@@ -5503,6 +5882,8 @@
5503
5882
  // avoid re-allocating memory
5504
5883
  var xx2 = new Float64Array(data.xx.buffer, 0, n-1);
5505
5884
  var yy2 = new Float64Array(data.yy.buffer, 0, n-1);
5885
+ var zz2 = arcs.isFlat() ? null : new Float64Array(data.zz.buffer, 0, n-1);
5886
+ var z = arcs.getRetainedInterval();
5506
5887
  var count = 0;
5507
5888
  var found = false;
5508
5889
  for (var j=0; j<nn.length; j++) {
@@ -5516,24 +5897,31 @@
5516
5897
  utils.copyElements(data.yy, 0, yy2, 0, i);
5517
5898
  utils.copyElements(data.xx, i+1, xx2, i, n-i-1);
5518
5899
  utils.copyElements(data.yy, i+1, yy2, i, n-i-1);
5519
- arcs.updateVertexData(nn, xx2, yy2, null);
5900
+ if (zz2) {
5901
+ utils.copyElements(data.zz, 0, zz2, 0, i);
5902
+ utils.copyElements(data.zz, i+1, zz2, i, n-i-1);
5903
+ }
5904
+ arcs.updateVertexData(nn, xx2, yy2, zz2);
5905
+ arcs.setRetainedInterval(z);
5520
5906
  }
5521
5907
 
5522
5908
  function insertVertex(arcs, i, p) {
5523
- // TODO: add extra bytes to the buffers, to reduce new memory allocation
5524
5909
  var data = arcs.getVertexData();
5525
5910
  var nn = data.nn;
5526
5911
  var n = data.xx.length;
5527
5912
  var count = 0;
5528
5913
  var found = false;
5529
- var xx2, yy2;
5914
+ var xx2, yy2, zz2;
5530
5915
  // avoid re-allocating memory on each insertion
5531
5916
  if (data.xx.buffer.byteLength >= data.xx.length * 8 + 8) {
5532
5917
  xx2 = new Float64Array(data.xx.buffer, 0, n+1);
5533
5918
  yy2 = new Float64Array(data.yy.buffer, 0, n+1);
5534
5919
  } else {
5535
- xx2 = new Float64Array(new ArrayBuffer((n + 20) * 8), 0, n+1);
5536
- yy2 = new Float64Array(new ArrayBuffer((n + 20) * 8), 0, n+1);
5920
+ xx2 = new Float64Array(new ArrayBuffer((n + 50) * 8), 0, n+1);
5921
+ yy2 = new Float64Array(new ArrayBuffer((n + 50) * 8), 0, n+1);
5922
+ }
5923
+ if (!arcs.isFlat()) {
5924
+ zz2 = new Float64Array(new ArrayBuffer((n + 1) * 8), 0, n+1);
5537
5925
  }
5538
5926
  for (var j=0; j<nn.length; j++) {
5539
5927
  count += nn[j];
@@ -5548,7 +5936,12 @@
5548
5936
  utils.copyElements(data.yy, i, yy2, i+1, n-i);
5549
5937
  xx2[i] = p[0];
5550
5938
  yy2[i] = p[1];
5551
- arcs.updateVertexData(nn, xx2, yy2, null);
5939
+ if (zz2) {
5940
+ zz2[i] = Infinity;
5941
+ utils.copyElements(data.zz, 0, zz2, 0, i);
5942
+ utils.copyElements(data.zz, i, zz2, i+1, n-i);
5943
+ }
5944
+ arcs.updateVertexData(nn, xx2, yy2, zz2);
5552
5945
  }
5553
5946
 
5554
5947
  function getShapeHitTest(displayLayer, ext, interactionMode) {
@@ -5675,7 +6068,7 @@
5675
6068
  hits.push(id);
5676
6069
  }
5677
6070
  });
5678
- // console.log(hitThreshold, bullseye);
6071
+ // TODO: add info on what part of a shape gets hit
5679
6072
  return {
5680
6073
  ids: utils$1.uniq(hits) // multipoint features can register multiple hits
5681
6074
  };
@@ -6283,15 +6676,18 @@
6283
6676
  function onMouseChange(e) {
6284
6677
  if (!enabled) return;
6285
6678
  if (isOverMap(e)) {
6286
- displayCoords(ext.translatePixelCoords(e.x, e.y));
6679
+ displayCoords(gui.map.translatePixelCoords(e.x, e.y));
6287
6680
  } else {
6288
6681
  clearCoords();
6289
6682
  }
6290
6683
  }
6291
6684
 
6292
6685
  function displayCoords(p) {
6293
- var decimals = internal.getBoundsPrecisionForDisplay(ext.getBounds().toArray());
6294
- var str = internal.getRoundedCoordString(p, decimals);
6686
+ var p1 = gui.map.translatePixelCoords(0, ext.height());
6687
+ var p2 = gui.map.translatePixelCoords(ext.width(), 0);
6688
+ var bbox = p1.concat(p2);
6689
+ var decimals = internal.getBoundsPrecisionForDisplay(bbox);
6690
+ var str = internal.getRoundedCoordString(p, decimals);
6295
6691
  readout.text(str).show();
6296
6692
  }
6297
6693
 
@@ -6756,6 +7152,10 @@
6756
7152
  ext.zoomToExtent(e.value, _fx, _fy);
6757
7153
  });
6758
7154
 
7155
+ mouse.on('click', function(e) {
7156
+ gui.dispatchEvent('map_click', e);
7157
+ });
7158
+
6759
7159
  mouse.on('dblclick', function(e) {
6760
7160
  if (disabled()) return;
6761
7161
  zoomByPct(getZoomInPct(), e.x / ext.width(), e.y / ext.height());
@@ -7491,31 +7891,38 @@
7491
7891
  }
7492
7892
 
7493
7893
  function initPointDragging(gui, ext, hit) {
7494
-
7894
+ var symbolInfo;
7495
7895
  function active(e) {
7496
7896
  return e.id > -1 && gui.interaction.getMode() == 'location';
7497
7897
  }
7498
7898
 
7499
7899
  hit.on('dragstart', function(e) {
7500
7900
  if (!active(e)) return;
7501
- gui.dispatchEvent('symbol_dragstart', {FID: e.id});
7901
+ var target = hit.getHitTarget();
7902
+ symbolInfo = {
7903
+ FID: e.id,
7904
+ startCoords: getPointCoords(target, e.id),
7905
+ target: target
7906
+ };
7502
7907
  });
7503
7908
 
7504
7909
  hit.on('drag', function(e) {
7505
7910
  if (!active(e)) return;
7506
- var lyr = hit.getHitTarget().layer;
7507
- var p = getPointCoordsById(e.id, lyr);
7911
+ // TODO: support multi points... get id of closest part to the pointer
7912
+ var p = getPointCoordsById(e.id, symbolInfo.target.layer);
7508
7913
  if (!p) return;
7509
7914
  var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
7510
7915
  p[0] += diff[0];
7511
7916
  p[1] += diff[1];
7512
7917
  gui.dispatchEvent('map-needs-refresh');
7513
- gui.dispatchEvent('symbol_drag', {FID: e.id});
7514
7918
  });
7515
7919
 
7516
7920
  hit.on('dragend', function(e) {
7517
- if (!active(e)) return;
7518
- gui.dispatchEvent('symbol_dragend', {FID: e.id});
7921
+ if (!active(e) || !symbolInfo ) return;
7922
+ updatePointCoords(symbolInfo.target, e.id);
7923
+ symbolInfo.endCoords = getPointCoords(symbolInfo.target, e.id);
7924
+ gui.dispatchEvent('symbol_dragend', symbolInfo);
7925
+ symbolInfo = null;
7519
7926
  });
7520
7927
 
7521
7928
  function translateDeltaDisplayCoords(dx, dy, ext) {
@@ -7569,8 +7976,11 @@
7569
7976
  if (pixelDist > HOVER_THRESHOLD) {
7570
7977
  return null;
7571
7978
  }
7572
- var points = nearestIds.map(function(i) {return target.arcs.getVertex2(i);});
7979
+ var points = nearestIds.map(function(i) {
7980
+ return getVertexCoords(target, i); // data coordinates
7981
+ });
7573
7982
  return {
7983
+ target: target,
7574
7984
  ids: nearestIds,
7575
7985
  points: points
7576
7986
  };
@@ -7580,8 +7990,7 @@
7580
7990
  var target = hit.getHitTarget();
7581
7991
  if (!target.arcs.isFlat()) return null; // vertex insertion not supported with simplification
7582
7992
  var p = ext.translatePixelCoords(e.x, e.y);
7583
- var shp = target.layer.shapes[e.id];
7584
- var midpoint = findNearestMidpoint(p, shp, target.arcs);
7993
+ var midpoint = findNearestMidpoint(p, e.id, target);
7585
7994
  if (!midpoint ||
7586
7995
  midpoint.distance / ext.getPixelSize() > MIDPOINT_THRESHOLD) return null;
7587
7996
  return midpoint;
@@ -7591,8 +8000,9 @@
7591
8000
  if (!active()) return;
7592
8001
  if (insertionPoint) {
7593
8002
  var target = hit.getHitTarget();
7594
- internal.insertVertex(target.arcs, insertionPoint.i, insertionPoint.point);
8003
+ insertVertex$1(target, insertionPoint.i, insertionPoint.point);
7595
8004
  dragInfo = {
8005
+ target: target,
7596
8006
  insertion: true,
7597
8007
  ids: [insertionPoint.i],
7598
8008
  points: [insertionPoint.point]
@@ -7624,6 +8034,7 @@
7624
8034
  // kludge to get dataset to recalculate internal bounding boxes
7625
8035
  hit.getHitTarget().arcs.transformPoints(function() {});
7626
8036
  clearHoverVertex();
8037
+ updateVertexCoords(dragInfo.target, dragInfo.ids);
7627
8038
  gui.dispatchEvent('vertex_dragend', dragInfo);
7628
8039
  gui.dispatchEvent('map-needs-refresh');
7629
8040
  dragInfo = null;
@@ -7641,9 +8052,10 @@
7641
8052
  return;
7642
8053
  }
7643
8054
  gui.dispatchEvent('vertex_delete', {
8055
+ target: target,
7644
8056
  vertex_id: vId
7645
8057
  });
7646
- internal.deleteVertex(target.arcs, vId);
8058
+ deleteVertex$1(target, vId);
7647
8059
  clearHoverVertex();
7648
8060
  gui.dispatchEvent('map-needs-refresh');
7649
8061
  });
@@ -7661,7 +8073,7 @@
7661
8073
  // if hovering near a segment midpoint: show the midpoint and save midpoint info
7662
8074
  insertionPoint = findVertexInsertionPoint(e);
7663
8075
  if (insertionPoint) {
7664
- hit.setHoverVertex(insertionPoint.point);
8076
+ hit.setHoverVertex(insertionPoint.displayPoint);
7665
8077
  } else {
7666
8078
  // pointer is not over a vertex: clear any hover effect
7667
8079
  clearHoverVertex();
@@ -7672,7 +8084,9 @@
7672
8084
 
7673
8085
  // Given a location @p (e.g. corresponding to the mouse pointer location),
7674
8086
  // find the midpoint of two vertices on @shp suitable for inserting a new vertex
7675
- function findNearestMidpoint(p, shp, arcs) {
8087
+ function findNearestMidpoint(p, fid, target) {
8088
+ var arcs = target.arcs;
8089
+ var shp = target.layer.shapes[fid];
7676
8090
  var minDist = Infinity, v;
7677
8091
  internal.forEachSegmentInShape(shp, arcs, function(i, j, xx, yy) {
7678
8092
  var x1 = xx[i],
@@ -7681,6 +8095,7 @@
7681
8095
  y2 = yy[j],
7682
8096
  cx = (x1 + x2) / 2,
7683
8097
  cy = (y1 + y2) / 2,
8098
+ midpoint = [cx, cy],
7684
8099
  dist = geom.distance2D(cx, cy, p[0], p[1]);
7685
8100
  if (dist < minDist) {
7686
8101
  minDist = dist;
@@ -7688,7 +8103,8 @@
7688
8103
  i: (i < j ? i : j) + 1, // insertion point
7689
8104
  segment: [i, j],
7690
8105
  segmentLen: geom.distance2D(x1, y1, x2, y2),
7691
- point: [cx, cy],
8106
+ displayPoint: midpoint,
8107
+ point: translateDisplayPoint(target, midpoint),
7692
8108
  distance: dist
7693
8109
  };
7694
8110
  }
@@ -9062,7 +9478,7 @@
9062
9478
  var popup = gui.container.findChild('.box-tool-options');
9063
9479
  var coords = popup.findChild('.box-coords');
9064
9480
  var _on = false;
9065
- var bboxCoords, bboxPixels;
9481
+ var bboxDisplayCoords, bboxPixels, bboxDataCoords;
9066
9482
 
9067
9483
  var infoBtn = new SimpleButton(popup.findChild('.info-btn')).on('click', function() {
9068
9484
  if (coords.visible()) hideCoords(); else showCoords();
@@ -9093,7 +9509,7 @@
9093
9509
  });
9094
9510
 
9095
9511
  new SimpleButton(popup.findChild('.clip-btn')).on('click', function() {
9096
- runCommand('-clip bbox2=' + bboxCoords.join(','));
9512
+ runCommand('-clip bbox2=' + bboxDataCoords.join(','));
9097
9513
  });
9098
9514
 
9099
9515
  gui.addMode('box_tool', turnOn, turnOff);
@@ -9109,8 +9525,8 @@
9109
9525
  // Update the visible rectangle when the map view changes
9110
9526
  // (e.g. during zooming or panning)
9111
9527
  ext.on('change', function() {
9112
- if (!_on || !box.visible() || !bboxCoords) return;
9113
- var b = coordsToPix(bboxCoords);
9528
+ if (!_on || !box.visible() || !bboxDisplayCoords) return;
9529
+ var b = coordsToPix(bboxDisplayCoords);
9114
9530
  var pos = ext.position();
9115
9531
  var dx = pos.pageX,
9116
9532
  dy = pos.pageY;
@@ -9125,7 +9541,7 @@
9125
9541
  gui.on('box_drag', function(e) {
9126
9542
  var b = e.page_bbox;
9127
9543
  bboxPixels = e.map_bbox;
9128
- bboxCoords = pixToCoords(bboxPixels);
9544
+ bboxDisplayCoords = pixToCoords(bboxPixels);
9129
9545
  if (_on || inZoomMode()) {
9130
9546
  box.show(b[0], b[1], b[2], b[3]);
9131
9547
  }
@@ -9133,7 +9549,8 @@
9133
9549
 
9134
9550
  gui.on('box_drag_end', function(e) {
9135
9551
  bboxPixels = e.map_bbox;
9136
- bboxCoords = pixToCoords(bboxPixels);
9552
+ bboxDisplayCoords = pixToCoords(bboxPixels);
9553
+ bboxDataCoords = getBBoxCoords(gui.map.getActiveLayer(), bboxDisplayCoords);
9137
9554
  if (inZoomMode()) {
9138
9555
  box.hide();
9139
9556
  nav.zoomToBbox(bboxPixels);
@@ -9157,7 +9574,7 @@
9157
9574
 
9158
9575
  function showCoords() {
9159
9576
  El(infoBtn.node()).addClass('selected-btn');
9160
- coords.text(bboxCoords.join(','));
9577
+ coords.text(bboxDataCoords.join(','));
9161
9578
  coords.show();
9162
9579
  GUI.selectElement(coords.node());
9163
9580
  }
@@ -9204,238 +9621,200 @@
9204
9621
  return self;
9205
9622
  }
9206
9623
 
9207
- // Create low-detail versions of large arc collections for faster rendering
9208
- // at zoomed-out scales.
9209
- function MultiScaleArcCollection(unfilteredArcs) {
9210
- var size = unfilteredArcs.getPointCount(),
9211
- filteredArcs, filteredSegLen;
9212
-
9213
- // Only generate low-detail arcs for larger datasets
9214
- if (size > 5e5) {
9215
- if (!!unfilteredArcs.getVertexData().zz) {
9216
- // Use precalculated simplification data for vertex filtering, if available
9217
- filteredArcs = initFilteredArcs(unfilteredArcs);
9218
- filteredSegLen = internal.getAvgSegment(filteredArcs);
9219
- } else {
9220
- // Use fast simplification as a fallback
9221
- filteredSegLen = internal.getAvgSegment(unfilteredArcs) * 4;
9222
- filteredArcs = internal.simplifyArcsFast(unfilteredArcs, filteredSegLen);
9223
- }
9224
- }
9225
-
9226
- function initFilteredArcs(arcs) {
9227
- var filterPct = 0.08;
9228
- var nth = Math.ceil(arcs.getPointCount() / 5e5);
9229
- var currInterval = arcs.getRetainedInterval();
9230
- var filterZ = arcs.getThresholdByPct(filterPct, nth);
9231
- var filteredArcs = arcs.setRetainedInterval(filterZ).getFilteredCopy();
9232
- arcs.setRetainedInterval(currInterval); // reset current simplification
9233
- return filteredArcs;
9624
+ function loadScript(url, cb) {
9625
+ var script = document.createElement('script');
9626
+ script.onload = cb;
9627
+ script.src = url;
9628
+ document.head.appendChild(script);
9629
+ }
9630
+
9631
+ function loadStylesheet(url) {
9632
+ var el = document.createElement('link');
9633
+ el.rel = 'stylesheet';
9634
+ el.type = 'text/css';
9635
+ el.media = 'screen';
9636
+ el.href = url;
9637
+ document.head.appendChild(el);
9638
+ }
9639
+
9640
+ function Basemap(gui, ext) {
9641
+ var menu = gui.container.findChild('.basemap-options');
9642
+ var list = menu.findChild('.basemap-styles');
9643
+ var container = gui.container.findChild('.basemap-container');
9644
+ var basemapBtn = gui.container.findChild('.basemap-btn');
9645
+ var basemapNote = gui.container.findChild('.basemap-note');
9646
+ var basemapWarning = gui.container.findChild('.basemap-warning');
9647
+ var mapEl = gui.container.findChild('.basemap');
9648
+ var extentNote = El('div').addClass('basemap-prompt').appendTo(container).hide();
9649
+ var params = window.mapboxParams;
9650
+ var map;
9651
+ var activeStyle;
9652
+ var loading = false;
9653
+
9654
+ if (params) {
9655
+ init();
9656
+ } else {
9657
+ basemapBtn.hide();
9234
9658
  }
9235
9659
 
9236
- unfilteredArcs.getScaledArcs = function(ext) {
9237
- if (filteredArcs) {
9238
- // match simplification of unfiltered arcs
9239
- filteredArcs.setRetainedInterval(unfilteredArcs.getRetainedInterval());
9240
- }
9241
- // switch to filtered version of arcs at small scales
9242
- var unitsPerPixel = 1/ext.getTransform().mx,
9243
- useFiltering = filteredArcs && unitsPerPixel > filteredSegLen * 1.5;
9244
- return useFiltering ? filteredArcs : unfilteredArcs;
9245
- };
9246
-
9247
- return unfilteredArcs;
9248
- }
9660
+ function init() {
9661
+ gui.addMode('basemap', turnOn, turnOff, basemapBtn);
9662
+ // model.on('select', function() {
9663
+ // TODO: hide basemap
9664
+ // if (gui.getMode() == 'basemap') gui.clearMode();
9665
+ // });
9249
9666
 
9250
- function getDisplayLayerForTable(table) {
9251
- var n = table.size(),
9252
- cellWidth = 12,
9253
- cellHeight = 5,
9254
- gutter = 6,
9255
- arcs = [],
9256
- shapes = [],
9257
- aspectRatio = 1.1,
9258
- x, y, col, row, blockSize;
9667
+ new SimpleButton(menu.findChild('.close-btn')).on('click', function() {
9668
+ gui.clearMode();
9669
+ turnOff();
9670
+ });
9259
9671
 
9260
- if (n > 10000) {
9261
- arcs = null;
9262
- gutter = 0;
9263
- cellWidth = 4;
9264
- cellHeight = 4;
9265
- aspectRatio = 1.45;
9266
- } else if (n > 5000) {
9267
- cellWidth = 5;
9268
- gutter = 3;
9269
- aspectRatio = 1.45;
9270
- } else if (n > 1000) {
9271
- gutter = 3;
9272
- cellWidth = 8;
9273
- aspectRatio = 1.3;
9274
- }
9672
+ gui.on('map_click', function() {
9673
+ // close menu if user click on the map
9674
+ if (gui.getMode() == 'basemap') gui.clearMode();
9675
+ });
9275
9676
 
9276
- if (n < 25) {
9277
- blockSize = n;
9278
- } else {
9279
- blockSize = Math.sqrt(n * (cellWidth + gutter) / cellHeight / aspectRatio) | 0;
9677
+ params.styles.forEach(function(style) {
9678
+ var btn = El('div').html(`<div class="basemap-style-btn"><img src="${style.icon}"></img></div><div class="basemap-style-label">${style.name}</div>`);
9679
+ btn.findChild('.basemap-style-btn').on('click', function() {
9680
+ updateStyle(style == activeStyle ? null : style);
9681
+ updateButtons();
9682
+ });
9683
+ btn.appendTo(list);
9684
+ });
9280
9685
  }
9281
9686
 
9282
- for (var i=0; i<n; i++) {
9283
- row = i % blockSize;
9284
- col = Math.floor(i / blockSize);
9285
- x = col * (cellWidth + gutter);
9286
- y = cellHeight * (blockSize - row);
9287
- if (arcs) {
9288
- arcs.push(getArc(x, y, cellWidth, cellHeight));
9289
- shapes.push([[i]]);
9687
+ function updateStyle(style) {
9688
+ activeStyle = style || null;
9689
+ if (!style) {
9690
+ gui.map.setDisplayCRS(null);
9691
+ hide();
9692
+ } else if (map) {
9693
+ map.setStyle(style.url);
9694
+ refresh();
9290
9695
  } else {
9291
- shapes.push([[x, y]]);
9696
+ initMap();
9292
9697
  }
9293
9698
  }
9294
9699
 
9295
- function getArc(x, y, w, h) {
9296
- return [[x, y], [x + w, y], [x + w, y - h], [x, y - h], [x, y]];
9297
- }
9298
-
9299
- return {
9300
- layer: {
9301
- geometry_type: arcs ? 'polygon' : 'point',
9302
- shapes: shapes,
9303
- data: table
9304
- },
9305
- arcs: arcs ? new internal.ArcCollection(arcs) : null
9306
- };
9307
- }
9308
-
9309
- // displayCRS: CRS to use for display, or null (which clears any current display CRS)
9310
- function projectDisplayLayer(lyr, displayCRS) {
9311
- var sourceCRS = internal.getDatasetCRS(lyr.source.dataset);
9312
- var lyr2;
9313
- if (!lyr.geographic || !sourceCRS) {
9314
- return lyr;
9315
- }
9316
- if (lyr.dynamic_crs && internal.crsAreEqual(sourceCRS, lyr.dynamic_crs)) {
9317
- return lyr;
9318
- }
9319
- lyr2 = getDisplayLayer(lyr.source.layer, lyr.source.dataset, {crs: displayCRS});
9320
- // kludge: copy projection-related properties to original layer
9321
- lyr.dynamic_crs = lyr2.dynamic_crs;
9322
- lyr.layer = lyr2.layer;
9323
- if (lyr.style && lyr.style.ids) {
9324
- // re-apply layer filter
9325
- lyr.layer = filterLayerByIds(lyr.layer, lyr.style.ids);
9700
+ function updateButtons() {
9701
+ list.findChildren('.basemap-style-btn').forEach(function(el, i) {
9702
+ el.classed('active', params.styles[i] == activeStyle);
9703
+ });
9326
9704
  }
9327
- lyr.bounds = lyr2.bounds;
9328
- lyr.arcs = lyr2.arcs;
9329
- }
9330
-
9331
-
9332
- // Wrap a layer in an object along with information needed for rendering
9333
- function getDisplayLayer(layer, dataset, opts) {
9334
- var obj = {
9335
- layer: null,
9336
- arcs: null,
9337
- // display_arcs: null,
9338
- style: null,
9339
- source: {
9340
- layer: layer,
9341
- dataset: dataset
9342
- },
9343
- empty: internal.getFeatureCount(layer) === 0
9344
- };
9345
9705
 
9346
- var sourceCRS = opts.crs && internal.getDatasetCRS(dataset); // get src iff display CRS is given
9347
- var displayCRS = opts.crs || null;
9348
- var displayArcs = dataset.displayArcs;
9349
- var emptyArcs;
9350
-
9351
- // Assume that dataset.displayArcs is in the display CRS
9352
- // (it should have been deleted upstream if reprojection is needed)
9353
- if (dataset.arcs && !displayArcs) {
9354
- // project arcs, if needed
9355
- if (needReprojectionForDisplay(sourceCRS, displayCRS)) {
9356
- displayArcs = projectArcsForDisplay(dataset.arcs, sourceCRS, displayCRS);
9706
+ function turnOn() {
9707
+ var activeLyr = gui.model.getActiveLayer();
9708
+ var dataCRS = internal.getDatasetCRS(activeLyr.dataset);
9709
+ var displayCRS = gui.map.getDisplayCRS();
9710
+ var warning;
9711
+
9712
+ if (!crsIsUsable(displayCRS) || !crsIsUsable(dataCRS)) {
9713
+ warning = 'The current layer is not compatible with the projection used by the basemaps.';
9714
+ basemapWarning.html(warning).show();
9715
+ basemapNote.hide();
9357
9716
  } else {
9358
- displayArcs = dataset.arcs;
9717
+ basemapNote.show();
9359
9718
  }
9719
+ menu.show();
9720
+ }
9360
9721
 
9361
- // init filtered arcs
9362
- dataset.displayArcs = new MultiScaleArcCollection(displayArcs);
9722
+ function turnOff() {
9723
+ basemapWarning.hide();
9724
+ basemapNote.hide();
9725
+ menu.hide();
9363
9726
  }
9364
9727
 
9365
- if (internal.layerHasFurniture(layer)) {
9366
- obj.furniture = true;
9367
- obj.furniture_type = internal.getFurnitureLayerType(layer);
9368
- obj.layer = layer;
9369
- // treating furniture layers (other than frame) as tabular for now,
9370
- // so there is something to show if they are selected
9371
- obj.tabular = obj.furniture_type != 'frame';
9372
- } else if (obj.empty) {
9373
- obj.layer = {shapes: []}; // ideally we should avoid empty layers
9374
- } else if (!layer.geometry_type) {
9375
- obj.tabular = true;
9376
- } else {
9377
- obj.geographic = true;
9378
- obj.layer = layer;
9379
- obj.arcs = displayArcs;
9728
+ function enabled() {
9729
+ return !!(mapEl && params);
9380
9730
  }
9381
9731
 
9382
- if (obj.tabular) {
9383
- utils$1.extend(obj, getDisplayLayerForTable(layer.data));
9732
+ function show() {
9733
+ gui.container.addClass('basemap-on');
9734
+ mapEl.node().style.display = 'block';
9384
9735
  }
9385
9736
 
9386
- // dynamic reprojection (arcs were already reprojected above)
9387
- if (obj.geographic && needReprojectionForDisplay(sourceCRS, displayCRS)) {
9388
- obj.dynamic_crs = displayCRS;
9389
- if (internal.layerHasPoints(layer)) {
9390
- obj.layer = projectPointsForDisplay(layer, sourceCRS, displayCRS);
9391
- } else if (internal.layerHasPaths(layer)) {
9392
- emptyArcs = findEmptyArcs(displayArcs);
9393
- if (emptyArcs.length > 0) {
9394
- // Don't try to draw paths containing coordinates that failed to project
9395
- obj.layer = internal.filterPathLayerByArcIds(obj.layer, emptyArcs);
9396
- }
9397
- }
9737
+ function hide() {
9738
+ gui.container.removeClass('basemap-on');
9739
+ mapEl.node().style.display = 'none';
9398
9740
  }
9399
9741
 
9400
- obj.bounds = getDisplayBounds(obj.layer, obj.arcs);
9401
- return obj;
9402
- }
9742
+ function getLonLatBounds() {
9743
+ var bbox = ext.getBounds().toArray();
9744
+ var tr = fromWebMercator(bbox[2], bbox[3]);
9745
+ var bl = fromWebMercator(bbox[0], bbox[1]);
9746
+ return bl.concat(tr);
9747
+ }
9403
9748
 
9404
9749
 
9405
- function getDisplayBounds(lyr, arcs) {
9406
- var arcBounds = arcs ? arcs.getBounds() : new Bounds(),
9407
- bounds = arcBounds, // default display extent: all arcs in the dataset
9408
- lyrBounds;
9750
+ function initMap() {
9751
+ if (!enabled() || map || loading) return;
9752
+ loading = true;
9753
+ loadStylesheet(params.css);
9754
+ loadScript(params.js, function() {
9755
+ map = new window.mapboxgl.Map({
9756
+ accessToken: params.key,
9757
+ logoPosition: 'bottom-left',
9758
+ container: mapEl.node(),
9759
+ style: activeStyle.url,
9760
+ bounds: getLonLatBounds(),
9761
+ doubleClickZoom: false,
9762
+ dragPan: false,
9763
+ dragRotate: false,
9764
+ scrollZoom: false,
9765
+ interactive: false,
9766
+ keyboard: false,
9767
+ maxPitch: 0,
9768
+ renderWorldCopies: true // false // false prevents panning off the map
9769
+ });
9770
+ map.on('load', function() {
9771
+ loading = false;
9772
+ refresh();
9773
+ });
9774
+ });
9775
+ }
9409
9776
 
9410
- if (lyr.geometry_type == 'point') {
9411
- lyrBounds = internal.getLayerBounds(lyr);
9412
- if (lyrBounds && lyrBounds.hasBounds()) {
9413
- if (lyrBounds.area() > 0 || !arcBounds.hasBounds()) {
9414
- bounds = lyrBounds;
9415
- } else {
9416
- // if a point layer has no extent (e.g. contains only a single point),
9417
- // then merge with arc bounds, to place the point in context.
9418
- bounds = arcBounds.mergeBounds(lyrBounds);
9419
- }
9777
+ function checkBounds(bbox) {
9778
+ var msg;
9779
+ if (bbox[1] >= -85 && bbox[3] <= 85) {
9780
+ extentNote.hide();
9781
+ return true;
9420
9782
  }
9783
+ if (bbox[1] > 0) msg = 'pan south to see the basemap';
9784
+ else if (bbox[3] < 0) msg = 'pan north to see the basemap';
9785
+ else msg = msg = 'zoom in to see the basemap';
9786
+ extentNote.html(msg).show();
9787
+ return false;
9421
9788
  }
9422
9789
 
9423
- if (!bounds || !bounds.hasBounds()) { // empty layer
9424
- bounds = new Bounds();
9790
+ function crsIsUsable(crs) {
9791
+ if (!crs) return false;
9792
+ if (!internal.isInvertibleCRS(crs)) return false;
9793
+ return true;
9425
9794
  }
9426
- return bounds;
9427
- }
9428
9795
 
9429
- // Returns an array of ids of empty arcs (arcs can be set to empty if errors occur while projecting them)
9430
- function findEmptyArcs(arcs) {
9431
- var nn = arcs.getVertexData().nn;
9432
- var ids = [];
9433
- for (var i=0, n=nn.length; i<n; i++) {
9434
- if (nn[i] === 0) {
9435
- ids.push(i);
9796
+ function refresh() {
9797
+ if (!enabled() || !map || loading || !activeStyle) return;
9798
+ var crs = gui.map.getDisplayCRS();
9799
+ if (!crsIsUsable(crs)) {
9800
+ hide();
9801
+ return;
9802
+ }
9803
+ if (!internal.isWebMercator(crs)) {
9804
+ gui.map.setDisplayCRS(internal.getCRS('webmercator'));
9805
+ }
9806
+ var bbox = getLonLatBounds();
9807
+ if (!checkBounds(bbox)) {
9808
+ // map does not display outside these bounds
9809
+ hide();
9810
+ } else {
9811
+ show();
9812
+ map.resize();
9813
+ map.fitBounds(bbox, {animate: false});
9436
9814
  }
9437
9815
  }
9438
- return ids;
9816
+
9817
+ return {refresh: refresh}; // called by map when extent changes
9439
9818
  }
9440
9819
 
9441
9820
  utils$1.inherit(MshpMap, EventDispatcher);
@@ -9458,6 +9837,8 @@
9458
9837
  _inspector, _stack,
9459
9838
  _dynamicCRS;
9460
9839
 
9840
+ var _basemap = new Basemap(gui, _ext);
9841
+
9461
9842
  if (gui.options.showMouseCoordinates) {
9462
9843
  new CoordinatesDisplay(gui, _ext, _mouse);
9463
9844
  }
@@ -9482,6 +9863,8 @@
9482
9863
 
9483
9864
  model.on('update', onUpdate);
9484
9865
 
9866
+
9867
+
9485
9868
  // Update display of segment intersections
9486
9869
  this.setIntersectionLayer = function(lyr, dataset) {
9487
9870
  if (lyr == _intersectionLyr) return; // no change
@@ -9499,6 +9882,12 @@
9499
9882
  target.layer.pinned = !!pinned;
9500
9883
  };
9501
9884
 
9885
+ this.translatePixelCoords = function(x, y) {
9886
+ var p = _ext.translatePixelCoords(x, y);
9887
+ if (!_dynamicCRS) return p;
9888
+ return internal.toLngLat(p, _dynamicCRS);
9889
+ };
9890
+
9502
9891
  this.getCenterLngLat = function() {
9503
9892
  var bounds = _ext.getBounds();
9504
9893
  var crs = this.getDisplayCRS();
@@ -9520,6 +9909,7 @@
9520
9909
  this.getExtent = function() {return _ext;};
9521
9910
  this.isActiveLayer = isActiveLayer;
9522
9911
  this.isVisibleLayer = isVisibleLayer;
9912
+ this.getActiveLayer = function() { return _activeLyr; };
9523
9913
 
9524
9914
  // called by layer menu after layer visibility is updated
9525
9915
  this.redraw = function() {
@@ -9551,6 +9941,7 @@
9551
9941
 
9552
9942
  // Update map extent (also triggers redraw)
9553
9943
  projectMapExtent(_ext, oldCRS, this.getDisplayCRS(), getFullBounds());
9944
+ _fullBounds = getFullBounds(); // update this so map extent doesn't get reset after next update
9554
9945
  };
9555
9946
 
9556
9947
  // Refresh map display in response to data changes, layer selection, etc.
@@ -9604,6 +9995,7 @@
9604
9995
  needReset = mapNeedsReset(fullBounds, _fullBounds, _ext.getBounds(), e.flags);
9605
9996
  }
9606
9997
 
9998
+
9607
9999
  if (isFrameView()) {
9608
10000
  _nav.setZoomFactor(0.05); // slow zooming way down to allow fine-tuning frame placement // 0.03
9609
10001
  _ext.setFrame(getFullBounds()); // TODO: remove redundancy with drawLayers()
@@ -9643,6 +10035,7 @@
9643
10035
  }
9644
10036
 
9645
10037
  _ext.on('change', function(e) {
10038
+ if (_basemap) _basemap.refresh(); // keep basemap synced up (if enabled)
9646
10039
  if (e.reset) return; // don't need to redraw map here if extent has been reset
9647
10040
  if (isFrameView()) {
9648
10041
  updateFrameExtent();
@@ -10013,7 +10406,9 @@
10013
10406
  startEditing = function() {};
10014
10407
 
10015
10408
  window.addEventListener('beforeunload', function(e) {
10016
- if (gui.session.unsavedChanges()) {
10409
+ // don't prompt if there are no datasets (this means the last layer was deleted,
10410
+ // hitting the 'cancel' button would leave the interface in a bad state)
10411
+ if (gui.session.unsavedChanges() && !gui.model.isEmpty()) {
10017
10412
  e.returnValue = 'There are unsaved changes.';
10018
10413
  e.preventDefault();
10019
10414
  }