mapshaper 0.5.97 → 0.5.100

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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ v0.5.100
2
+ * Added support for using the basemap feature with datasets in a wide variety of projections.
3
+
4
+ v0.5.99
5
+ * Added support for viewing data against a Mapbox basemap (enabled on the mapshaper.org site).
6
+
7
+ v0.5.98
8
+ * Better handling of null data by the -classify command.
9
+
1
10
  v0.5.97
2
11
  * Better warnings and error messages.
3
12
 
package/mapshaper.js CHANGED
@@ -1771,6 +1771,8 @@
1771
1771
  // avoid re-allocating memory
1772
1772
  var xx2 = new Float64Array(data.xx.buffer, 0, n-1);
1773
1773
  var yy2 = new Float64Array(data.yy.buffer, 0, n-1);
1774
+ var zz2 = arcs.isFlat() ? null : new Float64Array(data.zz.buffer, 0, n-1);
1775
+ var z = arcs.getRetainedInterval();
1774
1776
  var count = 0;
1775
1777
  var found = false;
1776
1778
  for (var j=0; j<nn.length; j++) {
@@ -1784,24 +1786,31 @@
1784
1786
  utils.copyElements(data.yy, 0, yy2, 0, i);
1785
1787
  utils.copyElements(data.xx, i+1, xx2, i, n-i-1);
1786
1788
  utils.copyElements(data.yy, i+1, yy2, i, n-i-1);
1787
- arcs.updateVertexData(nn, xx2, yy2, null);
1789
+ if (zz2) {
1790
+ utils.copyElements(data.zz, 0, zz2, 0, i);
1791
+ utils.copyElements(data.zz, i+1, zz2, i, n-i-1);
1792
+ }
1793
+ arcs.updateVertexData(nn, xx2, yy2, zz2);
1794
+ arcs.setRetainedInterval(z);
1788
1795
  }
1789
1796
 
