mapshaper 0.6.98 → 0.6.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +793 -610
- package/package.json +1 -1
- package/www/mapshaper-gui.js +366 -263
- package/www/mapshaper.js +793 -610
- package/www/page.css +4 -3
package/mapshaper.js
CHANGED
|
@@ -5165,12 +5165,12 @@
|
|
|
5165
5165
|
return getDatasetCrsInfo(dataset).crs;
|
|
5166
5166
|
}
|
|
5167
5167
|
|
|
5168
|
-
function requireDatasetsHaveCompatibleCRS(arr) {
|
|
5168
|
+
function requireDatasetsHaveCompatibleCRS(arr, msg) {
|
|
5169
5169
|
arr.reduce(function(memo, dataset) {
|
|
5170
5170
|
var P = getDatasetCRS(dataset);
|
|
5171
5171
|
if (memo && P) {
|
|
5172
5172
|
if (isLatLngCRS(memo) != isLatLngCRS(P)) {
|
|
5173
|
-
stop("Unable to combine projected and unprojected datasets");
|
|
5173
|
+
stop(msg || "Unable to combine projected and unprojected datasets");
|
|
5174
5174
|
}
|
|
5175
5175
|
}
|
|
5176
5176
|
return P || memo;
|
|
@@ -13640,7 +13640,7 @@
|
|
|
13640
13640
|
var px = units == 'in' && num * 72 ||
|
|
13641
13641
|
units == 'cm' && Math.round(num * 28.3465) ||
|
|
13642
13642
|
num;
|
|
13643
|
-
if (px
|
|
13643
|
+
if (px >= 0 === false || !units) {
|
|
13644
13644
|
stop('Invalid size:', str);
|
|
13645
13645
|
}
|
|
13646
13646
|
return px;
|
|
@@ -14715,6 +14715,7 @@
|
|
|
14715
14715
|
}
|
|
14716
14716
|
|
|
14717
14717
|
|
|
14718
|
+
|
|
14718
14719
|
function findCommandTargets(layers, pattern, type) {
|
|
14719
14720
|
var matches = findMatchingLayers(layers, pattern, true);
|
|
14720
14721
|
if (type) {
|
|
@@ -14931,6 +14932,24 @@
|
|
|
14931
14932
|
return opts.geojson_editor.get(_id);
|
|
14932
14933
|
}
|
|
14933
14934
|
});
|
|
14935
|
+
|
|
14936
|
+
Object.defineProperty(ctx, 'feature', {
|
|
14937
|
+
set: function(o) {
|
|
14938
|
+
opts.geojson_editor.set(o, _id);
|
|
14939
|
+
},
|
|
14940
|
+
get: function() {
|
|
14941
|
+
return opts.geojson_editor.get(_id);
|
|
14942
|
+
}
|
|
14943
|
+
});
|
|
14944
|
+
|
|
14945
|
+
Object.defineProperty(ctx, 'geometry', {
|
|
14946
|
+
set: function(o) {
|
|
14947
|
+
opts.geojson_editor.setGeometry(o, _id);
|
|
14948
|
+
},
|
|
14949
|
+
get: function() {
|
|
14950
|
+
return opts.geojson_editor.getGeometry(_id);
|
|
14951
|
+
}
|
|
14952
|
+
});
|
|
14934
14953
|
}
|
|
14935
14954
|
|
|
14936
14955
|
if (_records) {
|
|
@@ -18136,6 +18155,9 @@
|
|
|
18136
18155
|
if (ids) {
|
|
18137
18156
|
obj.id = ids[i];
|
|
18138
18157
|
}
|
|
18158
|
+
if (opts.no_null_props && !obj.properties) {
|
|
18159
|
+
obj.properties = {};
|
|
18160
|
+
}
|
|
18139
18161
|
} else if (!geom) {
|
|
18140
18162
|
return memo; // don't add null objects to GeometryCollection
|
|
18141
18163
|
} else {
|
|
@@ -18481,7 +18503,8 @@
|
|
|
18481
18503
|
var bounds = getLayerBounds(lyr, arcs);
|
|
18482
18504
|
var d = lyr.data.getReadOnlyRecordAt(0);
|
|
18483
18505
|
var w = d.width || 800;
|
|
18484
|
-
|
|
18506
|
+
// prevent rounding errors (like 1000.0000000002)
|
|
18507
|
+
var h = Math.round(w * bounds.height() / bounds.width());
|
|
18485
18508
|
return {
|
|
18486
18509
|
type: 'frame',
|
|
18487
18510
|
width: w,
|
|
@@ -18502,6 +18525,7 @@
|
|
|
18502
18525
|
var pixBounds = calcOutputSizeInPixels(bounds, opts);
|
|
18503
18526
|
return {
|
|
18504
18527
|
bbox: bounds.toArray(),
|
|
18528
|
+
bbox2: pixBounds.toArray(),
|
|
18505
18529
|
width: Math.round(pixBounds.width()),
|
|
18506
18530
|
height: Math.round(pixBounds.height()) || 1,
|
|
18507
18531
|
type: 'frame'
|
|
@@ -18916,7 +18940,7 @@
|
|
|
18916
18940
|
parts.shift();
|
|
18917
18941
|
var colors = [];
|
|
18918
18942
|
var background = parts.pop();
|
|
18919
|
-
var spacing =
|
|
18943
|
+
var spacing = parseNum(parts.pop());
|
|
18920
18944
|
var tmp;
|
|
18921
18945
|
while (parts.length > 0) {
|
|
18922
18946
|
tmp = parts.pop();
|
|
@@ -18927,11 +18951,11 @@
|
|
|
18927
18951
|
colors.push(tmp);
|
|
18928
18952
|
}
|
|
18929
18953
|
}
|
|
18930
|
-
var width =
|
|
18931
|
-
var dashes = [
|
|
18954
|
+
var width = parseNum(parts.pop());
|
|
18955
|
+
var dashes = [parseNum(parts.pop()), parseNum(parts.pop())].reverse();
|
|
18932
18956
|
var rotation = 45;
|
|
18933
18957
|
if (parts.length > 0) {
|
|
18934
|
-
rotation =
|
|
18958
|
+
rotation = parseNum(parts.pop());
|
|
18935
18959
|
}
|
|
18936
18960
|
if (parts.length > 0) {
|
|
18937
18961
|
return null;
|
|
@@ -18956,10 +18980,10 @@
|
|
|
18956
18980
|
// 1px red 1px white 1px black
|
|
18957
18981
|
// -45deg 3 #eee 3 rgb(0,0,0)
|
|
18958
18982
|
parts.shift();
|
|
18959
|
-
var rot = parts.length % 2 == 1 ?
|
|
18983
|
+
var rot = parts.length % 2 == 1 ? parseNum(parts.shift()) : 45, // default is 45
|
|
18960
18984
|
colors = [], widths = [];
|
|
18961
18985
|
for (var i=0; i<parts.length; i+=2) {
|
|
18962
|
-
widths.push(
|
|
18986
|
+
widths.push(parseNum(parts[i]));
|
|
18963
18987
|
colors.push(parts[i+1]);
|
|
18964
18988
|
}
|
|
18965
18989
|
if (Math.min.apply(null, widths) > 0 === false) return null;
|
|
@@ -18973,7 +18997,7 @@
|
|
|
18973
18997
|
}
|
|
18974
18998
|
|
|
18975
18999
|
function isSize(str) {
|
|
18976
|
-
return
|
|
19000
|
+
return parseNum(str) > 0;
|
|
18977
19001
|
}
|
|
18978
19002
|
|
|
18979
19003
|
function parseDots(parts) {
|
|
@@ -18986,11 +19010,11 @@
|
|
|
18986
19010
|
var type = parts.shift();
|
|
18987
19011
|
var rot = 0;
|
|
18988
19012
|
if (isSize(parts[1])) { // if rotation is present, there are two numbers
|
|
18989
|
-
rot =
|
|
19013
|
+
rot = parseNum(parts.shift());
|
|
18990
19014
|
}
|
|
18991
|
-
var size =
|
|
19015
|
+
var size = parseNum(parts.shift());
|
|
18992
19016
|
var bg = parts.pop();
|
|
18993
|
-
var spacing =
|
|
19017
|
+
var spacing = parseNum(parts.pop());
|
|
18994
19018
|
while (parts.length > 0) {
|
|
18995
19019
|
colors.push(parts.shift());
|
|
18996
19020
|
}
|
|
@@ -19008,6 +19032,12 @@
|
|
|
19008
19032
|
};
|
|
19009
19033
|
}
|
|
19010
19034
|
|
|
19035
|
+
function parseNum(str) {
|
|
19036
|
+
// return parseNum(str);
|
|
19037
|
+
// support sub-pixel sizes
|
|
19038
|
+
return parseFloat(str) || 0;
|
|
19039
|
+
}
|
|
19040
|
+
|
|
19011
19041
|
function splitPattern(str) {
|
|
19012
19042
|
// split apart space and comma-delimited tokens
|
|
19013
19043
|
// ... but don't split rgb(...) colors
|
|
@@ -20098,7 +20128,7 @@
|
|
|
20098
20128
|
|
|
20099
20129
|
function fitDatasetToFrame(dataset, frame, opts) {
|
|
20100
20130
|
var bounds = new Bounds(frame.bbox);
|
|
20101
|
-
var bounds2 = new Bounds(0, 0, frame.width, frame.height);
|
|
20131
|
+
var bounds2 = frame.bbox2 ? new Bounds(frame.bbox2) : new Bounds(0, 0, frame.width, frame.height);
|
|
20102
20132
|
var fwd = bounds.getTransform(bounds2, opts.invert_y);
|
|
20103
20133
|
transformPoints(dataset, function(x, y) {
|
|
20104
20134
|
return fwd.transform(x, y);
|
|
@@ -24484,6 +24514,10 @@ ${svg}
|
|
|
24484
24514
|
.option('geojson-type', {
|
|
24485
24515
|
describe: '[GeoJSON] FeatureCollection, GeometryCollection or Feature'
|
|
24486
24516
|
})
|
|
24517
|
+
.option('no-null-props', {
|
|
24518
|
+
describe: '[GeoJSON] use "properties":{} when a Feature has no data',
|
|
24519
|
+
type: 'flag'
|
|
24520
|
+
})
|
|
24487
24521
|
.option('hoist', {
|
|
24488
24522
|
describe: '[GeoJSON] move properties to the root level of each Feature',
|
|
24489
24523
|
type: 'strings'
|
|
@@ -26046,15 +26080,32 @@ ${svg}
|
|
|
26046
26080
|
});
|
|
26047
26081
|
|
|
26048
26082
|
parser.command('frame')
|
|
26049
|
-
|
|
26083
|
+
.describe('create a rectangular map frame layer at a given display width')
|
|
26084
|
+
.option('width', {
|
|
26085
|
+
describe: 'width of frame (e.g. 5in, 10cm, 600px; default is 800px)'
|
|
26086
|
+
})
|
|
26087
|
+
.option('height', {
|
|
26088
|
+
describe: '(optional) height of frame; similar to width= option'
|
|
26089
|
+
})
|
|
26090
|
+
.option('aspect-ratio', {
|
|
26091
|
+
describe: '(optional) aspect ratio of frame, if height= or width= is omitted',
|
|
26092
|
+
type: 'number'
|
|
26093
|
+
})
|
|
26050
26094
|
.option('bbox', {
|
|
26051
26095
|
describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
|
|
26052
26096
|
type: 'bbox'
|
|
26053
26097
|
})
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26098
|
+
.option('offset', {
|
|
26099
|
+
describe: 'padding in display units or pct of width, e.g. 5cm 20px 5%',
|
|
26100
|
+
type: 'strings'
|
|
26057
26101
|
})
|
|
26102
|
+
.option('offsets', {
|
|
26103
|
+
describe: 'separate offsets for each side, in l,b,r,t order',
|
|
26104
|
+
type: 'strings'
|
|
26105
|
+
})
|
|
26106
|
+
.option('name', nameOpt)
|
|
26107
|
+
.option('target', targetOpt);
|
|
26108
|
+
|
|
26058
26109
|
// .option('height', {
|
|
26059
26110
|
// describe: 'pixel height of output (may be a range)'
|
|
26060
26111
|
// })
|
|
@@ -26065,7 +26116,6 @@ ${svg}
|
|
|
26065
26116
|
// .option('source', {
|
|
26066
26117
|
// describe: 'name of layer to enclose'
|
|
26067
26118
|
// })
|
|
26068
|
-
.option('name', nameOpt);
|
|
26069
26119
|
|
|
26070
26120
|
parser.command('fuzzy-join')
|
|
26071
26121
|
.describe('join points to polygons, with data fill and fuzzy match')
|
|
@@ -35762,59 +35812,10 @@ ${svg}
|
|
|
35762
35812
|
getColorizerFunction: getColorizerFunction
|
|
35763
35813
|
});
|
|
35764
35814
|
|
|
35765
|
-
cmd.comment = function() {
|
|
35766
|
-
|
|
35767
|
-
|
|
35768
|
-
|
|
35769
|
-
}
|
|
35770
|
-
|
|
35771
|
-
function getFeatureEditor(lyr, dataset) {
|
|
35772
|
-
var api = {};
|
|
35773
|
-
// need to copy attribute to avoid circular references if geojson is assigned
|
|
35774
|
-
// to a data property.
|
|
35775
|
-
var copy = copyLayer(lyr);
|
|
35776
|
-
var features = exportLayerAsGeoJSON(copy, dataset, {rfc7946: true}, true);
|
|
35777
|
-
var features2 = [];
|
|
35778
|
-
|
|
35779
|
-
api.get = function(i) {
|
|
35780
|
-
if (i > 0) features[i-1] = null; // garbage-collect old features
|
|
35781
|
-
return features[i];
|
|
35782
|
-
};
|
|
35783
|
-
|
|
35784
|
-
api.set = function(feat, i) {
|
|
35785
|
-
var arr;
|
|
35786
|
-
|
|
35787
|
-
if (utils.isString(feat)) {
|
|
35788
|
-
feat = JSON.parse(feat);
|
|
35789
|
-
}
|
|
35790
|
-
|
|
35791
|
-
if (!feat) return;
|
|
35792
|
-
|
|
35793
|
-
if (feat.type == 'GeometryCollection') {
|
|
35794
|
-
arr = feat.geometries.map(geom => GeoJSON.toFeature(geom));
|
|
35795
|
-
} else if (feat.type == 'FeatureCollection') {
|
|
35796
|
-
arr = feat.features;
|
|
35797
|
-
} else {
|
|
35798
|
-
feat = GeoJSON.toFeature(feat);
|
|
35799
|
-
}
|
|
35800
|
-
|
|
35801
|
-
if (arr) {
|
|
35802
|
-
features2 = features2.concat(arr);
|
|
35803
|
-
} else {
|
|
35804
|
-
features2.push(feat);
|
|
35805
|
-
}
|
|
35806
|
-
};
|
|
35807
|
-
|
|
35808
|
-
api.done = function() {
|
|
35809
|
-
if (features2.length === 0) return; // read-only expression
|
|
35810
|
-
var geojson = {
|
|
35811
|
-
type: 'FeatureCollection',
|
|
35812
|
-
features: features2
|
|
35813
|
-
};
|
|
35814
|
-
return importGeoJSON(geojson);
|
|
35815
|
-
};
|
|
35816
|
-
return api;
|
|
35817
|
-
}
|
|
35815
|
+
cmd.comment = function(opts) {
|
|
35816
|
+
// TODO: print the comment in verbose mode
|
|
35817
|
+
// message('[comment]', opts.message);
|
|
35818
|
+
}; // no-op, so -comment doesn't trigger a parsing error
|
|
35818
35819
|
|
|
35819
35820
|
cmd.dashlines = function(lyr, dataset, opts) {
|
|
35820
35821
|
var crs = getDatasetCRS(dataset);
|
|
@@ -37780,6 +37781,76 @@ ${svg}
|
|
|
37780
37781
|
}
|
|
37781
37782
|
};
|
|
37782
37783
|
|
|
37784
|
+
function expressionUsesGeoJSON(exp) {
|
|
37785
|
+
return exp.includes('this.geojson') || exp.includes('this.geometry') || exp.includes('this.feature');
|
|
37786
|
+
}
|
|
37787
|
+
|
|
37788
|
+
function getFeatureEditor(lyr, dataset) {
|
|
37789
|
+
var api = {};
|
|
37790
|
+
// need to copy attribute to avoid circular references if geojson is assigned
|
|
37791
|
+
// to a data property.
|
|
37792
|
+
var copy = copyLayer(lyr);
|
|
37793
|
+
var features = exportLayerAsGeoJSON(copy, dataset, {rfc7946: true}, true);
|
|
37794
|
+
var features2 = [];
|
|
37795
|
+
|
|
37796
|
+
api.get = function(i) {
|
|
37797
|
+
if (i > 0) features[i-1] = null; // garbage-collect old features
|
|
37798
|
+
return features[i];
|
|
37799
|
+
};
|
|
37800
|
+
|
|
37801
|
+
api.getGeometry = function(i) {
|
|
37802
|
+
if (i > 0) features[i-1] = null; // garbage-collect old features
|
|
37803
|
+
return features[i] ? features[i].geometry : null;
|
|
37804
|
+
};
|
|
37805
|
+
|
|
37806
|
+
api.setGeometry = function(geom, i) {
|
|
37807
|
+
if (utils.isString(geom)) {
|
|
37808
|
+
geom = JSON.parse(geom);
|
|
37809
|
+
}
|
|
37810
|
+
// TODO: validate? validate geometry in feature setter?
|
|
37811
|
+
var feat = {
|
|
37812
|
+
type: 'Feature',
|
|
37813
|
+
properties: features[i] ? features[i].properties : null,
|
|
37814
|
+
geometry: geom || null
|
|
37815
|
+
};
|
|
37816
|
+
api.set(feat, i);
|
|
37817
|
+
};
|
|
37818
|
+
|
|
37819
|
+
api.set = function(feat, i) {
|
|
37820
|
+
var arr;
|
|
37821
|
+
|
|
37822
|
+
if (utils.isString(feat)) {
|
|
37823
|
+
feat = JSON.parse(feat);
|
|
37824
|
+
}
|
|
37825
|
+
|
|
37826
|
+
if (!feat) return;
|
|
37827
|
+
|
|
37828
|
+
if (feat.type == 'GeometryCollection') {
|
|
37829
|
+
arr = feat.geometries.map(geom => GeoJSON.toFeature(geom));
|
|
37830
|
+
} else if (feat.type == 'FeatureCollection') {
|
|
37831
|
+
arr = feat.features;
|
|
37832
|
+
} else {
|
|
37833
|
+
feat = GeoJSON.toFeature(feat);
|
|
37834
|
+
}
|
|
37835
|
+
|
|
37836
|
+
if (arr) {
|
|
37837
|
+
features2 = features2.concat(arr);
|
|
37838
|
+
} else {
|
|
37839
|
+
features2.push(feat);
|
|
37840
|
+
}
|
|
37841
|
+
};
|
|
37842
|
+
|
|
37843
|
+
api.done = function() {
|
|
37844
|
+
if (features2.length === 0) return; // read-only expression
|
|
37845
|
+
var geojson = {
|
|
37846
|
+
type: 'FeatureCollection',
|
|
37847
|
+
features: features2
|
|
37848
|
+
};
|
|
37849
|
+
return importGeoJSON(geojson);
|
|
37850
|
+
};
|
|
37851
|
+
return api;
|
|
37852
|
+
}
|
|
37853
|
+
|
|
37783
37854
|
cmd.filterGeom = function(lyr, arcs, opts) {
|
|
37784
37855
|
if (!layerHasGeometry(lyr)) {
|
|
37785
37856
|
stop("Layer is missing geometry");
|
|
@@ -38122,46 +38193,637 @@ ${svg}
|
|
|
38122
38193
|
return parsed[0];
|
|
38123
38194
|
}
|
|
38124
38195
|
|
|
38125
|
-
|
|
38126
|
-
|
|
38127
|
-
|
|
38128
|
-
|
|
38129
|
-
|
|
38130
|
-
|
|
38131
|
-
|
|
38132
|
-
|
|
38133
|
-
|
|
38134
|
-
|
|
38135
|
-
|
|
38136
|
-
|
|
38137
|
-
|
|
38138
|
-
|
|
38139
|
-
|
|
38140
|
-
|
|
38141
|
-
|
|
38142
|
-
|
|
38143
|
-
|
|
38144
|
-
|
|
38145
|
-
}
|
|
38146
|
-
|
|
38147
|
-
|
|
38148
|
-
|
|
38149
|
-
|
|
38150
|
-
|
|
38151
|
-
|
|
38152
|
-
|
|
38153
|
-
|
|
38154
|
-
|
|
38155
|
-
|
|
38156
|
-
|
|
38157
|
-
|
|
38158
|
-
|
|
38159
|
-
|
|
38160
|
-
|
|
38161
|
-
|
|
38162
|
-
|
|
38196
|
+
// Returns number of arcs that were removed
|
|
38197
|
+
function editArcs(arcs, onPoint) {
|
|
38198
|
+
var nn2 = [],
|
|
38199
|
+
xx2 = [],
|
|
38200
|
+
yy2 = [],
|
|
38201
|
+
errors = 0,
|
|
38202
|
+
n;
|
|
38203
|
+
|
|
38204
|
+
arcs.forEach(function(arc, i) {
|
|
38205
|
+
editArc(arc, onPoint);
|
|
38206
|
+
});
|
|
38207
|
+
arcs.updateVertexData(nn2, xx2, yy2);
|
|
38208
|
+
return errors;
|
|
38209
|
+
|
|
38210
|
+
function append(p) {
|
|
38211
|
+
if (p) {
|
|
38212
|
+
xx2.push(p[0]);
|
|
38213
|
+
yy2.push(p[1]);
|
|
38214
|
+
n++;
|
|
38215
|
+
}
|
|
38216
|
+
}
|
|
38217
|
+
|
|
38218
|
+
function editArc(arc, cb) {
|
|
38219
|
+
var x, y, xp, yp, retn;
|
|
38220
|
+
var valid = true;
|
|
38221
|
+
var i = 0;
|
|
38222
|
+
n = 0;
|
|
38223
|
+
while (arc.hasNext()) {
|
|
38224
|
+
x = arc.x;
|
|
38225
|
+
y = arc.y;
|
|
38226
|
+
retn = cb(append, x, y, xp, yp, i++);
|
|
38227
|
+
if (retn === false) {
|
|
38228
|
+
valid = false;
|
|
38229
|
+
// assumes that it's ok for the arc iterator to be interrupted.
|
|
38230
|
+
break;
|
|
38231
|
+
}
|
|
38232
|
+
xp = x;
|
|
38233
|
+
yp = y;
|
|
38234
|
+
}
|
|
38235
|
+
if (valid && n == 1) {
|
|
38236
|
+
// only one valid point was added to this arc (invalid)
|
|
38237
|
+
// e.g. this could happen during reprojection.
|
|
38238
|
+
// making this arc empty
|
|
38239
|
+
// error("An invalid arc was created");
|
|
38240
|
+
message("An invalid arc was created");
|
|
38241
|
+
valid = false;
|
|
38242
|
+
}
|
|
38243
|
+
if (valid) {
|
|
38244
|
+
nn2.push(n);
|
|
38245
|
+
} else {
|
|
38246
|
+
// remove any points that were added for an invalid arc
|
|
38247
|
+
while (n-- > 0) {
|
|
38248
|
+
xx2.pop();
|
|
38249
|
+
yy2.pop();
|
|
38250
|
+
}
|
|
38251
|
+
nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
|
|
38252
|
+
errors++;
|
|
38253
|
+
}
|
|
38254
|
+
}
|
|
38255
|
+
}
|
|
38256
|
+
|
|
38257
|
+
function DatasetEditor(dataset) {
|
|
38258
|
+
var layers = [];
|
|
38259
|
+
var arcs = [];
|
|
38260
|
+
|
|
38261
|
+
this.done = function() {
|
|
38262
|
+
dataset.layers = layers;
|
|
38263
|
+
if (arcs.length) {
|
|
38264
|
+
dataset.arcs = new ArcCollection(arcs);
|
|
38265
|
+
buildTopology(dataset);
|
|
38266
|
+
}
|
|
38267
|
+
};
|
|
38268
|
+
|
|
38269
|
+
this.editLayer = function(lyr, cb) {
|
|
38270
|
+
var type = lyr.geometry_type;
|
|
38271
|
+
if (dataset.layers.indexOf(lyr) != layers.length) {
|
|
38272
|
+
error('Layer was edited out-of-order');
|
|
38273
|
+
}
|
|
38274
|
+
if (!type) {
|
|
38275
|
+
layers.push(lyr);
|
|
38276
|
+
return;
|
|
38277
|
+
}
|
|
38278
|
+
var shapes = lyr.shapes.map(function(shape, shpId) {
|
|
38279
|
+
var shape2 = [], retn, input;
|
|
38280
|
+
for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
|
|
38281
|
+
input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
|
|
38282
|
+
retn = cb(input, i, shape);
|
|
38283
|
+
if (!Array.isArray(retn)) continue;
|
|
38284
|
+
if (type == 'point') {
|
|
38285
|
+
shape2.push(retn);
|
|
38286
|
+
} else if (type == 'polygon' || type == 'polyline') {
|
|
38287
|
+
extendPathShape(shape2, retn || []);
|
|
38288
|
+
}
|
|
38289
|
+
}
|
|
38290
|
+
return shape2.length > 0 ? shape2 : null;
|
|
38291
|
+
});
|
|
38292
|
+
layers.push(Object.assign(lyr, {shapes: shapes}));
|
|
38293
|
+
};
|
|
38294
|
+
|
|
38295
|
+
function extendPathShape(shape, parts) {
|
|
38296
|
+
for (var i=0; i<parts.length; i++) {
|
|
38297
|
+
shape.push([arcs.length]);
|
|
38298
|
+
arcs.push(parts[i]);
|
|
38299
|
+
}
|
|
38300
|
+
}
|
|
38301
|
+
|
|
38302
|
+
function idsToCoords(ids) {
|
|
38303
|
+
var coords = [];
|
|
38304
|
+
var iter = dataset.arcs.getShapeIter(ids);
|
|
38305
|
+
while (iter.hasNext()) {
|
|
38306
|
+
coords.push([iter.x, iter.y]);
|
|
38307
|
+
}
|
|
38308
|
+
return coords;
|
|
38309
|
+
}
|
|
38310
|
+
}
|
|
38311
|
+
|
|
38312
|
+
// Planar densification by an interval
|
|
38313
|
+
function densifyPathByInterval(coords, interval, interpolate) {
|
|
38314
|
+
if (findMaxPathInterval(coords) < interval) return coords;
|
|
38315
|
+
if (!interpolate) {
|
|
38316
|
+
interpolate = getIntervalInterpolator(interval);
|
|
38317
|
+
}
|
|
38318
|
+
var coords2 = [coords[0]], a, b;
|
|
38319
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
38320
|
+
a = coords[i-1];
|
|
38321
|
+
b = coords[i];
|
|
38322
|
+
if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
|
|
38323
|
+
appendArr(coords2, interpolate(a, b));
|
|
38324
|
+
}
|
|
38325
|
+
coords2.push(b);
|
|
38326
|
+
}
|
|
38327
|
+
return coords2;
|
|
38328
|
+
}
|
|
38329
|
+
|
|
38330
|
+
function getIntervalInterpolator(interval) {
|
|
38331
|
+
return function(a, b) {
|
|
38332
|
+
var points = [];
|
|
38333
|
+
// var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
|
|
38334
|
+
var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
|
|
38335
|
+
var n = Math.round(dist / interval) - 1;
|
|
38336
|
+
var dx = (b[0] - a[0]) / (n + 1),
|
|
38337
|
+
dy = (b[1] - a[1]) / (n + 1);
|
|
38338
|
+
for (var i=1; i<=n; i++) {
|
|
38339
|
+
points.push([a[0] + dx * i, a[1] + dy * i]);
|
|
38340
|
+
}
|
|
38341
|
+
return points;
|
|
38342
|
+
};
|
|
38343
|
+
}
|
|
38344
|
+
|
|
38345
|
+
|
|
38346
|
+
// Interpolate the same points regardless of segment direction
|
|
38347
|
+
function densifyAntimeridianSegment(a, b, interval) {
|
|
38348
|
+
var y1, y2;
|
|
38349
|
+
var coords = [];
|
|
38350
|
+
var ascending = a[1] < b[1];
|
|
38351
|
+
if (a[0] != b[0]) error('Expected an edge segment');
|
|
38352
|
+
if (interval > 0 === false) error('Expected a positive interval');
|
|
38353
|
+
if (ascending) {
|
|
38354
|
+
y1 = a[1];
|
|
38355
|
+
y2 = b[1];
|
|
38356
|
+
} else {
|
|
38357
|
+
y1 = b[1];
|
|
38358
|
+
y2 = a[1];
|
|
38359
|
+
}
|
|
38360
|
+
var y = Math.floor(y1 / interval) * interval + interval;
|
|
38361
|
+
while (y < y2) {
|
|
38362
|
+
coords.push([a[0], y]);
|
|
38363
|
+
y += interval;
|
|
38364
|
+
}
|
|
38365
|
+
if (!ascending) coords.reverse();
|
|
38366
|
+
return coords;
|
|
38367
|
+
}
|
|
38368
|
+
|
|
38369
|
+
function appendArr(dest, src) {
|
|
38370
|
+
for (var i=0; i<src.length; i++) dest.push(src[i]);
|
|
38371
|
+
}
|
|
38372
|
+
|
|
38373
|
+
function findMaxPathInterval(coords) {
|
|
38374
|
+
var maxSq = 0, intSq, a, b;
|
|
38375
|
+
for (var i=1, n=coords.length; i<n; i++) {
|
|
38376
|
+
a = coords[i-1];
|
|
38377
|
+
b = coords[i];
|
|
38378
|
+
intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
|
|
38379
|
+
if (intSq > maxSq) maxSq = intSq;
|
|
38380
|
+
}
|
|
38381
|
+
return Math.sqrt(maxSq);
|
|
38382
|
+
}
|
|
38383
|
+
|
|
38384
|
+
function projectAndDensifyArcs(arcs, proj) {
|
|
38385
|
+
var interval = getDefaultDensifyInterval(arcs, proj);
|
|
38386
|
+
var minIntervalSq = interval * interval * 25;
|
|
38387
|
+
var p;
|
|
38388
|
+
return editArcs(arcs, onPoint);
|
|
38389
|
+
|
|
38390
|
+
function onPoint(append, lng, lat, prevLng, prevLat, i) {
|
|
38391
|
+
var pp = p;
|
|
38392
|
+
p = proj(lng, lat);
|
|
38393
|
+
if (!p) return false; // signal that current arc contains an error
|
|
38394
|
+
|
|
38395
|
+
// Don't try to densify shorter segments (optimization)
|
|
38396
|
+
if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
|
|
38397
|
+
densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
|
|
38398
|
+
.forEach(append);
|
|
38399
|
+
}
|
|
38400
|
+
append(p);
|
|
38401
|
+
}
|
|
38402
|
+
}
|
|
38403
|
+
|
|
38404
|
+
// Use the median of intervals computed by projecting segments.
|
|
38405
|
+
// We're probing a number of points, because @proj might only be valid in
|
|
38406
|
+
// a sub-region of the dataset bbox (e.g. +proj=tpers)
|
|
38407
|
+
function findDensifyInterval(bounds, xy, proj) {
|
|
38408
|
+
var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
|
|
38409
|
+
var points = [];
|
|
38410
|
+
for (var i=0; i<steps.length; i++) {
|
|
38411
|
+
for (var j=0; j<steps.length; j++) {
|
|
38412
|
+
points.push([steps[i], steps[j]]);
|
|
38413
|
+
}
|
|
38414
|
+
}
|
|
38415
|
+
var intervals = points.map(function(pos) {
|
|
38416
|
+
var x = bounds.xmin + bounds.width() * pos[0];
|
|
38417
|
+
var y = bounds.ymin + bounds.height() * pos[1];
|
|
38418
|
+
var a = proj(x, y);
|
|
38419
|
+
var b = proj(x + xy[0], y + xy[1]);
|
|
38420
|
+
return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
|
|
38421
|
+
}).filter(function(int) {return int < Infinity;});
|
|
38422
|
+
return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
|
|
38423
|
+
}
|
|
38424
|
+
|
|
38425
|
+
// Kludgy way to get a useful interval for densifying a bounding box.
|
|
38426
|
+
// Uses a fraction of average bbox side length)
|
|
38427
|
+
// TODO: improve
|
|
38428
|
+
function findDensifyInterval2(bb, proj) {
|
|
38429
|
+
var a = proj(bb.centerX(), bb.centerY()),
|
|
38430
|
+
c = proj(bb.centerX(), bb.ymin), // right center
|
|
38431
|
+
d = proj(bb.xmax, bb.centerY()); // bottom center
|
|
38432
|
+
var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
|
|
38433
|
+
geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
|
|
38434
|
+
return interval;
|
|
38435
|
+
}
|
|
38436
|
+
|
|
38437
|
+
// Returns an interval in projected units
|
|
38438
|
+
function getDefaultDensifyInterval(arcs, proj) {
|
|
38439
|
+
var xy = getAvgSegment2(arcs),
|
|
38440
|
+
bb = arcs.getBounds(),
|
|
38441
|
+
intervalA = findDensifyInterval(bb, xy, proj),
|
|
38442
|
+
intervalB = findDensifyInterval2(bb, proj),
|
|
38443
|
+
interval = Math.min(intervalA, intervalB);
|
|
38444
|
+
if (interval == Infinity) {
|
|
38445
|
+
error('Densification error');
|
|
38446
|
+
}
|
|
38447
|
+
return interval;
|
|
38448
|
+
}
|
|
38449
|
+
|
|
38450
|
+
// Interpolate points into a projected line segment if needed to prevent large
|
|
38451
|
+
// deviations from path of original unprojected segment.
|
|
38452
|
+
// @points (optional) array of accumulated points
|
|
38453
|
+
function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
|
|
38454
|
+
// Find midpoint between two endpoints and project it (assumes longitude does
|
|
38455
|
+
// not wrap). TODO Consider bisecting along great circle path -- although this
|
|
38456
|
+
// would not be good for boundaries that follow line of constant latitude.
|
|
38457
|
+
var lng1 = (lng0 + lng2) / 2,
|
|
38458
|
+
lat1 = (lat0 + lat2) / 2,
|
|
38459
|
+
p = proj(lng1, lat1),
|
|
38460
|
+
distSq;
|
|
38461
|
+
if (!p) return; // TODO: consider if this is adequate for handling proj. errors
|
|
38462
|
+
distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
|
|
38463
|
+
points = points || [];
|
|
38464
|
+
// Bisect current segment if the projected midpoint deviates from original
|
|
38465
|
+
// segment by more than the @interval parameter.
|
|
38466
|
+
// ... but don't bisect very small segments to prevent infinite recursion
|
|
38467
|
+
// (e.g. if projection function is discontinuous)
|
|
38468
|
+
if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
|
|
38469
|
+
densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
|
|
38470
|
+
points.push(p);
|
|
38471
|
+
densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
|
|
38472
|
+
}
|
|
38473
|
+
return points;
|
|
38474
|
+
}
|
|
38475
|
+
|
|
38476
|
+
// Create rectangles around each feature in a layer
|
|
38477
|
+
cmd.rectangles = function(targetLyr, targetDataset, opts) {
|
|
38478
|
+
var crsInfo = getDatasetCrsInfo(targetDataset);
|
|
38479
|
+
var records = targetLyr.data ? targetLyr.data.getRecords() : null;
|
|
38480
|
+
var geometries;
|
|
38481
|
+
|
|
38482
|
+
if (opts.bbox) {
|
|
38483
|
+
geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
|
|
38484
|
+
|
|
38485
|
+
} else {
|
|
38486
|
+
if (!layerHasGeometry(targetLyr)) {
|
|
38487
|
+
stop("Layer is missing geometric shapes");
|
|
38488
|
+
}
|
|
38489
|
+
geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
|
|
38490
|
+
}
|
|
38491
|
+
|
|
38492
|
+
var geojson = {
|
|
38493
|
+
type: 'FeatureCollection',
|
|
38494
|
+
features: geometries.map(function(geom, i) {
|
|
38495
|
+
var rec = records && records[i] || null;
|
|
38496
|
+
if (rec && opts.no_replace) {
|
|
38497
|
+
rec = utils.extend({}, rec); // make a copy
|
|
38498
|
+
}
|
|
38499
|
+
return {
|
|
38500
|
+
type: 'Feature',
|
|
38501
|
+
properties: rec,
|
|
38502
|
+
geometry: geom
|
|
38503
|
+
};
|
|
38504
|
+
})
|
|
38505
|
+
};
|
|
38506
|
+
var dataset = importGeoJSON(geojson, {});
|
|
38507
|
+
setDatasetCrsInfo(dataset, crsInfo);
|
|
38508
|
+
var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
|
|
38509
|
+
setOutputLayerName(outputLayers[0], targetLyr, null, opts);
|
|
38510
|
+
return outputLayers;
|
|
38511
|
+
};
|
|
38512
|
+
|
|
38513
|
+
|
|
38514
|
+
|
|
38515
|
+
|
|
38516
|
+
function shapesToBoxGeometries(lyr, dataset, opts) {
|
|
38517
|
+
var crsInfo = getDatasetCrsInfo(dataset);
|
|
38518
|
+
return lyr.shapes.map(function(shp) {
|
|
38519
|
+
var bounds = lyr.geometry_type == 'point' ?
|
|
38520
|
+
getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
|
|
38521
|
+
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
38522
|
+
if (!bounds) return null;
|
|
38523
|
+
return bboxToPolygon(bounds.toArray(), opts);
|
|
38524
|
+
});
|
|
38525
|
+
}
|
|
38526
|
+
|
|
38527
|
+
function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
|
|
38528
|
+
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
|
|
38529
|
+
var n = getFeatureCount(lyr);
|
|
38530
|
+
var result;
|
|
38531
|
+
var geometries = [];
|
|
38532
|
+
for (var i=0; i<n; i++) {
|
|
38533
|
+
result = compiled(i);
|
|
38534
|
+
if (!looksLikeBbox(result)) {
|
|
38535
|
+
stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
|
|
38536
|
+
}
|
|
38537
|
+
geometries.push(bboxToPolygon(result));
|
|
38538
|
+
}
|
|
38539
|
+
return geometries;
|
|
38540
|
+
}
|
|
38541
|
+
|
|
38542
|
+
function looksLikeBbox(o) {
|
|
38543
|
+
if (!o || o.length != 4) return false;
|
|
38544
|
+
if (o.some(isNaN)) return false;
|
|
38545
|
+
if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
|
|
38546
|
+
return true;
|
|
38547
|
+
}
|
|
38548
|
+
|
|
38549
|
+
// Create rectangles around one or more target layers
|
|
38550
|
+
//
|
|
38551
|
+
cmd.rectangle2 = function(target, opts) {
|
|
38552
|
+
// if target layer is a rectangle and we're applying frame properties,
|
|
38553
|
+
// turn the target into a frame instead of creating a new rectangle
|
|
38554
|
+
if (target.layers.length == 1 && opts.width &&
|
|
38555
|
+
layerIsRectangle(target.layers[0], target.dataset.arcs)) {
|
|
38556
|
+
applyFrameProperties(target.layers[0], opts);
|
|
38557
|
+
return;
|
|
38558
|
+
}
|
|
38559
|
+
var datasets = target.layers.map(function(lyr) {
|
|
38560
|
+
var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
|
|
38561
|
+
setOutputLayerName(dataset.layers[0], lyr, null, opts);
|
|
38562
|
+
if (!opts.no_replace) {
|
|
38563
|
+
dataset.layers[0].name = lyr.name || dataset.layers[0].name;
|
|
38564
|
+
}
|
|
38565
|
+
return dataset;
|
|
38566
|
+
});
|
|
38567
|
+
return mergeDatasetsIntoDataset(target.dataset, datasets);
|
|
38568
|
+
};
|
|
38569
|
+
|
|
38570
|
+
cmd.rectangle = function(target, opts) {
|
|
38571
|
+
var bounds, crsInfo;
|
|
38572
|
+
if (opts.bbox) {
|
|
38573
|
+
bounds = new Bounds(opts.bbox);
|
|
38574
|
+
crsInfo = target && getDatasetCrsInfo(target.dataset) ||
|
|
38575
|
+
probablyDecimalDegreeBounds(bounds) && getCrsInfo('wgs84') || {};
|
|
38576
|
+
} else if (target) {
|
|
38577
|
+
bounds = getLayerBounds(target.layer, target.dataset.arcs);
|
|
38578
|
+
crsInfo = getDatasetCrsInfo(target.dataset);
|
|
38579
|
+
}
|
|
38580
|
+
bounds = bounds && applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
38581
|
+
if (!bounds || !bounds.hasBounds()) {
|
|
38582
|
+
stop('Missing rectangle extent');
|
|
38583
|
+
}
|
|
38584
|
+
var feature = {
|
|
38585
|
+
type: 'Feature',
|
|
38586
|
+
properties: {},
|
|
38587
|
+
geometry: bboxToPolygon(bounds.toArray(), opts)
|
|
38588
|
+
};
|
|
38589
|
+
var dataset = importGeoJSON(feature, {});
|
|
38590
|
+
applyFrameProperties(dataset.layers[0], opts);
|
|
38591
|
+
dataset.layers[0].name = opts.name || 'rectangle';
|
|
38592
|
+
setDatasetCrsInfo(dataset, crsInfo);
|
|
38593
|
+
return dataset;
|
|
38594
|
+
};
|
|
38595
|
+
|
|
38596
|
+
function applyFrameProperties(lyr, opts) {
|
|
38597
|
+
if (!opts.width) return;
|
|
38598
|
+
if (!lyr.data) initDataTable(lyr);
|
|
38599
|
+
var d = lyr.data.getRecords()[0] || {};
|
|
38600
|
+
d.width = parseSizeParam(opts.width);
|
|
38601
|
+
d.type = 'frame';
|
|
38602
|
+
}
|
|
38603
|
+
|
|
38604
|
+
function applyRectangleOptions(bounds, crs, opts) {
|
|
38605
|
+
var isGeoBox = probablyDecimalDegreeBounds(bounds);
|
|
38606
|
+
if (opts.offset) {
|
|
38607
|
+
bounds = applyBoundsOffset(opts.offset, bounds, crs);
|
|
38608
|
+
}
|
|
38609
|
+
if (bounds.area() > 0 === false) return null;
|
|
38610
|
+
if (opts.aspect_ratio) {
|
|
38611
|
+
bounds = applyAspectRatio(opts.aspect_ratio, bounds);
|
|
38612
|
+
}
|
|
38613
|
+
if (isGeoBox) {
|
|
38614
|
+
bounds = clampToWorldBounds(bounds);
|
|
38615
|
+
}
|
|
38616
|
+
return bounds;
|
|
38617
|
+
}
|
|
38618
|
+
|
|
38619
|
+
// opt: aspect ratio as a single number or a range (e.g. "1,2");
|
|
38620
|
+
function applyAspectRatio(opt, bounds) {
|
|
38621
|
+
var range = String(opt).split(',').map(parseFloat),
|
|
38622
|
+
aspectRatio = bounds.width() / bounds.height(),
|
|
38623
|
+
min, max; // min is height limit, max is width limit
|
|
38624
|
+
if (range.length == 1) {
|
|
38625
|
+
range.push(range[0]);
|
|
38626
|
+
} else if (range[0] > range[1]) {
|
|
38627
|
+
range.reverse();
|
|
38628
|
+
}
|
|
38629
|
+
min = range[0];
|
|
38630
|
+
max = range[1];
|
|
38631
|
+
if (!min && !max) return bounds;
|
|
38632
|
+
if (!min) min = -Infinity;
|
|
38633
|
+
if (!max) max = Infinity;
|
|
38634
|
+
if (aspectRatio < min) {
|
|
38635
|
+
bounds.fillOut(min);
|
|
38636
|
+
} else if (aspectRatio > max) {
|
|
38637
|
+
bounds.fillOut(max);
|
|
38638
|
+
}
|
|
38639
|
+
return bounds;
|
|
38640
|
+
}
|
|
38641
|
+
|
|
38642
|
+
function applyBoundsOffset(offsetOpt, bounds, crs) {
|
|
38643
|
+
var offsets = convertFourSides(offsetOpt, crs, bounds);
|
|
38644
|
+
bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
|
|
38645
|
+
return bounds;
|
|
38646
|
+
}
|
|
38647
|
+
|
|
38648
|
+
function bboxToPolygon(bbox, optsArg) {
|
|
38649
|
+
var opts = optsArg || {};
|
|
38650
|
+
var coords = bboxToCoords(bbox);
|
|
38651
|
+
if (opts.interval > 0) {
|
|
38652
|
+
coords = densifyPathByInterval(coords, opts.interval);
|
|
38653
|
+
}
|
|
38654
|
+
return {
|
|
38655
|
+
type: 'Polygon',
|
|
38656
|
+
coordinates: [coords]
|
|
38657
|
+
};
|
|
38658
|
+
}
|
|
38659
|
+
|
|
38660
|
+
var Rectangle = /*#__PURE__*/Object.freeze({
|
|
38661
|
+
__proto__: null,
|
|
38662
|
+
applyAspectRatio: applyAspectRatio,
|
|
38663
|
+
bboxToPolygon: bboxToPolygon
|
|
38664
|
+
});
|
|
38665
|
+
|
|
38666
|
+
cmd.frame = function(catalog, targets, opts) {
|
|
38667
|
+
var widthPx, heightPx, aspectRatio, bbox;
|
|
38668
|
+
if (opts.width) {
|
|
38669
|
+
widthPx = parseSizeParam(opts.width);
|
|
38670
|
+
if (widthPx > 0 === false) {
|
|
38671
|
+
stop('Invalid width parameter:', opts.width);
|
|
38672
|
+
}
|
|
38673
|
+
}
|
|
38674
|
+
if (opts.height) {
|
|
38675
|
+
heightPx = parseSizeParam(opts.height);
|
|
38676
|
+
if (heightPx > 0 === false) {
|
|
38677
|
+
stop('Invalid height parameter:', opts.height);
|
|
38678
|
+
}
|
|
38679
|
+
}
|
|
38680
|
+
if (!widthPx && !heightPx) {
|
|
38681
|
+
widthPx = 800;
|
|
38682
|
+
message('Using default 800px frame width');
|
|
38683
|
+
}
|
|
38684
|
+
|
|
38685
|
+
if (opts.aspect_ratio) {
|
|
38686
|
+
if (opts.aspect_ratio > 0 === false) {
|
|
38687
|
+
stop('Invalid aspect-ratio parameter:', opts.aspect_ratio);
|
|
38688
|
+
}
|
|
38689
|
+
if (!heightPx) {
|
|
38690
|
+
heightPx = roundToDigits(widthPx / opts.aspect_ratio, 1);
|
|
38691
|
+
} else if (!widthPx) {
|
|
38692
|
+
widthPx = roundToDigits(heightPx * opts.aspect_ratio, 1);
|
|
38693
|
+
}
|
|
38694
|
+
}
|
|
38695
|
+
|
|
38696
|
+
if (opts.bbox) {
|
|
38697
|
+
bbox = opts.bbox;
|
|
38698
|
+
// TODO: validate
|
|
38699
|
+
} else {
|
|
38700
|
+
var datasets = utils.pluck(targets, 'dataset');
|
|
38701
|
+
requireDatasetsHaveCompatibleCRS(datasets, 'Targets include both projected and unprojected coordinates');
|
|
38702
|
+
bbox = getTargetBbox(targets);
|
|
38703
|
+
if (!bbox) {
|
|
38704
|
+
stop('Command target is missing geographical bounds');
|
|
38705
|
+
}
|
|
38706
|
+
}
|
|
38707
|
+
|
|
38708
|
+
applyPercentageOffsets(bbox, opts.offset || opts.offsets);
|
|
38709
|
+
applyPixelOffsets(bbox, widthPx, heightPx, opts.offset || opts.offsets);
|
|
38710
|
+
|
|
38711
|
+
if (bbox[3] - bbox[1] > 0 === false || bbox[2] - bbox[0] > 0 === false) {
|
|
38712
|
+
stop('Frame has a collapsed bbox');
|
|
38713
|
+
}
|
|
38714
|
+
|
|
38715
|
+
aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
38716
|
+
if (!widthPx) {
|
|
38717
|
+
widthPx = roundToDigits(heightPx * aspectRatio, 1);
|
|
38718
|
+
} else if (!heightPx) {
|
|
38719
|
+
heightPx = roundToDigits(widthPx / aspectRatio, 1);
|
|
38720
|
+
}
|
|
38721
|
+
|
|
38722
|
+
var feature = {
|
|
38723
|
+
type: 'Feature',
|
|
38724
|
+
properties: {type: 'frame', width: widthPx, height: heightPx},
|
|
38725
|
+
geometry: bboxToPolygon(bbox)
|
|
38726
|
+
};
|
|
38727
|
+
var frameDataset = importGeoJSON(feature);
|
|
38728
|
+
// set CRS from target dataset
|
|
38729
|
+
// TODO: handle case: targets have different projections
|
|
38730
|
+
// TODO: handle case: first target is missing CRS
|
|
38731
|
+
if (targets.length > 0) {
|
|
38732
|
+
var crsInfo = getDatasetCrsInfo(targets[0].dataset);
|
|
38733
|
+
setDatasetCrsInfo(frameDataset, crsInfo);
|
|
38734
|
+
}
|
|
38735
|
+
frameDataset.layers[0].name = opts.name || 'frame';
|
|
38736
|
+
catalog.addDataset(frameDataset);
|
|
38163
38737
|
};
|
|
38164
38738
|
|
|
38739
|
+
function fillOutBbox(bbox, widthPx, heightPx) {
|
|
38740
|
+
var hpad = 0, vpad = 0;
|
|
38741
|
+
var w = bbox[2] - bbox[0];
|
|
38742
|
+
var h = bbox[3] - bbox[1];
|
|
38743
|
+
if (widthPx / heightPx > w / h) { // need to add horizontal padding
|
|
38744
|
+
hpad = h * widthPx / heightPx - w;
|
|
38745
|
+
} else {
|
|
38746
|
+
vpad = w * heightPx / widthPx - h;
|
|
38747
|
+
}
|
|
38748
|
+
bbox[0] -= hpad / 2;
|
|
38749
|
+
bbox[1] -= vpad / 2;
|
|
38750
|
+
bbox[2] += hpad / 2;
|
|
38751
|
+
bbox[3] += vpad / 2;
|
|
38752
|
+
}
|
|
38753
|
+
|
|
38754
|
+
function applyPercentageOffsets(bbox, arg) {
|
|
38755
|
+
var sides = getPctOffsets(arg);
|
|
38756
|
+
var l = sides[0],
|
|
38757
|
+
b = sides[1],
|
|
38758
|
+
r = sides[2],
|
|
38759
|
+
t = sides[3],
|
|
38760
|
+
w2 = (bbox[2] - bbox[0]) / (1 - l - r),
|
|
38761
|
+
h2 = (bbox[3] - bbox[1]) / (1 - t - b);
|
|
38762
|
+
bbox[0] -= l * w2;
|
|
38763
|
+
bbox[1] -= b * h2;
|
|
38764
|
+
bbox[2] += r * w2;
|
|
38765
|
+
bbox[3] += t * h2;
|
|
38766
|
+
}
|
|
38767
|
+
|
|
38768
|
+
function applyPixelOffsets(bbox, widthPx, heightPx, arg) {
|
|
38769
|
+
var sides = getPixelOffsets(arg);
|
|
38770
|
+
var l = sides[0],
|
|
38771
|
+
b = sides[1],
|
|
38772
|
+
r = sides[2],
|
|
38773
|
+
t = sides[3],
|
|
38774
|
+
scale, w;
|
|
38775
|
+
|
|
38776
|
+
if (widthPx && heightPx) {
|
|
38777
|
+
// add padding to bbox to match pixel dimensions, if needed
|
|
38778
|
+
fillOutBbox(bbox, widthPx, heightPx);
|
|
38779
|
+
}
|
|
38780
|
+
|
|
38781
|
+
w = bbox[2] - bbox[0];
|
|
38782
|
+
bbox[3] - bbox[1];
|
|
38783
|
+
|
|
38784
|
+
if (widthPx) {
|
|
38785
|
+
scale = w / (widthPx - l - r);
|
|
38786
|
+
} else {
|
|
38787
|
+
scale = w / (heightPx - t - b);
|
|
38788
|
+
}
|
|
38789
|
+
|
|
38790
|
+
bbox[0] -= scale * l;
|
|
38791
|
+
bbox[1] -= scale * b;
|
|
38792
|
+
bbox[2] += scale * r;
|
|
38793
|
+
bbox[3] += scale * t;
|
|
38794
|
+
return scale;
|
|
38795
|
+
}
|
|
38796
|
+
|
|
38797
|
+
function getPctOffsets(arg) {
|
|
38798
|
+
return adjustOffsetsArg(arg).map(str => {
|
|
38799
|
+
return str.includes('%') ? utils.parsePercent(str) : 0;
|
|
38800
|
+
});
|
|
38801
|
+
}
|
|
38802
|
+
|
|
38803
|
+
function getPixelOffsets(arg) {
|
|
38804
|
+
return adjustOffsetsArg(arg).map(str => {
|
|
38805
|
+
return str.includes('%') ? 0 : parseSizeParam(str);
|
|
38806
|
+
});
|
|
38807
|
+
}
|
|
38808
|
+
|
|
38809
|
+
function adjustOffsetsArg(arg) {
|
|
38810
|
+
if (!arg) arg = ['0'];
|
|
38811
|
+
if (arg.length == 1) {
|
|
38812
|
+
return [arg[0], arg[0], arg[0], arg[0]];
|
|
38813
|
+
}
|
|
38814
|
+
if (arg.length != 4) {
|
|
38815
|
+
stop('List of offsets should have 4 values');
|
|
38816
|
+
}
|
|
38817
|
+
return arg;
|
|
38818
|
+
}
|
|
38819
|
+
|
|
38820
|
+
function getTargetBbox(targets) {
|
|
38821
|
+
var expanded = expandCommandTargets(targets);
|
|
38822
|
+
var bounds = expanded.reduce(function(memo, o) {
|
|
38823
|
+
return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
|
|
38824
|
+
}, new Bounds());
|
|
38825
|
+
return bounds.hasBounds() ? bounds.toArray() : null;
|
|
38826
|
+
}
|
|
38165
38827
|
|
|
38166
38828
|
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
38167
38829
|
function getAspectRatioArg(widthArg, heightArg) {
|
|
@@ -38175,21 +38837,6 @@ ${svg}
|
|
|
38175
38837
|
}).reverse().join(',');
|
|
38176
38838
|
}
|
|
38177
38839
|
|
|
38178
|
-
// export function renderFrame(d) {
|
|
38179
|
-
// var lineWidth = 1,
|
|
38180
|
-
// // inset stroke by half of line width
|
|
38181
|
-
// off = lineWidth / 2,
|
|
38182
|
-
// obj = importPolygon([[[off, off], [off, d.height - off],
|
|
38183
|
-
// [d.width - off, d.height - off],
|
|
38184
|
-
// [d.width - off, off], [off, off]]]);
|
|
38185
|
-
// utils.extend(obj.properties, {
|
|
38186
|
-
// fill: 'none',
|
|
38187
|
-
// stroke: d.stroke || 'black',
|
|
38188
|
-
// 'stroke-width': d['stroke-width'] || lineWidth
|
|
38189
|
-
// });
|
|
38190
|
-
// return [obj];
|
|
38191
|
-
// }
|
|
38192
|
-
|
|
38193
38840
|
var Frame = /*#__PURE__*/Object.freeze({
|
|
38194
38841
|
__proto__: null,
|
|
38195
38842
|
getAspectRatioArg: getAspectRatioArg
|
|
@@ -38643,473 +39290,6 @@ ${svg}
|
|
|
38643
39290
|
return maxValue;
|
|
38644
39291
|
}
|
|
38645
39292
|
|
|
38646
|
-
// Returns number of arcs that were removed
|
|
38647
|
-
function editArcs(arcs, onPoint) {
|
|
38648
|
-
var nn2 = [],
|
|
38649
|
-
xx2 = [],
|
|
38650
|
-
yy2 = [],
|
|
38651
|
-
errors = 0,
|
|
38652
|
-
n;
|
|
38653
|
-
|
|
38654
|
-
arcs.forEach(function(arc, i) {
|
|
38655
|
-
editArc(arc, onPoint);
|
|
38656
|
-
});
|
|
38657
|
-
arcs.updateVertexData(nn2, xx2, yy2);
|
|
38658
|
-
return errors;
|
|
38659
|
-
|
|
38660
|
-
function append(p) {
|
|
38661
|
-
if (p) {
|
|
38662
|
-
xx2.push(p[0]);
|
|
38663
|
-
yy2.push(p[1]);
|
|
38664
|
-
n++;
|
|
38665
|
-
}
|
|
38666
|
-
}
|
|
38667
|
-
|
|
38668
|
-
function editArc(arc, cb) {
|
|
38669
|
-
var x, y, xp, yp, retn;
|
|
38670
|
-
var valid = true;
|
|
38671
|
-
var i = 0;
|
|
38672
|
-
n = 0;
|
|
38673
|
-
while (arc.hasNext()) {
|
|
38674
|
-
x = arc.x;
|
|
38675
|
-
y = arc.y;
|
|
38676
|
-
retn = cb(append, x, y, xp, yp, i++);
|
|
38677
|
-
if (retn === false) {
|
|
38678
|
-
valid = false;
|
|
38679
|
-
// assumes that it's ok for the arc iterator to be interrupted.
|
|
38680
|
-
break;
|
|
38681
|
-
}
|
|
38682
|
-
xp = x;
|
|
38683
|
-
yp = y;
|
|
38684
|
-
}
|
|
38685
|
-
if (valid && n == 1) {
|
|
38686
|
-
// only one valid point was added to this arc (invalid)
|
|
38687
|
-
// e.g. this could happen during reprojection.
|
|
38688
|
-
// making this arc empty
|
|
38689
|
-
// error("An invalid arc was created");
|
|
38690
|
-
message("An invalid arc was created");
|
|
38691
|
-
valid = false;
|
|
38692
|
-
}
|
|
38693
|
-
if (valid) {
|
|
38694
|
-
nn2.push(n);
|
|
38695
|
-
} else {
|
|
38696
|
-
// remove any points that were added for an invalid arc
|
|
38697
|
-
while (n-- > 0) {
|
|
38698
|
-
xx2.pop();
|
|
38699
|
-
yy2.pop();
|
|
38700
|
-
}
|
|
38701
|
-
nn2.push(0); // add empty arc (to preserve mapping from paths to arcs)
|
|
38702
|
-
errors++;
|
|
38703
|
-
}
|
|
38704
|
-
}
|
|
38705
|
-
}
|
|
38706
|
-
|
|
38707
|
-
function DatasetEditor(dataset) {
|
|
38708
|
-
var layers = [];
|
|
38709
|
-
var arcs = [];
|
|
38710
|
-
|
|
38711
|
-
this.done = function() {
|
|
38712
|
-
dataset.layers = layers;
|
|
38713
|
-
if (arcs.length) {
|
|
38714
|
-
dataset.arcs = new ArcCollection(arcs);
|
|
38715
|
-
buildTopology(dataset);
|
|
38716
|
-
}
|
|
38717
|
-
};
|
|
38718
|
-
|
|
38719
|
-
this.editLayer = function(lyr, cb) {
|
|
38720
|
-
var type = lyr.geometry_type;
|
|
38721
|
-
if (dataset.layers.indexOf(lyr) != layers.length) {
|
|
38722
|
-
error('Layer was edited out-of-order');
|
|
38723
|
-
}
|
|
38724
|
-
if (!type) {
|
|
38725
|
-
layers.push(lyr);
|
|
38726
|
-
return;
|
|
38727
|
-
}
|
|
38728
|
-
var shapes = lyr.shapes.map(function(shape, shpId) {
|
|
38729
|
-
var shape2 = [], retn, input;
|
|
38730
|
-
for (var i=0, n=shape ? shape.length : 0; i<n; i++) {
|
|
38731
|
-
input = type == 'point' ? shape[i] : idsToCoords(shape[i]);
|
|
38732
|
-
retn = cb(input, i, shape);
|
|
38733
|
-
if (!Array.isArray(retn)) continue;
|
|
38734
|
-
if (type == 'point') {
|
|
38735
|
-
shape2.push(retn);
|
|
38736
|
-
} else if (type == 'polygon' || type == 'polyline') {
|
|
38737
|
-
extendPathShape(shape2, retn || []);
|
|
38738
|
-
}
|
|
38739
|
-
}
|
|
38740
|
-
return shape2.length > 0 ? shape2 : null;
|
|
38741
|
-
});
|
|
38742
|
-
layers.push(Object.assign(lyr, {shapes: shapes}));
|
|
38743
|
-
};
|
|
38744
|
-
|
|
38745
|
-
function extendPathShape(shape, parts) {
|
|
38746
|
-
for (var i=0; i<parts.length; i++) {
|
|
38747
|
-
shape.push([arcs.length]);
|
|
38748
|
-
arcs.push(parts[i]);
|
|
38749
|
-
}
|
|
38750
|
-
}
|
|
38751
|
-
|
|
38752
|
-
function idsToCoords(ids) {
|
|
38753
|
-
var coords = [];
|
|
38754
|
-
var iter = dataset.arcs.getShapeIter(ids);
|
|
38755
|
-
while (iter.hasNext()) {
|
|
38756
|
-
coords.push([iter.x, iter.y]);
|
|
38757
|
-
}
|
|
38758
|
-
return coords;
|
|
38759
|
-
}
|
|
38760
|
-
}
|
|
38761
|
-
|
|
38762
|
-
// Planar densification by an interval
|
|
38763
|
-
function densifyPathByInterval(coords, interval, interpolate) {
|
|
38764
|
-
if (findMaxPathInterval(coords) < interval) return coords;
|
|
38765
|
-
if (!interpolate) {
|
|
38766
|
-
interpolate = getIntervalInterpolator(interval);
|
|
38767
|
-
}
|
|
38768
|
-
var coords2 = [coords[0]], a, b;
|
|
38769
|
-
for (var i=1, n=coords.length; i<n; i++) {
|
|
38770
|
-
a = coords[i-1];
|
|
38771
|
-
b = coords[i];
|
|
38772
|
-
if (geom.distance2D(a[0], a[1], b[0], b[1]) > interval + 1e-4) {
|
|
38773
|
-
appendArr(coords2, interpolate(a, b));
|
|
38774
|
-
}
|
|
38775
|
-
coords2.push(b);
|
|
38776
|
-
}
|
|
38777
|
-
return coords2;
|
|
38778
|
-
}
|
|
38779
|
-
|
|
38780
|
-
function getIntervalInterpolator(interval) {
|
|
38781
|
-
return function(a, b) {
|
|
38782
|
-
var points = [];
|
|
38783
|
-
// var rev = a[0] == b[0] ? a[1] > b[1] : a[0] > b[0];
|
|
38784
|
-
var dist = geom.distance2D(a[0], a[1], b[0], b[1]);
|
|
38785
|
-
var n = Math.round(dist / interval) - 1;
|
|
38786
|
-
var dx = (b[0] - a[0]) / (n + 1),
|
|
38787
|
-
dy = (b[1] - a[1]) / (n + 1);
|
|
38788
|
-
for (var i=1; i<=n; i++) {
|
|
38789
|
-
points.push([a[0] + dx * i, a[1] + dy * i]);
|
|
38790
|
-
}
|
|
38791
|
-
return points;
|
|
38792
|
-
};
|
|
38793
|
-
}
|
|
38794
|
-
|
|
38795
|
-
|
|
38796
|
-
// Interpolate the same points regardless of segment direction
|
|
38797
|
-
function densifyAntimeridianSegment(a, b, interval) {
|
|
38798
|
-
var y1, y2;
|
|
38799
|
-
var coords = [];
|
|
38800
|
-
var ascending = a[1] < b[1];
|
|
38801
|
-
if (a[0] != b[0]) error('Expected an edge segment');
|
|
38802
|
-
if (interval > 0 === false) error('Expected a positive interval');
|
|
38803
|
-
if (ascending) {
|
|
38804
|
-
y1 = a[1];
|
|
38805
|
-
y2 = b[1];
|
|
38806
|
-
} else {
|
|
38807
|
-
y1 = b[1];
|
|
38808
|
-
y2 = a[1];
|
|
38809
|
-
}
|
|
38810
|
-
var y = Math.floor(y1 / interval) * interval + interval;
|
|
38811
|
-
while (y < y2) {
|
|
38812
|
-
coords.push([a[0], y]);
|
|
38813
|
-
y += interval;
|
|
38814
|
-
}
|
|
38815
|
-
if (!ascending) coords.reverse();
|
|
38816
|
-
return coords;
|
|
38817
|
-
}
|
|
38818
|
-
|
|
38819
|
-
function appendArr(dest, src) {
|
|
38820
|
-
for (var i=0; i<src.length; i++) dest.push(src[i]);
|
|
38821
|
-
}
|
|
38822
|
-
|
|
38823
|
-
function findMaxPathInterval(coords) {
|
|
38824
|
-
var maxSq = 0, intSq, a, b;
|
|
38825
|
-
for (var i=1, n=coords.length; i<n; i++) {
|
|
38826
|
-
a = coords[i-1];
|
|
38827
|
-
b = coords[i];
|
|
38828
|
-
intSq = geom.distanceSq(a[0], a[1], b[0], b[1]);
|
|
38829
|
-
if (intSq > maxSq) maxSq = intSq;
|
|
38830
|
-
}
|
|
38831
|
-
return Math.sqrt(maxSq);
|
|
38832
|
-
}
|
|
38833
|
-
|
|
38834
|
-
function projectAndDensifyArcs(arcs, proj) {
|
|
38835
|
-
var interval = getDefaultDensifyInterval(arcs, proj);
|
|
38836
|
-
var minIntervalSq = interval * interval * 25;
|
|
38837
|
-
var p;
|
|
38838
|
-
return editArcs(arcs, onPoint);
|
|
38839
|
-
|
|
38840
|
-
function onPoint(append, lng, lat, prevLng, prevLat, i) {
|
|
38841
|
-
var pp = p;
|
|
38842
|
-
p = proj(lng, lat);
|
|
38843
|
-
if (!p) return false; // signal that current arc contains an error
|
|
38844
|
-
|
|
38845
|
-
// Don't try to densify shorter segments (optimization)
|
|
38846
|
-
if (i > 0 && geom.distanceSq(p[0], p[1], pp[0], pp[1]) > minIntervalSq) {
|
|
38847
|
-
densifySegment(prevLng, prevLat, pp[0], pp[1], lng, lat, p[0], p[1], proj, interval)
|
|
38848
|
-
.forEach(append);
|
|
38849
|
-
}
|
|
38850
|
-
append(p);
|
|
38851
|
-
}
|
|
38852
|
-
}
|
|
38853
|
-
|
|
38854
|
-
// Use the median of intervals computed by projecting segments.
|
|
38855
|
-
// We're probing a number of points, because @proj might only be valid in
|
|
38856
|
-
// a sub-region of the dataset bbox (e.g. +proj=tpers)
|
|
38857
|
-
function findDensifyInterval(bounds, xy, proj) {
|
|
38858
|
-
var steps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
|
|
38859
|
-
var points = [];
|
|
38860
|
-
for (var i=0; i<steps.length; i++) {
|
|
38861
|
-
for (var j=0; j<steps.length; j++) {
|
|
38862
|
-
points.push([steps[i], steps[j]]);
|
|
38863
|
-
}
|
|
38864
|
-
}
|
|
38865
|
-
var intervals = points.map(function(pos) {
|
|
38866
|
-
var x = bounds.xmin + bounds.width() * pos[0];
|
|
38867
|
-
var y = bounds.ymin + bounds.height() * pos[1];
|
|
38868
|
-
var a = proj(x, y);
|
|
38869
|
-
var b = proj(x + xy[0], y + xy[1]);
|
|
38870
|
-
return a && b ? geom.distance2D(a[0], a[1], b[0], b[1]) : Infinity;
|
|
38871
|
-
}).filter(function(int) {return int < Infinity;});
|
|
38872
|
-
return intervals.length > 0 ? utils.findMedian(intervals) : Infinity;
|
|
38873
|
-
}
|
|
38874
|
-
|
|
38875
|
-
// Kludgy way to get a useful interval for densifying a bounding box.
|
|
38876
|
-
// Uses a fraction of average bbox side length)
|
|
38877
|
-
// TODO: improve
|
|
38878
|
-
function findDensifyInterval2(bb, proj) {
|
|
38879
|
-
var a = proj(bb.centerX(), bb.centerY()),
|
|
38880
|
-
c = proj(bb.centerX(), bb.ymin), // right center
|
|
38881
|
-
d = proj(bb.xmax, bb.centerY()); // bottom center
|
|
38882
|
-
var interval = a && c && d ? (geom.distance2D(a[0], a[1], c[0], c[1]) +
|
|
38883
|
-
geom.distance2D(a[0], a[1], d[0], d[1])) / 5000 : Infinity;
|
|
38884
|
-
return interval;
|
|
38885
|
-
}
|
|
38886
|
-
|
|
38887
|
-
// Returns an interval in projected units
|
|
38888
|
-
function getDefaultDensifyInterval(arcs, proj) {
|
|
38889
|
-
var xy = getAvgSegment2(arcs),
|
|
38890
|
-
bb = arcs.getBounds(),
|
|
38891
|
-
intervalA = findDensifyInterval(bb, xy, proj),
|
|
38892
|
-
intervalB = findDensifyInterval2(bb, proj),
|
|
38893
|
-
interval = Math.min(intervalA, intervalB);
|
|
38894
|
-
if (interval == Infinity) {
|
|
38895
|
-
error('Densification error');
|
|
38896
|
-
}
|
|
38897
|
-
return interval;
|
|
38898
|
-
}
|
|
38899
|
-
|
|
38900
|
-
// Interpolate points into a projected line segment if needed to prevent large
|
|
38901
|
-
// deviations from path of original unprojected segment.
|
|
38902
|
-
// @points (optional) array of accumulated points
|
|
38903
|
-
function densifySegment(lng0, lat0, x0, y0, lng2, lat2, x2, y2, proj, interval, points) {
|
|
38904
|
-
// Find midpoint between two endpoints and project it (assumes longitude does
|
|
38905
|
-
// not wrap). TODO Consider bisecting along great circle path -- although this
|
|
38906
|
-
// would not be good for boundaries that follow line of constant latitude.
|
|
38907
|
-
var lng1 = (lng0 + lng2) / 2,
|
|
38908
|
-
lat1 = (lat0 + lat2) / 2,
|
|
38909
|
-
p = proj(lng1, lat1),
|
|
38910
|
-
distSq;
|
|
38911
|
-
if (!p) return; // TODO: consider if this is adequate for handling proj. errors
|
|
38912
|
-
distSq = geom.pointSegDistSq2(p[0], p[1], x0, y0, x2, y2); // sq displacement
|
|
38913
|
-
points = points || [];
|
|
38914
|
-
// Bisect current segment if the projected midpoint deviates from original
|
|
38915
|
-
// segment by more than the @interval parameter.
|
|
38916
|
-
// ... but don't bisect very small segments to prevent infinite recursion
|
|
38917
|
-
// (e.g. if projection function is discontinuous)
|
|
38918
|
-
if (distSq > interval * interval * 0.25 && geom.distance2D(lng0, lat0, lng2, lat2) > 0.01) {
|
|
38919
|
-
densifySegment(lng0, lat0, x0, y0, lng1, lat1, p[0], p[1], proj, interval, points);
|
|
38920
|
-
points.push(p);
|
|
38921
|
-
densifySegment(lng1, lat1, p[0], p[1], lng2, lat2, x2, y2, proj, interval, points);
|
|
38922
|
-
}
|
|
38923
|
-
return points;
|
|
38924
|
-
}
|
|
38925
|
-
|
|
38926
|
-
// Create rectangles around each feature in a layer
|
|
38927
|
-
cmd.rectangles = function(targetLyr, targetDataset, opts) {
|
|
38928
|
-
var crsInfo = getDatasetCrsInfo(targetDataset);
|
|
38929
|
-
var records = targetLyr.data ? targetLyr.data.getRecords() : null;
|
|
38930
|
-
var geometries;
|
|
38931
|
-
|
|
38932
|
-
if (opts.bbox) {
|
|
38933
|
-
geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset);
|
|
38934
|
-
|
|
38935
|
-
} else {
|
|
38936
|
-
if (!layerHasGeometry(targetLyr)) {
|
|
38937
|
-
stop("Layer is missing geometric shapes");
|
|
38938
|
-
}
|
|
38939
|
-
geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
|
|
38940
|
-
}
|
|
38941
|
-
|
|
38942
|
-
var geojson = {
|
|
38943
|
-
type: 'FeatureCollection',
|
|
38944
|
-
features: geometries.map(function(geom, i) {
|
|
38945
|
-
var rec = records && records[i] || null;
|
|
38946
|
-
if (rec && opts.no_replace) {
|
|
38947
|
-
rec = utils.extend({}, rec); // make a copy
|
|
38948
|
-
}
|
|
38949
|
-
return {
|
|
38950
|
-
type: 'Feature',
|
|
38951
|
-
properties: rec,
|
|
38952
|
-
geometry: geom
|
|
38953
|
-
};
|
|
38954
|
-
})
|
|
38955
|
-
};
|
|
38956
|
-
var dataset = importGeoJSON(geojson, {});
|
|
38957
|
-
setDatasetCrsInfo(dataset, crsInfo);
|
|
38958
|
-
var outputLayers = mergeDatasetsIntoDataset(targetDataset, [dataset]);
|
|
38959
|
-
setOutputLayerName(outputLayers[0], targetLyr, null, opts);
|
|
38960
|
-
return outputLayers;
|
|
38961
|
-
};
|
|
38962
|
-
|
|
38963
|
-
function shapesToBoxGeometries(lyr, dataset, opts) {
|
|
38964
|
-
var crsInfo = getDatasetCrsInfo(dataset);
|
|
38965
|
-
return lyr.shapes.map(function(shp) {
|
|
38966
|
-
var bounds = lyr.geometry_type == 'point' ?
|
|
38967
|
-
getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
|
|
38968
|
-
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
38969
|
-
if (!bounds) return null;
|
|
38970
|
-
return bboxToPolygon(bounds.toArray(), opts);
|
|
38971
|
-
});
|
|
38972
|
-
}
|
|
38973
|
-
|
|
38974
|
-
function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
|
|
38975
|
-
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
|
|
38976
|
-
var n = getFeatureCount(lyr);
|
|
38977
|
-
var result;
|
|
38978
|
-
var geometries = [];
|
|
38979
|
-
for (var i=0; i<n; i++) {
|
|
38980
|
-
result = compiled(i);
|
|
38981
|
-
if (!looksLikeBbox(result)) {
|
|
38982
|
-
stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
|
|
38983
|
-
}
|
|
38984
|
-
geometries.push(bboxToPolygon(result));
|
|
38985
|
-
}
|
|
38986
|
-
return geometries;
|
|
38987
|
-
}
|
|
38988
|
-
|
|
38989
|
-
function looksLikeBbox(o) {
|
|
38990
|
-
if (!o || o.length != 4) return false;
|
|
38991
|
-
if (o.some(isNaN)) return false;
|
|
38992
|
-
if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
|
|
38993
|
-
return true;
|
|
38994
|
-
}
|
|
38995
|
-
|
|
38996
|
-
// Create rectangles around one or more target layers
|
|
38997
|
-
//
|
|
38998
|
-
cmd.rectangle2 = function(target, opts) {
|
|
38999
|
-
// if target layer is a rectangle and we're applying frame properties,
|
|
39000
|
-
// turn the target into a frame instead of creating a new rectangle
|
|
39001
|
-
if (target.layers.length == 1 && opts.width &&
|
|
39002
|
-
layerIsRectangle(target.layers[0], target.dataset.arcs)) {
|
|
39003
|
-
applyFrameProperties(target.layers[0], opts);
|
|
39004
|
-
return;
|
|
39005
|
-
}
|
|
39006
|
-
var datasets = target.layers.map(function(lyr) {
|
|
39007
|
-
var dataset = cmd.rectangle({layer: lyr, dataset: target.dataset}, opts);
|
|
39008
|
-
setOutputLayerName(dataset.layers[0], lyr, null, opts);
|
|
39009
|
-
if (!opts.no_replace) {
|
|
39010
|
-
dataset.layers[0].name = lyr.name || dataset.layers[0].name;
|
|
39011
|
-
}
|
|
39012
|
-
return dataset;
|
|
39013
|
-
});
|
|
39014
|
-
return mergeDatasetsIntoDataset(target.dataset, datasets);
|
|
39015
|
-
};
|
|
39016
|
-
|
|
39017
|
-
cmd.rectangle = function(target, opts) {
|
|
39018
|
-
var bounds, crsInfo;
|
|
39019
|
-
if (opts.bbox) {
|
|
39020
|
-
bounds = new Bounds(opts.bbox);
|
|
39021
|
-
crsInfo = target && getDatasetCrsInfo(target.dataset) ||
|
|
39022
|
-
probablyDecimalDegreeBounds(bounds) && getCrsInfo('wgs84') || {};
|
|
39023
|
-
} else if (target) {
|
|
39024
|
-
bounds = getLayerBounds(target.layer, target.dataset.arcs);
|
|
39025
|
-
crsInfo = getDatasetCrsInfo(target.dataset);
|
|
39026
|
-
}
|
|
39027
|
-
bounds = bounds && applyRectangleOptions(bounds, crsInfo.crs, opts);
|
|
39028
|
-
if (!bounds || !bounds.hasBounds()) {
|
|
39029
|
-
stop('Missing rectangle extent');
|
|
39030
|
-
}
|
|
39031
|
-
var feature = {
|
|
39032
|
-
type: 'Feature',
|
|
39033
|
-
properties: {},
|
|
39034
|
-
geometry: bboxToPolygon(bounds.toArray(), opts)
|
|
39035
|
-
};
|
|
39036
|
-
var dataset = importGeoJSON(feature, {});
|
|
39037
|
-
applyFrameProperties(dataset.layers[0], opts);
|
|
39038
|
-
dataset.layers[0].name = opts.name || 'rectangle';
|
|
39039
|
-
setDatasetCrsInfo(dataset, crsInfo);
|
|
39040
|
-
return dataset;
|
|
39041
|
-
};
|
|
39042
|
-
|
|
39043
|
-
function applyFrameProperties(lyr, opts) {
|
|
39044
|
-
if (!opts.width) return;
|
|
39045
|
-
if (!lyr.data) initDataTable(lyr);
|
|
39046
|
-
var d = lyr.data.getRecords()[0] || {};
|
|
39047
|
-
d.width = parseSizeParam(opts.width);
|
|
39048
|
-
d.type = 'frame';
|
|
39049
|
-
}
|
|
39050
|
-
|
|
39051
|
-
function applyRectangleOptions(bounds, crs, opts) {
|
|
39052
|
-
var isGeoBox = probablyDecimalDegreeBounds(bounds);
|
|
39053
|
-
if (opts.offset) {
|
|
39054
|
-
bounds = applyBoundsOffset(opts.offset, bounds, crs);
|
|
39055
|
-
}
|
|
39056
|
-
if (bounds.area() > 0 === false) return null;
|
|
39057
|
-
if (opts.aspect_ratio) {
|
|
39058
|
-
bounds = applyAspectRatio(opts.aspect_ratio, bounds);
|
|
39059
|
-
}
|
|
39060
|
-
if (isGeoBox) {
|
|
39061
|
-
bounds = clampToWorldBounds(bounds);
|
|
39062
|
-
}
|
|
39063
|
-
return bounds;
|
|
39064
|
-
}
|
|
39065
|
-
|
|
39066
|
-
// opt: aspect ratio as a single number or a range (e.g. "1,2");
|
|
39067
|
-
function applyAspectRatio(opt, bounds) {
|
|
39068
|
-
var range = String(opt).split(',').map(parseFloat),
|
|
39069
|
-
aspectRatio = bounds.width() / bounds.height(),
|
|
39070
|
-
min, max; // min is height limit, max is width limit
|
|
39071
|
-
if (range.length == 1) {
|
|
39072
|
-
range.push(range[0]);
|
|
39073
|
-
} else if (range[0] > range[1]) {
|
|
39074
|
-
range.reverse();
|
|
39075
|
-
}
|
|
39076
|
-
min = range[0];
|
|
39077
|
-
max = range[1];
|
|
39078
|
-
if (!min && !max) return bounds;
|
|
39079
|
-
if (!min) min = -Infinity;
|
|
39080
|
-
if (!max) max = Infinity;
|
|
39081
|
-
if (aspectRatio < min) {
|
|
39082
|
-
bounds.fillOut(min);
|
|
39083
|
-
} else if (aspectRatio > max) {
|
|
39084
|
-
bounds.fillOut(max);
|
|
39085
|
-
}
|
|
39086
|
-
return bounds;
|
|
39087
|
-
}
|
|
39088
|
-
|
|
39089
|
-
function applyBoundsOffset(offsetOpt, bounds, crs) {
|
|
39090
|
-
var offsets = convertFourSides(offsetOpt, crs, bounds);
|
|
39091
|
-
bounds.padBounds(offsets[0], offsets[1], offsets[2], offsets[3]);
|
|
39092
|
-
return bounds;
|
|
39093
|
-
}
|
|
39094
|
-
|
|
39095
|
-
function bboxToPolygon(bbox, optsArg) {
|
|
39096
|
-
var opts = optsArg || {};
|
|
39097
|
-
var coords = bboxToCoords(bbox);
|
|
39098
|
-
if (opts.interval > 0) {
|
|
39099
|
-
coords = densifyPathByInterval(coords, opts.interval);
|
|
39100
|
-
}
|
|
39101
|
-
return {
|
|
39102
|
-
type: 'Polygon',
|
|
39103
|
-
coordinates: [coords]
|
|
39104
|
-
};
|
|
39105
|
-
}
|
|
39106
|
-
|
|
39107
|
-
var Rectangle = /*#__PURE__*/Object.freeze({
|
|
39108
|
-
__proto__: null,
|
|
39109
|
-
applyAspectRatio: applyAspectRatio,
|
|
39110
|
-
bboxToPolygon: bboxToPolygon
|
|
39111
|
-
});
|
|
39112
|
-
|
|
39113
39293
|
function getSemiMinorAxis(P) {
|
|
39114
39294
|
return P.a * Math.sqrt(1 - (P.es || 0));
|
|
39115
39295
|
}
|
|
@@ -45223,14 +45403,16 @@ ${svg}
|
|
|
45223
45403
|
}
|
|
45224
45404
|
|
|
45225
45405
|
function commandAcceptsMultipleTargetDatasets(name) {
|
|
45226
|
-
return name == 'rotate' || name == 'info' || name == 'proj' ||
|
|
45227
|
-
name == '
|
|
45228
|
-
name == '
|
|
45406
|
+
return name == 'rotate' || name == 'info' || name == 'proj' ||
|
|
45407
|
+
name == 'require' || name == 'drop' || name == 'target' ||
|
|
45408
|
+
name == 'if' || name == 'elif' || name == 'else' || name == 'endif' ||
|
|
45409
|
+
name == 'run' || name == 'i' || name == 'snap' || name == 'frame' ||
|
|
45410
|
+
name == 'comment';
|
|
45229
45411
|
}
|
|
45230
45412
|
|
|
45231
45413
|
function commandAcceptsEmptyTarget(name) {
|
|
45232
45414
|
return name == 'graticule' || name == 'i' || name == 'help' ||
|
|
45233
|
-
name == 'point-grid' || name == 'shape' || name == 'rectangle' ||
|
|
45415
|
+
name == 'point-grid' || name == 'shape' || name == 'rectangle' || name == 'frame' ||
|
|
45234
45416
|
name == 'require' || name == 'run' || name == 'define' ||
|
|
45235
45417
|
name == 'include' || name == 'print' || name == 'comment' || name == 'if' || name == 'elif' ||
|
|
45236
45418
|
name == 'else' || name == 'endif' || name == 'stop' || name == 'add-shape' ||
|
|
@@ -45254,12 +45436,14 @@ ${svg}
|
|
|
45254
45436
|
}
|
|
45255
45437
|
|
|
45256
45438
|
if (name == 'comment') {
|
|
45439
|
+
cmd.comment(opts);
|
|
45257
45440
|
return done(null);
|
|
45258
45441
|
}
|
|
45259
45442
|
|
|
45260
45443
|
if (!job) job = new Job();
|
|
45261
45444
|
job.startCommand(command);
|
|
45262
45445
|
|
|
45446
|
+
|
|
45263
45447
|
try { // catch errors from synchronous functions
|
|
45264
45448
|
T$1.start();
|
|
45265
45449
|
|
|
@@ -45358,8 +45542,8 @@ ${svg}
|
|
|
45358
45542
|
} else if (name == 'colorizer') {
|
|
45359
45543
|
outputLayers = cmd.colorizer(opts);
|
|
45360
45544
|
|
|
45361
|
-
} else if (name == 'comment') {
|
|
45362
|
-
|
|
45545
|
+
// } else if (name == 'comment') {
|
|
45546
|
+
// // no-op
|
|
45363
45547
|
|
|
45364
45548
|
} else if (name == 'dashlines') {
|
|
45365
45549
|
applyCommandToEachLayer(cmd.dashlines, targetLayers, targetDataset, opts);
|
|
@@ -45417,9 +45601,8 @@ ${svg}
|
|
|
45417
45601
|
} else if (name == 'filter-slivers') {
|
|
45418
45602
|
applyCommandToEachLayer(cmd.filterSlivers, targetLayers, targetDataset, opts);
|
|
45419
45603
|
|
|
45420
|
-
|
|
45421
|
-
|
|
45422
|
-
// cmd.frame(job.catalog, source, opts);
|
|
45604
|
+
} else if (name == 'frame') {
|
|
45605
|
+
cmd.frame(job.catalog, targets, opts);
|
|
45423
45606
|
|
|
45424
45607
|
} else if (name == 'fuzzy-join') {
|
|
45425
45608
|
applyCommandToEachLayer(cmd.fuzzyJoin, targetLayers, arcs, source, opts);
|
|
@@ -45521,10 +45704,7 @@ ${svg}
|
|
|
45521
45704
|
cmd.proj(targ.dataset, job.catalog, opts);
|
|
45522
45705
|
});
|
|
45523
45706
|
|
|
45524
|
-
} else if (name == 'rectangle'
|
|
45525
|
-
if (name == 'frame' && !opts.width) {
|
|
45526
|
-
stop('Command requires a width= argument');
|
|
45527
|
-
}
|
|
45707
|
+
} else if (name == 'rectangle') {
|
|
45528
45708
|
if (source || opts.bbox || targets.length === 0) {
|
|
45529
45709
|
job.catalog.addDataset(cmd.rectangle(source || targets?.[0], opts));
|
|
45530
45710
|
} else {
|
|
@@ -45690,7 +45870,7 @@ ${svg}
|
|
|
45690
45870
|
});
|
|
45691
45871
|
}
|
|
45692
45872
|
|
|
45693
|
-
var version = "0.6.
|
|
45873
|
+
var version = "0.6.100";
|
|
45694
45874
|
|
|
45695
45875
|
// Parse command line args into commands and run them
|
|
45696
45876
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
|
@@ -45841,13 +46021,16 @@ ${svg}
|
|
|
45841
46021
|
|
|
45842
46022
|
function nextGroup(prevJob, commands, next) {
|
|
45843
46023
|
runParsedCommands(commands, new Job(), function(err, job) {
|
|
45844
|
-
err =
|
|
46024
|
+
err = handleNonFatalError(err);
|
|
45845
46025
|
next(err, job);
|
|
45846
46026
|
});
|
|
45847
46027
|
}
|
|
45848
46028
|
|
|
45849
46029
|
function done(err, job) {
|
|
45850
|
-
|
|
46030
|
+
if (job && inControlBlock(job)) {
|
|
46031
|
+
message('Warning: -if command is missing a matching -endif');
|
|
46032
|
+
}
|
|
46033
|
+
err = handleNonFatalError(err);
|
|
45851
46034
|
if (err) printError(err);
|
|
45852
46035
|
callback(err, job);
|
|
45853
46036
|
}
|
|
@@ -45924,7 +46107,7 @@ ${svg}
|
|
|
45924
46107
|
}
|
|
45925
46108
|
}
|
|
45926
46109
|
|
|
45927
|
-
function
|
|
46110
|
+
function handleNonFatalError(err) {
|
|
45928
46111
|
if (err && err.name == 'NonFatalError') {
|
|
45929
46112
|
printError(err);
|
|
45930
46113
|
return null;
|