mapshaper 0.5.97 → 0.5.98

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,6 @@
1
+ v0.5.98
2
+ * Better handling of null data by the -classify command.
3
+
1
4
  v0.5.97
2
5
  * Better warnings and error messages.
3
6
 
package/mapshaper.js CHANGED
@@ -29059,6 +29059,31 @@ ${svg}
29059
29059
  return ramp;
29060
29060
  }
29061
29061
 
29062
+ function getNullValue(opts) {
29063
+ var nullValue;
29064
+ if ('null_value' in opts) {
29065
+ nullValue = parseNullValue(opts.null_value);
29066
+ } else if (opts.colors) {
29067
+ nullValue = '#eee';
29068
+ } else if (opts.values) {
29069
+ nullValue = null;
29070
+ } else {
29071
+ nullValue = -1; // kludge, to match behavior of getClassValues()
29072
+ }
29073
+ return nullValue;
29074
+ }
29075
+
29076
+ // Parse command line string arguments to the correct data type
29077
+ function parseNullValue(val) {
29078
+ if (utils.isString(val) && !isNaN(+val)) {
29079
+ val = +val;
29080
+ }
29081
+ if (val === 'null') {
29082
+ val = null;
29083
+ }
29084
+ return val;
29085
+ }
29086
+
29062
29087
  function getClassValues(method, n, opts) {
29063
29088
  var categorical = method == 'categorical' || method == 'non-adjacent';
29064
29089
  var colorArg = opts.colors && opts.colors.length == 1 ? opts.colors[0] : null;
@@ -29132,27 +29157,33 @@ ${svg}
29132
29157
  return values;
29133
29158
  }
29134
29159
 
29135
- var sequential = ['quantile', 'nice', 'equal-interval', 'hybrid'];
29160
+ var sequential = ['quantile', 'nice', 'equal-interval', 'hybrid', 'breaks'];
29136
29161
  var all = ['non-adjacent', 'indexed', 'categorical'].concat(sequential);
29137
29162
 