1790
1797
  function insertVertex(arcs, i, p) {
1791
- // TODO: add extra bytes to the buffers, to reduce new memory allocation
1792
1798
  var data = arcs.getVertexData();
1793
1799
  var nn = data.nn;
1794
1800
  var n = data.xx.length;
1795
1801
  var count = 0;
1796
1802
  var found = false;
1797
- var xx2, yy2;
1803
+ var xx2, yy2, zz2;
1798
1804
  // avoid re-allocating memory on each insertion
1799
1805
  if (data.xx.buffer.byteLength >= data.xx.length * 8 + 8) {
1800
1806
  xx2 = new Float64Array(data.xx.buffer, 0, n+1);
1801
1807
  yy2 = new Float64Array(data.yy.buffer, 0, n+1);
1802
1808
  } else {
1803
- xx2 = new Float64Array(new ArrayBuffer((n + 20) * 8), 0, n+1);
1804
- yy2 = new Float64Array(new ArrayBuffer((n + 20) * 8), 0, n+1);
1809
+ xx2 = new Float64Array(new ArrayBuffer((n + 50) * 8), 0, n+1);
1810
+ yy2 = new Float64Array(new ArrayBuffer((n + 50) * 8), 0, n+1);
1811
+ }
1812
+ if (!arcs.isFlat()) {
1813
+ zz2 = new Float64Array(new ArrayBuffer((n + 1) * 8), 0, n+1);
1805
1814
  }
1806
1815
  for (var j=0; j<nn.length; j++) {
1807
1816
  count += nn[j];
@@ -1816,7 +1825,12 @@
1816
1825
  utils.copyElements(data.yy, i, yy2, i+1, n-i);
1817
1826
  xx2[i] = p[0];
1818
1827
  yy2[i] = p[1];
1819
- arcs.updateVertexData(nn, xx2, yy2, null);
1828
+ if (zz2) {
1829
+ zz2[i] = Infinity;
1830
+ utils.copyElements(data.zz, 0, zz2, 0, i);
1831
+ utils.copyElements(data.zz, i, zz2, i+1, n-i);
1832
+ }
1833
+ arcs.updateVertexData(nn, xx2, yy2, zz2);
1820
1834
  }
1821
1835
 
1822
1836
  var ArcUtils = /*#__PURE__*/Object.freeze({
@@ -4663,8 +4677,8 @@
4663
4677
  if (isLatLngCRS(P)) {
4664
4678
  return xy.concat();
4665
4679
  }
4666
- proj = getProjInfo(P, getCRS('wgs84'));
4667
- return proj(xy);
4680
+ proj = getProjTransform(P, getCRS('wgs84'));
4681
+ return proj(xy[0], xy[1]);
4668
4682
  }
4669
4683
 
4670
4684
  function getProjInfo(dataset) {
@@ -4806,10 +4820,30 @@
4806
4820
  return !isLatLngCRS(P);
4807
4821
  }
4808
4822
 
4823
+ function isInvertibleCRS(P) {
4824
+ if (!P || !P.inv) return false;
4825
+ return true;
4826
+ }
4827
+
4809
4828
  function isLatLngCRS(P) {
4810
4829
  return P && P.is_latlong || false;
4811
4830
  }
4812
4831
 
4832
+ function isWGS84(P) {
4833
+ if (!isLatLngCRS(P)) return false;
4834
+ var proj4 = crsToProj4(P);
4835
+ return proj4.toLowerCase().includes('84');
4836
+ }
4837
+
4838
+ function isWebMercator(P) {
4839
+ if (!P) return false;
4840
+ var str = crsToProj4(P);
4841
+ // e.g. +proj=merc +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +wktext +a=6378137 +b=6378137 +nadgrids=@null
4842
+ // e.g. +proj=merc +a=6378137 +b=6378137
4843
+ // TODO: support https://proj.org/operations/projections/webmerc.html
4844
+ return str.includes('+proj=merc') && str.includes('+a=6378137') && str.includes('+b=6378137');
4845
+ }
4846
+
4813
4847
  function isLatLngDataset(dataset) {
4814
4848
  return isLatLngCRS(getDatasetCRS(dataset));
4815
4849
  }
@@ -4863,7 +4897,10 @@
4863
4897
  requireDatasetsHaveCompatibleCRS: requireDatasetsHaveCompatibleCRS,
4864
4898
  getScaleFactorAtXY: getScaleFactorAtXY,
4865
4899
  isProjectedCRS: isProjectedCRS,
4900
+ isInvertibleCRS: isInvertibleCRS,
4866
4901
  isLatLngCRS: isLatLngCRS,
4902
+ isWGS84: isWGS84,
4903
+ isWebMercator: isWebMercator,
4867
4904
  isLatLngDataset: isLatLngDataset,
4868
4905
  printProjections: printProjections,
4869
4906
  translatePrj: translatePrj,
@@ -8357,8 +8394,8 @@
8357
8394
  }
8358
8395
 
8359
8396
  function getBoundsPrecisionForDisplay(bbox) {
8360
- var w = bbox[2] - bbox[0],
8361
- h = bbox[3] - bbox[1],
8397
+ var w = Math.abs(bbox[2] - bbox[0]),
8398
+ h = Math.abs(bbox[3] - bbox[1]),
8362
8399
  range = Math.min(w, h) + 1e-8,
8363
8400
  digits = 0;
8364
8401
  while (range < 2000) {
@@ -29059,6 +29096,31 @@ ${svg}
29059
29096
  return ramp;
29060
29097
  }
29061
29098
 
29099
+ function getNullValue(opts) {
29100
+ var nullValue;
29101
+ if ('null_value' in opts) {
29102
+ nullValue = parseNullValue(opts.null_value);
29103
+ } else if (opts.colors) {
29104
+ nullValue = '#eee';
29105
+ } else if (opts.values) {
29106
+ nullValue = null;
29107
+ } else {
29108
+ nullValue = -1; // kludge, to match behavior of getClassValues()
29109
+ }
29110
+ return nullValue;
29111
+ }
29112
+
29113
+ // Parse command line string arguments to the correct data type
29114
+ function parseNullValue(val) {
29115
+ if (utils.isString(val) && !isNaN(+val)) {
29116
+ val = +val;
29117
+ }
29118
+ if (val === 'null') {
29119
+ val = null;
29120
+ }
29121
+ return val;
29122
+ }
29123
+
29062
29124
  function getClassValues(method, n, opts) {
29063
29125
  var categorical = method == 'categorical' || method == 'non-adjacent';
29064
29126
  var colorArg = opts.colors && opts.colors.length == 1 ? opts.colors[0] : null;
@@ -29132,27 +29194,36 @@ ${svg}
29132
29194
  return values;
29133
29195
  }
29134
29196
 
29135
- var sequential = ['quantile', 'nice', 'equal-interval', 'hybrid'];
29197
+ var sequential = ['quantile', 'nice', 'equal-interval', 'hybrid', 'breaks'];
29136
29198
  var all = ['non-adjacent', 'indexed', 'categorical'].concat(sequential);
29137
29199
 
29138
- function getClassifyMethod(opts, dataFieldType) {
29200
+ function getClassifyMethod(opts, dataType) {
29139
29201
  var method;
29140
29202
  if (opts.method) {
29141
29203
  method = opts.method;
29204
+ } else if (opts.breaks) {
29205
+ method = 'breaks';
29142
29206
  } else if (opts.index_field) {
29143
29207
  method = 'indexed';
29144
- } else if (opts.categories || dataFieldType == 'string') {
29208
+ } else if (opts.categories || dataType == 'string') {
29145
29209
  method = 'categorical';
29146
- } else if (dataFieldType == 'number') {
29210
+ } else if (dataType == 'number') {
29147
29211
  method = 'quantile'; // TODO: validate data field
29212
+ } else if (dataType == 'date' || dataType == 'object') {
29213
+ stop('Data type does not support classification:', dataType);
29214
+ } else if (dataType === null) {
29215
+ // data field is empty
29216
+ return null; // kludge
29217
+ } else if (dataType === undefined) {
29218
+ // no data field was given
29219
+ stop('Expected a data field to classify or the non-adjacent option');
29148
29220
  } else {
29149
- // stop('Unable to determine which classification method to use.');
29150
- stop('Missing a data field and/or classification method');
29221
+ stop('Unable to determine which classification method to use.');
29151
29222
  }
29152
29223
  if (!all.includes(method)) {
29153
29224
  stop('Not a recognized classification method:', method);
29154
29225
  }
29155
- if (sequential.includes(method) && dataFieldType != 'number') {
29226
+ if (sequential.includes(method) && dataType != 'number' && dataType !== null) {
29156
29227
  stop('The', method, 'method requires a numerical data field');
29157
29228
  }
29158
29229
  return method;
@@ -29165,7 +29236,7 @@ ${svg}
29165
29236
  var opts = optsArg || {};
29166
29237
  var records = lyr.data && lyr.data.getRecords();
29167
29238
  var valuesAreColors = !!opts.colors;
29168
- var dataField, dataFieldType, outputField;
29239
+ var dataField, fieldType, outputField;
29169
29240
  var values, nullValue;
29170
29241
  var classifyByValue, classifyByRecordId;
29171
29242
  var numClasses, numValues;
@@ -29179,9 +29250,10 @@ ${svg}
29179
29250
  //
29180
29251
  if (opts.index_field) {
29181
29252
  dataField = opts.index_field;
29182
- } else if (opts.field) {
29253
+ fieldType = getColumnType(opts.field, records);
29254
+ } else if (opts.field) {
29183
29255
  dataField = opts.field;
29184
- dataFieldType = getColumnType(opts.field, records);
29256
+ fieldType = getColumnType(opts.field, records);
29185
29257
  }
29186
29258
  if (dataField) {
29187
29259
  requireDataField(lyr.data, dataField);
@@ -29189,7 +29261,7 @@ ${svg}
29189
29261
 
29190
29262
  // get classification method
29191
29263
  //
29192
- method = getClassifyMethod(opts, dataFieldType);
29264
+ method = getClassifyMethod(opts, fieldType);
29193
29265
 
29194
29266
  // validate classification method
29195
29267
  if (method == 'non-adjacent') {
@@ -29207,6 +29279,7 @@ ${svg}
29207
29279
  // get the number of classes and the number of values
29208
29280
  //
29209
29281
  // expand categories if value is '*'
29282
+ // use all unique values if categories option is missing
29210
29283
  if (method == 'categorical') {
29211
29284
  if ((!opts.categories || opts.categories.includes('*')) && dataField) {
29212
29285
  opts.categories = getUniqFieldValues(records, dataField);
@@ -29248,24 +29321,16 @@ ${svg}
29248
29321
  message('Colors:', formatValuesForLogging(values));
29249
29322
  }
29250
29323
 
29251
- // get null value
29252
- //
29253
- if ('null_value' in opts) {
29254
- nullValue = opts.null_value;
29255
- } else if (valuesAreColors) {
29256
- nullValue = '#eee';
29257
- } else if (opts.values) {
29258
- nullValue = null;
29259
- } else {
29260
- nullValue = -1; // kludge, to match behavior of getClassValues()
29261
- }
29262
-
29324
+ nullValue = getNullValue(opts);
29263
29325
 
29264
29326
  // get a function to convert input data to class indexes
29265
29327
  //
29266
- if (method == 'non-adjacent') {
29328
+ if (fieldType === null) {
29329
+ // no valid data -- always return null value
29330
+ classifyByRecordId = function() {return nullValue;};
29331
+ } else if (method == 'non-adjacent') {
29267
29332
  classifyByRecordId = getNonAdjacentClassifier(lyr, dataset, values);
29268
- } else if (opts.index_field) {
29333
+ } else if (method == 'indexed') {
29269
29334
  // data is pre-classified... just read the index from a field
29270
29335
  classifyByValue = getIndexedClassifier(values, nullValue, opts);
29271
29336
  } else if (method == 'categorical') {
@@ -34082,7 +34147,7 @@ ${svg}
34082
34147
  isCentered = ['tmerc', 'etmerc'].includes(str);
34083
34148
  proj4 = '+proj=' + str;
34084
34149
  if (isConic2SP || isCentered) {
34085
- bbox = getBBox(dataset);
34150
+ bbox = getBBox(dataset); // TODO: support projected datasets
34086
34151
  decimals = getBoundsPrecisionForDisplay(bbox);
34087
34152
  params = isCentered ? getCenterParams(bbox, decimals) : getConicParams(bbox, decimals);
34088
34153
  proj4 += ' ' + params;
@@ -34287,14 +34352,17 @@ ${svg}
34287
34352
  // old simplification data will not be optimal after reprojection;
34288
34353
  // re-using for now to avoid error in web ui
34289
34354
  zz = data.zz,
34355
+ z = arcs.getRetainedInterval(),
34290
34356
  p;
34291
34357
 
34292
34358
  for (var i=0, n=xx.length; i<n; i++) {
34293
34359
  p = proj(xx[i], yy[i]);
34360
+ if (!p) error('Unprojectable point:', xx[i], yy[i]);
34294
34361
  xx[i] = p[0];
34295
34362
  yy[i] = p[1];
34296
34363
  }
34297
34364
  arcs.updateVertexData(data.nn, xx, yy, zz);
34365
+ arcs.setRetainedInterval(z);
34298
34366
  }
34299
34367
 
34300
34368
  function projectArcs2(arcs, proj) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.97",
3
+ "version": "0.5.100",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -35,6 +35,7 @@
35
35
  "files": [
36
36
  "/bin/**",
37
37
  "/www/**",
38
+ "!/www/basemap.js",
38
39
  "!/www/nacis/",
39
40
  "/mapshaper.js",
40
41
  "!.DS_Store"
Binary file
Binary file
package/www/index.html CHANGED
@@ -73,7 +73,7 @@
73
73
  <input type="text" value="label" class="clicktext" />
74
74
  </div></div>
75
75
  <div id="mode-buttons" class="page-header-buttons">
76
- <span class="simplify-btn header-btn btn">Simplify</span><span class="separator"></span><span class="console-btn header-btn btn">Console</span><span class="separator"></span><span class="export-btn header-btn btn">Export</span>
76
+ <span class="basemap-btn header-btn btn">Basemap</span><span class="separator"></span><span class="simplify-btn header-btn btn">Simplify</span><span class="separator"></span><span class="console-btn header-btn btn">Console</span><span class="separator"></span><span class="export-btn header-btn btn">Export</span>
77
77
  </div>
78
78
  <div id="splash-buttons" class="page-header-buttons">
79
79
  <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>
@@ -148,6 +148,18 @@
148
148
  </div>
149
149
  </div>
150
150
 
151
+ <div class="basemap-options main-area popup-dialog">
152
+ <div class="info-box">
153
+ <h3>Basemap options</h3>
154
+ <p class="basemap-note">Your data will be displayed using the Mercator projection.</p>
155
+ <p class="basemap-warning"></p>
156
+ <div class="basemap-styles"></div>
157
+ <div>
158
+ <div class="close-btn btn dialog-btn">Close</div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
151
163
  <div class="simplify-options main-area popup-dialog">
152
164
  <div class="info-box">
153
165
  <h3>Simplification menu</h3>
@@ -280,6 +292,7 @@ interface. Examples: "no-topology"
280
292
  <div class="repair-btn text-btn colored-text">Repair</div>
281
293
  </div>
282
294
  <div class="map-layers"></div>
295
+ <div class="basemap-container"><div class="basemap"></div></div>
283
296
  </div>
284
297
  </div>
285
298
 
@@ -287,6 +300,7 @@ interface. Examples: "no-topology"
287
300
  <div class="drop-area"></div>
288
301
  </div>
289
302
 
303
+ <script src="basemap.js" type="text/javascript"></script>
290
304
  <script src="zip.js" type="text/javascript"></script>
291
305
  <script src="modules.js" type="text/javascript"></script>
292
306
  <script src="mapshaper.js" type="text/javascript"></script>