mapshaper 0.5.70 → 0.5.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +258 -160
- package/package.json +1 -1
- package/www/mapshaper.js +258 -160
package/mapshaper.js
CHANGED
|
@@ -18678,7 +18678,8 @@ ${svg}
|
|
|
18678
18678
|
.option('no-replace', noReplaceOpt);
|
|
18679
18679
|
|
|
18680
18680
|
parser.command('classify')
|
|
18681
|
-
.describe('apply sequential or categorical classification')
|
|
18681
|
+
// .describe('apply sequential or categorical classification')
|
|
18682
|
+
.describe('assign colors or values using one of several methods')
|
|
18682
18683
|
.option('field', {
|
|
18683
18684
|
describe: 'name of field to classify',
|
|
18684
18685
|
DEFAULT: true
|
|
@@ -18698,6 +18699,10 @@ ${svg}
|
|
|
18698
18699
|
// deprecated in favor of colors=
|
|
18699
18700
|
// describe: 'name of a predefined color scheme (see -colors command)'
|
|
18700
18701
|
})
|
|
18702
|
+
.option('non-adjacent', {
|
|
18703
|
+
describe: 'assign non-adjacent colors to a polygon layer',
|
|
18704
|
+
assign_to: 'method'
|
|
18705
|
+
})
|
|
18701
18706
|
.option('stops', {
|
|
18702
18707
|
describe: 'a pair of values (0-100) for limiting a color ramp',
|
|
18703
18708
|
type: 'numbers'
|
|
@@ -18782,7 +18787,6 @@ ${svg}
|
|
|
18782
18787
|
.option('key-last-suffix', {
|
|
18783
18788
|
describe: 'string to append to last label'
|
|
18784
18789
|
})
|
|
18785
|
-
|
|
18786
18790
|
.option('target', targetOpt);
|
|
18787
18791
|
|
|
18788
18792
|
parser.command('clean')
|
|
@@ -28063,25 +28067,241 @@ ${svg}
|
|
|
28063
28067
|
return maxId + 1;
|
|
28064
28068
|
}
|
|
28065
28069
|
|
|
28066
|
-
|
|
28070
|
+
// Returns a function for constructing a query function that accepts an arc id and
|
|
28071
|
+
// returns information about the polygon or polygons that use the given arc.
|
|
28072
|
+
// TODO: explain this better.
|
|
28073
|
+
//
|
|
28074
|
+
// options:
|
|
28075
|
+
// filter: optional filter function; signature: function(idA, idB or -1) : boolean
|
|
28076
|
+
// reusable: flag that lets an arc be queried multiple times.
|
|
28077
|
+
function getArcClassifier(shapes, arcs) {
|
|
28078
|
+
var opts = arguments[2] || {},
|
|
28079
|
+
useOnce = !opts.reusable,
|
|
28080
|
+
n = arcs.size(),
|
|
28081
|
+
a = new Int32Array(n),
|
|
28082
|
+
b = new Int32Array(n);
|
|
28083
|
+
|
|
28084
|
+
utils.initializeArray(a, -1);
|
|
28085
|
+
utils.initializeArray(b, -1);
|
|
28086
|
+
|
|
28087
|
+
traversePaths(shapes, function(o) {
|
|
28088
|
+
var i = absArcId(o.arcId);
|
|
28089
|
+
var shpId = o.shapeId;
|
|
28090
|
+
var aval = a[i];
|
|
28091
|
+
if (aval == -1) {
|
|
28092
|
+
a[i] = shpId;
|
|
28093
|
+
} else if (shpId < aval) {
|
|
28094
|
+
b[i] = aval;
|
|
28095
|
+
a[i] = shpId;
|
|
28096
|
+
} else {
|
|
28097
|
+
b[i] = shpId;
|
|
28098
|
+
}
|
|
28099
|
+
});
|
|
28100
|
+
|
|
28101
|
+
function classify(arcId, getKey) {
|
|
28102
|
+
var i = absArcId(arcId);
|
|
28103
|
+
var shpA = a[i];
|
|
28104
|
+
var shpB = b[i];
|
|
28105
|
+
var key;
|
|
28106
|
+
if (shpA == -1) return null;
|
|
28107
|
+
key = getKey(shpA, shpB);
|
|
28108
|
+
if (key === null || key === false) return null;
|
|
28109
|
+
if (useOnce) {
|
|
28110
|
+
// arc can only be queried once
|
|
28111
|
+
a[i] = -1;
|
|
28112
|
+
b[i] = -1;
|
|
28113
|
+
}
|
|
28114
|
+
// use optional filter to exclude some arcs
|
|
28115
|
+
if (opts.filter && !opts.filter(shpA, shpB)) return null;
|
|
28116
|
+
return key;
|
|
28117
|
+
}
|
|
28118
|
+
|
|
28119
|
+
return function(getKey) {
|
|
28120
|
+
return function(arcId) {
|
|
28121
|
+
return classify(arcId, getKey);
|
|
28122
|
+
};
|
|
28123
|
+
};
|
|
28124
|
+
}
|
|
28125
|
+
|
|
28126
|
+
var ArcClassifier = /*#__PURE__*/Object.freeze({
|
|
28127
|
+
__proto__: null,
|
|
28128
|
+
getArcClassifier: getArcClassifier
|
|
28129
|
+
});
|
|
28130
|
+
|
|
28131
|
+
// Returns a function for querying the neighbors of a given shape. The function
|
|
28132
|
+
// can be called in either of two ways:
|
|
28133
|
+
//
|
|
28134
|
+
// 1. function(shapeId, callback)
|
|
28135
|
+
// Callback signature: function(adjacentShapeId, arcId)
|
|
28136
|
+
// The callback function is called once for each arc that the given feature
|
|
28137
|
+
// shares with another feature.
|
|
28138
|
+
//
|
|
28139
|
+
// 2. function(shapeId)
|
|
28140
|
+
// The function returns an array of unique ids of neighboring shapes, or
|
|
28141
|
+
// an empty array if a shape has no neighbors.
|
|
28142
|
+
//
|
|
28143
|
+
function getNeighborLookupFunction(lyr, arcs) {
|
|
28144
|
+
var classifier = getArcClassifier(lyr.shapes, arcs, {reusable: true});
|
|
28145
|
+
var classify = classifier(onShapes);
|
|
28146
|
+
var currShapeId;
|
|
28147
|
+
var neighbors;
|
|
28148
|
+
var callback;
|
|
28149
|
+
|
|
28150
|
+
function onShapes(a, b) {
|
|
28151
|
+
if (b == -1) return -1; // outer edges are b == -1
|
|
28152
|
+
return a == currShapeId ? b : a;
|
|
28153
|
+
}
|
|
28154
|
+
|
|
28155
|
+
function onArc(arcId) {
|
|
28156
|
+
var nabeId = classify(arcId);
|
|
28157
|
+
if (nabeId == -1) return;
|
|
28158
|
+
if (callback) {
|
|
28159
|
+
callback(nabeId, arcId);
|
|
28160
|
+
} else if (neighbors.indexOf(nabeId) == -1) {
|
|
28161
|
+
neighbors.push(nabeId);
|
|
28162
|
+
}
|
|
28163
|
+
}
|
|
28164
|
+
|
|
28165
|
+
return function(shpId, cb) {
|
|
28166
|
+
currShapeId = shpId;
|
|
28167
|
+
if (cb) {
|
|
28168
|
+
callback = cb;
|
|
28169
|
+
forEachArcId(lyr.shapes[shpId], onArc);
|
|
28170
|
+
callback = null;
|
|
28171
|
+
} else {
|
|
28172
|
+
neighbors = [];
|
|
28173
|
+
forEachArcId(lyr.shapes[shpId], onArc);
|
|
28174
|
+
return neighbors;
|
|
28175
|
+
}
|
|
28176
|
+
};
|
|
28177
|
+
}
|
|
28178
|
+
|
|
28179
|
+
|
|
28180
|
+
// Returns an array containing all pairs of adjacent shapes
|
|
28181
|
+
// in a collection of polygon shapes. A pair of shapes is represented as
|
|
28182
|
+
// an array of two shape indexes [a, b].
|
|
28183
|
+
function findPairsOfNeighbors(shapes, arcs) {
|
|
28184
|
+
var getKey = function(a, b) {
|
|
28185
|
+
return b > -1 && a > -1 ? [a, b] : null;
|
|
28186
|
+
};
|
|
28187
|
+
var classify = getArcClassifier(shapes, arcs)(getKey);
|
|
28188
|
+
var arr = [];
|
|
28189
|
+
var index = {};
|
|
28190
|
+
var onArc = function(arcId) {
|
|
28191
|
+
var obj = classify(arcId);
|
|
28192
|
+
var key;
|
|
28193
|
+
if (obj) {
|
|
28194
|
+
key = obj.join('~');
|
|
28195
|
+
if (key in index === false) {
|
|
28196
|
+
arr.push(obj);
|
|
28197
|
+
index[key] = true;
|
|
28198
|
+
}
|
|
28199
|
+
}
|
|
28200
|
+
};
|
|
28201
|
+
forEachArcId(shapes, onArc);
|
|
28202
|
+
return arr;
|
|
28203
|
+
}
|
|
28204
|
+
|
|
28205
|
+
var PolygonNeighbors = /*#__PURE__*/Object.freeze({
|
|
28206
|
+
__proto__: null,
|
|
28207
|
+
getNeighborLookupFunction: getNeighborLookupFunction,
|
|
28208
|
+
findPairsOfNeighbors: findPairsOfNeighbors
|
|
28209
|
+
});
|
|
28210
|
+
|
|
28211
|
+
function getNonAdjacentClassifier(lyr, dataset, colors) {
|
|
28212
|
+
requirePolygonLayer(lyr);
|
|
28213
|
+
var getNeighbors = getNeighborLookupFunction(lyr, dataset.arcs);
|
|
28214
|
+
var errorCount = 0;
|
|
28215
|
+
var data = utils.range(getFeatureCount(lyr)).map(function(shpId) {
|
|
28216
|
+
var nabes = getNeighbors(shpId) || [];
|
|
28217
|
+
return {
|
|
28218
|
+
nabes: nabes,
|
|
28219
|
+
n: nabes.length,
|
|
28220
|
+
colorId: -1
|
|
28221
|
+
};
|
|
28222
|
+
});
|
|
28223
|
+
var getSortedColorIds = getUpdateFunction(colors.length);
|
|
28224
|
+
var colorIds = getSortedColorIds();
|
|
28225
|
+
// Sort adjacency data by number of neighbors in descending order
|
|
28226
|
+
var sorted = data.concat();
|
|
28227
|
+
utils.sortOn(sorted, 'n', false);
|
|
28228
|
+
// Assign colors, starting with polygons with the largest number of neighbors
|
|
28229
|
+
sorted.forEach(function(d) {
|
|
28230
|
+
var colorId = pickColor(d, data, colorIds);
|
|
28231
|
+
if (colorId == -1) {
|
|
28232
|
+
errorCount++;
|
|
28233
|
+
colorId = colorIds[0];
|
|
28234
|
+
}
|
|
28235
|
+
d.colorId = colorId;
|
|
28236
|
+
colorIds = getSortedColorIds(colorId);
|
|
28237
|
+
});
|
|
28238
|
+
|
|
28239
|
+
if (errorCount > 0) {
|
|
28240
|
+
message(`Unable to find non-adjacent colors for ${errorCount} ${errorCount == 1 ? 'polygon' : 'polygons'}`);
|
|
28241
|
+
}
|
|
28242
|
+
return function(shpId) {
|
|
28243
|
+
return colors[data[shpId].colorId];
|
|
28244
|
+
};
|
|
28245
|
+
}
|
|
28246
|
+
|
|
28247
|
+
// Pick the id of a color that is not shared with a neighboring polygon
|
|
28248
|
+
function pickColor(d, data, colorIds) {
|
|
28249
|
+
var candidateId;
|
|
28250
|
+
for (var i=0; i<colorIds.length; i++) {
|
|
28251
|
+
candidateId = colorIds[i];
|
|
28252
|
+
if (isAvailableColor(d, data, candidateId)) {
|
|
28253
|
+
return candidateId;
|
|
28254
|
+
}
|
|
28255
|
+
}
|
|
28256
|
+
return -1; // no colors are available
|
|
28257
|
+
}
|
|
28258
|
+
|
|
28259
|
+
function isAvailableColor(d, data, colorId) {
|
|
28260
|
+
var nabes = d.nabes;
|
|
28261
|
+
for (var i=0; i<nabes.length; i++) {
|
|
28262
|
+
if (data[nabes[i]].colorId === colorId) return false;
|
|
28263
|
+
}
|
|
28264
|
+
return true;
|
|
28265
|
+
}
|
|
28266
|
+
|
|
28267
|
+
// Update function returns an array of ids, sorted in descending order of preference
|
|
28268
|
+
// (less-used ids are preferred).
|
|
28269
|
+
// Function recieves an (optional) id that was just used.
|
|
28270
|
+
function getUpdateFunction(n) {
|
|
28271
|
+
var ids = utils.range(n);
|
|
28272
|
+
var counts = new Uint32Array(n);
|
|
28273
|
+
return function(i) {
|
|
28274
|
+
if (i >= 0 && i < n) {
|
|
28275
|
+
counts[i]++;
|
|
28276
|
+
utils.sortArrayIndex(ids, counts, true);
|
|
28277
|
+
} else if (i !== undefined) {
|
|
28278
|
+
error('Unexpected color index:', i);
|
|
28279
|
+
}
|
|
28280
|
+
return ids;
|
|
28281
|
+
};
|
|
28282
|
+
}
|
|
28283
|
+
|
|
28284
|
+
cmd.classify = function(lyr, dataset, optsArg) {
|
|
28285
|
+
if (!lyr.data) {
|
|
28286
|
+
initDataTable(lyr);
|
|
28287
|
+
}
|
|
28067
28288
|
var opts = optsArg || {};
|
|
28068
28289
|
var records = lyr.data && lyr.data.getRecords();
|
|
28069
28290
|
var nullValue = opts.null_value || null;
|
|
28070
28291
|
var looksLikeColors = !!opts.colors || !!opts.color_scheme;
|
|
28071
28292
|
var colorScheme;
|
|
28072
|
-
var classValues,
|
|
28293
|
+
var classValues, classifyByValue, classifyById;
|
|
28073
28294
|
var numBuckets, numValues;
|
|
28074
28295
|
var dataField, outputField;
|
|
28075
28296
|
|
|
28076
28297
|
// validate explicitly set classes
|
|
28077
28298
|
if (opts.classes) {
|
|
28078
28299
|
if (!utils.isInteger(opts.classes) || opts.classes > 1 === false) {
|
|
28079
|
-
stop('Invalid classes
|
|
28300
|
+
stop('Invalid number of classes:', opts.classes, '(expected a value greater than 1)');
|
|
28080
28301
|
}
|
|
28081
28302
|
numBuckets = opts.classes;
|
|
28082
28303
|
}
|
|
28083
28304
|
|
|
28084
|
-
|
|
28085
28305
|
// TODO: better validation of breaks values
|
|
28086
28306
|
if (opts.breaks) {
|
|
28087
28307
|
numBuckets = opts.breaks.length + 1;
|
|
@@ -28099,9 +28319,6 @@ ${svg}
|
|
|
28099
28319
|
|
|
28100
28320
|
} else if (opts.field) {
|
|
28101
28321
|
dataField = opts.field;
|
|
28102
|
-
|
|
28103
|
-
} else {
|
|
28104
|
-
stop('Missing a data field to classify');
|
|
28105
28322
|
}
|
|
28106
28323
|
|
|
28107
28324
|
// expand categories if value is '*'
|
|
@@ -28109,7 +28326,18 @@ ${svg}
|
|
|
28109
28326
|
opts.categories = getUniqFieldValues(records, dataField);
|
|
28110
28327
|
}
|
|
28111
28328
|
|
|
28112
|
-
|
|
28329
|
+
if (opts.method == 'non-adjacent') {
|
|
28330
|
+
if (lyr.geometry_type != 'polygon') {
|
|
28331
|
+
stop('The non-adjacent option requires a polygon layer');
|
|
28332
|
+
}
|
|
28333
|
+
if (dataField) {
|
|
28334
|
+
stop('The non-adjacent option does not accept a data field argument');
|
|
28335
|
+
}
|
|
28336
|
+
} else if (!dataField) {
|
|
28337
|
+
stop('Missing a data field to classify');
|
|
28338
|
+
} else {
|
|
28339
|
+
requireDataField(lyr.data, dataField);
|
|
28340
|
+
}
|
|
28113
28341
|
|
|
28114
28342
|
if (numBuckets) {
|
|
28115
28343
|
numValues = opts.continuous ? numBuckets + 1 : numBuckets;
|
|
@@ -28177,15 +28405,25 @@ ${svg}
|
|
|
28177
28405
|
|
|
28178
28406
|
// get a function to convert input data to class indexes
|
|
28179
28407
|
//
|
|
28180
|
-
if (opts.
|
|
28408
|
+
if (opts.method == 'non-adjacent') {
|
|
28409
|
+
classifyById = getNonAdjacentClassifier(lyr, dataset, classValues);
|
|
28410
|
+
} else if (opts.index_field) {
|
|
28181
28411
|
// data is pre-classified... just read the index from a field
|
|
28182
|
-
|
|
28412
|
+
classifyByValue = getIndexedClassifier(classValues, nullValue, opts);
|
|
28183
28413
|
} else if (opts.categories) {
|
|
28184
|
-
|
|
28414
|
+
classifyByValue = getCategoricalClassifier(classValues, nullValue, opts);
|
|
28185
28415
|
} else {
|
|
28186
|
-
|
|
28416
|
+
classifyByValue = getSequentialClassifier(classValues, nullValue, getFieldValues(records, dataField), opts);
|
|
28417
|
+
}
|
|
28418
|
+
|
|
28419
|
+
if (classifyByValue) {
|
|
28420
|
+
classifyById = function(id) {
|
|
28421
|
+
var d = records[id] || {};
|
|
28422
|
+
return classifyByValue(d[dataField]);
|
|
28423
|
+
};
|
|
28187
28424
|
}
|
|
28188
28425
|
|
|
28426
|
+
|
|
28189
28427
|
// get the name of the output field
|
|
28190
28428
|
//
|
|
28191
28429
|
if (looksLikeColors) {
|
|
@@ -28201,8 +28439,7 @@ ${svg}
|
|
|
28201
28439
|
}
|
|
28202
28440
|
|
|
28203
28441
|
records.forEach(function(d, i) {
|
|
28204
|
-
d =
|
|
28205
|
-
d[outputField] = classify(d[dataField]);
|
|
28442
|
+
d[outputField] = classifyById(i);
|
|
28206
28443
|
});
|
|
28207
28444
|
};
|
|
28208
28445
|
|
|
@@ -29014,147 +29251,6 @@ ${svg}
|
|
|
29014
29251
|
getClipMessage: getClipMessage
|
|
29015
29252
|
});
|
|
29016
29253
|
|
|
29017
|
-
// Returns a function for constructing a query function that accepts an arc id and
|
|
29018
|
-
// returns information about the polygon or polygons that use the given arc.
|
|
29019
|
-
// TODO: explain this better.
|
|
29020
|
-
//
|
|
29021
|
-
// options:
|
|
29022
|
-
// filter: optional filter function; signature: function(idA, idB or -1) : boolean
|
|
29023
|
-
// reusable: flag that lets an arc be queried multiple times.
|
|
29024
|
-
function getArcClassifier(shapes, arcs) {
|
|
29025
|
-
var opts = arguments[2] || {},
|
|
29026
|
-
useOnce = !opts.reusable,
|
|
29027
|
-
n = arcs.size(),
|
|
29028
|
-
a = new Int32Array(n),
|
|
29029
|
-
b = new Int32Array(n);
|
|
29030
|
-
|
|
29031
|
-
utils.initializeArray(a, -1);
|
|
29032
|
-
utils.initializeArray(b, -1);
|
|
29033
|
-
|
|
29034
|
-
traversePaths(shapes, function(o) {
|
|
29035
|
-
var i = absArcId(o.arcId);
|
|
29036
|
-
var shpId = o.shapeId;
|
|
29037
|
-
var aval = a[i];
|
|
29038
|
-
if (aval == -1) {
|
|
29039
|
-
a[i] = shpId;
|
|
29040
|
-
} else if (shpId < aval) {
|
|
29041
|
-
b[i] = aval;
|
|
29042
|
-
a[i] = shpId;
|
|
29043
|
-
} else {
|
|
29044
|
-
b[i] = shpId;
|
|
29045
|
-
}
|
|
29046
|
-
});
|
|
29047
|
-
|
|
29048
|
-
function classify(arcId, getKey) {
|
|
29049
|
-
var i = absArcId(arcId);
|
|
29050
|
-
var shpA = a[i];
|
|
29051
|
-
var shpB = b[i];
|
|
29052
|
-
var key;
|
|
29053
|
-
if (shpA == -1) return null;
|
|
29054
|
-
key = getKey(shpA, shpB);
|
|
29055
|
-
if (key === null || key === false) return null;
|
|
29056
|
-
if (useOnce) {
|
|
29057
|
-
// arc can only be queried once
|
|
29058
|
-
a[i] = -1;
|
|
29059
|
-
b[i] = -1;
|
|
29060
|
-
}
|
|
29061
|
-
// use optional filter to exclude some arcs
|
|
29062
|
-
if (opts.filter && !opts.filter(shpA, shpB)) return null;
|
|
29063
|
-
return key;
|
|
29064
|
-
}
|
|
29065
|
-
|
|
29066
|
-
return function(getKey) {
|
|
29067
|
-
return function(arcId) {
|
|
29068
|
-
return classify(arcId, getKey);
|
|
29069
|
-
};
|
|
29070
|
-
};
|
|
29071
|
-
}
|
|
29072
|
-
|
|
29073
|
-
var ArcClassifier = /*#__PURE__*/Object.freeze({
|
|
29074
|
-
__proto__: null,
|
|
29075
|
-
getArcClassifier: getArcClassifier
|
|
29076
|
-
});
|
|
29077
|
-
|
|
29078
|
-
// Returns a function for querying the neighbors of a given shape. The function
|
|
29079
|
-
// can be called in either of two ways:
|
|
29080
|
-
//
|
|
29081
|
-
// 1. function(shapeId, callback)
|
|
29082
|
-
// Callback signature: function(adjacentShapeId, arcId)
|
|
29083
|
-
// The callback function is called once for each arc that the given feature
|
|
29084
|
-
// shares with another feature.
|
|
29085
|
-
//
|
|
29086
|
-
// 2. function(shapeId)
|
|
29087
|
-
// The function returns an array of unique ids of neighboring shapes, or
|
|
29088
|
-
// an empty array if a shape has no neighbors.
|
|
29089
|
-
//
|
|
29090
|
-
function getNeighborLookupFunction(lyr, arcs) {
|
|
29091
|
-
var classifier = getArcClassifier(lyr.shapes, arcs, {reusable: true});
|
|
29092
|
-
var classify = classifier(onShapes);
|
|
29093
|
-
var currShapeId;
|
|
29094
|
-
var neighbors;
|
|
29095
|
-
var callback;
|
|
29096
|
-
|
|
29097
|
-
function onShapes(a, b) {
|
|
29098
|
-
if (b == -1) return -1; // outer edges are b == -1
|
|
29099
|
-
return a == currShapeId ? b : a;
|
|
29100
|
-
}
|
|
29101
|
-
|
|
29102
|
-
function onArc(arcId) {
|
|
29103
|
-
var nabeId = classify(arcId);
|
|
29104
|
-
if (nabeId == -1) return;
|
|
29105
|
-
if (callback) {
|
|
29106
|
-
callback(nabeId, arcId);
|
|
29107
|
-
} else if (neighbors.indexOf(nabeId) == -1) {
|
|
29108
|
-
neighbors.push(nabeId);
|
|
29109
|
-
}
|
|
29110
|
-
}
|
|
29111
|
-
|
|
29112
|
-
return function(shpId, cb) {
|
|
29113
|
-
currShapeId = shpId;
|
|
29114
|
-
if (cb) {
|
|
29115
|
-
callback = cb;
|
|
29116
|
-
forEachArcId(lyr.shapes[shpId], onArc);
|
|
29117
|
-
callback = null;
|
|
29118
|
-
} else {
|
|
29119
|
-
neighbors = [];
|
|
29120
|
-
forEachArcId(lyr.shapes[shpId], onArc);
|
|
29121
|
-
return neighbors;
|
|
29122
|
-
}
|
|
29123
|
-
};
|
|
29124
|
-
}
|
|
29125
|
-
|
|
29126
|
-
|
|
29127
|
-
// Returns an array containing all pairs of adjacent shapes
|
|
29128
|
-
// in a collection of polygon shapes. A pair of shapes is represented as
|
|
29129
|
-
// an array of two shape indexes [a, b].
|
|
29130
|
-
function findPairsOfNeighbors(shapes, arcs) {
|
|
29131
|
-
var getKey = function(a, b) {
|
|
29132
|
-
return b > -1 && a > -1 ? [a, b] : null;
|
|
29133
|
-
};
|
|
29134
|
-
var classify = getArcClassifier(shapes, arcs)(getKey);
|
|
29135
|
-
var arr = [];
|
|
29136
|
-
var index = {};
|
|
29137
|
-
var onArc = function(arcId) {
|
|
29138
|
-
var obj = classify(arcId);
|
|
29139
|
-
var key;
|
|
29140
|
-
if (obj) {
|
|
29141
|
-
key = obj.join('~');
|
|
29142
|
-
if (key in index === false) {
|
|
29143
|
-
arr.push(obj);
|
|
29144
|
-
index[key] = true;
|
|
29145
|
-
}
|
|
29146
|
-
}
|
|
29147
|
-
};
|
|
29148
|
-
forEachArcId(shapes, onArc);
|
|
29149
|
-
return arr;
|
|
29150
|
-
}
|
|
29151
|
-
|
|
29152
|
-
var PolygonNeighbors = /*#__PURE__*/Object.freeze({
|
|
29153
|
-
__proto__: null,
|
|
29154
|
-
getNeighborLookupFunction: getNeighborLookupFunction,
|
|
29155
|
-
findPairsOfNeighbors: findPairsOfNeighbors
|
|
29156
|
-
});
|
|
29157
|
-
|
|
29158
29254
|
// Assign a cluster id to each polygon in a dataset, which can be used with
|
|
29159
29255
|
// one of the dissolve commands to dissolve the clusters
|
|
29160
29256
|
// Works by iteratively grouping pairs of polygons with the smallest distance
|
|
@@ -29840,11 +29936,12 @@ ${svg}
|
|
|
29840
29936
|
getJoinFilter: getJoinFilter
|
|
29841
29937
|
});
|
|
29842
29938
|
|
|
29939
|
+
// Join data from @src table to records in @dest table
|
|
29843
29940
|
function joinTables(dest, src, join, opts) {
|
|
29844
29941
|
return joinTableToLayer({data: dest}, src, join, opts);
|
|
29845
29942
|
}
|
|
29846
29943
|
|
|
29847
|
-
// Join data from @src table to records in @
|
|
29944
|
+
// Join data from @src table to records in @destLyr layer.
|
|
29848
29945
|
// @join function
|
|
29849
29946
|
// Receives index of record in the dest table
|
|
29850
29947
|
// Returns array of matching records in src table, or null if no matches
|
|
@@ -29867,7 +29964,7 @@ ${svg}
|
|
|
29867
29964
|
retn = {},
|
|
29868
29965
|
srcRec, srcId, destRec, joins, count, filter, calc, i, j, n, m;
|
|
29869
29966
|
|
|
29870
|
-
// support for duplication
|
|
29967
|
+
// support for duplication of destination records
|
|
29871
29968
|
var duplicateRecords, destShapes;
|
|
29872
29969
|
if (useDuplication) {
|
|
29873
29970
|
if (opts.calc) stop('duplication and calc options cannot be used together');
|
|
@@ -29896,6 +29993,7 @@ ${svg}
|
|
|
29896
29993
|
for (j=0, count=0, m=joins ? joins.length : 0; j<m; j++) {
|
|
29897
29994
|
srcId = joins[j];
|
|
29898
29995
|
srcRec = srcRecords[srcId];
|
|
29996
|
+
// duplication mode: many-to-one joins add new features to the target layer.
|
|
29899
29997
|
if (count > 0 && useDuplication) {
|
|
29900
29998
|
destRec = copyRecord(duplicateRecords[i]);
|
|
29901
29999
|
destRecords.push(destRec);
|
|
@@ -37689,7 +37787,7 @@ ${svg}
|
|
|
37689
37787
|
applyCommandToEachLayer(cmd.calc, targetLayers, arcs, opts);
|
|
37690
37788
|
|
|
37691
37789
|
} else if (name == 'classify') {
|
|
37692
|
-
applyCommandToEachLayer(cmd.classify, targetLayers, opts);
|
|
37790
|
+
applyCommandToEachLayer(cmd.classify, targetLayers, targetDataset, opts);
|
|
37693
37791
|
|
|
37694
37792
|
} else if (name == 'clean') {
|
|
37695
37793
|
cmd.cleanLayers(targetLayers, targetDataset, opts);
|
package/package.json
CHANGED
package/www/mapshaper.js
CHANGED
|
@@ -18678,7 +18678,8 @@ ${svg}
|
|
|
18678
18678
|
.option('no-replace', noReplaceOpt);
|
|
18679
18679
|
|
|
18680
18680
|
parser.command('classify')
|
|
18681
|
-
.describe('apply sequential or categorical classification')
|
|
18681
|
+
// .describe('apply sequential or categorical classification')
|
|
18682
|
+
.describe('assign colors or values using one of several methods')
|
|
18682
18683
|
.option('field', {
|
|
18683
18684
|
describe: 'name of field to classify',
|
|
18684
18685
|
DEFAULT: true
|
|
@@ -18698,6 +18699,10 @@ ${svg}
|
|
|
18698
18699
|
// deprecated in favor of colors=
|
|
18699
18700
|
// describe: 'name of a predefined color scheme (see -colors command)'
|
|
18700
18701
|
})
|
|
18702
|
+
.option('non-adjacent', {
|
|
18703
|
+
describe: 'assign non-adjacent colors to a polygon layer',
|
|
18704
|
+
assign_to: 'method'
|
|
18705
|
+
})
|
|
18701
18706
|
.option('stops', {
|
|
18702
18707
|
describe: 'a pair of values (0-100) for limiting a color ramp',
|
|
18703
18708
|
type: 'numbers'
|
|
@@ -18782,7 +18787,6 @@ ${svg}
|
|
|
18782
18787
|
.option('key-last-suffix', {
|
|
18783
18788
|
describe: 'string to append to last label'
|
|
18784
18789
|
})
|
|
18785
|
-
|
|
18786
18790
|
.option('target', targetOpt);
|
|
18787
18791
|
|
|
18788
18792
|
parser.command('clean')
|
|
@@ -28063,25 +28067,241 @@ ${svg}
|
|
|
28063
28067
|
return maxId + 1;
|
|
28064
28068
|
}
|
|
28065
28069
|
|
|
28066
|
-
|
|
28070
|
+
// Returns a function for constructing a query function that accepts an arc id and
|
|
28071
|
+
// returns information about the polygon or polygons that use the given arc.
|
|
28072
|
+
// TODO: explain this better.
|
|
28073
|
+
//
|
|
28074
|
+
// options:
|
|
28075
|
+
// filter: optional filter function; signature: function(idA, idB or -1) : boolean
|
|
28076
|
+
// reusable: flag that lets an arc be queried multiple times.
|
|
28077
|
+
function getArcClassifier(shapes, arcs) {
|
|
28078
|
+
var opts = arguments[2] || {},
|
|
28079
|
+
useOnce = !opts.reusable,
|
|
28080
|
+
n = arcs.size(),
|
|
28081
|
+
a = new Int32Array(n),
|
|
28082
|
+
b = new Int32Array(n);
|
|
28083
|
+
|
|
28084
|
+
utils.initializeArray(a, -1);
|
|
28085
|
+
utils.initializeArray(b, -1);
|
|
28086
|
+
|
|
28087
|
+
traversePaths(shapes, function(o) {
|
|
28088
|
+
var i = absArcId(o.arcId);
|
|
28089
|
+
var shpId = o.shapeId;
|
|
28090
|
+
var aval = a[i];
|
|
28091
|
+
if (aval == -1) {
|
|
28092
|
+
a[i] = shpId;
|
|
28093
|
+
} else if (shpId < aval) {
|
|
28094
|
+
b[i] = aval;
|
|
28095
|
+
a[i] = shpId;
|
|
28096
|
+
} else {
|
|
28097
|
+
b[i] = shpId;
|
|
28098
|
+
}
|
|
28099
|
+
});
|
|
28100
|
+
|
|
28101
|
+
function classify(arcId, getKey) {
|
|
28102
|
+
var i = absArcId(arcId);
|
|
28103
|
+
var shpA = a[i];
|
|
28104
|
+
var shpB = b[i];
|
|
28105
|
+
var key;
|
|
28106
|
+
if (shpA == -1) return null;
|
|
28107
|
+
key = getKey(shpA, shpB);
|
|
28108
|
+
if (key === null || key === false) return null;
|
|
28109
|
+
if (useOnce) {
|
|
28110
|
+
// arc can only be queried once
|
|
28111
|
+
a[i] = -1;
|
|
28112
|
+
b[i] = -1;
|
|
28113
|
+
}
|
|
28114
|
+
// use optional filter to exclude some arcs
|
|
28115
|
+
if (opts.filter && !opts.filter(shpA, shpB)) return null;
|
|
28116
|
+
return key;
|
|
28117
|
+
}
|
|
28118
|
+
|
|
28119
|
+
return function(getKey) {
|
|
28120
|
+
return function(arcId) {
|
|
28121
|
+
return classify(arcId, getKey);
|
|
28122
|
+
};
|
|
28123
|
+
};
|
|
28124
|
+
}
|
|
28125
|
+
|
|
28126
|
+
var ArcClassifier = /*#__PURE__*/Object.freeze({
|
|
28127
|
+
__proto__: null,
|
|
28128
|
+
getArcClassifier: getArcClassifier
|
|
28129
|
+
});
|
|
28130
|
+
|
|
28131
|
+
// Returns a function for querying the neighbors of a given shape. The function
|
|
28132
|
+
// can be called in either of two ways:
|
|
28133
|
+
//
|
|
28134
|
+
// 1. function(shapeId, callback)
|
|
28135
|
+
// Callback signature: function(adjacentShapeId, arcId)
|
|
28136
|
+
// The callback function is called once for each arc that the given feature
|
|
28137
|
+
// shares with another feature.
|
|
28138
|
+
//
|
|
28139
|
+
// 2. function(shapeId)
|
|
28140
|
+
// The function returns an array of unique ids of neighboring shapes, or
|
|
28141
|
+
// an empty array if a shape has no neighbors.
|
|
28142
|
+
//
|
|
28143
|
+
function getNeighborLookupFunction(lyr, arcs) {
|
|
28144
|
+
var classifier = getArcClassifier(lyr.shapes, arcs, {reusable: true});
|
|
28145
|
+
var classify = classifier(onShapes);
|
|
28146
|
+
var currShapeId;
|
|
28147
|
+
var neighbors;
|
|
28148
|
+
var callback;
|
|
28149
|
+
|
|
28150
|
+
function onShapes(a, b) {
|
|
28151
|
+
if (b == -1) return -1; // outer edges are b == -1
|
|
28152
|
+
return a == currShapeId ? b : a;
|
|
28153
|
+
}
|
|
28154
|
+
|
|
28155
|
+
function onArc(arcId) {
|
|
28156
|
+
var nabeId = classify(arcId);
|
|
28157
|
+
if (nabeId == -1) return;
|
|
28158
|
+
if (callback) {
|
|
28159
|
+
callback(nabeId, arcId);
|
|
28160
|
+
} else if (neighbors.indexOf(nabeId) == -1) {
|
|
28161
|
+
neighbors.push(nabeId);
|
|
28162
|
+
}
|
|
28163
|
+
}
|
|
28164
|
+
|
|
28165
|
+
return function(shpId, cb) {
|
|
28166
|
+
currShapeId = shpId;
|
|
28167
|
+
if (cb) {
|
|
28168
|
+
callback = cb;
|
|
28169
|
+
forEachArcId(lyr.shapes[shpId], onArc);
|
|
28170
|
+
callback = null;
|
|
28171
|
+
} else {
|
|
28172
|
+
neighbors = [];
|
|
28173
|
+
forEachArcId(lyr.shapes[shpId], onArc);
|
|
28174
|
+
return neighbors;
|
|
28175
|
+
}
|
|
28176
|
+
};
|
|
28177
|
+
}
|
|
28178
|
+
|
|
28179
|
+
|
|
28180
|
+
// Returns an array containing all pairs of adjacent shapes
|
|
28181
|
+
// in a collection of polygon shapes. A pair of shapes is represented as
|
|
28182
|
+
// an array of two shape indexes [a, b].
|
|
28183
|
+
function findPairsOfNeighbors(shapes, arcs) {
|
|
28184
|
+
var getKey = function(a, b) {
|
|
28185
|
+
return b > -1 && a > -1 ? [a, b] : null;
|
|
28186
|
+
};
|
|
28187
|
+
var classify = getArcClassifier(shapes, arcs)(getKey);
|
|
28188
|
+
var arr = [];
|
|
28189
|
+
var index = {};
|
|
28190
|
+
var onArc = function(arcId) {
|
|
28191
|
+
var obj = classify(arcId);
|
|
28192
|
+
var key;
|
|
28193
|
+
if (obj) {
|
|
28194
|
+
key = obj.join('~');
|
|
28195
|
+
if (key in index === false) {
|
|
28196
|
+
arr.push(obj);
|
|
28197
|
+
index[key] = true;
|
|
28198
|
+
}
|
|
28199
|
+
}
|
|
28200
|
+
};
|
|
28201
|
+
forEachArcId(shapes, onArc);
|
|
28202
|
+
return arr;
|
|
28203
|
+
}
|
|
28204
|
+
|
|
28205
|
+
var PolygonNeighbors = /*#__PURE__*/Object.freeze({
|
|
28206
|
+
__proto__: null,
|
|
28207
|
+
getNeighborLookupFunction: getNeighborLookupFunction,
|
|
28208
|
+
findPairsOfNeighbors: findPairsOfNeighbors
|
|
28209
|
+
});
|
|
28210
|
+
|
|
28211
|
+
function getNonAdjacentClassifier(lyr, dataset, colors) {
|
|
28212
|
+
requirePolygonLayer(lyr);
|
|
28213
|
+
var getNeighbors = getNeighborLookupFunction(lyr, dataset.arcs);
|
|
28214
|
+
var errorCount = 0;
|
|
28215
|
+
var data = utils.range(getFeatureCount(lyr)).map(function(shpId) {
|
|
28216
|
+
var nabes = getNeighbors(shpId) || [];
|
|
28217
|
+
return {
|
|
28218
|
+
nabes: nabes,
|
|
28219
|
+
n: nabes.length,
|
|
28220
|
+
colorId: -1
|
|
28221
|
+
};
|
|
28222
|
+
});
|
|
28223
|
+
var getSortedColorIds = getUpdateFunction(colors.length);
|
|
28224
|
+
var colorIds = getSortedColorIds();
|
|
28225
|
+
// Sort adjacency data by number of neighbors in descending order
|
|
28226
|
+
var sorted = data.concat();
|
|
28227
|
+
utils.sortOn(sorted, 'n', false);
|
|
28228
|
+
// Assign colors, starting with polygons with the largest number of neighbors
|
|
28229
|
+
sorted.forEach(function(d) {
|
|
28230
|
+
var colorId = pickColor(d, data, colorIds);
|
|
28231
|
+
if (colorId == -1) {
|
|
28232
|
+
errorCount++;
|
|
28233
|
+
colorId = colorIds[0];
|
|
28234
|
+
}
|
|
28235
|
+
d.colorId = colorId;
|
|
28236
|
+
colorIds = getSortedColorIds(colorId);
|
|
28237
|
+
});
|
|
28238
|
+
|
|
28239
|
+
if (errorCount > 0) {
|
|
28240
|
+
message(`Unable to find non-adjacent colors for ${errorCount} ${errorCount == 1 ? 'polygon' : 'polygons'}`);
|
|
28241
|
+
}
|
|
28242
|
+
return function(shpId) {
|
|
28243
|
+
return colors[data[shpId].colorId];
|
|
28244
|
+
};
|
|
28245
|
+
}
|
|
28246
|
+
|
|
28247
|
+
// Pick the id of a color that is not shared with a neighboring polygon
|
|
28248
|
+
function pickColor(d, data, colorIds) {
|
|
28249
|
+
var candidateId;
|
|
28250
|
+
for (var i=0; i<colorIds.length; i++) {
|
|
28251
|
+
candidateId = colorIds[i];
|
|
28252
|
+
if (isAvailableColor(d, data, candidateId)) {
|
|
28253
|
+
return candidateId;
|
|
28254
|
+
}
|
|
28255
|
+
}
|
|
28256
|
+
return -1; // no colors are available
|
|
28257
|
+
}
|
|
28258
|
+
|
|
28259
|
+
function isAvailableColor(d, data, colorId) {
|
|
28260
|
+
var nabes = d.nabes;
|
|
28261
|
+
for (var i=0; i<nabes.length; i++) {
|
|
28262
|
+
if (data[nabes[i]].colorId === colorId) return false;
|
|
28263
|
+
}
|
|
28264
|
+
return true;
|
|
28265
|
+
}
|
|
28266
|
+
|
|
28267
|
+
// Update function returns an array of ids, sorted in descending order of preference
|
|
28268
|
+
// (less-used ids are preferred).
|
|
28269
|
+
// Function recieves an (optional) id that was just used.
|
|
28270
|
+
function getUpdateFunction(n) {
|
|
28271
|
+
var ids = utils.range(n);
|
|
28272
|
+
var counts = new Uint32Array(n);
|
|
28273
|
+
return function(i) {
|
|
28274
|
+
if (i >= 0 && i < n) {
|
|
28275
|
+
counts[i]++;
|
|
28276
|
+
utils.sortArrayIndex(ids, counts, true);
|
|
28277
|
+
} else if (i !== undefined) {
|
|
28278
|
+
error('Unexpected color index:', i);
|
|
28279
|
+
}
|
|
28280
|
+
return ids;
|
|
28281
|
+
};
|
|
28282
|
+
}
|
|
28283
|
+
|
|
28284
|
+
cmd.classify = function(lyr, dataset, optsArg) {
|
|
28285
|
+
if (!lyr.data) {
|
|
28286
|
+
initDataTable(lyr);
|
|
28287
|
+
}
|
|
28067
28288
|
var opts = optsArg || {};
|
|
28068
28289
|
var records = lyr.data && lyr.data.getRecords();
|
|
28069
28290
|
var nullValue = opts.null_value || null;
|
|
28070
28291
|
var looksLikeColors = !!opts.colors || !!opts.color_scheme;
|
|
28071
28292
|
var colorScheme;
|
|
28072
|
-
var classValues,
|
|
28293
|
+
var classValues, classifyByValue, classifyById;
|
|
28073
28294
|
var numBuckets, numValues;
|
|
28074
28295
|
var dataField, outputField;
|
|
28075
28296
|
|
|
28076
28297
|
// validate explicitly set classes
|
|
28077
28298
|
if (opts.classes) {
|
|
28078
28299
|
if (!utils.isInteger(opts.classes) || opts.classes > 1 === false) {
|
|
28079
|
-
stop('Invalid classes
|
|
28300
|
+
stop('Invalid number of classes:', opts.classes, '(expected a value greater than 1)');
|
|
28080
28301
|
}
|
|
28081
28302
|
numBuckets = opts.classes;
|
|
28082
28303
|
}
|
|
28083
28304
|
|
|
28084
|
-
|
|
28085
28305
|
// TODO: better validation of breaks values
|
|
28086
28306
|
if (opts.breaks) {
|
|
28087
28307
|
numBuckets = opts.breaks.length + 1;
|
|
@@ -28099,9 +28319,6 @@ ${svg}
|
|
|
28099
28319
|
|
|
28100
28320
|
} else if (opts.field) {
|
|
28101
28321
|
dataField = opts.field;
|
|
28102
|
-
|
|
28103
|
-
} else {
|
|
28104
|
-
stop('Missing a data field to classify');
|
|
28105
28322
|
}
|
|
28106
28323
|
|
|
28107
28324
|
// expand categories if value is '*'
|
|
@@ -28109,7 +28326,18 @@ ${svg}
|
|
|
28109
28326
|
opts.categories = getUniqFieldValues(records, dataField);
|
|
28110
28327
|
}
|
|
28111
28328
|
|
|
28112
|
-
|
|
28329
|
+
if (opts.method == 'non-adjacent') {
|
|
28330
|
+
if (lyr.geometry_type != 'polygon') {
|
|
28331
|
+
stop('The non-adjacent option requires a polygon layer');
|
|
28332
|
+
}
|
|
28333
|
+
if (dataField) {
|
|
28334
|
+
stop('The non-adjacent option does not accept a data field argument');
|
|
28335
|
+
}
|
|
28336
|
+
} else if (!dataField) {
|
|
28337
|
+
stop('Missing a data field to classify');
|
|
28338
|
+
} else {
|
|
28339
|
+
requireDataField(lyr.data, dataField);
|
|
28340
|
+
}
|
|
28113
28341
|
|
|
28114
28342
|
if (numBuckets) {
|
|
28115
28343
|
numValues = opts.continuous ? numBuckets + 1 : numBuckets;
|
|
@@ -28177,15 +28405,25 @@ ${svg}
|
|
|
28177
28405
|
|
|
28178
28406
|
// get a function to convert input data to class indexes
|
|
28179
28407
|
//
|
|
28180
|
-
if (opts.
|
|
28408
|
+
if (opts.method == 'non-adjacent') {
|
|
28409
|
+
classifyById = getNonAdjacentClassifier(lyr, dataset, classValues);
|
|
28410
|
+
} else if (opts.index_field) {
|
|
28181
28411
|
// data is pre-classified... just read the index from a field
|
|
28182
|
-
|
|
28412
|
+
classifyByValue = getIndexedClassifier(classValues, nullValue, opts);
|
|
28183
28413
|
} else if (opts.categories) {
|
|
28184
|
-
|
|
28414
|
+
classifyByValue = getCategoricalClassifier(classValues, nullValue, opts);
|
|
28185
28415
|
} else {
|
|
28186
|
-
|
|
28416
|
+
classifyByValue = getSequentialClassifier(classValues, nullValue, getFieldValues(records, dataField), opts);
|
|
28417
|
+
}
|
|
28418
|
+
|
|
28419
|
+
if (classifyByValue) {
|
|
28420
|
+
classifyById = function(id) {
|
|
28421
|
+
var d = records[id] || {};
|
|
28422
|
+
return classifyByValue(d[dataField]);
|
|
28423
|
+
};
|
|
28187
28424
|
}
|
|
28188
28425
|
|
|
28426
|
+
|
|
28189
28427
|
// get the name of the output field
|
|
28190
28428
|
//
|
|
28191
28429
|
if (looksLikeColors) {
|
|
@@ -28201,8 +28439,7 @@ ${svg}
|
|
|
28201
28439
|
}
|
|
28202
28440
|
|
|
28203
28441
|
records.forEach(function(d, i) {
|
|
28204
|
-
d =
|
|
28205
|
-
d[outputField] = classify(d[dataField]);
|
|
28442
|
+
d[outputField] = classifyById(i);
|
|
28206
28443
|
});
|
|
28207
28444
|
};
|
|
28208
28445
|
|
|
@@ -29014,147 +29251,6 @@ ${svg}
|
|
|
29014
29251
|
getClipMessage: getClipMessage
|
|
29015
29252
|
});
|
|
29016
29253
|
|
|
29017
|
-
// Returns a function for constructing a query function that accepts an arc id and
|
|
29018
|
-
// returns information about the polygon or polygons that use the given arc.
|
|
29019
|
-
// TODO: explain this better.
|
|
29020
|
-
//
|
|
29021
|
-
// options:
|
|
29022
|
-
// filter: optional filter function; signature: function(idA, idB or -1) : boolean
|
|
29023
|
-
// reusable: flag that lets an arc be queried multiple times.
|
|
29024
|
-
function getArcClassifier(shapes, arcs) {
|
|
29025
|
-
var opts = arguments[2] || {},
|
|
29026
|
-
useOnce = !opts.reusable,
|
|
29027
|
-
n = arcs.size(),
|
|
29028
|
-
a = new Int32Array(n),
|
|
29029
|
-
b = new Int32Array(n);
|
|
29030
|
-
|
|
29031
|
-
utils.initializeArray(a, -1);
|
|
29032
|
-
utils.initializeArray(b, -1);
|
|
29033
|
-
|
|
29034
|
-
traversePaths(shapes, function(o) {
|
|
29035
|
-
var i = absArcId(o.arcId);
|
|
29036
|
-
var shpId = o.shapeId;
|
|
29037
|
-
var aval = a[i];
|
|
29038
|
-
if (aval == -1) {
|
|
29039
|
-
a[i] = shpId;
|
|
29040
|
-
} else if (shpId < aval) {
|
|
29041
|
-
b[i] = aval;
|
|
29042
|
-
a[i] = shpId;
|
|
29043
|
-
} else {
|
|
29044
|
-
b[i] = shpId;
|
|
29045
|
-
}
|
|
29046
|
-
});
|
|
29047
|
-
|
|
29048
|
-
function classify(arcId, getKey) {
|
|
29049
|
-
var i = absArcId(arcId);
|
|
29050
|
-
var shpA = a[i];
|
|
29051
|
-
var shpB = b[i];
|
|
29052
|
-
var key;
|
|
29053
|
-
if (shpA == -1) return null;
|
|
29054
|
-
key = getKey(shpA, shpB);
|
|
29055
|
-
if (key === null || key === false) return null;
|
|
29056
|
-
if (useOnce) {
|
|
29057
|
-
// arc can only be queried once
|
|
29058
|
-
a[i] = -1;
|
|
29059
|
-
b[i] = -1;
|
|
29060
|
-
}
|
|
29061
|
-
// use optional filter to exclude some arcs
|
|
29062
|
-
if (opts.filter && !opts.filter(shpA, shpB)) return null;
|
|
29063
|
-
return key;
|
|
29064
|
-
}
|
|
29065
|
-
|
|
29066
|
-
return function(getKey) {
|
|
29067
|
-
return function(arcId) {
|
|
29068
|
-
return classify(arcId, getKey);
|
|
29069
|
-
};
|
|
29070
|
-
};
|
|
29071
|
-
}
|
|
29072
|
-
|
|
29073
|
-
var ArcClassifier = /*#__PURE__*/Object.freeze({
|
|
29074
|
-
__proto__: null,
|
|
29075
|
-
getArcClassifier: getArcClassifier
|
|
29076
|
-
});
|
|
29077
|
-
|
|
29078
|
-
// Returns a function for querying the neighbors of a given shape. The function
|
|
29079
|
-
// can be called in either of two ways:
|
|
29080
|
-
//
|
|
29081
|
-
// 1. function(shapeId, callback)
|
|
29082
|
-
// Callback signature: function(adjacentShapeId, arcId)
|
|
29083
|
-
// The callback function is called once for each arc that the given feature
|
|
29084
|
-
// shares with another feature.
|
|
29085
|
-
//
|
|
29086
|
-
// 2. function(shapeId)
|
|
29087
|
-
// The function returns an array of unique ids of neighboring shapes, or
|
|
29088
|
-
// an empty array if a shape has no neighbors.
|
|
29089
|
-
//
|
|
29090
|
-
function getNeighborLookupFunction(lyr, arcs) {
|
|
29091
|
-
var classifier = getArcClassifier(lyr.shapes, arcs, {reusable: true});
|
|
29092
|
-
var classify = classifier(onShapes);
|
|
29093
|
-
var currShapeId;
|
|
29094
|
-
var neighbors;
|
|
29095
|
-
var callback;
|
|
29096
|
-
|
|
29097
|
-
function onShapes(a, b) {
|
|
29098
|
-
if (b == -1) return -1; // outer edges are b == -1
|
|
29099
|
-
return a == currShapeId ? b : a;
|
|
29100
|
-
}
|
|
29101
|
-
|
|
29102
|
-
function onArc(arcId) {
|
|
29103
|
-
var nabeId = classify(arcId);
|
|
29104
|
-
if (nabeId == -1) return;
|
|
29105
|
-
if (callback) {
|
|
29106
|
-
callback(nabeId, arcId);
|
|
29107
|
-
} else if (neighbors.indexOf(nabeId) == -1) {
|
|
29108
|
-
neighbors.push(nabeId);
|
|
29109
|
-
}
|
|
29110
|
-
}
|
|
29111
|
-
|
|
29112
|
-
return function(shpId, cb) {
|
|
29113
|
-
currShapeId = shpId;
|
|
29114
|
-
if (cb) {
|
|
29115
|
-
callback = cb;
|
|
29116
|
-
forEachArcId(lyr.shapes[shpId], onArc);
|
|
29117
|
-
callback = null;
|
|
29118
|
-
} else {
|
|
29119
|
-
neighbors = [];
|
|
29120
|
-
forEachArcId(lyr.shapes[shpId], onArc);
|
|
29121
|
-
return neighbors;
|
|
29122
|
-
}
|
|
29123
|
-
};
|
|
29124
|
-
}
|
|
29125
|
-
|
|
29126
|
-
|
|
29127
|
-
// Returns an array containing all pairs of adjacent shapes
|
|
29128
|
-
// in a collection of polygon shapes. A pair of shapes is represented as
|
|
29129
|
-
// an array of two shape indexes [a, b].
|
|
29130
|
-
function findPairsOfNeighbors(shapes, arcs) {
|
|
29131
|
-
var getKey = function(a, b) {
|
|
29132
|
-
return b > -1 && a > -1 ? [a, b] : null;
|
|
29133
|
-
};
|
|
29134
|
-
var classify = getArcClassifier(shapes, arcs)(getKey);
|
|
29135
|
-
var arr = [];
|
|
29136
|
-
var index = {};
|
|
29137
|
-
var onArc = function(arcId) {
|
|
29138
|
-
var obj = classify(arcId);
|
|
29139
|
-
var key;
|
|
29140
|
-
if (obj) {
|
|
29141
|
-
key = obj.join('~');
|
|
29142
|
-
if (key in index === false) {
|
|
29143
|
-
arr.push(obj);
|
|
29144
|
-
index[key] = true;
|
|
29145
|
-
}
|
|
29146
|
-
}
|
|
29147
|
-
};
|
|
29148
|
-
forEachArcId(shapes, onArc);
|
|
29149
|
-
return arr;
|
|
29150
|
-
}
|
|
29151
|
-
|
|
29152
|
-
var PolygonNeighbors = /*#__PURE__*/Object.freeze({
|
|
29153
|
-
__proto__: null,
|
|
29154
|
-
getNeighborLookupFunction: getNeighborLookupFunction,
|
|
29155
|
-
findPairsOfNeighbors: findPairsOfNeighbors
|
|
29156
|
-
});
|
|
29157
|
-
|
|
29158
29254
|
// Assign a cluster id to each polygon in a dataset, which can be used with
|
|
29159
29255
|
// one of the dissolve commands to dissolve the clusters
|
|
29160
29256
|
// Works by iteratively grouping pairs of polygons with the smallest distance
|
|
@@ -29840,11 +29936,12 @@ ${svg}
|
|
|
29840
29936
|
getJoinFilter: getJoinFilter
|
|
29841
29937
|
});
|
|
29842
29938
|
|
|
29939
|
+
// Join data from @src table to records in @dest table
|
|
29843
29940
|
function joinTables(dest, src, join, opts) {
|
|
29844
29941
|
return joinTableToLayer({data: dest}, src, join, opts);
|
|
29845
29942
|
}
|
|
29846
29943
|
|
|
29847
|
-
// Join data from @src table to records in @
|
|
29944
|
+
// Join data from @src table to records in @destLyr layer.
|
|
29848
29945
|
// @join function
|
|
29849
29946
|
// Receives index of record in the dest table
|
|
29850
29947
|
// Returns array of matching records in src table, or null if no matches
|
|
@@ -29867,7 +29964,7 @@ ${svg}
|
|
|
29867
29964
|
retn = {},
|
|
29868
29965
|
srcRec, srcId, destRec, joins, count, filter, calc, i, j, n, m;
|
|
29869
29966
|
|
|
29870
|
-
// support for duplication
|
|
29967
|
+
// support for duplication of destination records
|
|
29871
29968
|
var duplicateRecords, destShapes;
|
|
29872
29969
|
if (useDuplication) {
|
|
29873
29970
|
if (opts.calc) stop('duplication and calc options cannot be used together');
|
|
@@ -29896,6 +29993,7 @@ ${svg}
|
|
|
29896
29993
|
for (j=0, count=0, m=joins ? joins.length : 0; j<m; j++) {
|
|
29897
29994
|
srcId = joins[j];
|
|
29898
29995
|
srcRec = srcRecords[srcId];
|
|
29996
|
+
// duplication mode: many-to-one joins add new features to the target layer.
|
|
29899
29997
|
if (count > 0 && useDuplication) {
|
|
29900
29998
|
destRec = copyRecord(duplicateRecords[i]);
|
|
29901
29999
|
destRecords.push(destRec);
|
|
@@ -37689,7 +37787,7 @@ ${svg}
|
|
|
37689
37787
|
applyCommandToEachLayer(cmd.calc, targetLayers, arcs, opts);
|
|
37690
37788
|
|
|
37691
37789
|
} else if (name == 'classify') {
|
|
37692
|
-
applyCommandToEachLayer(cmd.classify, targetLayers, opts);
|
|
37790
|
+
applyCommandToEachLayer(cmd.classify, targetLayers, targetDataset, opts);
|
|
37693
37791
|
|
|
37694
37792
|
} else if (name == 'clean') {
|
|
37695
37793
|
cmd.cleanLayers(targetLayers, targetDataset, opts);
|