29138
- function getClassifyMethod(opts, dataFieldType) {
29163
+ function getClassifyMethod(opts, dataType) {
29139
29164
  var method;
29140
29165
  if (opts.method) {
29141
29166
  method = opts.method;
29167
+ } else if (opts.breaks) {
29168
+ method = 'breaks';
29142
29169
  } else if (opts.index_field) {
29143
29170
  method = 'indexed';
29144
- } else if (opts.categories || dataFieldType == 'string') {
29171
+ } else if (opts.categories || dataType == 'string') {
29145
29172
  method = 'categorical';
29146
- } else if (dataFieldType == 'number') {
29173
+ } else if (dataType == 'number') {
29147
29174
  method = 'quantile'; // TODO: validate data field
29175
+ } else if (dataType == 'date' || dataType == 'object') {
29176
+ stop('Data type does not support classification:', dataType);
29177
+ } else if (dataType === null) {
29178
+ // data field is empty
29179
+ return null; // kludge
29148
29180
  } else {
29149
- // stop('Unable to determine which classification method to use.');
29150
- stop('Missing a data field and/or classification method');
29181
+ stop('Unable to determine which classification method to use.');
29151
29182
  }
29152
29183
  if (!all.includes(method)) {
29153
29184
  stop('Not a recognized classification method:', method);
29154
29185
  }
29155
- if (sequential.includes(method) && dataFieldType != 'number') {
29186
+ if (sequential.includes(method) && dataType != 'number' && dataType !== null) {
29156
29187
  stop('The', method, 'method requires a numerical data field');
29157
29188
  }
29158
29189
  return method;
@@ -29165,7 +29196,7 @@ ${svg}
29165
29196
  var opts = optsArg || {};
29166
29197
  var records = lyr.data && lyr.data.getRecords();
29167
29198
  var valuesAreColors = !!opts.colors;
29168
- var dataField, dataFieldType, outputField;
29199
+ var dataField, fieldType, outputField;
29169
29200
  var values, nullValue;
29170
29201
  var classifyByValue, classifyByRecordId;
29171
29202
  var numClasses, numValues;
@@ -29179,9 +29210,10 @@ ${svg}
29179
29210
  //
29180
29211
  if (opts.index_field) {
29181
29212
  dataField = opts.index_field;
29182
- } else if (opts.field) {
29213
+ fieldType = getColumnType(opts.field, records);
29214
+ } else if (opts.field) {
29183
29215
  dataField = opts.field;
29184
- dataFieldType = getColumnType(opts.field, records);
29216
+ fieldType = getColumnType(opts.field, records);
29185
29217
  }
29186
29218
  if (dataField) {
29187
29219
  requireDataField(lyr.data, dataField);
@@ -29189,7 +29221,7 @@ ${svg}
29189
29221
 
29190
29222
  // get classification method
29191
29223
  //
29192
- method = getClassifyMethod(opts, dataFieldType);
29224
+ method = getClassifyMethod(opts, fieldType);
29193
29225
 
29194
29226
  // validate classification method
29195
29227
  if (method == 'non-adjacent') {
@@ -29207,6 +29239,7 @@ ${svg}
29207
29239
  // get the number of classes and the number of values
29208
29240
  //
29209
29241
  // expand categories if value is '*'
29242
+ // use all unique values if categories option is missing
29210
29243
  if (method == 'categorical') {
29211
29244
  if ((!opts.categories || opts.categories.includes('*')) && dataField) {
29212
29245
  opts.categories = getUniqFieldValues(records, dataField);
@@ -29248,24 +29281,16 @@ ${svg}
29248
29281
  message('Colors:', formatValuesForLogging(values));
29249
29282
  }
29250
29283
 
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
-
29284
+ nullValue = getNullValue(opts);
29263
29285
 
29264
29286
  // get a function to convert input data to class indexes
29265
29287
  //
29266
- if (method == 'non-adjacent') {
29288
+ if (fieldType === null) {
29289
+ // no valid data -- always return null value
29290
+ classifyByRecordId = function() {return nullValue;};
29291
+ } else if (method == 'non-adjacent') {
29267
29292
  classifyByRecordId = getNonAdjacentClassifier(lyr, dataset, values);
29268
- } else if (opts.index_field) {
29293
+ } else if (method == 'indexed') {
29269
29294
  // data is pre-classified... just read the index from a field
29270
29295
  classifyByValue = getIndexedClassifier(values, nullValue, opts);
29271
29296
  } else if (method == 'categorical') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.5.97",
3
+ "version": "0.5.98",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
package/www/mapshaper.js CHANGED
@@ -29059,6 +29059,31 @@ ${svg}
29059
29059
  return ramp;
29060
29060
  }
29061
29061
 
29062
+ function getNullValue(opts) {
29063
+ var nullValue;
29064
+ if ('null_value' in opts) {
29065
+ nullValue = parseNullValue(opts.null_value);
29066
+ } else if (opts.colors) {
29067
+ nullValue = '#eee';
29068
+ } else if (opts.values) {
29069
+ nullValue = null;
29070
+ } else {
29071
+ nullValue = -1; // kludge, to match behavior of getClassValues()
29072
+ }
29073
+ return nullValue;
29074
+ }
29075
+
29076
+ // Parse command line string arguments to the correct data type
29077
+ function parseNullValue(val) {
29078
+ if (utils.isString(val) && !isNaN(+val)) {
29079
+ val = +val;
29080
+ }
29081
+ if (val === 'null') {
29082
+ val = null;
29083
+ }
29084
+ return val;
29085
+ }
29086
+
29062
29087
  function getClassValues(method, n, opts) {
29063
29088
  var categorical = method == 'categorical' || method == 'non-adjacent';
29064
29089
  var colorArg = opts.colors && opts.colors.length == 1 ? opts.colors[0] : null;
@@ -29132,27 +29157,33 @@ ${svg}
29132
29157
  return values;
29133
29158
  }
29134
29159
 
29135
- var sequential = ['quantile', 'nice', 'equal-interval', 'hybrid'];
29160
+ var sequential = ['quantile', 'nice', 'equal-interval', 'hybrid', 'breaks'];
29136
29161
  var all = ['non-adjacent', 'indexed', 'categorical'].concat(sequential);
29137
29162
 
29138
- function getClassifyMethod(opts, dataFieldType) {
29163
+ function getClassifyMethod(opts, dataType) {
29139
29164
  var method;
29140
29165
  if (opts.method) {
29141
29166
  method = opts.method;
29167
+ } else if (opts.breaks) {
29168
+ method = 'breaks';
29142
29169
  } else if (opts.index_field) {
29143
29170
  method = 'indexed';
29144
- } else if (opts.categories || dataFieldType == 'string') {
29171
+ } else if (opts.categories || dataType == 'string') {
29145
29172
  method = 'categorical';
29146
- } else if (dataFieldType == 'number') {
29173
+ } else if (dataType == 'number') {
29147
29174
  method = 'quantile'; // TODO: validate data field
29175
+ } else if (dataType == 'date' || dataType == 'object') {
29176
+ stop('Data type does not support classification:', dataType);
29177
+ } else if (dataType === null) {
29178
+ // data field is empty
29179
+ return null; // kludge
29148
29180
  } else {
29149
- // stop('Unable to determine which classification method to use.');
29150
- stop('Missing a data field and/or classification method');
29181
+ stop('Unable to determine which classification method to use.');
29151
29182
  }
29152
29183
  if (!all.includes(method)) {
29153
29184
  stop('Not a recognized classification method:', method);
29154
29185
  }
29155
- if (sequential.includes(method) && dataFieldType != 'number') {
29186
+ if (sequential.includes(method) && dataType != 'number' && dataType !== null) {
29156
29187
  stop('The', method, 'method requires a numerical data field');
29157
29188
  }
29158
29189
  return method;
@@ -29165,7 +29196,7 @@ ${svg}
29165
29196
  var opts = optsArg || {};
29166
29197
  var records = lyr.data && lyr.data.getRecords();
29167
29198
  var valuesAreColors = !!opts.colors;
29168
- var dataField, dataFieldType, outputField;
29199
+ var dataField, fieldType, outputField;
29169
29200
  var values, nullValue;
29170
29201
  var classifyByValue, classifyByRecordId;
29171
29202
  var numClasses, numValues;
@@ -29179,9 +29210,10 @@ ${svg}
29179
29210
  //
29180
29211
  if (opts.index_field) {
29181
29212
  dataField = opts.index_field;
29182
- } else if (opts.field) {
29213
+ fieldType = getColumnType(opts.field, records);
29214
+ } else if (opts.field) {
29183
29215
  dataField = opts.field;
29184
- dataFieldType = getColumnType(opts.field, records);
29216
+ fieldType = getColumnType(opts.field, records);
29185
29217
  }
29186
29218
  if (dataField) {
29187
29219
  requireDataField(lyr.data, dataField);
@@ -29189,7 +29221,7 @@ ${svg}
29189
29221
 
29190
29222
  // get classification method
29191
29223
  //
29192
- method = getClassifyMethod(opts, dataFieldType);
29224
+ method = getClassifyMethod(opts, fieldType);
29193
29225
 
29194
29226
  // validate classification method
29195
29227
  if (method == 'non-adjacent') {
@@ -29207,6 +29239,7 @@ ${svg}
29207
29239
  // get the number of classes and the number of values
29208
29240
  //
29209
29241
  // expand categories if value is '*'
29242
+ // use all unique values if categories option is missing
29210
29243
  if (method == 'categorical') {
29211
29244
  if ((!opts.categories || opts.categories.includes('*')) && dataField) {
29212
29245
  opts.categories = getUniqFieldValues(records, dataField);
@@ -29248,24 +29281,16 @@ ${svg}
29248
29281
  message('Colors:', formatValuesForLogging(values));
29249
29282
  }
29250
29283
 
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
-
29284
+ nullValue = getNullValue(opts);
29263
29285
 
29264
29286
  // get a function to convert input data to class indexes
29265
29287
  //
29266
- if (method == 'non-adjacent') {
29288
+ if (fieldType === null) {
29289
+ // no valid data -- always return null value
29290
+ classifyByRecordId = function() {return nullValue;};
29291
+ } else if (method == 'non-adjacent') {
29267
29292
  classifyByRecordId = getNonAdjacentClassifier(lyr, dataset, values);
29268
- } else if (opts.index_field) {
29293
+ } else if (method == 'indexed') {
29269
29294
  // data is pre-classified... just read the index from a field
29270
29295
  classifyByValue = getIndexedClassifier(values, nullValue, opts);
29271
29296
  } else if (method == 'categorical') {