mapshaper 0.6.52 → 0.6.54
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 +203 -110
- package/package.json +1 -1
- package/www/index.html +4 -19
- package/www/mapshaper-gui.js +168 -131
- package/www/mapshaper.js +203 -110
- package/www/page.css +15 -2
package/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.6.
|
|
3
|
+
var VERSION = "0.6.54";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -11075,7 +11075,7 @@
|
|
|
11075
11075
|
}
|
|
11076
11076
|
|
|
11077
11077
|
function isGzipFile(file) {
|
|
11078
|
-
return /\.gz
|
|
11078
|
+
return /\.gz$/i.test(file);
|
|
11079
11079
|
}
|
|
11080
11080
|
|
|
11081
11081
|
function isSupportedOutputFormat(fmt) {
|
|
@@ -23703,14 +23703,14 @@ ${svg}
|
|
|
23703
23703
|
.option('geojson-type', {
|
|
23704
23704
|
describe: '[GeoJSON] FeatureCollection, GeometryCollection or Feature'
|
|
23705
23705
|
})
|
|
23706
|
-
.option('ndjson', {
|
|
23707
|
-
describe: '[GeoJSON/JSON] output newline-delimited features or records',
|
|
23708
|
-
type: 'flag'
|
|
23709
|
-
})
|
|
23710
23706
|
.option('hoist', {
|
|
23711
23707
|
describe: '[GeoJSON] move properties to the root level of each Feature',
|
|
23712
23708
|
type: 'strings'
|
|
23713
23709
|
})
|
|
23710
|
+
.option('ndjson', {
|
|
23711
|
+
describe: '[GeoJSON/JSON] output newline-delimited features or records',
|
|
23712
|
+
type: 'flag'
|
|
23713
|
+
})
|
|
23714
23714
|
.option('width', {
|
|
23715
23715
|
describe: '[SVG/TopoJSON] pixel width of output (SVG default is 800)',
|
|
23716
23716
|
type: 'number'
|
|
@@ -23865,10 +23865,6 @@ ${svg}
|
|
|
23865
23865
|
describe: 'a pair of values (0-100) for limiting a color ramp',
|
|
23866
23866
|
type: 'numbers'
|
|
23867
23867
|
})
|
|
23868
|
-
.option('range', {
|
|
23869
|
-
// describe: 'a pair of numbers defining the effective data range',
|
|
23870
|
-
type: 'numbers'
|
|
23871
|
-
})
|
|
23872
23868
|
.option('null-value', {
|
|
23873
23869
|
describe: 'value (or color) to use for invalid or missing data'
|
|
23874
23870
|
})
|
|
@@ -23895,6 +23891,11 @@ ${svg}
|
|
|
23895
23891
|
describe: 'user-defined sequential class breaks',
|
|
23896
23892
|
type: 'numbers'
|
|
23897
23893
|
})
|
|
23894
|
+
.option('outer-breaks', {
|
|
23895
|
+
describe: 'min,max breakpoints, to limit the effect of outliers',
|
|
23896
|
+
old_alias: 'range',
|
|
23897
|
+
type: 'numbers'
|
|
23898
|
+
})
|
|
23898
23899
|
.option('classes', {
|
|
23899
23900
|
describe: 'number of classes (can be inferred from other options)',
|
|
23900
23901
|
type: 'integer'
|
|
@@ -23904,7 +23905,7 @@ ${svg}
|
|
|
23904
23905
|
type: 'flag'
|
|
23905
23906
|
})
|
|
23906
23907
|
.option('continuous', {
|
|
23907
|
-
describe: 'output
|
|
23908
|
+
describe: 'output interpolated values, for unclassed colors',
|
|
23908
23909
|
type: 'flag'
|
|
23909
23910
|
})
|
|
23910
23911
|
.option('index-field', {
|
|
@@ -25337,7 +25338,7 @@ ${svg}
|
|
|
25337
25338
|
.describe('create commands on-the-fly and run them')
|
|
25338
25339
|
.option('expression', {
|
|
25339
25340
|
DEFAULT: true,
|
|
25340
|
-
describe: 'JS expression to generate command(s)'
|
|
25341
|
+
describe: 'JS expression or template to generate command(s)'
|
|
25341
25342
|
})
|
|
25342
25343
|
// deprecated
|
|
25343
25344
|
.option('commands', {alias_to: 'expression'})
|
|
@@ -32134,23 +32135,22 @@ ${svg}
|
|
|
32134
32135
|
}
|
|
32135
32136
|
|
|
32136
32137
|
var ascending = getAscendingNumbers(dataValues);
|
|
32137
|
-
if (opts.
|
|
32138
|
-
ascending = applyDataRange(ascending, opts.
|
|
32138
|
+
if (opts.outer_breaks) {
|
|
32139
|
+
ascending = applyDataRange(ascending, opts.outer_breaks);
|
|
32139
32140
|
}
|
|
32140
32141
|
var nullCount = dataValues.length - ascending.length;
|
|
32141
32142
|
var minVal = ascending[0];
|
|
32142
32143
|
var maxVal = ascending[ascending.length - 1];
|
|
32143
32144
|
|
|
32144
|
-
|
|
32145
|
-
|
|
32146
|
-
if (val
|
|
32147
|
-
if (val > opts.range[1]) val = opts.range[1];
|
|
32145
|
+
var clamp = opts.outer_breaks ? function(val) {
|
|
32146
|
+
if (val < opts.outer_breaks[0]) val = opts.outer_breaks[0];
|
|
32147
|
+
if (val > opts.outer_breaks[1]) val = opts.outer_breaks[1];
|
|
32148
32148
|
return val;
|
|
32149
32149
|
} : null;
|
|
32150
32150
|
|
|
32151
|
-
if (opts.
|
|
32152
|
-
minVal = opts.
|
|
32153
|
-
maxVal = opts.
|
|
32151
|
+
if (opts.outer_breaks) {
|
|
32152
|
+
minVal = opts.outer_breaks[0];
|
|
32153
|
+
maxVal = opts.outer_breaks[1];
|
|
32154
32154
|
}
|
|
32155
32155
|
|
|
32156
32156
|
if (numBreaks === 0) {
|
|
@@ -41842,6 +41842,185 @@ ${svg}
|
|
|
41842
41842
|
}, {});
|
|
41843
41843
|
}
|
|
41844
41844
|
|
|
41845
|
+
// import { importGeoJSON } from '../geojson/geojson-import';
|
|
41846
|
+
|
|
41847
|
+
function getTargetProxy(target) {
|
|
41848
|
+
var lyr = target.layers[0];
|
|
41849
|
+
var data = getLayerInfo(lyr, target.dataset); // layer_name, feature_count etc
|
|
41850
|
+
data.layer = lyr;
|
|
41851
|
+
data.dataset = target.dataset;
|
|
41852
|
+
addGetters(data, {
|
|
41853
|
+
// export as an object, not a string or buffer
|
|
41854
|
+
geojson: getGeoJSON
|
|
41855
|
+
});
|
|
41856
|
+
|
|
41857
|
+
function getGeoJSON() {
|
|
41858
|
+
var features = exportLayerAsGeoJSON(lyr, target.dataset, {rfc7946: true}, true);
|
|
41859
|
+
return {
|
|
41860
|
+
type: 'FeatureCollection',
|
|
41861
|
+
features: features
|
|
41862
|
+
};
|
|
41863
|
+
}
|
|
41864
|
+
|
|
41865
|
+
return data;
|
|
41866
|
+
}
|
|
41867
|
+
|
|
41868
|
+
// Support for evaluating expressions embedded in curly-brace templates
|
|
41869
|
+
|
|
41870
|
+
// Returns: a string (e.g. a command string used by the -run command)
|
|
41871
|
+
async function evalTemplateExpression(expression, targets, ctx) {
|
|
41872
|
+
ctx = ctx || getBaseContext();
|
|
41873
|
+
// TODO: throw an error if target is used when there are multiple targets
|
|
41874
|
+
if (targets && targets.length == 1) {
|
|
41875
|
+
Object.defineProperty(ctx, 'target', {value: getTargetProxy(targets[0])});
|
|
41876
|
+
}
|
|
41877
|
+
// Add global functions and data to the expression context
|
|
41878
|
+
// (e.g. functions imported via the -require command)
|
|
41879
|
+
var globals = getStashedVar('defs') || {};
|
|
41880
|
+
ctx.global = globals;
|
|
41881
|
+
utils.extend(ctx, ctx.global);
|
|
41882
|
+
|
|
41883
|
+
var output = await compileTemplate(expression, ctx);
|
|
41884
|
+
if (hasFunctionCall(output, ctx)) {
|
|
41885
|
+
// also evaluate function calls that are not enclosed in curly braces
|
|
41886
|
+
// (convenience syntax)
|
|
41887
|
+
output = await evalExpression(output, ctx);
|
|
41888
|
+
}
|
|
41889
|
+
return output;
|
|
41890
|
+
}
|
|
41891
|
+
|
|
41892
|
+
async function compileTemplate(template, ctx) {
|
|
41893
|
+
var subExpressions = parseTemplate(template);
|
|
41894
|
+
var promises = subExpressions.map(expr => evalExpression(expr, ctx));
|
|
41895
|
+
var replacements = await Promise.all(promises);
|
|
41896
|
+
return applyReplacements(template, replacements);
|
|
41897
|
+
}
|
|
41898
|
+
|
|
41899
|
+
async function evalExpression(expression, ctx) {
|
|
41900
|
+
var output;
|
|
41901
|
+
try {
|
|
41902
|
+
output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
|
|
41903
|
+
} catch(e) {
|
|
41904
|
+
stop(e.name, 'in JS source:', e.message);
|
|
41905
|
+
}
|
|
41906
|
+
return output;
|
|
41907
|
+
}
|
|
41908
|
+
|
|
41909
|
+
// Returns array of 0 or more embedded curly-brace expressions
|
|
41910
|
+
function parseTemplate(str) {
|
|
41911
|
+
var arr = [];
|
|
41912
|
+
parseTemplateParts(str).forEach(function(s, i) {
|
|
41913
|
+
if (i % 2 == 1) {
|
|
41914
|
+
arr.push(s.substring(1, s.length-1)); // remove braces
|
|
41915
|
+
}
|
|
41916
|
+
});
|
|
41917
|
+
return arr;
|
|
41918
|
+
}
|
|
41919
|
+
|
|
41920
|
+
// template: template string
|
|
41921
|
+
// replacements: array of strings or values that can be coerced to strings
|
|
41922
|
+
function applyReplacements(template, replacements) {
|
|
41923
|
+
var parts = parseTemplateParts(template);
|
|
41924
|
+
return parts.reduce(function(memo, s, i) {
|
|
41925
|
+
return i % 2 == 1 ? memo + (replacements.shift() || '') : memo + s;
|
|
41926
|
+
}, '');
|
|
41927
|
+
}
|
|
41928
|
+
|
|
41929
|
+
// Divides a string into substrings; even-index strings contain literal strings,
|
|
41930
|
+
// Odd-indexed strings contain curly-brace-delimited template expressions.
|
|
41931
|
+
// JSON objects are treated as literal strings; other top-level curly braces are
|
|
41932
|
+
// assumed to be embedded expressions.
|
|
41933
|
+
// For example: parseTemplateParts('{"hello"}, world!') => ['', '{"hello"}', ', world!']
|
|
41934
|
+
//
|
|
41935
|
+
function parseTemplateParts(str) {
|
|
41936
|
+
// TODO: consider adding \ escapes
|
|
41937
|
+
var depth=0;
|
|
41938
|
+
var parts = [];
|
|
41939
|
+
var part = '';
|
|
41940
|
+
var c;
|
|
41941
|
+
|
|
41942
|
+
for (var i=0, n=str.length; i<n; i++) {
|
|
41943
|
+
c = str.charAt(i);
|
|
41944
|
+
if (c == '{') {
|
|
41945
|
+
if (depth == 0) {
|
|
41946
|
+
parts.push(part);
|
|
41947
|
+
part = '';
|
|
41948
|
+
}
|
|
41949
|
+
depth++;
|
|
41950
|
+
}
|
|
41951
|
+
part += c;
|
|
41952
|
+
if (c == '}') {
|
|
41953
|
+
depth--;
|
|
41954
|
+
if (depth < 0) {
|
|
41955
|
+
// unexpected... throw an error?
|
|
41956
|
+
depth++;
|
|
41957
|
+
} else if (depth == 0 && isValidJSON(part)) {
|
|
41958
|
+
// embedded JSON objects are not template parts -- undo
|
|
41959
|
+
part = parts.pop() + part;
|
|
41960
|
+
} else if (depth == 0) {
|
|
41961
|
+
parts.push(part);
|
|
41962
|
+
part = '';
|
|
41963
|
+
}
|
|
41964
|
+
}
|
|
41965
|
+
}
|
|
41966
|
+
parts.push(part);
|
|
41967
|
+
return parts;
|
|
41968
|
+
}
|
|
41969
|
+
|
|
41970
|
+
// Tests if an expression string contains a call to an indexed function
|
|
41971
|
+
// defs: Object containing functions indexed by function name
|
|
41972
|
+
function hasFunctionCall(str, defs) {
|
|
41973
|
+
var rxp = /([$_a-z][$_a-z0-9]*)\(/ig;
|
|
41974
|
+
return Array.from(str.matchAll(rxp)).some(match => match[1] in defs);
|
|
41975
|
+
}
|
|
41976
|
+
|
|
41977
|
+
function isValidJSON(str) {
|
|
41978
|
+
try {
|
|
41979
|
+
JSON.parse(str);
|
|
41980
|
+
} catch(e) {
|
|
41981
|
+
return false;
|
|
41982
|
+
}
|
|
41983
|
+
return true;
|
|
41984
|
+
}
|
|
41985
|
+
|
|
41986
|
+
cmd.require = async function(targets, opts) {
|
|
41987
|
+
var defs = getStashedVar('defs');
|
|
41988
|
+
var moduleFile, moduleName, mod;
|
|
41989
|
+
if (!opts.module) {
|
|
41990
|
+
stop("Missing module name or path to module");
|
|
41991
|
+
}
|
|
41992
|
+
if (cli.isFile(opts.module)) {
|
|
41993
|
+
moduleFile = opts.module;
|
|
41994
|
+
} else if (cli.isFile(opts.module + '.js')) {
|
|
41995
|
+
moduleFile = opts.module + '.js';
|
|
41996
|
+
} else {
|
|
41997
|
+
moduleName = opts.module;
|
|
41998
|
+
}
|
|
41999
|
+
if (moduleFile && !require$1('path').isAbsolute(moduleFile)) {
|
|
42000
|
+
moduleFile = require$1('path').join(process.cwd(), moduleFile);
|
|
42001
|
+
}
|
|
42002
|
+
try {
|
|
42003
|
+
mod = require$1(moduleFile || moduleName);
|
|
42004
|
+
if (typeof mod == 'function') {
|
|
42005
|
+
// -require now includes the functionality of the old -external command
|
|
42006
|
+
var retn = mod(api);
|
|
42007
|
+
if (retn && isValidExternalCommand(retn)) {
|
|
42008
|
+
cmd.registerCommand(retn.name, retn);
|
|
42009
|
+
}
|
|
42010
|
+
}
|
|
42011
|
+
} catch(e) {
|
|
42012
|
+
stop('Unable to load external module:', e.message, getErrorDetail(e));
|
|
42013
|
+
}
|
|
42014
|
+
if (moduleName || opts.alias) {
|
|
42015
|
+
defs[opts.alias || moduleName] = mod;
|
|
42016
|
+
} else {
|
|
42017
|
+
Object.assign(defs, mod);
|
|
42018
|
+
}
|
|
42019
|
+
if (opts.init) {
|
|
42020
|
+
await evalTemplateExpression(opts.init, targets);
|
|
42021
|
+
}
|
|
42022
|
+
};
|
|
42023
|
+
|
|
41845
42024
|
// Parse an array or a string of command line tokens into an array of
|
|
41846
42025
|
// command objects.
|
|
41847
42026
|
function parseCommands(tokens) {
|
|
@@ -41894,29 +42073,6 @@ ${svg}
|
|
|
41894
42073
|
parseConsoleCommands: parseConsoleCommands
|
|
41895
42074
|
});
|
|
41896
42075
|
|
|
41897
|
-
// import { importGeoJSON } from '../geojson/geojson-import';
|
|
41898
|
-
|
|
41899
|
-
function getTargetProxy(target) {
|
|
41900
|
-
var lyr = target.layers[0];
|
|
41901
|
-
var data = getLayerInfo(lyr, target.dataset); // layer_name, feature_count etc
|
|
41902
|
-
data.layer = lyr;
|
|
41903
|
-
data.dataset = target.dataset;
|
|
41904
|
-
addGetters(data, {
|
|
41905
|
-
// export as an object, not a string or buffer
|
|
41906
|
-
geojson: getGeoJSON
|
|
41907
|
-
});
|
|
41908
|
-
|
|
41909
|
-
function getGeoJSON() {
|
|
41910
|
-
var features = exportLayerAsGeoJSON(lyr, target.dataset, {rfc7946: true}, true);
|
|
41911
|
-
return {
|
|
41912
|
-
type: 'FeatureCollection',
|
|
41913
|
-
features: features
|
|
41914
|
-
};
|
|
41915
|
-
}
|
|
41916
|
-
|
|
41917
|
-
return data;
|
|
41918
|
-
}
|
|
41919
|
-
|
|
41920
42076
|
function getIOProxy(job) {
|
|
41921
42077
|
var obj = {
|
|
41922
42078
|
_cache: {}
|
|
@@ -41955,20 +42111,14 @@ ${svg}
|
|
|
41955
42111
|
// }
|
|
41956
42112
|
|
|
41957
42113
|
cmd.run = async function(job, targets, opts) {
|
|
41958
|
-
var tmp, commands;
|
|
42114
|
+
var tmp, commands, ctx;
|
|
41959
42115
|
if (!opts.expression) {
|
|
41960
42116
|
stop("Missing expression parameter");
|
|
41961
42117
|
}
|
|
41962
|
-
|
|
42118
|
+
ctx = getBaseContext();
|
|
41963
42119
|
// io proxy adds ability to add datasets dynamically in a required function
|
|
41964
|
-
var ctx = getBaseContext();
|
|
41965
42120
|
ctx.io = getIOProxy();
|
|
41966
|
-
tmp =
|
|
41967
|
-
|
|
41968
|
-
// Support async functions as expressions
|
|
41969
|
-
if (utils.isPromise(tmp)) {
|
|
41970
|
-
tmp = await tmp;
|
|
41971
|
-
}
|
|
42121
|
+
tmp = await evalTemplateExpression(opts.expression, targets, ctx);
|
|
41972
42122
|
if (tmp && !utils.isString(tmp)) {
|
|
41973
42123
|
stop('Expected a string containing mapshaper commands; received:', tmp);
|
|
41974
42124
|
}
|
|
@@ -41987,63 +42137,6 @@ ${svg}
|
|
|
41987
42137
|
}
|
|
41988
42138
|
};
|
|
41989
42139
|
|
|
41990
|
-
// This could return a Promise or a value or nothing
|
|
41991
|
-
function runGlobalExpression(expression, targets, ctx) {
|
|
41992
|
-
ctx = ctx || getBaseContext();
|
|
41993
|
-
var output;
|
|
41994
|
-
// TODO: throw an informative error if target is used when there are multiple targets
|
|
41995
|
-
if (targets && targets.length == 1) {
|
|
41996
|
-
Object.defineProperty(ctx, 'target', {value: getTargetProxy(targets[0])});
|
|
41997
|
-
}
|
|
41998
|
-
// Add defined functions and data to the expression context
|
|
41999
|
-
// (Such as functions imported via the -require command)
|
|
42000
|
-
utils.extend(ctx, getStashedVar('defs'));
|
|
42001
|
-
try {
|
|
42002
|
-
output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
|
|
42003
|
-
} catch(e) {
|
|
42004
|
-
stop(e.name, 'in JS source:', e.message);
|
|
42005
|
-
}
|
|
42006
|
-
return output;
|
|
42007
|
-
}
|
|
42008
|
-
|
|
42009
|
-
cmd.require = function(targets, opts) {
|
|
42010
|
-
var defs = getStashedVar('defs');
|
|
42011
|
-
var moduleFile, moduleName, mod;
|
|
42012
|
-
if (!opts.module) {
|
|
42013
|
-
stop("Missing module name or path to module");
|
|
42014
|
-
}
|
|
42015
|
-
if (cli.isFile(opts.module)) {
|
|
42016
|
-
moduleFile = opts.module;
|
|
42017
|
-
} else if (cli.isFile(opts.module + '.js')) {
|
|
42018
|
-
moduleFile = opts.module + '.js';
|
|
42019
|
-
} else {
|
|
42020
|
-
moduleName = opts.module;
|
|
42021
|
-
}
|
|
42022
|
-
if (moduleFile && !require$1('path').isAbsolute(moduleFile)) {
|
|
42023
|
-
moduleFile = require$1('path').join(process.cwd(), moduleFile);
|
|
42024
|
-
}
|
|
42025
|
-
try {
|
|
42026
|
-
mod = require$1(moduleFile || moduleName);
|
|
42027
|
-
if (typeof mod == 'function') {
|
|
42028
|
-
// -require now includes the functionality of the old -external command
|
|
42029
|
-
var retn = mod(api);
|
|
42030
|
-
if (retn && isValidExternalCommand(retn)) {
|
|
42031
|
-
cmd.registerCommand(retn.name, retn);
|
|
42032
|
-
}
|
|
42033
|
-
}
|
|
42034
|
-
} catch(e) {
|
|
42035
|
-
stop('Unable to load external module:', e.message, getErrorDetail(e));
|
|
42036
|
-
}
|
|
42037
|
-
if (moduleName || opts.alias) {
|
|
42038
|
-
defs[opts.alias || moduleName] = mod;
|
|
42039
|
-
} else {
|
|
42040
|
-
Object.assign(defs, mod);
|
|
42041
|
-
}
|
|
42042
|
-
if (opts.init) {
|
|
42043
|
-
runGlobalExpression(opts.init, targets);
|
|
42044
|
-
}
|
|
42045
|
-
};
|
|
42046
|
-
|
|
42047
42140
|
cmd.shape = function(targetDataset, opts) {
|
|
42048
42141
|
var geojson, dataset;
|
|
42049
42142
|
if (opts.coordinates) {
|
package/package.json
CHANGED
package/www/index.html
CHANGED
|
@@ -276,27 +276,11 @@ a smoother appearance.</div></div></div></div>
|
|
|
276
276
|
|
|
277
277
|
<div class="option-menu">
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
<div><label for="repair-intersections-opt"><input type="checkbox" checked class="checkbox" id="repair-intersections-opt"/>detect line intersections</label>
|
|
283
|
-
<div class="tip-button">?<div class="tip-anchor">
|
|
284
|
-
|
|
285
|
-
<div class="tip">Detect line intersections, including
|
|
286
|
-
self-intersections, to help identify
|
|
287
|
-
topological errors in a dataset.</div></div></div></div>
|
|
288
|
-
|
|
289
|
-
<!-- <div><label for="snap-points-opt"><input type="checkbox" class="checkbox" id="snap-points-opt" />snap vertices</label>
|
|
290
|
-
<div class="tip-button">?<div class="tip-anchor">
|
|
291
|
-
<div class="tip">Fix topology errors by snapping
|
|
292
|
-
together points with nearly identical
|
|
293
|
-
coordinates. This option does not
|
|
294
|
-
apply to TopoJSON files.</div></div></div></div> -->
|
|
295
|
-
<div style="height:5px"></div>
|
|
296
|
-
|
|
279
|
+
<div id="path-import-options">
|
|
280
|
+
<h4>Options</h4>
|
|
297
281
|
</div>
|
|
298
282
|
|
|
299
|
-
<div><input type="text" class="text-input advanced-options" placeholder="import options" />
|
|
283
|
+
<div><input type="text" class="text-input advanced-options" placeholder="command line import options" />
|
|
300
284
|
<a href="https://github.com/mbloch/mapshaper/wiki/Command-Reference#-i-input" target="_mapshaper_import_docs">
|
|
301
285
|
<div class="tip-button">?<div class="tip-anchor">
|
|
302
286
|
<div class="tip">Enter options from the command line
|
|
@@ -324,6 +308,7 @@ encoding=big5</span>. Click to see all options.</div></div></div></div>
|
|
|
324
308
|
<div class="mshp-main-map main-area map-area">
|
|
325
309
|
<div class="coordinate-info colored-text selectable"></div>
|
|
326
310
|
<div class="intersection-display">
|
|
311
|
+
<div class="intersection-check text-btn colored-text">Check line intersections</div>
|
|
327
312
|
<div class="intersection-count">0 line intersections</div>
|
|
328
313
|
<div class="repair-btn text-btn colored-text">Repair</div>
|
|
329
314
|
</div>
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -2101,8 +2101,6 @@
|
|
|
2101
2101
|
} else {
|
|
2102
2102
|
var freeform = El('#import-options .advanced-options').node().value;
|
|
2103
2103
|
importOpts = GUI.parseFreeformOptions(freeform, 'i');
|
|
2104
|
-
importOpts.no_repair = !El("#repair-intersections-opt").node().checked;
|
|
2105
|
-
// importOpts.snap = !!El("#snap-points-opt").node().checked;
|
|
2106
2104
|
}
|
|
2107
2105
|
return importOpts;
|
|
2108
2106
|
}
|
|
@@ -3495,117 +3493,208 @@
|
|
|
3495
3493
|
|
|
3496
3494
|
}
|
|
3497
3495
|
|
|
3496
|
+
// Test if map should be re-framed to show updated layer
|
|
3497
|
+
function mapNeedsReset(newBounds, prevBounds, viewportBounds, flags) {
|
|
3498
|
+
var viewportPct = getIntersectionPct(newBounds, viewportBounds);
|
|
3499
|
+
var contentPct = getIntersectionPct(viewportBounds, newBounds);
|
|
3500
|
+
var boundsChanged = !prevBounds.equals(newBounds);
|
|
3501
|
+
var inView = newBounds.intersects(viewportBounds);
|
|
3502
|
+
var areaChg = newBounds.area() / prevBounds.area();
|
|
3503
|
+
var chgThreshold = flags.proj ? 1e3 : 1e8;
|
|
3504
|
+
// don't reset if layer extent hasn't changed
|
|
3505
|
+
if (!boundsChanged) return false;
|
|
3506
|
+
// reset if layer is out-of-view
|
|
3507
|
+
if (!inView) return true;
|
|
3508
|
+
// reset if content is mostly offscreen
|
|
3509
|
+
if (viewportPct < 0.3 && contentPct < 0.9) return true;
|
|
3510
|
+
// reset if content bounds have changed a lot (e.g. after projection)
|
|
3511
|
+
if (areaChg > chgThreshold || areaChg < 1/chgThreshold) return true;
|
|
3512
|
+
return false;
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
// Test if an update may have affected the visible shape of arcs
|
|
3516
|
+
// @flags Flags from update event
|
|
3517
|
+
function arcsMayHaveChanged(flags) {
|
|
3518
|
+
return flags.simplify_method || flags.simplify || flags.proj ||
|
|
3519
|
+
flags.arc_count || flags.repair || flags.clip || flags.erase ||
|
|
3520
|
+
flags.slice || flags.affine || flags.rectangle || flags.buffer ||
|
|
3521
|
+
flags.union || flags.mosaic || flags.snap || flags.clean || flags.drop || false;
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
// check for operations that may change the number of self intersections in the
|
|
3525
|
+
// target layer.
|
|
3526
|
+
function intersectionsMayHaveChanged(flags) {
|
|
3527
|
+
return arcsMayHaveChanged(flags) || flags.select || flags['merge-layers'] ||
|
|
3528
|
+
flags.filter || flags.dissolve || flags.dissolve2;
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
// Test if an update allows hover popup to stay open
|
|
3532
|
+
function popupCanStayOpen(flags) {
|
|
3533
|
+
// keeping popup open after -drop geometry causes problems...
|
|
3534
|
+
// // if (arcsMayHaveChanged(flags)) return false;
|
|
3535
|
+
if (arcsMayHaveChanged(flags)) return false;
|
|
3536
|
+
if (flags.points || flags.proj) return false;
|
|
3537
|
+
if (!flags.same_table) return false;
|
|
3538
|
+
return true;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
// Returns proportion of bb2 occupied by bb1
|
|
3542
|
+
function getIntersectionPct(bb1, bb2) {
|
|
3543
|
+
return getBoundsIntersection(bb1, bb2).area() / bb2.area() || 0;
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
function getBoundsIntersection(a, b) {
|
|
3547
|
+
var c = new Bounds();
|
|
3548
|
+
if (a.intersects(b)) {
|
|
3549
|
+
c.setBounds(Math.max(a.xmin, b.xmin), Math.max(a.ymin, b.ymin),
|
|
3550
|
+
Math.min(a.xmax, b.xmax), Math.min(a.ymax, b.ymax));
|
|
3551
|
+
}
|
|
3552
|
+
return c;
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3498
3555
|
function RepairControl(gui) {
|
|
3499
3556
|
var map = gui.map,
|
|
3500
3557
|
model = gui.model,
|
|
3501
3558
|
el = gui.container.findChild(".intersection-display"),
|
|
3502
3559
|
readout = el.findChild(".intersection-count"),
|
|
3560
|
+
checkBtn = el.findChild(".intersection-check"),
|
|
3503
3561
|
repairBtn = el.findChild(".repair-btn"),
|
|
3504
|
-
//
|
|
3505
|
-
//
|
|
3506
|
-
|
|
3507
|
-
_currLayer,
|
|
3508
|
-
_currXX;
|
|
3562
|
+
_simplifiedXX, // saved simplified intersections, for repair
|
|
3563
|
+
_unsimplifiedXX, // saved unsimplified intersection data, for performance
|
|
3564
|
+
_disabled = false;
|
|
3509
3565
|
|
|
3510
|
-
gui.on('simplify_drag_start',
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
model.on('update', function(e) {
|
|
3514
|
-
var flags = e.flags;
|
|
3515
|
-
var intersectionsMayHaveChanged = flags.simplify || flags.proj ||
|
|
3516
|
-
flags.arc_count || flags.snap || flags.affine || flags.points || flags['merge-layers'];
|
|
3517
|
-
if (intersectionsMayHaveChanged) {
|
|
3518
|
-
// delete any cached intersection data, to trigger re-calculation
|
|
3519
|
-
e.dataset._intersections = null;
|
|
3520
|
-
updateAsync();
|
|
3521
|
-
} else if (flags.select) {
|
|
3522
|
-
// new active layer, but no editing commands were run -- use cached intersections (if available)
|
|
3523
|
-
updateAsync();
|
|
3566
|
+
gui.on('simplify_drag_start', function() {
|
|
3567
|
+
if (intersectionsAreOn()) {
|
|
3568
|
+
hide();
|
|
3524
3569
|
}
|
|
3525
3570
|
});
|
|
3526
3571
|
|
|
3572
|
+
gui.on('simplify_drag_end', function() {
|
|
3573
|
+
updateAsync();
|
|
3574
|
+
});
|
|
3575
|
+
|
|
3576
|
+
checkBtn.on('click', function() {
|
|
3577
|
+
checkBtn.hide();
|
|
3578
|
+
refreshSync();
|
|
3579
|
+
});
|
|
3580
|
+
|
|
3527
3581
|
repairBtn.on('click', function() {
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3582
|
+
var e = model.getActiveLayer();
|
|
3583
|
+
if (!_simplifiedXX || !e.dataset.arcs) return;
|
|
3584
|
+
var xx = _simplifiedXX = internal.repairIntersections(e.dataset.arcs, _simplifiedXX);
|
|
3585
|
+
showIntersections(xx, e.layer, e.dataset.arcs);
|
|
3586
|
+
repairBtn.hide();
|
|
3531
3587
|
model.updated({repair: true});
|
|
3532
3588
|
gui.session.simplificationRepair();
|
|
3533
3589
|
});
|
|
3534
3590
|
|
|
3535
|
-
function
|
|
3536
|
-
|
|
3537
|
-
|
|
3591
|
+
model.on('update', function(e) {
|
|
3592
|
+
if (!intersectionsAreOn()) {
|
|
3593
|
+
reset(); // need this?
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3596
|
+
var needRefresh = e.flags.simplify_method || e.flags.simplify || e.flags.repair;
|
|
3597
|
+
if (needRefresh) {
|
|
3598
|
+
updateAsync();
|
|
3599
|
+
} else if (e.flags.simplify_amount) {
|
|
3600
|
+
// slider is being dragged - hide readout and dots, retain data
|
|
3601
|
+
hide();
|
|
3602
|
+
} else if (intersectionsMayHaveChanged(e.flags)) {
|
|
3603
|
+
// intersections may have changed -- reset the display
|
|
3604
|
+
reset();
|
|
3605
|
+
} else {
|
|
3606
|
+
// keep displaying the current intersections
|
|
3607
|
+
}
|
|
3608
|
+
});
|
|
3609
|
+
|
|
3610
|
+
function intersectionsAreOn() {
|
|
3611
|
+
return !!(_simplifiedXX || _unsimplifiedXX);
|
|
3538
3612
|
}
|
|
3539
3613
|
|
|
3540
|
-
function
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
return !opts.no_repair && !info.no_intersections;
|
|
3614
|
+
function clearSavedData() {
|
|
3615
|
+
_simplifiedXX = null;
|
|
3616
|
+
_unsimplifiedXX = null;
|
|
3544
3617
|
}
|
|
3545
3618
|
|
|
3546
|
-
|
|
3547
|
-
|
|
3619
|
+
function reset() {
|
|
3620
|
+
clearSavedData();
|
|
3621
|
+
hide();
|
|
3622
|
+
if (_disabled) {
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
var e = model.getActiveLayer();
|
|
3626
|
+
if (internal.layerHasPaths(e.layer)) {
|
|
3627
|
+
el.show();
|
|
3628
|
+
checkBtn.show();
|
|
3629
|
+
readout.hide();
|
|
3630
|
+
repairBtn.hide();
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
function dismissForever() {
|
|
3635
|
+
_disabled = true;
|
|
3636
|
+
clearSavedData();
|
|
3637
|
+
hide();
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
function hide() {
|
|
3641
|
+
map.setIntersectionLayer(null);
|
|
3642
|
+
el.hide();
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
// Update intersection display, after a short delay so map can redraw after previous
|
|
3646
|
+
// operation (e.g. simplification change)
|
|
3548
3647
|
function updateAsync() {
|
|
3549
|
-
|
|
3550
|
-
|
|
3648
|
+
if (intersectionsAreOn()) {
|
|
3649
|
+
setTimeout(refreshSync, 10);
|
|
3650
|
+
}
|
|
3551
3651
|
}
|
|
3552
3652
|
|
|
3553
|
-
function
|
|
3653
|
+
function refreshSync() {
|
|
3554
3654
|
var e = model.getActiveLayer();
|
|
3555
|
-
var
|
|
3556
|
-
var
|
|
3557
|
-
var XX, showBtn;
|
|
3558
|
-
var opts = {
|
|
3655
|
+
var arcs = e.dataset && e.dataset.arcs;
|
|
3656
|
+
var intersectionOpts = {
|
|
3559
3657
|
unique: true,
|
|
3560
3658
|
tolerance: 0
|
|
3561
3659
|
};
|
|
3562
|
-
if (!arcs || !internal.layerHasPaths(e.layer)
|
|
3660
|
+
if (!arcs || !internal.layerHasPaths(e.layer)) {
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3563
3663
|
if (arcs.getRetainedInterval() > 0) {
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
// cache intersections at 0 simplification, to avoid recalculating
|
|
3664
|
+
_simplifiedXX = internal.findSegmentIntersections(arcs, intersectionOpts);
|
|
3665
|
+
} else {
|
|
3666
|
+
// no simplification
|
|
3667
|
+
_simplifiedXX = null; // clear old simplified XX
|
|
3668
|
+
if (!_unsimplifiedXX) {
|
|
3669
|
+
// save intersections at 0 simplification, to avoid recalculating
|
|
3571
3670
|
// every time the simplification slider is set to 100% or the layer is selected at 100%
|
|
3572
|
-
|
|
3671
|
+
_unsimplifiedXX = internal.findSegmentIntersections(arcs, intersectionOpts);
|
|
3573
3672
|
}
|
|
3574
|
-
showBtn = false;
|
|
3575
3673
|
}
|
|
3576
|
-
|
|
3577
|
-
_currLayer = e.layer;
|
|
3578
|
-
_currArcs = arcs;
|
|
3579
|
-
_currXX = XX;
|
|
3580
|
-
showIntersections();
|
|
3581
|
-
repairBtn.classed('disabled', !showBtn);
|
|
3674
|
+
showIntersections(_simplifiedXX || _unsimplifiedXX, e.layer, arcs);
|
|
3582
3675
|
}
|
|
3583
3676
|
|
|
3584
|
-
function
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
hide();
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
function showIntersections() {
|
|
3599
|
-
var n = _currXX.length, pointLyr;
|
|
3600
|
-
if (n > 0) {
|
|
3601
|
-
// console.log("first intersection:", internal.getIntersectionDebugData(XX[0], arcs));
|
|
3602
|
-
pointLyr = internal.getIntersectionLayer(_currXX, _currLayer, _currArcs);
|
|
3677
|
+
function showIntersections(xx, lyr, arcs) {
|
|
3678
|
+
var pointLyr, count = 0;
|
|
3679
|
+
el.show();
|
|
3680
|
+
readout.show();
|
|
3681
|
+
checkBtn.hide();
|
|
3682
|
+
if (xx.length > 0) {
|
|
3683
|
+
pointLyr = internal.getIntersectionLayer(xx, lyr, arcs);
|
|
3684
|
+
count = internal.countPointsInLayer(pointLyr);
|
|
3685
|
+
}
|
|
3686
|
+
if (count == 0) {
|
|
3687
|
+
map.setIntersectionLayer(null);
|
|
3688
|
+
readout.html('<span class="icon black"></span>No self-intersections');
|
|
3689
|
+
} else {
|
|
3603
3690
|
map.setIntersectionLayer(pointLyr, {layers:[pointLyr]});
|
|
3604
|
-
readout.html(utils$1.format('<span class="icon"></span>%s line intersection%s <img class="close-btn" src="images/close.png">',
|
|
3605
|
-
readout.findChild('.close-btn').on('click',
|
|
3691
|
+
readout.html(utils$1.format('<span class="icon"></span>%s line intersection%s <img class="close-btn" src="images/close.png">', count, utils$1.pluralSuffix(count)));
|
|
3692
|
+
readout.findChild('.close-btn').on('click', dismissForever);
|
|
3693
|
+
}
|
|
3694
|
+
if (_simplifiedXX && count > 0) {
|
|
3695
|
+
repairBtn.show();
|
|
3606
3696
|
} else {
|
|
3607
|
-
|
|
3608
|
-
readout.html('');
|
|
3697
|
+
repairBtn.hide();
|
|
3609
3698
|
}
|
|
3610
3699
|
}
|
|
3611
3700
|
}
|
|
@@ -8472,39 +8561,6 @@
|
|
|
8472
8561
|
return _self;
|
|
8473
8562
|
}
|
|
8474
8563
|
|
|
8475
|
-
// Test if map should be re-framed to show updated layer
|
|
8476
|
-
function mapNeedsReset(newBounds, prevBounds, viewportBounds, flags) {
|
|
8477
|
-
var viewportPct = getIntersectionPct(newBounds, viewportBounds);
|
|
8478
|
-
var contentPct = getIntersectionPct(viewportBounds, newBounds);
|
|
8479
|
-
var boundsChanged = !prevBounds.equals(newBounds);
|
|
8480
|
-
var inView = newBounds.intersects(viewportBounds);
|
|
8481
|
-
var areaChg = newBounds.area() / prevBounds.area();
|
|
8482
|
-
var chgThreshold = flags.proj ? 1e3 : 1e8;
|
|
8483
|
-
// don't reset if layer extent hasn't changed
|
|
8484
|
-
if (!boundsChanged) return false;
|
|
8485
|
-
// reset if layer is out-of-view
|
|
8486
|
-
if (!inView) return true;
|
|
8487
|
-
// reset if content is mostly offscreen
|
|
8488
|
-
if (viewportPct < 0.3 && contentPct < 0.9) return true;
|
|
8489
|
-
// reset if content bounds have changed a lot (e.g. after projection)
|
|
8490
|
-
if (areaChg > chgThreshold || areaChg < 1/chgThreshold) return true;
|
|
8491
|
-
return false;
|
|
8492
|
-
}
|
|
8493
|
-
|
|
8494
|
-
// Returns proportion of bb2 occupied by bb1
|
|
8495
|
-
function getIntersectionPct(bb1, bb2) {
|
|
8496
|
-
return getBoundsIntersection(bb1, bb2).area() / bb2.area() || 0;
|
|
8497
|
-
}
|
|
8498
|
-
|
|
8499
|
-
function getBoundsIntersection(a, b) {
|
|
8500
|
-
var c = new Bounds();
|
|
8501
|
-
if (a.intersects(b)) {
|
|
8502
|
-
c.setBounds(Math.max(a.xmin, b.xmin), Math.max(a.ymin, b.ymin),
|
|
8503
|
-
Math.min(a.xmax, b.xmax), Math.min(a.ymax, b.ymax));
|
|
8504
|
-
}
|
|
8505
|
-
return c;
|
|
8506
|
-
}
|
|
8507
|
-
|
|
8508
8564
|
function isMultilineLabel(textNode) {
|
|
8509
8565
|
return textNode.childNodes.length > 1;
|
|
8510
8566
|
}
|
|
@@ -11297,25 +11353,6 @@
|
|
|
11297
11353
|
};
|
|
11298
11354
|
}
|
|
11299
11355
|
|
|
11300
|
-
// Test if an update may have affected the visible shape of arcs
|
|
11301
|
-
// @flags Flags from update event
|
|
11302
|
-
function arcsMayHaveChanged(flags) {
|
|
11303
|
-
return flags.simplify_method || flags.simplify || flags.proj ||
|
|
11304
|
-
flags.arc_count || flags.repair || flags.clip || flags.erase ||
|
|
11305
|
-
flags.slice || flags.affine || flags.rectangle || flags.buffer ||
|
|
11306
|
-
flags.union || flags.mosaic || flags.snap || flags.clean || flags.drop || false;
|
|
11307
|
-
}
|
|
11308
|
-
|
|
11309
|
-
// Test if an update allows hover popup to stay open
|
|
11310
|
-
function popupCanStayOpen(flags) {
|
|
11311
|
-
// keeping popup open after -drop geometry causes problems...
|
|
11312
|
-
// // if (arcsMayHaveChanged(flags)) return false;
|
|
11313
|
-
if (arcsMayHaveChanged(flags)) return false;
|
|
11314
|
-
if (flags.points || flags.proj) return false;
|
|
11315
|
-
if (!flags.same_table) return false;
|
|
11316
|
-
return true;
|
|
11317
|
-
}
|
|
11318
|
-
|
|
11319
11356
|
|
|
11320
11357
|
// Update map frame after user navigates the map in frame edit mode
|
|
11321
11358
|
function updateFrameExtent() {
|
package/www/mapshaper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var VERSION = "0.6.
|
|
3
|
+
var VERSION = "0.6.54";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var utils = /*#__PURE__*/Object.freeze({
|
|
@@ -11075,7 +11075,7 @@
|
|
|
11075
11075
|
}
|
|
11076
11076
|
|
|
11077
11077
|
function isGzipFile(file) {
|
|
11078
|
-
return /\.gz
|
|
11078
|
+
return /\.gz$/i.test(file);
|
|
11079
11079
|
}
|
|
11080
11080
|
|
|
11081
11081
|
function isSupportedOutputFormat(fmt) {
|
|
@@ -23703,14 +23703,14 @@ ${svg}
|
|
|
23703
23703
|
.option('geojson-type', {
|
|
23704
23704
|
describe: '[GeoJSON] FeatureCollection, GeometryCollection or Feature'
|
|
23705
23705
|
})
|
|
23706
|
-
.option('ndjson', {
|
|
23707
|
-
describe: '[GeoJSON/JSON] output newline-delimited features or records',
|
|
23708
|
-
type: 'flag'
|
|
23709
|
-
})
|
|
23710
23706
|
.option('hoist', {
|
|
23711
23707
|
describe: '[GeoJSON] move properties to the root level of each Feature',
|
|
23712
23708
|
type: 'strings'
|
|
23713
23709
|
})
|
|
23710
|
+
.option('ndjson', {
|
|
23711
|
+
describe: '[GeoJSON/JSON] output newline-delimited features or records',
|
|
23712
|
+
type: 'flag'
|
|
23713
|
+
})
|
|
23714
23714
|
.option('width', {
|
|
23715
23715
|
describe: '[SVG/TopoJSON] pixel width of output (SVG default is 800)',
|
|
23716
23716
|
type: 'number'
|
|
@@ -23865,10 +23865,6 @@ ${svg}
|
|
|
23865
23865
|
describe: 'a pair of values (0-100) for limiting a color ramp',
|
|
23866
23866
|
type: 'numbers'
|
|
23867
23867
|
})
|
|
23868
|
-
.option('range', {
|
|
23869
|
-
// describe: 'a pair of numbers defining the effective data range',
|
|
23870
|
-
type: 'numbers'
|
|
23871
|
-
})
|
|
23872
23868
|
.option('null-value', {
|
|
23873
23869
|
describe: 'value (or color) to use for invalid or missing data'
|
|
23874
23870
|
})
|
|
@@ -23895,6 +23891,11 @@ ${svg}
|
|
|
23895
23891
|
describe: 'user-defined sequential class breaks',
|
|
23896
23892
|
type: 'numbers'
|
|
23897
23893
|
})
|
|
23894
|
+
.option('outer-breaks', {
|
|
23895
|
+
describe: 'min,max breakpoints, to limit the effect of outliers',
|
|
23896
|
+
old_alias: 'range',
|
|
23897
|
+
type: 'numbers'
|
|
23898
|
+
})
|
|
23898
23899
|
.option('classes', {
|
|
23899
23900
|
describe: 'number of classes (can be inferred from other options)',
|
|
23900
23901
|
type: 'integer'
|
|
@@ -23904,7 +23905,7 @@ ${svg}
|
|
|
23904
23905
|
type: 'flag'
|
|
23905
23906
|
})
|
|
23906
23907
|
.option('continuous', {
|
|
23907
|
-
describe: 'output
|
|
23908
|
+
describe: 'output interpolated values, for unclassed colors',
|
|
23908
23909
|
type: 'flag'
|
|
23909
23910
|
})
|
|
23910
23911
|
.option('index-field', {
|
|
@@ -25337,7 +25338,7 @@ ${svg}
|
|
|
25337
25338
|
.describe('create commands on-the-fly and run them')
|
|
25338
25339
|
.option('expression', {
|
|
25339
25340
|
DEFAULT: true,
|
|
25340
|
-
describe: 'JS expression to generate command(s)'
|
|
25341
|
+
describe: 'JS expression or template to generate command(s)'
|
|
25341
25342
|
})
|
|
25342
25343
|
// deprecated
|
|
25343
25344
|
.option('commands', {alias_to: 'expression'})
|
|
@@ -32134,23 +32135,22 @@ ${svg}
|
|
|
32134
32135
|
}
|
|
32135
32136
|
|
|
32136
32137
|
var ascending = getAscendingNumbers(dataValues);
|
|
32137
|
-
if (opts.
|
|
32138
|
-
ascending = applyDataRange(ascending, opts.
|
|
32138
|
+
if (opts.outer_breaks) {
|
|
32139
|
+
ascending = applyDataRange(ascending, opts.outer_breaks);
|
|
32139
32140
|
}
|
|
32140
32141
|
var nullCount = dataValues.length - ascending.length;
|
|
32141
32142
|
var minVal = ascending[0];
|
|
32142
32143
|
var maxVal = ascending[ascending.length - 1];
|
|
32143
32144
|
|
|
32144
|
-
|
|
32145
|
-
|
|
32146
|
-
if (val
|
|
32147
|
-
if (val > opts.range[1]) val = opts.range[1];
|
|
32145
|
+
var clamp = opts.outer_breaks ? function(val) {
|
|
32146
|
+
if (val < opts.outer_breaks[0]) val = opts.outer_breaks[0];
|
|
32147
|
+
if (val > opts.outer_breaks[1]) val = opts.outer_breaks[1];
|
|
32148
32148
|
return val;
|
|
32149
32149
|
} : null;
|
|
32150
32150
|
|
|
32151
|
-
if (opts.
|
|
32152
|
-
minVal = opts.
|
|
32153
|
-
maxVal = opts.
|
|
32151
|
+
if (opts.outer_breaks) {
|
|
32152
|
+
minVal = opts.outer_breaks[0];
|
|
32153
|
+
maxVal = opts.outer_breaks[1];
|
|
32154
32154
|
}
|
|
32155
32155
|
|
|
32156
32156
|
if (numBreaks === 0) {
|
|
@@ -41842,6 +41842,185 @@ ${svg}
|
|
|
41842
41842
|
}, {});
|
|
41843
41843
|
}
|
|
41844
41844
|
|
|
41845
|
+
// import { importGeoJSON } from '../geojson/geojson-import';
|
|
41846
|
+
|
|
41847
|
+
function getTargetProxy(target) {
|
|
41848
|
+
var lyr = target.layers[0];
|
|
41849
|
+
var data = getLayerInfo(lyr, target.dataset); // layer_name, feature_count etc
|
|
41850
|
+
data.layer = lyr;
|
|
41851
|
+
data.dataset = target.dataset;
|
|
41852
|
+
addGetters(data, {
|
|
41853
|
+
// export as an object, not a string or buffer
|
|
41854
|
+
geojson: getGeoJSON
|
|
41855
|
+
});
|
|
41856
|
+
|
|
41857
|
+
function getGeoJSON() {
|
|
41858
|
+
var features = exportLayerAsGeoJSON(lyr, target.dataset, {rfc7946: true}, true);
|
|
41859
|
+
return {
|
|
41860
|
+
type: 'FeatureCollection',
|
|
41861
|
+
features: features
|
|
41862
|
+
};
|
|
41863
|
+
}
|
|
41864
|
+
|
|
41865
|
+
return data;
|
|
41866
|
+
}
|
|
41867
|
+
|
|
41868
|
+
// Support for evaluating expressions embedded in curly-brace templates
|
|
41869
|
+
|
|
41870
|
+
// Returns: a string (e.g. a command string used by the -run command)
|
|
41871
|
+
async function evalTemplateExpression(expression, targets, ctx) {
|
|
41872
|
+
ctx = ctx || getBaseContext();
|
|
41873
|
+
// TODO: throw an error if target is used when there are multiple targets
|
|
41874
|
+
if (targets && targets.length == 1) {
|
|
41875
|
+
Object.defineProperty(ctx, 'target', {value: getTargetProxy(targets[0])});
|
|
41876
|
+
}
|
|
41877
|
+
// Add global functions and data to the expression context
|
|
41878
|
+
// (e.g. functions imported via the -require command)
|
|
41879
|
+
var globals = getStashedVar('defs') || {};
|
|
41880
|
+
ctx.global = globals;
|
|
41881
|
+
utils.extend(ctx, ctx.global);
|
|
41882
|
+
|
|
41883
|
+
var output = await compileTemplate(expression, ctx);
|
|
41884
|
+
if (hasFunctionCall(output, ctx)) {
|
|
41885
|
+
// also evaluate function calls that are not enclosed in curly braces
|
|
41886
|
+
// (convenience syntax)
|
|
41887
|
+
output = await evalExpression(output, ctx);
|
|
41888
|
+
}
|
|
41889
|
+
return output;
|
|
41890
|
+
}
|
|
41891
|
+
|
|
41892
|
+
async function compileTemplate(template, ctx) {
|
|
41893
|
+
var subExpressions = parseTemplate(template);
|
|
41894
|
+
var promises = subExpressions.map(expr => evalExpression(expr, ctx));
|
|
41895
|
+
var replacements = await Promise.all(promises);
|
|
41896
|
+
return applyReplacements(template, replacements);
|
|
41897
|
+
}
|
|
41898
|
+
|
|
41899
|
+
async function evalExpression(expression, ctx) {
|
|
41900
|
+
var output;
|
|
41901
|
+
try {
|
|
41902
|
+
output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
|
|
41903
|
+
} catch(e) {
|
|
41904
|
+
stop(e.name, 'in JS source:', e.message);
|
|
41905
|
+
}
|
|
41906
|
+
return output;
|
|
41907
|
+
}
|
|
41908
|
+
|
|
41909
|
+
// Returns array of 0 or more embedded curly-brace expressions
|
|
41910
|
+
function parseTemplate(str) {
|
|
41911
|
+
var arr = [];
|
|
41912
|
+
parseTemplateParts(str).forEach(function(s, i) {
|
|
41913
|
+
if (i % 2 == 1) {
|
|
41914
|
+
arr.push(s.substring(1, s.length-1)); // remove braces
|
|
41915
|
+
}
|
|
41916
|
+
});
|
|
41917
|
+
return arr;
|
|
41918
|
+
}
|
|
41919
|
+
|
|
41920
|
+
// template: template string
|
|
41921
|
+
// replacements: array of strings or values that can be coerced to strings
|
|
41922
|
+
function applyReplacements(template, replacements) {
|
|
41923
|
+
var parts = parseTemplateParts(template);
|
|
41924
|
+
return parts.reduce(function(memo, s, i) {
|
|
41925
|
+
return i % 2 == 1 ? memo + (replacements.shift() || '') : memo + s;
|
|
41926
|
+
}, '');
|
|
41927
|
+
}
|
|
41928
|
+
|
|
41929
|
+
// Divides a string into substrings; even-index strings contain literal strings,
|
|
41930
|
+
// Odd-indexed strings contain curly-brace-delimited template expressions.
|
|
41931
|
+
// JSON objects are treated as literal strings; other top-level curly braces are
|
|
41932
|
+
// assumed to be embedded expressions.
|
|
41933
|
+
// For example: parseTemplateParts('{"hello"}, world!') => ['', '{"hello"}', ', world!']
|
|
41934
|
+
//
|
|
41935
|
+
function parseTemplateParts(str) {
|
|
41936
|
+
// TODO: consider adding \ escapes
|
|
41937
|
+
var depth=0;
|
|
41938
|
+
var parts = [];
|
|
41939
|
+
var part = '';
|
|
41940
|
+
var c;
|
|
41941
|
+
|
|
41942
|
+
for (var i=0, n=str.length; i<n; i++) {
|
|
41943
|
+
c = str.charAt(i);
|
|
41944
|
+
if (c == '{') {
|
|
41945
|
+
if (depth == 0) {
|
|
41946
|
+
parts.push(part);
|
|
41947
|
+
part = '';
|
|
41948
|
+
}
|
|
41949
|
+
depth++;
|
|
41950
|
+
}
|
|
41951
|
+
part += c;
|
|
41952
|
+
if (c == '}') {
|
|
41953
|
+
depth--;
|
|
41954
|
+
if (depth < 0) {
|
|
41955
|
+
// unexpected... throw an error?
|
|
41956
|
+
depth++;
|
|
41957
|
+
} else if (depth == 0 && isValidJSON(part)) {
|
|
41958
|
+
// embedded JSON objects are not template parts -- undo
|
|
41959
|
+
part = parts.pop() + part;
|
|
41960
|
+
} else if (depth == 0) {
|
|
41961
|
+
parts.push(part);
|
|
41962
|
+
part = '';
|
|
41963
|
+
}
|
|
41964
|
+
}
|
|
41965
|
+
}
|
|
41966
|
+
parts.push(part);
|
|
41967
|
+
return parts;
|
|
41968
|
+
}
|
|
41969
|
+
|
|
41970
|
+
// Tests if an expression string contains a call to an indexed function
|
|
41971
|
+
// defs: Object containing functions indexed by function name
|
|
41972
|
+
function hasFunctionCall(str, defs) {
|
|
41973
|
+
var rxp = /([$_a-z][$_a-z0-9]*)\(/ig;
|
|
41974
|
+
return Array.from(str.matchAll(rxp)).some(match => match[1] in defs);
|
|
41975
|
+
}
|
|
41976
|
+
|
|
41977
|
+
function isValidJSON(str) {
|
|
41978
|
+
try {
|
|
41979
|
+
JSON.parse(str);
|
|
41980
|
+
} catch(e) {
|
|
41981
|
+
return false;
|
|
41982
|
+
}
|
|
41983
|
+
return true;
|
|
41984
|
+
}
|
|
41985
|
+
|
|
41986
|
+
cmd.require = async function(targets, opts) {
|
|
41987
|
+
var defs = getStashedVar('defs');
|
|
41988
|
+
var moduleFile, moduleName, mod;
|
|
41989
|
+
if (!opts.module) {
|
|
41990
|
+
stop("Missing module name or path to module");
|
|
41991
|
+
}
|
|
41992
|
+
if (cli.isFile(opts.module)) {
|
|
41993
|
+
moduleFile = opts.module;
|
|
41994
|
+
} else if (cli.isFile(opts.module + '.js')) {
|
|
41995
|
+
moduleFile = opts.module + '.js';
|
|
41996
|
+
} else {
|
|
41997
|
+
moduleName = opts.module;
|
|
41998
|
+
}
|
|
41999
|
+
if (moduleFile && !require$1('path').isAbsolute(moduleFile)) {
|
|
42000
|
+
moduleFile = require$1('path').join(process.cwd(), moduleFile);
|
|
42001
|
+
}
|
|
42002
|
+
try {
|
|
42003
|
+
mod = require$1(moduleFile || moduleName);
|
|
42004
|
+
if (typeof mod == 'function') {
|
|
42005
|
+
// -require now includes the functionality of the old -external command
|
|
42006
|
+
var retn = mod(api);
|
|
42007
|
+
if (retn && isValidExternalCommand(retn)) {
|
|
42008
|
+
cmd.registerCommand(retn.name, retn);
|
|
42009
|
+
}
|
|
42010
|
+
}
|
|
42011
|
+
} catch(e) {
|
|
42012
|
+
stop('Unable to load external module:', e.message, getErrorDetail(e));
|
|
42013
|
+
}
|
|
42014
|
+
if (moduleName || opts.alias) {
|
|
42015
|
+
defs[opts.alias || moduleName] = mod;
|
|
42016
|
+
} else {
|
|
42017
|
+
Object.assign(defs, mod);
|
|
42018
|
+
}
|
|
42019
|
+
if (opts.init) {
|
|
42020
|
+
await evalTemplateExpression(opts.init, targets);
|
|
42021
|
+
}
|
|
42022
|
+
};
|
|
42023
|
+
|
|
41845
42024
|
// Parse an array or a string of command line tokens into an array of
|
|
41846
42025
|
// command objects.
|
|
41847
42026
|
function parseCommands(tokens) {
|
|
@@ -41894,29 +42073,6 @@ ${svg}
|
|
|
41894
42073
|
parseConsoleCommands: parseConsoleCommands
|
|
41895
42074
|
});
|
|
41896
42075
|
|
|
41897
|
-
// import { importGeoJSON } from '../geojson/geojson-import';
|
|
41898
|
-
|
|
41899
|
-
function getTargetProxy(target) {
|
|
41900
|
-
var lyr = target.layers[0];
|
|
41901
|
-
var data = getLayerInfo(lyr, target.dataset); // layer_name, feature_count etc
|
|
41902
|
-
data.layer = lyr;
|
|
41903
|
-
data.dataset = target.dataset;
|
|
41904
|
-
addGetters(data, {
|
|
41905
|
-
// export as an object, not a string or buffer
|
|
41906
|
-
geojson: getGeoJSON
|
|
41907
|
-
});
|
|
41908
|
-
|
|
41909
|
-
function getGeoJSON() {
|
|
41910
|
-
var features = exportLayerAsGeoJSON(lyr, target.dataset, {rfc7946: true}, true);
|
|
41911
|
-
return {
|
|
41912
|
-
type: 'FeatureCollection',
|
|
41913
|
-
features: features
|
|
41914
|
-
};
|
|
41915
|
-
}
|
|
41916
|
-
|
|
41917
|
-
return data;
|
|
41918
|
-
}
|
|
41919
|
-
|
|
41920
42076
|
function getIOProxy(job) {
|
|
41921
42077
|
var obj = {
|
|
41922
42078
|
_cache: {}
|
|
@@ -41955,20 +42111,14 @@ ${svg}
|
|
|
41955
42111
|
// }
|
|
41956
42112
|
|
|
41957
42113
|
cmd.run = async function(job, targets, opts) {
|
|
41958
|
-
var tmp, commands;
|
|
42114
|
+
var tmp, commands, ctx;
|
|
41959
42115
|
if (!opts.expression) {
|
|
41960
42116
|
stop("Missing expression parameter");
|
|
41961
42117
|
}
|
|
41962
|
-
|
|
42118
|
+
ctx = getBaseContext();
|
|
41963
42119
|
// io proxy adds ability to add datasets dynamically in a required function
|
|
41964
|
-
var ctx = getBaseContext();
|
|
41965
42120
|
ctx.io = getIOProxy();
|
|
41966
|
-
tmp =
|
|
41967
|
-
|
|
41968
|
-
// Support async functions as expressions
|
|
41969
|
-
if (utils.isPromise(tmp)) {
|
|
41970
|
-
tmp = await tmp;
|
|
41971
|
-
}
|
|
42121
|
+
tmp = await evalTemplateExpression(opts.expression, targets, ctx);
|
|
41972
42122
|
if (tmp && !utils.isString(tmp)) {
|
|
41973
42123
|
stop('Expected a string containing mapshaper commands; received:', tmp);
|
|
41974
42124
|
}
|
|
@@ -41987,63 +42137,6 @@ ${svg}
|
|
|
41987
42137
|
}
|
|
41988
42138
|
};
|
|
41989
42139
|
|
|
41990
|
-
// This could return a Promise or a value or nothing
|
|
41991
|
-
function runGlobalExpression(expression, targets, ctx) {
|
|
41992
|
-
ctx = ctx || getBaseContext();
|
|
41993
|
-
var output;
|
|
41994
|
-
// TODO: throw an informative error if target is used when there are multiple targets
|
|
41995
|
-
if (targets && targets.length == 1) {
|
|
41996
|
-
Object.defineProperty(ctx, 'target', {value: getTargetProxy(targets[0])});
|
|
41997
|
-
}
|
|
41998
|
-
// Add defined functions and data to the expression context
|
|
41999
|
-
// (Such as functions imported via the -require command)
|
|
42000
|
-
utils.extend(ctx, getStashedVar('defs'));
|
|
42001
|
-
try {
|
|
42002
|
-
output = Function('ctx', 'with(ctx) {return (' + expression + ');}').call({}, ctx);
|
|
42003
|
-
} catch(e) {
|
|
42004
|
-
stop(e.name, 'in JS source:', e.message);
|
|
42005
|
-
}
|
|
42006
|
-
return output;
|
|
42007
|
-
}
|
|
42008
|
-
|
|
42009
|
-
cmd.require = function(targets, opts) {
|
|
42010
|
-
var defs = getStashedVar('defs');
|
|
42011
|
-
var moduleFile, moduleName, mod;
|
|
42012
|
-
if (!opts.module) {
|
|
42013
|
-
stop("Missing module name or path to module");
|
|
42014
|
-
}
|
|
42015
|
-
if (cli.isFile(opts.module)) {
|
|
42016
|
-
moduleFile = opts.module;
|
|
42017
|
-
} else if (cli.isFile(opts.module + '.js')) {
|
|
42018
|
-
moduleFile = opts.module + '.js';
|
|
42019
|
-
} else {
|
|
42020
|
-
moduleName = opts.module;
|
|
42021
|
-
}
|
|
42022
|
-
if (moduleFile && !require$1('path').isAbsolute(moduleFile)) {
|
|
42023
|
-
moduleFile = require$1('path').join(process.cwd(), moduleFile);
|
|
42024
|
-
}
|
|
42025
|
-
try {
|
|
42026
|
-
mod = require$1(moduleFile || moduleName);
|
|
42027
|
-
if (typeof mod == 'function') {
|
|
42028
|
-
// -require now includes the functionality of the old -external command
|
|
42029
|
-
var retn = mod(api);
|
|
42030
|
-
if (retn && isValidExternalCommand(retn)) {
|
|
42031
|
-
cmd.registerCommand(retn.name, retn);
|
|
42032
|
-
}
|
|
42033
|
-
}
|
|
42034
|
-
} catch(e) {
|
|
42035
|
-
stop('Unable to load external module:', e.message, getErrorDetail(e));
|
|
42036
|
-
}
|
|
42037
|
-
if (moduleName || opts.alias) {
|
|
42038
|
-
defs[opts.alias || moduleName] = mod;
|
|
42039
|
-
} else {
|
|
42040
|
-
Object.assign(defs, mod);
|
|
42041
|
-
}
|
|
42042
|
-
if (opts.init) {
|
|
42043
|
-
runGlobalExpression(opts.init, targets);
|
|
42044
|
-
}
|
|
42045
|
-
};
|
|
42046
|
-
|
|
42047
42140
|
cmd.shape = function(targetDataset, opts) {
|
|
42048
42141
|
var geojson, dataset;
|
|
42049
42142
|
if (opts.coordinates) {
|
package/www/page.css
CHANGED
|
@@ -468,6 +468,11 @@ body.dragover #import-options-drop-area .drop-area {
|
|
|
468
468
|
margin: 0 0 5px 0;
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
::placeholder {
|
|
472
|
+
color: #aaa;
|
|
473
|
+
opacity: 1;
|
|
474
|
+
}
|
|
475
|
+
|
|
471
476
|
|
|
472
477
|
#mshp-not-supported {
|
|
473
478
|
display: none;
|
|
@@ -958,6 +963,10 @@ img.close-btn:hover,
|
|
|
958
963
|
vertical-align: middle;
|
|
959
964
|
}
|
|
960
965
|
|
|
966
|
+
.intersection-check {
|
|
967
|
+
display: none;
|
|
968
|
+
}
|
|
969
|
+
|
|
961
970
|
.intersection-count .icon {
|
|
962
971
|
background-color: #F24400;
|
|
963
972
|
display: inline-block;
|
|
@@ -966,6 +975,10 @@ img.close-btn:hover,
|
|
|
966
975
|
margin: 0 5px 2px 0;
|
|
967
976
|
}
|
|
968
977
|
|
|
978
|
+
.intersection-count .icon.black {
|
|
979
|
+
background-color: black;
|
|
980
|
+
}
|
|
981
|
+
|
|
969
982
|
.intersection-count .close-btn {
|
|
970
983
|
width: 16px;
|
|
971
984
|
height: 16px;
|
|
@@ -975,9 +988,9 @@ img.close-btn:hover,
|
|
|
975
988
|
margin-left: 2px;
|
|
976
989
|
}
|
|
977
990
|
|
|
978
|
-
|
|
991
|
+
/*.intersection-display .text-btn.disabled {
|
|
979
992
|
visibility: hidden;
|
|
980
|
-
}
|
|
993
|
+
}*/
|
|
981
994
|
|
|
982
995
|
/* --- Popup -------------------- */
|
|
983
996
|
|