mapshaper 0.7.67 → 0.7.68
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 +858 -410
- package/package.json +1 -1
- package/www/index.html +10 -3
- package/www/mapshaper-gui.js +417 -240
- package/www/mapshaper.js +858 -410
- package/www/page.css +261 -112
package/mapshaper.js
CHANGED
|
@@ -27236,9 +27236,16 @@
|
|
|
27236
27236
|
// The two copies are identical apart from their paint, so they sit in exactly
|
|
27237
27237
|
// the same place whatever the label's position, alignment or path.
|
|
27238
27238
|
//
|
|
27239
|
-
// The halo copy is unfilled
|
|
27240
|
-
// the stroke's inner half and nothing else
|
|
27241
|
-
//
|
|
27239
|
+
// The halo copy is unfilled, which matches paint-order, where the fill covers
|
|
27240
|
+
// the stroke's inner half and nothing else. Its halo-opacity is written as
|
|
27241
|
+
// opacity rather than stroke-opacity, which Illustrator ignores on import; the
|
|
27242
|
+
// two look the same on a copy with nothing painted but its stroke.
|
|
27243
|
+
//
|
|
27244
|
+
// Illustrator's GPU preview compounds a translucent halo where glyphs
|
|
27245
|
+
// overlap; its CPU preview and its output do not. Nothing in the SVG was
|
|
27246
|
+
// found to avoid it: filling the copy (imported as two objects, and no
|
|
27247
|
+
// better), and putting the opacity on a group or two nested groups around
|
|
27248
|
+
// the copy, all show the same thing.
|
|
27242
27249
|
//
|
|
27243
27250
|
// Things that apply to the label as a whole move to the group: its transform,
|
|
27244
27251
|
// which places it, and its opacity, which would otherwise fade each copy on its
|
|
@@ -27269,6 +27276,10 @@
|
|
|
27269
27276
|
delete halo.properties['paint-order'];
|
|
27270
27277
|
delete halo.properties['fill-opacity'];
|
|
27271
27278
|
halo.properties.fill = 'none';
|
|
27279
|
+
if ('stroke-opacity' in halo.properties) {
|
|
27280
|
+
halo.properties.opacity = halo.properties['stroke-opacity'];
|
|
27281
|
+
delete halo.properties['stroke-opacity'];
|
|
27282
|
+
}
|
|
27272
27283
|
HALO_STROKE_PROPERTIES.forEach(function(k) {
|
|
27273
27284
|
delete text.properties[k];
|
|
27274
27285
|
});
|
|
@@ -27340,6 +27351,23 @@
|
|
|
27340
27351
|
// different one would put the label somewhere it is not drawn.
|
|
27341
27352
|
var DEFAULT_LABEL_FONT_SIZE = 12;
|
|
27342
27353
|
|
|
27354
|
+
// The line height a label is drawn with when it carries none, as a multiple of
|
|
27355
|
+
// its font size.
|
|
27356
|
+
var DEFAULT_LINE_HEIGHT = 1.1;
|
|
27357
|
+
|
|
27358
|
+
// The dy of each line after a label's first, from its line-height.
|
|
27359
|
+
//
|
|
27360
|
+
// A bare number is a multiple of the font size, as in CSS, and not px, as a
|
|
27361
|
+
// bare dy would be in SVG -- so it is written in ems. Nobody who types 1.3
|
|
27362
|
+
// into a field called line height wants lines 1.3px apart, and a multiple
|
|
27363
|
+
// keeps its proportions when the font size changes. Values with units are
|
|
27364
|
+
// passed through.
|
|
27365
|
+
function getLineHeightDy(val) {
|
|
27366
|
+
var measure = val === undefined || val === null || val === '' ?
|
|
27367
|
+
DEFAULT_LINE_HEIGHT : parseSvgMeasure(val);
|
|
27368
|
+
return utils.isFiniteNumber(measure) ? measure + 'em' : measure;
|
|
27369
|
+
}
|
|
27370
|
+
|
|
27343
27371
|
function toLabelString(val) {
|
|
27344
27372
|
if (val || val === 0 || val === false) return String(val);
|
|
27345
27373
|
return '';
|
|
@@ -27387,7 +27415,7 @@
|
|
|
27387
27415
|
value: line,
|
|
27388
27416
|
properties: {
|
|
27389
27417
|
x: dx,
|
|
27390
|
-
dy: rec['line-height']
|
|
27418
|
+
dy: getLineHeightDy(rec['line-height'])
|
|
27391
27419
|
}
|
|
27392
27420
|
};
|
|
27393
27421
|
obj.children.push(tspan);
|
|
@@ -27481,8 +27509,10 @@
|
|
|
27481
27509
|
var SvgLabels = /*#__PURE__*/Object.freeze({
|
|
27482
27510
|
__proto__: null,
|
|
27483
27511
|
DEFAULT_LABEL_FONT_SIZE: DEFAULT_LABEL_FONT_SIZE,
|
|
27512
|
+
DEFAULT_LINE_HEIGHT: DEFAULT_LINE_HEIGHT,
|
|
27484
27513
|
LABEL_SOFT_BREAK: LABEL_SOFT_BREAK,
|
|
27485
27514
|
getDrawnLabelOffset: getDrawnLabelOffset,
|
|
27515
|
+
getLineHeightDy: getLineHeightDy,
|
|
27486
27516
|
labelNewlineRxp: labelNewlineRxp,
|
|
27487
27517
|
removeSoftBreaks: removeSoftBreaks,
|
|
27488
27518
|
renderLabel: renderLabel$1,
|
|
@@ -27505,7 +27535,6 @@
|
|
|
27505
27535
|
|
|
27506
27536
|
var DEFAULT_PADDING$1 = 3;
|
|
27507
27537
|
var DEFAULT_LINE_WIDTH = 1;
|
|
27508
|
-
var DEFAULT_LINE_HEIGHT = '1.1em';
|
|
27509
27538
|
// Space left between the anchor's symbol and the end of the line
|
|
27510
27539
|
var SYMBOL_CLEARANCE = 2;
|
|
27511
27540
|
// How far an automatic curve bows out, as a fraction of its chord
|
|
@@ -27526,6 +27555,9 @@
|
|
|
27526
27555
|
var ASCENT = 0.8;
|
|
27527
27556
|
var DESCENT = 0.2;
|
|
27528
27557
|
var MIDLINE = 0.35;
|
|
27558
|
+
// Average advance of a character, in ems, for estimating the width of text
|
|
27559
|
+
// that cannot be measured
|
|
27560
|
+
var AVERAGE_CHAR_WIDTH = 0.6;
|
|
27529
27561
|
|
|
27530
27562
|
function labelHasCallout(rec) {
|
|
27531
27563
|
var type = rec && rec.callout ? parseCalloutType(rec.callout) : null;
|
|
@@ -27589,18 +27621,24 @@
|
|
|
27589
27621
|
// no measurement the box takes the label's width if it is a text block, and is
|
|
27590
27622
|
// otherwise a vertical line through the text's origin -- a callout then meets
|
|
27591
27623
|
// the text where it starts rather than not being drawn.
|
|
27592
|
-
|
|
27624
|
+
//
|
|
27625
|
+
// opts.estimate_width: with no measurement and no label-width, estimate the
|
|
27626
|
+
// width from the length of the longest line instead of taking it as zero
|
|
27627
|
+
function getLabelTextBox(rec, opts) {
|
|
27593
27628
|
var fontSize = getFontSizeInPx$1(rec);
|
|
27594
27629
|
var offset = getDrawnLabelOffset(rec);
|
|
27595
27630
|
var lines = splitLabelLines(toLabelString(rec['label-text']));
|
|
27596
|
-
var lineHeight = measureToPx(rec['line-height']
|
|
27631
|
+
var lineHeight = measureToPx(getLineHeightDy(rec['line-height']), fontSize);
|
|
27597
27632
|
var width = getMeasuredTextWidth(rec) || getNumber(rec['label-width'], 0);
|
|
27633
|
+
if (!width && opts && opts.estimate_width) {
|
|
27634
|
+
width = estimateTextWidth(lines, fontSize);
|
|
27635
|
+
}
|
|
27598
27636
|
// An anchor the record does not set is inherited from the layer's group,
|
|
27599
27637
|
// which is 'middle' -- see getLabelTextDefaults().
|
|
27600
27638
|
var anchor = offset['text-anchor'] || 'middle';
|
|
27601
27639
|
var xmin = offset.dx - width * (anchor == 'middle' ? 0.5 : anchor == 'end' ? 1 : 0);
|
|
27602
27640
|
var baseline = offset.dy + getBaselineShift(rec['dominant-baseline']) * fontSize;
|
|
27603
|
-
if (!(lineHeight > 0)) lineHeight = fontSize *
|
|
27641
|
+
if (!(lineHeight > 0)) lineHeight = fontSize * DEFAULT_LINE_HEIGHT;
|
|
27604
27642
|
return {
|
|
27605
27643
|
xmin: xmin,
|
|
27606
27644
|
xmax: xmin + width,
|
|
@@ -27610,6 +27648,18 @@
|
|
|
27610
27648
|
};
|
|
27611
27649
|
}
|
|
27612
27650
|
|
|
27651
|
+
function estimateTextWidth(lines, fontSize) {
|
|
27652
|
+
var chars = lines.reduce(function(max, line) {
|
|
27653
|
+
return Math.max(max, Array.from(line).length);
|
|
27654
|
+
}, 0);
|
|
27655
|
+
return chars * AVERAGE_CHAR_WIDTH * fontSize;
|
|
27656
|
+
}
|
|
27657
|
+
|
|
27658
|
+
// The size a label's text is drawn at, in px
|
|
27659
|
+
function getLabelFontSize(rec) {
|
|
27660
|
+
return getFontSizeInPx$1(rec);
|
|
27661
|
+
}
|
|
27662
|
+
|
|
27613
27663
|
// Where a line meets a callout's text and how it gets there from the anchor.
|
|
27614
27664
|
//
|
|
27615
27665
|
// o.type 'line', 'elbow' or 'curve'
|
|
@@ -27636,7 +27686,7 @@
|
|
|
27636
27686
|
// point where the line meets it, the elbow's corner or the curve's midpoint
|
|
27637
27687
|
// (null for a straight line), and where the line stops short of the anchor.
|
|
27638
27688
|
function getCalloutShape(o) {
|
|
27639
|
-
var box = padBox(o.box, o.padding || 0);
|
|
27689
|
+
var box = padBox$1(o.box, o.padding || 0);
|
|
27640
27690
|
var a = [0, 0];
|
|
27641
27691
|
var size = o.endSize > 0 ? o.endSize : getDefaultCalloutEndSize(o.end, o.width);
|
|
27642
27692
|
var t, v, path, tip, dir, out, lineWidth, headLen, curved;
|
|
@@ -27997,7 +28047,7 @@
|
|
|
27997
28047
|
|
|
27998
28048
|
// Utilities
|
|
27999
28049
|
|
|
28000
|
-
function padBox(box, pad) {
|
|
28050
|
+
function padBox$1(box, pad) {
|
|
28001
28051
|
return {
|
|
28002
28052
|
xmin: box.xmin - pad,
|
|
28003
28053
|
xmax: box.xmax + pad,
|
|
@@ -28070,6 +28120,7 @@
|
|
|
28070
28120
|
getControlPoint: getControlPoint,
|
|
28071
28121
|
getDefaultCalloutEndSize: getDefaultCalloutEndSize,
|
|
28072
28122
|
getLabelCalloutShape: getLabelCalloutShape,
|
|
28123
|
+
getLabelFontSize: getLabelFontSize,
|
|
28073
28124
|
getLabelTextBox: getLabelTextBox,
|
|
28074
28125
|
labelHasCallout: labelHasCallout,
|
|
28075
28126
|
renderLabelCallout: renderLabelCallout
|
|
@@ -28366,7 +28417,9 @@
|
|
|
28366
28417
|
|
|
28367
28418
|
var SvgSymbols = /*#__PURE__*/Object.freeze({
|
|
28368
28419
|
__proto__: null,
|
|
28420
|
+
featureHasIcon: featureHasIcon,
|
|
28369
28421
|
getAnchorSymbolRadius: getAnchorSymbolRadius,
|
|
28422
|
+
getIconRadius: getIconRadius,
|
|
28370
28423
|
getTransform: getTransform,
|
|
28371
28424
|
renderPoint: renderPoint,
|
|
28372
28425
|
symbolRenderers: symbolRenderers
|
|
@@ -41392,7 +41445,7 @@ ${svg}
|
|
|
41392
41445
|
'font-weight': {describe: 'normal, bold or a numeric weight'},
|
|
41393
41446
|
'font-stretch': {describe: 'e.g. condensed'},
|
|
41394
41447
|
'letter-spacing': {describe: 'extra space between letters'},
|
|
41395
|
-
'line-height': {describe: 'spacing between lines of a multi-line label'},
|
|
41448
|
+
'line-height': {describe: 'spacing between lines of a multi-line label; a bare number is a multiple of the font size'},
|
|
41396
41449
|
'text-anchor': {describe: 'start, middle or end'},
|
|
41397
41450
|
'label-align': {describe: 'how the lines of a multi-line label align: left, center or right'},
|
|
41398
41451
|
'dominant-baseline': {describe: 'vertical alignment, e.g. central'},
|
|
@@ -43478,7 +43531,7 @@ ${svg}
|
|
|
43478
43531
|
describe: 'CSS letter-spacing property of labels'
|
|
43479
43532
|
})
|
|
43480
43533
|
.option('line-height', {
|
|
43481
|
-
describe: 'line spacing of multi-line labels (default is 1.
|
|
43534
|
+
describe: 'line spacing of multi-line labels; a bare number is a multiple of the font size, as in CSS (default is 1.1)'
|
|
43482
43535
|
})
|
|
43483
43536
|
.option('dominant-baseline', {
|
|
43484
43537
|
describe: 'vertical alignment of labels (e.g. central)'
|
|
@@ -43812,6 +43865,13 @@ ${svg}
|
|
|
43812
43865
|
describe: 'frame coordinates (xmin,ymin,xmax,ymax)',
|
|
43813
43866
|
type: 'bbox'
|
|
43814
43867
|
})
|
|
43868
|
+
.option('fit', {
|
|
43869
|
+
describe: 'layer(s) to fit the frame to, including their symbols and labels'
|
|
43870
|
+
})
|
|
43871
|
+
.option('ignore-symbols', {
|
|
43872
|
+
describe: 'with fit=, fit to point locations, not symbol and label extents',
|
|
43873
|
+
type: 'flag'
|
|
43874
|
+
})
|
|
43815
43875
|
.option('width', {
|
|
43816
43876
|
describe: 'nominal frame width (e.g. 5in, 10cm, 600px)'
|
|
43817
43877
|
})
|
|
@@ -43963,6 +44023,10 @@ ${svg}
|
|
|
43963
44023
|
describe: 'separate offsets for each side, in l,b,r,t order',
|
|
43964
44024
|
type: 'strings'
|
|
43965
44025
|
})
|
|
44026
|
+
.option('ignore-symbols', {
|
|
44027
|
+
describe: 'fit to point locations, not the extent of symbols and labels',
|
|
44028
|
+
type: 'flag'
|
|
44029
|
+
})
|
|
43966
44030
|
.option('name', nameOpt)
|
|
43967
44031
|
.option('replace', {
|
|
43968
44032
|
describe: 'replace the existing map frame',
|
|
@@ -51575,8 +51639,637 @@ ${svg}
|
|
|
51575
51639
|
bboxToPolygon: bboxToPolygon
|
|
51576
51640
|
});
|
|
51577
51641
|
|
|
51642
|
+
// Apply rotation, scale and/or shift to some or all of the features in a dataset
|
|
51643
|
+
//
|
|
51644
|
+
cmd.affine = function(targetLayers, dataset, opts) {
|
|
51645
|
+
// Need to separate the targeted shapes from any other shapes that share
|
|
51646
|
+
// the same topology. So we duplicate any arcs that are shared by the targeted
|
|
51647
|
+
// shapes and their topological neighbors and remap arc references in the
|
|
51648
|
+
// neighbors to point to the copies.
|
|
51649
|
+
// TODO: explore alternative: if some arcs are shared between transformed and
|
|
51650
|
+
// non-transformed shapes, first remove topology, then tranform, then rebuild topology
|
|
51651
|
+
//
|
|
51652
|
+
var rotateArg = opts.rotate || 0;
|
|
51653
|
+
var scaleArg = opts.scale || 1;
|
|
51654
|
+
var shiftArg = opts.shift ? convertIntervalPair(opts.shift, getDatasetCRS(dataset)) : [0, 0];
|
|
51655
|
+
var arcs = dataset.arcs;
|
|
51656
|
+
var targetShapes = [];
|
|
51657
|
+
var otherShapes = [];
|
|
51658
|
+
var targetPoints = [];
|
|
51659
|
+
var targetPointLayers = [];
|
|
51660
|
+
var targetFlags, otherFlags, transform;
|
|
51661
|
+
dataset.layers.filter(layerHasGeometry$1).forEach(function(lyr) {
|
|
51662
|
+
var hits = [],
|
|
51663
|
+
misses = [],
|
|
51664
|
+
test;
|
|
51665
|
+
if (targetLayers.indexOf(lyr) == -1) {
|
|
51666
|
+
misses = lyr.shapes;
|
|
51667
|
+
} else if (opts.where) {
|
|
51668
|
+
test = compileFeatureExpression(opts.where, lyr, dataset.arcs);
|
|
51669
|
+
lyr.shapes.forEach(function(shp, i) {
|
|
51670
|
+
(test(i) ? hits : misses).push(shp);
|
|
51671
|
+
});
|
|
51672
|
+
} else {
|
|
51673
|
+
hits = lyr.shapes;
|
|
51674
|
+
}
|
|
51675
|
+
if (lyr.geometry_type == 'point') {
|
|
51676
|
+
targetPoints = targetPoints.concat(hits);
|
|
51677
|
+
if (hits.length > 0) targetPointLayers.push(lyr);
|
|
51678
|
+
} else {
|
|
51679
|
+
targetShapes = targetShapes.concat(hits);
|
|
51680
|
+
otherShapes = otherShapes.concat(misses);
|
|
51681
|
+
}
|
|
51682
|
+
});
|
|
51683
|
+
var anchorArg = getAffineAnchor({arcs: dataset.arcs, layers: [{
|
|
51684
|
+
geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
|
|
51685
|
+
shapes: targetShapes}]}, opts);
|
|
51686
|
+
transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
|
|
51687
|
+
if (opts.fit_bbox) {
|
|
51688
|
+
transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
|
|
51689
|
+
}
|
|
51690
|
+
if (targetShapes.length > 0) {
|
|
51691
|
+
targetFlags = new Uint8Array(arcs.size());
|
|
51692
|
+
otherFlags = new Uint8Array(arcs.size());
|
|
51693
|
+
countArcsInShapes(targetShapes, targetFlags);
|
|
51694
|
+
if (otherShapes.length > 0) {
|
|
51695
|
+
countArcsInShapes(otherShapes, otherFlags);
|
|
51696
|
+
applyArrayMask(otherFlags, targetFlags);
|
|
51697
|
+
noteDatasetWillChange(dataset, {operation: 'affine', unit: 'arcs'});
|
|
51698
|
+
dataset.arcs = duplicateSelectedArcs(otherShapes, arcs, otherFlags);
|
|
51699
|
+
markDatasetChanged(dataset, {operation: 'affine', unit: 'arcs'});
|
|
51700
|
+
}
|
|
51701
|
+
dataset.arcs.transformPoints(function(x, y, arcId) {
|
|
51702
|
+
if (arcId < targetFlags.length && targetFlags[arcId] > 0) {
|
|
51703
|
+
return transform(x, y);
|
|
51704
|
+
}
|
|
51705
|
+
});
|
|
51706
|
+
}
|
|
51707
|
+
targetPointLayers.forEach(function(lyr) {
|
|
51708
|
+
noteLayerWillChange(lyr, {operation: 'affine', unit: 'shapes'});
|
|
51709
|
+
});
|
|
51710
|
+
forEachPoint(targetPoints, function(p) {
|
|
51711
|
+
var p2 = transform(p[0], p[1]);
|
|
51712
|
+
p[0] = p2[0];
|
|
51713
|
+
p[1] = p2[1];
|
|
51714
|
+
});
|
|
51715
|
+
targetPointLayers.forEach(function(lyr) {
|
|
51716
|
+
markLayerChanged(lyr, {operation: 'affine', unit: 'shapes'});
|
|
51717
|
+
});
|
|
51718
|
+
};
|
|
51719
|
+
|
|
51720
|
+
function getAffineAnchor(dataset, opts) {
|
|
51721
|
+
var anchor, bounds;
|
|
51722
|
+
if (opts.anchor) {
|
|
51723
|
+
anchor = opts.anchor;
|
|
51724
|
+
} else {
|
|
51725
|
+
// get bounds of selected shapes to calculate center of rotation/scale
|
|
51726
|
+
bounds = getDatasetBounds(dataset);
|
|
51727
|
+
anchor = [bounds.centerX(), bounds.centerY()];
|
|
51728
|
+
}
|
|
51729
|
+
return anchor;
|
|
51730
|
+
}
|
|
51731
|
+
|
|
51732
|
+
// TODO: handle problems with unprojected datasets
|
|
51733
|
+
// option 1: don't allow affine transformation of unprojected data
|
|
51734
|
+
// option 2: error if transformed data exceeds valid coordinate range
|
|
51735
|
+
// source: http://mathworld.wolfram.com/AffineTransformation.html
|
|
51736
|
+
function getAffineTransform(rotation, scale, shift, anchor) {
|
|
51737
|
+
var angle = rotation * Math.PI / 180;
|
|
51738
|
+
var a = scale * Math.cos(angle);
|
|
51739
|
+
var b = -scale * Math.sin(angle);
|
|
51740
|
+
return function(x, y) {
|
|
51741
|
+
var x2 = a * (x - anchor[0]) - b * (y - anchor[1]) + shift[0] + anchor[0];
|
|
51742
|
+
var y2 = b * (x - anchor[0]) + a * (y - anchor[1]) + shift[1] + anchor[1];
|
|
51743
|
+
return [x2, y2];
|
|
51744
|
+
};
|
|
51745
|
+
}
|
|
51746
|
+
|
|
51747
|
+
function getFitBoxTransform(bbox, points, shapes, arcs) {
|
|
51748
|
+
var dataset = {
|
|
51749
|
+
arcs: arcs,
|
|
51750
|
+
layers: []
|
|
51751
|
+
};
|
|
51752
|
+
if (points && points.length) {
|
|
51753
|
+
dataset.layers.push({
|
|
51754
|
+
geometry_type: 'point',
|
|
51755
|
+
shapes: points
|
|
51756
|
+
});
|
|
51757
|
+
}
|
|
51758
|
+
if (shapes && shapes.length) {
|
|
51759
|
+
dataset.layers.push({
|
|
51760
|
+
geometry_type: 'polyline',
|
|
51761
|
+
shapes: shapes
|
|
51762
|
+
});
|
|
51763
|
+
}
|
|
51764
|
+
var frame = calcFrameData(dataset, {fit_bbox: bbox});
|
|
51765
|
+
var fromBounds = new Bounds(frame.bbox);
|
|
51766
|
+
var toBounds = new Bounds(frame.bbox2);
|
|
51767
|
+
var fwd = fromBounds.getTransform(toBounds, false);
|
|
51768
|
+
return function(x, y) {
|
|
51769
|
+
return fwd.transform(x, y);
|
|
51770
|
+
};
|
|
51771
|
+
}
|
|
51772
|
+
|
|
51773
|
+
function applyArrayMask(destArr, maskArr) {
|
|
51774
|
+
for (var i=0, n=destArr.length; i<n; i++) {
|
|
51775
|
+
if (maskArr[i] === 0) destArr[i] = 0;
|
|
51776
|
+
}
|
|
51777
|
+
}
|
|
51778
|
+
|
|
51779
|
+
function duplicateSelectedArcs(shapes, arcs, flags) {
|
|
51780
|
+
var arcCount = 0;
|
|
51781
|
+
var vertexCount = 0;
|
|
51782
|
+
var data = arcs.getVertexData();
|
|
51783
|
+
var xx = [], yy = [], nn = [], map = [], n;
|
|
51784
|
+
for (var i=0, len=flags.length; i<len; i++) {
|
|
51785
|
+
if (flags[i] > 0) {
|
|
51786
|
+
map[i] = arcs.size() + arcCount;
|
|
51787
|
+
n = data.nn[i];
|
|
51788
|
+
utils.copyElements(data.xx, data.ii[i], xx, vertexCount, n);
|
|
51789
|
+
utils.copyElements(data.yy, data.ii[i], yy, vertexCount, n);
|
|
51790
|
+
nn.push(n);
|
|
51791
|
+
vertexCount += n;
|
|
51792
|
+
arcCount++;
|
|
51793
|
+
}
|
|
51794
|
+
}
|
|
51795
|
+
forEachArcId(shapes, function(id) {
|
|
51796
|
+
var absId = absArcId(id);
|
|
51797
|
+
if (flags[absId] > 0) {
|
|
51798
|
+
return id < 0 ? ~map[absId] : map[absId];
|
|
51799
|
+
}
|
|
51800
|
+
});
|
|
51801
|
+
return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
|
|
51802
|
+
}
|
|
51803
|
+
|
|
51804
|
+
var roundCoord$1 = getRoundingFunction(0.01);
|
|
51805
|
+
|
|
51806
|
+
function getSymbolFillColor(d) {
|
|
51807
|
+
return d.fill || 'magenta';
|
|
51808
|
+
}
|
|
51809
|
+
|
|
51810
|
+
function getSymbolStrokeColor(d) {
|
|
51811
|
+
return d.stroke || d.fill || 'magenta';
|
|
51812
|
+
}
|
|
51813
|
+
|
|
51814
|
+
function applySymbolStyles(sym, d) {
|
|
51815
|
+
if (sym.type == 'polyline') {
|
|
51816
|
+
sym.stroke = getSymbolStrokeColor(d);
|
|
51817
|
+
} else {
|
|
51818
|
+
sym.fill = getSymbolFillColor(d);
|
|
51819
|
+
}
|
|
51820
|
+
if (d.opacity) {
|
|
51821
|
+
sym.opacity = d.opacity;
|
|
51822
|
+
}
|
|
51823
|
+
return sym;
|
|
51824
|
+
}
|
|
51825
|
+
|
|
51826
|
+
function getSymbolRadius(d) {
|
|
51827
|
+
if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
|
|
51828
|
+
return d.radius || d.length || d.r || 5; // use a default value
|
|
51829
|
+
}
|
|
51830
|
+
|
|
51831
|
+
// Converts the value of a list-valued option into an array of items. The value
|
|
51832
|
+
// arrives either as a whole string (from a direct call, rather than from the
|
|
51833
|
+
// -symbols option accessor) or as an array of items already resolved one by
|
|
51834
|
+
// one. An item that holds an array of its own, as an expression like
|
|
51835
|
+
// [R, R * 0.5] does, is flattened into the list. Items themselves are left
|
|
51836
|
+
// unsplit, because a single item can contain a comma, as rgba() colors do.
|
|
51837
|
+
function toItemList(val) {
|
|
51838
|
+
var items = utils.isString(val) ? splitListItems(val) : [].concat(val);
|
|
51839
|
+
var list = [];
|
|
51840
|
+
for (var i=0; i<items.length; i++) {
|
|
51841
|
+
if (Array.isArray(items[i])) {
|
|
51842
|
+
list = list.concat(items[i]);
|
|
51843
|
+
} else {
|
|
51844
|
+
list.push(items[i]);
|
|
51845
|
+
}
|
|
51846
|
+
}
|
|
51847
|
+
return list;
|
|
51848
|
+
}
|
|
51849
|
+
|
|
51850
|
+
// Like toItemList(), for options that take numbers. An item that is a string
|
|
51851
|
+
// containing commas is split, so that a data field holding a value like "2,4"
|
|
51852
|
+
// works as a list. Values that aren't numbers become NaN, for the caller to
|
|
51853
|
+
// report or ignore.
|
|
51854
|
+
function toNumberList(val) {
|
|
51855
|
+
var list = [];
|
|
51856
|
+
toItemList(val).forEach(function(item) {
|
|
51857
|
+
if (utils.isString(item) && item.indexOf(',') > -1) {
|
|
51858
|
+
list = list.concat(parseNumberList(item));
|
|
51859
|
+
} else {
|
|
51860
|
+
list.push(Number(item));
|
|
51861
|
+
}
|
|
51862
|
+
});
|
|
51863
|
+
return list;
|
|
51864
|
+
}
|
|
51865
|
+
|
|
51866
|
+
// Returns the radius of the smallest circle centered on a symbol's point that
|
|
51867
|
+
// covers the symbol, or null if the symbol contains a part whose reach can't be
|
|
51868
|
+
// measured.
|
|
51869
|
+
//
|
|
51870
|
+
// Only a symbol drawn around its point can be described this way: a circle, or
|
|
51871
|
+
// a group of the circles and polygons that make up ring, pie and donut symbols.
|
|
51872
|
+
// An arrow is drawn from its point outward, so the circle around its point that
|
|
51873
|
+
// covers it says nothing useful about the space it occupies -- arrows are plain
|
|
51874
|
+
// polygon symbols, which reach this function only as group parts, which they
|
|
51875
|
+
// never are.
|
|
51876
|
+
function getSymbolBoundingRadius(sym) {
|
|
51877
|
+
var max = 0;
|
|
51878
|
+
var parts, r, i;
|
|
51879
|
+
if (!sym) return null;
|
|
51880
|
+
if (sym.type == 'circle') {
|
|
51881
|
+
// Half of a stroke lies outside the circle it follows, which is how a ring
|
|
51882
|
+
// symbol paints its bands.
|
|
51883
|
+
return getPositiveNumber(sym.r) + getStrokeOutset(sym);
|
|
51884
|
+
}
|
|
51885
|
+
if (sym.type == 'polygon') {
|
|
51886
|
+
forEachSymbolCoord(sym.coordinates || [], function(p) {
|
|
51887
|
+
var dist = Math.sqrt(p[0] * p[0] + p[1] * p[1]);
|
|
51888
|
+
if (dist > max) max = dist;
|
|
51889
|
+
});
|
|
51890
|
+
return max + getStrokeOutset(sym);
|
|
51891
|
+
}
|
|
51892
|
+
if (sym.type == 'group') {
|
|
51893
|
+
parts = sym.parts || [];
|
|
51894
|
+
for (i=0; i<parts.length; i++) {
|
|
51895
|
+
r = getSymbolBoundingRadius(parts[i]);
|
|
51896
|
+
if (r === null) return null;
|
|
51897
|
+
if (r > max) max = r;
|
|
51898
|
+
}
|
|
51899
|
+
return max;
|
|
51900
|
+
}
|
|
51901
|
+
return null;
|
|
51902
|
+
}
|
|
51903
|
+
|
|
51904
|
+
// A stroke straddles the path it is drawn on, so half of its width extends
|
|
51905
|
+
// beyond the shape.
|
|
51906
|
+
function getStrokeOutset(sym) {
|
|
51907
|
+
var width = sym['stroke-width'];
|
|
51908
|
+
if (!sym.stroke || sym.stroke == 'none') return 0;
|
|
51909
|
+
// an SVG stroke is one pixel wide unless stroke-width says otherwise
|
|
51910
|
+
if (width === undefined || width === null || width === '') return 0.5;
|
|
51911
|
+
return getPositiveNumber(width) / 2;
|
|
51912
|
+
}
|
|
51913
|
+
|
|
51914
|
+
function getPositiveNumber(val) {
|
|
51915
|
+
var num = +val;
|
|
51916
|
+
return num > 0 ? num : 0;
|
|
51917
|
+
}
|
|
51918
|
+
|
|
51919
|
+
function forEachSymbolCoord(coords, cb) {
|
|
51920
|
+
var isPoint = coords && utils.isNumber(coords[0]);
|
|
51921
|
+
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
51922
|
+
if (isPoint) return cb(coords);
|
|
51923
|
+
for (var i=0; i<coords.length; i++) {
|
|
51924
|
+
if (isNested) forEachSymbolCoord(coords[i], cb);
|
|
51925
|
+
}
|
|
51926
|
+
}
|
|
51927
|
+
|
|
51928
|
+
function flipY(coords) {
|
|
51929
|
+
forEachSymbolCoord(coords, function(p) {
|
|
51930
|
+
p[1] = -p[1];
|
|
51931
|
+
});
|
|
51932
|
+
}
|
|
51933
|
+
|
|
51934
|
+
function scaleAndShiftCoords(coords, scale, shift) {
|
|
51935
|
+
forEachSymbolCoord(coords, function(xy) {
|
|
51936
|
+
xy[0] = xy[0] * scale + shift[0];
|
|
51937
|
+
xy[1] = xy[1] * scale + shift[1];
|
|
51938
|
+
});
|
|
51939
|
+
}
|
|
51940
|
+
|
|
51941
|
+
function roundCoordsForSVG(coords) {
|
|
51942
|
+
forEachSymbolCoord(coords, function(p) {
|
|
51943
|
+
p[0] = roundCoord$1(p[0]);
|
|
51944
|
+
p[1] = roundCoord$1(p[1]);
|
|
51945
|
+
});
|
|
51946
|
+
}
|
|
51947
|
+
|
|
51948
|
+
function rotateCoords(coords, rotation) {
|
|
51949
|
+
if (!rotation) return;
|
|
51950
|
+
var f = getAffineTransform(rotation, 1, [0, 0], [0, 0]);
|
|
51951
|
+
forEachSymbolCoord(coords, function(p) {
|
|
51952
|
+
var p2 = f(p[0], p[1]);
|
|
51953
|
+
p[0] = p2[0];
|
|
51954
|
+
p[1] = p2[1];
|
|
51955
|
+
});
|
|
51956
|
+
}
|
|
51957
|
+
|
|
51958
|
+
// The space that a point's symbol and label take up when drawn, for fitting a
|
|
51959
|
+
// map frame around them.
|
|
51960
|
+
//
|
|
51961
|
+
// Boxes are [xmin, ymin, xmax, ymax] in px, relative to the point, with y down:
|
|
51962
|
+
// the space symbols are drawn in (see renderPoint() in svg-symbols.mjs, which
|
|
51963
|
+
// this follows). A box can leave out parts of what gets drawn -- anti-aliasing,
|
|
51964
|
+
// miter joins, a curve bowing past its control points -- but not by more than a
|
|
51965
|
+
// pixel or so.
|
|
51966
|
+
|
|
51967
|
+
// The box around what renderPoint() draws for @rec, or null if it draws nothing
|
|
51968
|
+
// that can be sized.
|
|
51969
|
+
// @cache: optional Map, for reusing the boxes of svg-symbol strings, which are
|
|
51970
|
+
// often the same for every feature in a layer
|
|
51971
|
+
function getPointSymbolBox(rec, cache) {
|
|
51972
|
+
var box = null;
|
|
51973
|
+
if (!rec) return null;
|
|
51974
|
+
if (featureHasSvgSymbol(rec)) {
|
|
51975
|
+
box = getSymbolBox(rec, cache);
|
|
51976
|
+
}
|
|
51977
|
+
if (featureHasLabel(rec)) {
|
|
51978
|
+
box = mergeBoxes(box, getLabelBox(rec));
|
|
51979
|
+
}
|
|
51980
|
+
return box;
|
|
51981
|
+
}
|
|
51982
|
+
|
|
51983
|
+
// How far the text of a label drawn along a path can reach from the path, in
|
|
51984
|
+
// px. The side the text sits on turns with the path, so it is allowed for on
|
|
51985
|
+
// every side.
|
|
51986
|
+
function getPathLabelPadding(rec) {
|
|
51987
|
+
return getLabelFontSize(rec) + getHaloWidth(rec);
|
|
51988
|
+
}
|
|
51989
|
+
|
|
51990
|
+
function getLabelBox(rec) {
|
|
51991
|
+
var o = getLabelTextBox(rec, {estimate_width: true});
|
|
51992
|
+
var pad = getHaloWidth(rec);
|
|
51993
|
+
var box = [o.xmin - pad, o.ymin - pad, o.xmax + pad, o.ymax + pad];
|
|
51994
|
+
var via = labelHasCallout(rec) ? parsePointPair(rec['callout-via'] || '') : null;
|
|
51995
|
+
if (via) {
|
|
51996
|
+
box = mergeBoxes(box, [via[0], via[1], via[0], via[1]]);
|
|
51997
|
+
}
|
|
51998
|
+
return box;
|
|
51999
|
+
}
|
|
52000
|
+
|
|
52001
|
+
function getHaloWidth(rec) {
|
|
52002
|
+
var w = +rec['halo-width'];
|
|
52003
|
+
return w > 0 ? w : 0;
|
|
52004
|
+
}
|
|
52005
|
+
|
|
52006
|
+
function getSymbolBox(d, cache) {
|
|
52007
|
+
var sym = d['svg-symbol'];
|
|
52008
|
+
if (sym) return getComplexSymbolBox(sym, cache);
|
|
52009
|
+
if (featureHasIcon(d)) return getIconBox(d);
|
|
52010
|
+
if (d.r > 0) return getSquareBox(+d.r + getStrokeOutset(d), 0, 0);
|
|
52011
|
+
return null;
|
|
52012
|
+
}
|
|
52013
|
+
|
|
52014
|
+
function getIconBox(d) {
|
|
52015
|
+
var type = d.icon || 'circle';
|
|
52016
|
+
var r = getIconRadius(d, type);
|
|
52017
|
+
var sw;
|
|
52018
|
+
if (!(r > 0)) return null;
|
|
52019
|
+
if (type == 'ring') {
|
|
52020
|
+
// drawn as a stroke, one px wide unless stroke-width says otherwise
|
|
52021
|
+
sw = +d['stroke-width'];
|
|
52022
|
+
return getSquareBox(r + (sw > 0 ? sw : 1) / 2, 0, 0);
|
|
52023
|
+
}
|
|
52024
|
+
if (type == 'circle' || type == 'square' || type == 'star') {
|
|
52025
|
+
return getSquareBox(r + getStrokeOutset(d), 0, 0);
|
|
52026
|
+
}
|
|
52027
|
+
return null; // unsupported icons are not drawn
|
|
52028
|
+
}
|
|
52029
|
+
|
|
52030
|
+
function getComplexSymbolBox(sym, cache) {
|
|
52031
|
+
var box;
|
|
52032
|
+
if (!utils.isString(sym)) return getSymbolPartBox(sym, 0, 0);
|
|
52033
|
+
if (cache && cache.has(sym)) return cache.get(sym);
|
|
52034
|
+
try {
|
|
52035
|
+
box = getSymbolPartBox(JSON.parse(sym), 0, 0);
|
|
52036
|
+
} catch(e) {
|
|
52037
|
+
box = null;
|
|
52038
|
+
}
|
|
52039
|
+
if (cache) cache.set(sym, box);
|
|
52040
|
+
return box;
|
|
52041
|
+
}
|
|
52042
|
+
|
|
52043
|
+
// Follows the renderers in svg-symbols.mjs: @x, @y is where the part is drawn,
|
|
52044
|
+
// which circles, squares, images, lines and labels are placed at and polygons
|
|
52045
|
+
// and polylines ignore.
|
|
52046
|
+
function getSymbolPartBox(sym, x, y) {
|
|
52047
|
+
var type = sym && sym.type;
|
|
52048
|
+
var w, h;
|
|
52049
|
+
if (!sym || sym.tag) return null; // raw SVG can't be sized
|
|
52050
|
+
if (type == 'circle' || type == 'square') {
|
|
52051
|
+
return getSquareBox(getPositive(sym.r) + getStrokeOutset(sym), x, y);
|
|
52052
|
+
}
|
|
52053
|
+
if (type == 'image') {
|
|
52054
|
+
w = sym.width || 20;
|
|
52055
|
+
h = sym.height || 20;
|
|
52056
|
+
return [x - w / 2, y - h / 2, x + w / 2, y + h / 2];
|
|
52057
|
+
}
|
|
52058
|
+
if (type == 'polygon') {
|
|
52059
|
+
return padBox(getCoordsBox(sym.coordinates), getStrokeOutset(sym));
|
|
52060
|
+
}
|
|
52061
|
+
if (type == 'polyline') {
|
|
52062
|
+
return padBox(getCoordsBox(sym.coordinates), getLineOutset(sym));
|
|
52063
|
+
}
|
|
52064
|
+
if (type == 'line') {
|
|
52065
|
+
return padBox(getCoordsBox([[x, y], [x + (sym.dx || 0), y + (sym.dy || 0)]]),
|
|
52066
|
+
getLineOutset(sym));
|
|
52067
|
+
}
|
|
52068
|
+
if (type == 'label') {
|
|
52069
|
+
return shiftBox(getLabelBox(sym), x, y);
|
|
52070
|
+
}
|
|
52071
|
+
if (type == 'group') {
|
|
52072
|
+
return getGroupBox(sym, x, y);
|
|
52073
|
+
}
|
|
52074
|
+
return null; // offset draws nothing, and unknown types are not drawn
|
|
52075
|
+
}
|
|
52076
|
+
|
|
52077
|
+
// A line part moves the parts after it to its far end; an offset part moves
|
|
52078
|
+
// them into a group translated by its offset.
|
|
52079
|
+
function getGroupBox(sym, x, y) {
|
|
52080
|
+
var box = null;
|
|
52081
|
+
var ox = 0, oy = 0;
|
|
52082
|
+
(sym.parts || []).forEach(function(part) {
|
|
52083
|
+
box = mergeBoxes(box, shiftBox(getSymbolPartBox(part, x, y), ox, oy));
|
|
52084
|
+
if (!part) return;
|
|
52085
|
+
if (part.type == 'line') {
|
|
52086
|
+
x += part.dx || 0;
|
|
52087
|
+
y += part.dy || 0;
|
|
52088
|
+
} else if (part.type == 'offset') {
|
|
52089
|
+
ox += x + (part.dx || 0);
|
|
52090
|
+
oy += y + (part.dy || 0);
|
|
52091
|
+
x = y = 0;
|
|
52092
|
+
}
|
|
52093
|
+
});
|
|
52094
|
+
return box;
|
|
52095
|
+
}
|
|
52096
|
+
|
|
52097
|
+
// A stroked line is one px wide unless stroke-width says otherwise
|
|
52098
|
+
function getLineOutset(sym) {
|
|
52099
|
+
var sw = +sym['stroke-width'];
|
|
52100
|
+
return (sw > 0 ? sw : 1) / 2;
|
|
52101
|
+
}
|
|
52102
|
+
|
|
52103
|
+
function getCoordsBox(coords) {
|
|
52104
|
+
var box = null;
|
|
52105
|
+
forEachSymbolCoord(coords || [], function(p) {
|
|
52106
|
+
if (!isFinite(p[0]) || !isFinite(p[1])) return;
|
|
52107
|
+
box = mergeBoxes(box, [p[0], p[1], p[0], p[1]]);
|
|
52108
|
+
});
|
|
52109
|
+
return box;
|
|
52110
|
+
}
|
|
52111
|
+
|
|
52112
|
+
function getSquareBox(r, x, y) {
|
|
52113
|
+
return r > 0 ? [x - r, y - r, x + r, y + r] : null;
|
|
52114
|
+
}
|
|
52115
|
+
|
|
52116
|
+
function padBox(box, pad) {
|
|
52117
|
+
return box ? [box[0] - pad, box[1] - pad, box[2] + pad, box[3] + pad] : null;
|
|
52118
|
+
}
|
|
52119
|
+
|
|
52120
|
+
function shiftBox(box, dx, dy) {
|
|
52121
|
+
if (!box || !dx && !dy) return box;
|
|
52122
|
+
return [box[0] + dx, box[1] + dy, box[2] + dx, box[3] + dy];
|
|
52123
|
+
}
|
|
52124
|
+
|
|
52125
|
+
function mergeBoxes(a, b) {
|
|
52126
|
+
if (!a) return b;
|
|
52127
|
+
if (!b) return a;
|
|
52128
|
+
return [Math.min(a[0], b[0]), Math.min(a[1], b[1]),
|
|
52129
|
+
Math.max(a[2], b[2]), Math.max(a[3], b[3])];
|
|
52130
|
+
}
|
|
52131
|
+
|
|
52132
|
+
function getPositive(val) {
|
|
52133
|
+
var num = +val;
|
|
52134
|
+
return num > 0 ? num : 0;
|
|
52135
|
+
}
|
|
52136
|
+
|
|
52137
|
+
// Fitting a map frame to its content, including the symbols and labels drawn
|
|
52138
|
+
// at points, which take up room that the point coordinates alone do not.
|
|
52139
|
+
//
|
|
52140
|
+
// Symbols are sized in output pixels, and how many map units a pixel covers is
|
|
52141
|
+
// set by the frame being fitted, so the extent can't simply be padded. It is
|
|
52142
|
+
// found by solving for the scale instead: the smallest scale s (map units per
|
|
52143
|
+
// output pixel) at which the content -- points and their symbols, drawn at s --
|
|
52144
|
+
// gets a frame whose own scale is no larger than s.
|
|
52145
|
+
//
|
|
52146
|
+
// Every point's box moves each side of the extent linearly with s, so the
|
|
52147
|
+
// extent's width and height are convex, piecewise-linear functions of s, and so
|
|
52148
|
+
// is the frame scale they produce. Newton's method converges on the smallest
|
|
52149
|
+
// fixed point of such a function from below, without overshooting, in as many
|
|
52150
|
+
// steps as there are pieces along the way -- usually two or three.
|
|
52151
|
+
|
|
52152
|
+
var MAX_ITERATIONS = 50;
|
|
52153
|
+
var TOLERANCE = 1e-9;
|
|
52154
|
+
|
|
52155
|
+
// Returns the extent of @targets as a bbox array, padded to hold the symbols
|
|
52156
|
+
// and labels at their points, or null if the targets have no extent.
|
|
52157
|
+
//
|
|
52158
|
+
// @targets [{layer, dataset}]
|
|
52159
|
+
// @getScale function(bbox) -> map units per output px of a frame fitted to
|
|
52160
|
+
// @bbox; this is where the caller's offsets and page shape come in.
|
|
52161
|
+
// May return NaN for a bbox the frame can't be fitted to.
|
|
52162
|
+
// @opts.ignore_symbols fit to the coordinates alone
|
|
52163
|
+
function getFrameContentBbox(targets, getScale, opts) {
|
|
52164
|
+
var bounds = targets.reduce(function(memo, o) {
|
|
52165
|
+
return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
|
|
52166
|
+
}, new Bounds());
|
|
52167
|
+
var raw, items, bbox;
|
|
52168
|
+
if (!bounds.hasBounds()) return null;
|
|
52169
|
+
raw = bounds.toArray();
|
|
52170
|
+
if (opts && opts.ignore_symbols) return raw;
|
|
52171
|
+
items = getSymbolExtents(targets);
|
|
52172
|
+
if (items.length === 0) return raw;
|
|
52173
|
+
bbox = fitBboxToSymbols(raw, items, getScale);
|
|
52174
|
+
if (!bbox) {
|
|
52175
|
+
warn('Symbols and labels are too large to fit in the frame; ' +
|
|
52176
|
+
'fitting the frame to point locations only.');
|
|
52177
|
+
return raw;
|
|
52178
|
+
}
|
|
52179
|
+
return bbox;
|
|
52180
|
+
}
|
|
52181
|
+
|
|
52182
|
+
// The map units per output px of a frame with extent @bbox and nominal width
|
|
52183
|
+
// @width. A frame with a fixed page shape is filled out to that shape when it is
|
|
52184
|
+
// rendered (see fitDatasetToFrame()), so its scale is set by whichever of its
|
|
52185
|
+
// width and height is the tighter fit.
|
|
52186
|
+
function getFrameScale(bbox, width, fixedAspect) {
|
|
52187
|
+
var w = bbox[2] - bbox[0];
|
|
52188
|
+
var h = bbox[3] - bbox[1];
|
|
52189
|
+
return fixedAspect > 0 ? Math.max(w, h * fixedAspect) / width : w / width;
|
|
52190
|
+
}
|
|
52191
|
+
|
|
52192
|
+
// @raw bbox of the content without symbols
|
|
52193
|
+
// @items flat array of [x, y, xmin, ymin, xmax, ymax, ...]: each point and the
|
|
52194
|
+
// box around it in px, oriented like the map (y up)
|
|
52195
|
+
// Returns the padded bbox, or null if there is no scale at which the symbols
|
|
52196
|
+
// fit -- when they add up to more than the width of the page.
|
|
52197
|
+
function fitBboxToSymbols(raw, items, getScale) {
|
|
52198
|
+
var s = getScale(raw);
|
|
52199
|
+
var bbox, h, delta, slope, i;
|
|
52200
|
+
for (i = 0; i < MAX_ITERATIONS; i++) {
|
|
52201
|
+
if (!isValidScale(s)) return null;
|
|
52202
|
+
bbox = expandBbox(raw, items, s);
|
|
52203
|
+
h = getScale(bbox);
|
|
52204
|
+
if (!isValidScale(h)) return null;
|
|
52205
|
+
// Content with no extent of its own, like a single point, stops here with
|
|
52206
|
+
// s = 0: nothing about it sets a scale.
|
|
52207
|
+
if (h <= s * (1 + TOLERANCE)) return bbox;
|
|
52208
|
+
if (!(s > 0)) return null;
|
|
52209
|
+
delta = s * 1e-7;
|
|
52210
|
+
slope = (getScale(expandBbox(raw, items, s + delta)) - h) / delta;
|
|
52211
|
+
if (!(slope < 1)) return null;
|
|
52212
|
+
s += (h - s) / (1 - slope);
|
|
52213
|
+
}
|
|
52214
|
+
return expandBbox(raw, items, s);
|
|
52215
|
+
}
|
|
52216
|
+
|
|
52217
|
+
function isValidScale(s) {
|
|
52218
|
+
return s >= 0 && s < Infinity;
|
|
52219
|
+
}
|
|
52220
|
+
|
|
52221
|
+
function expandBbox(raw, items, s) {
|
|
52222
|
+
var xmin = raw[0], ymin = raw[1], xmax = raw[2], ymax = raw[3];
|
|
52223
|
+
var x, y, v;
|
|
52224
|
+
for (var i = 0, n = items.length; i < n; i += 6) {
|
|
52225
|
+
x = items[i];
|
|
52226
|
+
y = items[i + 1];
|
|
52227
|
+
v = x + items[i + 2] * s;
|
|
52228
|
+
if (v < xmin) xmin = v;
|
|
52229
|
+
v = y + items[i + 3] * s;
|
|
52230
|
+
if (v < ymin) ymin = v;
|
|
52231
|
+
v = x + items[i + 4] * s;
|
|
52232
|
+
if (v > xmax) xmax = v;
|
|
52233
|
+
v = y + items[i + 5] * s;
|
|
52234
|
+
if (v > ymax) ymax = v;
|
|
52235
|
+
}
|
|
52236
|
+
return [xmin, ymin, xmax, ymax];
|
|
52237
|
+
}
|
|
52238
|
+
|
|
52239
|
+
// Boxes come from the renderer's space, where y is down, and are flipped here
|
|
52240
|
+
// to the map's.
|
|
52241
|
+
function getSymbolExtents(targets) {
|
|
52242
|
+
var items = [];
|
|
52243
|
+
var cache = new Map();
|
|
52244
|
+
targets.forEach(function(o) {
|
|
52245
|
+
var lyr = o.layer;
|
|
52246
|
+
var records;
|
|
52247
|
+
if (lyr.geometry_type != 'point' || !lyr.shapes || !lyr.data) return;
|
|
52248
|
+
records = lyr.data.getRecords();
|
|
52249
|
+
lyr.shapes.forEach(function(shp, i) {
|
|
52250
|
+
var rec = records[i];
|
|
52251
|
+
var box, pad;
|
|
52252
|
+
if (!shp || !rec) return;
|
|
52253
|
+
if (shp.length > 1 && featureHasLabel(rec)) {
|
|
52254
|
+
// a multipoint label is drawn along a path through its points,
|
|
52255
|
+
// with no symbol (see featureIsPathLabel())
|
|
52256
|
+
pad = getPathLabelPadding(rec);
|
|
52257
|
+
box = [-pad, -pad, pad, pad];
|
|
52258
|
+
} else {
|
|
52259
|
+
box = getPointSymbolBox(rec, cache);
|
|
52260
|
+
}
|
|
52261
|
+
if (!box) return;
|
|
52262
|
+
for (var j = 0; j < shp.length; j++) {
|
|
52263
|
+
if (!shp[j] || !isFinite(shp[j][0]) || !isFinite(shp[j][1])) continue;
|
|
52264
|
+
items.push(shp[j][0], shp[j][1], box[0], -box[3], box[2], -box[1]);
|
|
52265
|
+
}
|
|
52266
|
+
});
|
|
52267
|
+
});
|
|
52268
|
+
return items;
|
|
52269
|
+
}
|
|
52270
|
+
|
|
51578
52271
|
cmd.frame = function(catalog, targets, opts) {
|
|
51579
|
-
var widthPx, heightPx,
|
|
52272
|
+
var widthPx, heightPx, bbox;
|
|
51580
52273
|
var existingFrame = getActiveFrame(catalog);
|
|
51581
52274
|
if (opts.width) {
|
|
51582
52275
|
widthPx = parseFrameSize(opts.width).valuePx;
|
|
@@ -51612,30 +52305,24 @@ ${svg}
|
|
|
51612
52305
|
} else {
|
|
51613
52306
|
var datasets = utils.pluck(targets, 'dataset');
|
|
51614
52307
|
requireDatasetsHaveCompatibleCRS(datasets, 'Targets include both projected and unprojected coordinates');
|
|
51615
|
-
bbox =
|
|
52308
|
+
bbox = getFrameContentBbox(expandCommandTargets(targets), function(contentBbox) {
|
|
52309
|
+
var extent = getFrameExtent(contentBbox, widthPx, heightPx, opts);
|
|
52310
|
+
return getFrameScale(extent.bbox, extent.width, getFixedAspect$1(extent, opts));
|
|
52311
|
+
}, opts);
|
|
51616
52312
|
if (!bbox) {
|
|
51617
52313
|
stop$1('Command target is missing geographical bounds');
|
|
51618
52314
|
}
|
|
51619
52315
|
}
|
|
51620
52316
|
|
|
51621
|
-
|
|
51622
|
-
|
|
51623
|
-
|
|
51624
|
-
if (bbox[3] - bbox[1] > 0 === false || bbox[2] - bbox[0] > 0 === false) {
|
|
52317
|
+
var extent = getFrameExtent(bbox, widthPx, heightPx, opts);
|
|
52318
|
+
if (!extent.valid) {
|
|
51625
52319
|
stop$1('Frame has a collapsed bbox');
|
|
51626
52320
|
}
|
|
51627
52321
|
|
|
51628
|
-
aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
51629
|
-
if (!widthPx) {
|
|
51630
|
-
widthPx = roundToDigits(heightPx * aspectRatio, 1);
|
|
51631
|
-
} else if (!heightPx) {
|
|
51632
|
-
heightPx = roundToDigits(widthPx / aspectRatio, 1);
|
|
51633
|
-
}
|
|
51634
|
-
|
|
51635
52322
|
var feature = {
|
|
51636
52323
|
type: 'Feature',
|
|
51637
|
-
properties: getFrameProperties(
|
|
51638
|
-
geometry: bboxToPolygon(bbox)
|
|
52324
|
+
properties: getFrameProperties(extent.width, extent.height, opts),
|
|
52325
|
+
geometry: bboxToPolygon(extent.bbox)
|
|
51639
52326
|
};
|
|
51640
52327
|
var frameDataset = importGeoJSON(feature);
|
|
51641
52328
|
// set CRS from target dataset
|
|
@@ -51655,6 +52342,34 @@ ${svg}
|
|
|
51655
52342
|
catalog.addDataset(frameDataset);
|
|
51656
52343
|
};
|
|
51657
52344
|
|
|
52345
|
+
// The frame's extent and nominal size, from the extent of its content and the
|
|
52346
|
+
// size options. Pure, so that fitting to symbols can ask what scale an extent
|
|
52347
|
+
// would give the frame.
|
|
52348
|
+
function getFrameExtent(contentBbox, widthPx, heightPx, opts) {
|
|
52349
|
+
var offsets = opts.offset || opts.offsets;
|
|
52350
|
+
var bbox = contentBbox.slice();
|
|
52351
|
+
var aspectRatio;
|
|
52352
|
+
applyPercentageOffsets(bbox, offsets);
|
|
52353
|
+
applyPixelOffsets(bbox, widthPx, heightPx, offsets);
|
|
52354
|
+
aspectRatio = (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
52355
|
+
if (!widthPx) {
|
|
52356
|
+
widthPx = roundToDigits(heightPx * aspectRatio, 1);
|
|
52357
|
+
} else if (!heightPx) {
|
|
52358
|
+
heightPx = roundToDigits(widthPx / aspectRatio, 1);
|
|
52359
|
+
}
|
|
52360
|
+
return {
|
|
52361
|
+
bbox: bbox,
|
|
52362
|
+
width: widthPx,
|
|
52363
|
+
height: heightPx,
|
|
52364
|
+
valid: bbox[3] - bbox[1] > 0 && bbox[2] - bbox[0] > 0
|
|
52365
|
+
};
|
|
52366
|
+
}
|
|
52367
|
+
|
|
52368
|
+
function getFixedAspect$1(extent, opts) {
|
|
52369
|
+
return opts.aspect_ratio > 0 || opts.width && opts.height ?
|
|
52370
|
+
extent.width / extent.height : null;
|
|
52371
|
+
}
|
|
52372
|
+
|
|
51658
52373
|
function getFrameProperties(width, height, opts) {
|
|
51659
52374
|
var properties = {
|
|
51660
52375
|
type: 'frame',
|
|
@@ -51750,14 +52465,6 @@ ${svg}
|
|
|
51750
52465
|
return arg;
|
|
51751
52466
|
}
|
|
51752
52467
|
|
|
51753
|
-
function getTargetBbox(targets) {
|
|
51754
|
-
var expanded = expandCommandTargets(targets);
|
|
51755
|
-
var bounds = expanded.reduce(function(memo, o) {
|
|
51756
|
-
return memo.mergeBounds(getLayerBounds(o.layer, o.dataset.arcs));
|
|
51757
|
-
}, new Bounds());
|
|
51758
|
-
return bounds.hasBounds() ? bounds.toArray() : null;
|
|
51759
|
-
}
|
|
51760
|
-
|
|
51761
52468
|
// Convert width and height args to aspect ratio arg for the rectangle() function
|
|
51762
52469
|
function getAspectRatioArg(widthArg, heightArg) {
|
|
51763
52470
|
// heightArg is a string containing either a number or a
|
|
@@ -51902,7 +52609,8 @@ ${svg}
|
|
|
51902
52609
|
|
|
51903
52610
|
var OPERATION$1 = 'update-frame';
|
|
51904
52611
|
|
|
51905
|
-
|
|
52612
|
+
// @fitTargets: [{layer, dataset}] named by the fit= option
|
|
52613
|
+
function updateFrame(targetLayers, dataset, opts, fitTargets) {
|
|
51906
52614
|
if (!targetLayers || targetLayers.length != 1) {
|
|
51907
52615
|
stop$1('-update-frame expects a single target layer');
|
|
51908
52616
|
}
|
|
@@ -51926,15 +52634,14 @@ ${svg}
|
|
|
51926
52634
|
if (opts.fix_scale && (opts.width !== undefined || opts.height !== undefined)) {
|
|
51927
52635
|
stop$1('fix-scale cannot be combined with width= or height=');
|
|
51928
52636
|
}
|
|
52637
|
+
if (opts.bbox !== undefined && opts.fit !== undefined) {
|
|
52638
|
+
stop$1('bbox= and fit= are mutually exclusive');
|
|
52639
|
+
}
|
|
51929
52640
|
|
|
51930
52641
|
var rec = lyr.data.getRecords()[0];
|
|
51931
52642
|
var frame = getFrameLayerData(lyr, dataset.arcs);
|
|
51932
|
-
var bbox = (opts.bbox || frame.bbox).slice();
|
|
51933
|
-
var offsetArg = opts.offset || opts.offsets;
|
|
51934
52643
|
var fixedAspect = getFixedAspect(rec);
|
|
51935
|
-
var
|
|
51936
|
-
var units = rec.frame_units || 'px';
|
|
51937
|
-
var widthSize, heightSize, height, effectiveAspect;
|
|
52644
|
+
var sizes, contentBbox, update;
|
|
51938
52645
|
|
|
51939
52646
|
// 1. Aspect mode, resolved first because whether the page shape is fixed
|
|
51940
52647
|
// decides whether it may constrain the extent while offsets are applied.
|
|
@@ -51946,8 +52653,87 @@ ${svg}
|
|
|
51946
52653
|
}
|
|
51947
52654
|
fixedAspect = opts.aspect_ratio;
|
|
51948
52655
|
}
|
|
52656
|
+
sizes = parseSizeOptions(opts);
|
|
52657
|
+
|
|
52658
|
+
// 2. Extent, which fit= finds by trying extents out on steps 3 and 4
|
|
52659
|
+
if (opts.fit !== undefined) {
|
|
52660
|
+
contentBbox = getFrameContentBbox(getFitTargets(fitTargets, dataset), function(bbox) {
|
|
52661
|
+
var o = resolveFrameUpdate(bbox, frame, fixedAspect, sizes, opts);
|
|
52662
|
+
return getFrameScale(o.bbox, o.width, o.fixedAspect);
|
|
52663
|
+
}, opts);
|
|
52664
|
+
if (!contentBbox) {
|
|
52665
|
+
stop$1('Layers to fit are missing geographical bounds');
|
|
52666
|
+
}
|
|
52667
|
+
} else {
|
|
52668
|
+
contentBbox = opts.bbox || frame.bbox;
|
|
52669
|
+
}
|
|
52670
|
+
|
|
52671
|
+
update = resolveFrameUpdate(contentBbox, frame, fixedAspect, sizes, opts);
|
|
52672
|
+
if (!update.valid) {
|
|
52673
|
+
stop$1('Frame has a collapsed bbox');
|
|
52674
|
+
}
|
|
52675
|
+
if (opts.fix_scale) {
|
|
52676
|
+
requirePositiveSize(update.width, 'fix-scale', update.width);
|
|
52677
|
+
}
|
|
52678
|
+
|
|
52679
|
+
noteLayerWillChange(lyr, {operation: OPERATION$1, unit: 'shapes'});
|
|
52680
|
+
lyr.data.captureTableBefore({operation: OPERATION$1});
|
|
52681
|
+
rec.width = update.width;
|
|
52682
|
+
rec.height = update.height;
|
|
52683
|
+
rec.frame_units = update.units;
|
|
52684
|
+
if (update.fixedAspect) {
|
|
52685
|
+
rec.frame_aspect_ratio = update.fixedAspect;
|
|
52686
|
+
} else {
|
|
52687
|
+
delete rec.frame_aspect_ratio;
|
|
52688
|
+
}
|
|
52689
|
+
lyr.data.markChanged({operation: OPERATION$1});
|
|
52690
|
+
rebuildFrameLayerGeometry(lyr, dataset, new Bounds(update.bbox));
|
|
52691
|
+
markLayerChanged(lyr, {operation: OPERATION$1, unit: 'shapes'});
|
|
52692
|
+
}
|
|
52693
|
+
|
|
52694
|
+
// The frame's layers are not content to fit, and a frame is only fitted to
|
|
52695
|
+
// layers it shares coordinates with.
|
|
52696
|
+
function getFitTargets(fitTargets, frameDataset) {
|
|
52697
|
+
var targets = (fitTargets || []).filter(function(o) {
|
|
52698
|
+
return !isFrameLayer(o.layer, o.dataset.arcs);
|
|
52699
|
+
});
|
|
52700
|
+
if (targets.length === 0) {
|
|
52701
|
+
stop$1('fit= found no layers to fit the frame to');
|
|
52702
|
+
}
|
|
52703
|
+
requireDatasetsHaveCompatibleCRS(
|
|
52704
|
+
utils.uniq(targets.map(function(o) { return o.dataset; }).concat(frameDataset)),
|
|
52705
|
+
'Layers to fit and the frame have incompatible coordinates'
|
|
52706
|
+
);
|
|
52707
|
+
return targets;
|
|
52708
|
+
}
|
|
51949
52709
|
|
|
51950
|
-
|
|
52710
|
+
function parseSizeOptions(opts) {
|
|
52711
|
+
var sizes = {width: null, height: null};
|
|
52712
|
+
if (opts.width !== undefined) {
|
|
52713
|
+
sizes.width = parseFrameSize(opts.width);
|
|
52714
|
+
requirePositiveSize(sizes.width.valuePx, 'width', opts.width);
|
|
52715
|
+
}
|
|
52716
|
+
if (opts.height !== undefined) {
|
|
52717
|
+
sizes.height = parseFrameSize(opts.height);
|
|
52718
|
+
requirePositiveSize(sizes.height.valuePx, 'height', opts.height);
|
|
52719
|
+
}
|
|
52720
|
+
if (sizes.width && sizes.height && opts.aspect_ratio !== undefined &&
|
|
52721
|
+
ratiosDiffer(sizes.width.valuePx / sizes.height.valuePx, opts.aspect_ratio)) {
|
|
52722
|
+
stop$1('Contradictory width, height and aspect-ratio values');
|
|
52723
|
+
}
|
|
52724
|
+
return sizes;
|
|
52725
|
+
}
|
|
52726
|
+
|
|
52727
|
+
// Steps 3 and 4 as a pure function of the content extent, so that fit= can
|
|
52728
|
+
// ask what scale an extent would give the frame.
|
|
52729
|
+
function resolveFrameUpdate(contentBbox, frame, fixedAspect, sizes, opts) {
|
|
52730
|
+
var bbox = contentBbox.slice();
|
|
52731
|
+
var offsetArg = opts.offset || opts.offsets;
|
|
52732
|
+
var width = frame.width;
|
|
52733
|
+
var units = frame.units || 'px';
|
|
52734
|
+
var valid, effectiveAspect;
|
|
52735
|
+
|
|
52736
|
+
// 3. Padding
|
|
51951
52737
|
if (offsetArg) {
|
|
51952
52738
|
applyPercentageOffsets(bbox, offsetArg);
|
|
51953
52739
|
// Pass a page height only when the shape is fixed. A derived height
|
|
@@ -51956,65 +52742,44 @@ ${svg}
|
|
|
51956
52742
|
applyPixelOffsets(bbox, frame.width,
|
|
51957
52743
|
fixedAspect ? frame.width / fixedAspect : null, offsetArg);
|
|
51958
52744
|
}
|
|
51959
|
-
|
|
52745
|
+
valid = isValidBbox(bbox);
|
|
51960
52746
|
if (fixedAspect) {
|
|
51961
52747
|
fillOutBbox(bbox, fixedAspect, 1);
|
|
51962
52748
|
}
|
|
51963
52749
|
|
|
51964
|
-
//
|
|
51965
|
-
if (
|
|
51966
|
-
|
|
51967
|
-
|
|
51968
|
-
|
|
51969
|
-
if (opts.height !== undefined) {
|
|
51970
|
-
heightSize = parseFrameSize(opts.height);
|
|
51971
|
-
requirePositiveSize(heightSize.valuePx, 'height', opts.height);
|
|
51972
|
-
}
|
|
51973
|
-
if (widthSize && heightSize) {
|
|
51974
|
-
effectiveAspect = widthSize.valuePx / heightSize.valuePx;
|
|
51975
|
-
if (opts.aspect_ratio !== undefined &&
|
|
51976
|
-
ratiosDiffer(effectiveAspect, opts.aspect_ratio)) {
|
|
51977
|
-
stop$1('Contradictory width, height and aspect-ratio values');
|
|
51978
|
-
}
|
|
51979
|
-
width = widthSize.valuePx;
|
|
51980
|
-
units = widthSize.units;
|
|
51981
|
-
fixedAspect = effectiveAspect;
|
|
52750
|
+
// 4. Nominal size
|
|
52751
|
+
if (sizes.width && sizes.height) {
|
|
52752
|
+
width = sizes.width.valuePx;
|
|
52753
|
+
units = sizes.width.units;
|
|
52754
|
+
fixedAspect = sizes.width.valuePx / sizes.height.valuePx;
|
|
51982
52755
|
fillOutBbox(bbox, fixedAspect, 1);
|
|
51983
|
-
} else if (
|
|
51984
|
-
width =
|
|
51985
|
-
units =
|
|
51986
|
-
} else if (
|
|
52756
|
+
} else if (sizes.width) {
|
|
52757
|
+
width = sizes.width.valuePx;
|
|
52758
|
+
units = sizes.width.units;
|
|
52759
|
+
} else if (sizes.height) {
|
|
51987
52760
|
effectiveAspect = fixedAspect || getBboxAspect(bbox);
|
|
51988
|
-
width =
|
|
51989
|
-
units =
|
|
52761
|
+
width = sizes.height.valuePx * effectiveAspect;
|
|
52762
|
+
units = sizes.height.units;
|
|
51990
52763
|
} else if (opts.fix_scale) {
|
|
51991
52764
|
// Hold ground units per output pixel, so the page grows and shrinks with
|
|
51992
52765
|
// the extent instead of the scale changing to fit the extent on it.
|
|
51993
52766
|
width = frame.width * getBboxWidth(bbox) / getBboxWidth(frame.bbox);
|
|
51994
|
-
requirePositiveSize(width, 'fix-scale', width);
|
|
51995
52767
|
}
|
|
51996
52768
|
|
|
51997
|
-
requireValidBbox(bbox);
|
|
51998
52769
|
effectiveAspect = fixedAspect || getBboxAspect(bbox);
|
|
51999
|
-
|
|
52000
|
-
|
|
52001
|
-
|
|
52002
|
-
|
|
52003
|
-
|
|
52004
|
-
|
|
52005
|
-
|
|
52006
|
-
|
|
52007
|
-
rec.frame_aspect_ratio = fixedAspect;
|
|
52008
|
-
} else {
|
|
52009
|
-
delete rec.frame_aspect_ratio;
|
|
52010
|
-
}
|
|
52011
|
-
lyr.data.markChanged({operation: OPERATION$1});
|
|
52012
|
-
rebuildFrameLayerGeometry(lyr, dataset, new Bounds(bbox));
|
|
52013
|
-
markLayerChanged(lyr, {operation: OPERATION$1, unit: 'shapes'});
|
|
52770
|
+
return {
|
|
52771
|
+
bbox: bbox,
|
|
52772
|
+
width: width,
|
|
52773
|
+
height: Math.round(width / effectiveAspect),
|
|
52774
|
+
units: units,
|
|
52775
|
+
fixedAspect: fixedAspect,
|
|
52776
|
+
valid: valid && isValidBbox(bbox)
|
|
52777
|
+
};
|
|
52014
52778
|
}
|
|
52015
52779
|
|
|
52016
52780
|
function hasUpdateOptions(opts) {
|
|
52017
52781
|
return opts.bbox !== undefined ||
|
|
52782
|
+
opts.fit !== undefined ||
|
|
52018
52783
|
opts.width !== undefined ||
|
|
52019
52784
|
opts.height !== undefined ||
|
|
52020
52785
|
opts.aspect_ratio !== undefined ||
|
|
@@ -52037,12 +52802,10 @@ ${svg}
|
|
|
52037
52802
|
return (bbox[2] - bbox[0]) / (bbox[3] - bbox[1]);
|
|
52038
52803
|
}
|
|
52039
52804
|
|
|
52040
|
-
function
|
|
52041
|
-
|
|
52042
|
-
|
|
52043
|
-
|
|
52044
|
-
stop$1('Frame has a collapsed bbox');
|
|
52045
|
-
}
|
|
52805
|
+
function isValidBbox(bbox) {
|
|
52806
|
+
return bbox.every(utils.isFiniteNumber) &&
|
|
52807
|
+
bbox[2] - bbox[0] > 0 &&
|
|
52808
|
+
bbox[3] - bbox[1] > 0;
|
|
52046
52809
|
}
|
|
52047
52810
|
|
|
52048
52811
|
function requirePositiveSize(value, name, arg) {
|
|
@@ -52272,168 +53035,6 @@ ${svg}
|
|
|
52272
53035
|
stop$1('Unable to import coordinates');
|
|
52273
53036
|
}
|
|
52274
53037
|
|
|
52275
|
-
// Apply rotation, scale and/or shift to some or all of the features in a dataset
|
|
52276
|
-
//
|
|
52277
|
-
cmd.affine = function(targetLayers, dataset, opts) {
|
|
52278
|
-
// Need to separate the targeted shapes from any other shapes that share
|
|
52279
|
-
// the same topology. So we duplicate any arcs that are shared by the targeted
|
|
52280
|
-
// shapes and their topological neighbors and remap arc references in the
|
|
52281
|
-
// neighbors to point to the copies.
|
|
52282
|
-
// TODO: explore alternative: if some arcs are shared between transformed and
|
|
52283
|
-
// non-transformed shapes, first remove topology, then tranform, then rebuild topology
|
|
52284
|
-
//
|
|
52285
|
-
var rotateArg = opts.rotate || 0;
|
|
52286
|
-
var scaleArg = opts.scale || 1;
|
|
52287
|
-
var shiftArg = opts.shift ? convertIntervalPair(opts.shift, getDatasetCRS(dataset)) : [0, 0];
|
|
52288
|
-
var arcs = dataset.arcs;
|
|
52289
|
-
var targetShapes = [];
|
|
52290
|
-
var otherShapes = [];
|
|
52291
|
-
var targetPoints = [];
|
|
52292
|
-
var targetPointLayers = [];
|
|
52293
|
-
var targetFlags, otherFlags, transform;
|
|
52294
|
-
dataset.layers.filter(layerHasGeometry$1).forEach(function(lyr) {
|
|
52295
|
-
var hits = [],
|
|
52296
|
-
misses = [],
|
|
52297
|
-
test;
|
|
52298
|
-
if (targetLayers.indexOf(lyr) == -1) {
|
|
52299
|
-
misses = lyr.shapes;
|
|
52300
|
-
} else if (opts.where) {
|
|
52301
|
-
test = compileFeatureExpression(opts.where, lyr, dataset.arcs);
|
|
52302
|
-
lyr.shapes.forEach(function(shp, i) {
|
|
52303
|
-
(test(i) ? hits : misses).push(shp);
|
|
52304
|
-
});
|
|
52305
|
-
} else {
|
|
52306
|
-
hits = lyr.shapes;
|
|
52307
|
-
}
|
|
52308
|
-
if (lyr.geometry_type == 'point') {
|
|
52309
|
-
targetPoints = targetPoints.concat(hits);
|
|
52310
|
-
if (hits.length > 0) targetPointLayers.push(lyr);
|
|
52311
|
-
} else {
|
|
52312
|
-
targetShapes = targetShapes.concat(hits);
|
|
52313
|
-
otherShapes = otherShapes.concat(misses);
|
|
52314
|
-
}
|
|
52315
|
-
});
|
|
52316
|
-
var anchorArg = getAffineAnchor({arcs: dataset.arcs, layers: [{
|
|
52317
|
-
geometry_type: 'point', shapes: targetPoints}, {geometry_type: 'polyline',
|
|
52318
|
-
shapes: targetShapes}]}, opts);
|
|
52319
|
-
transform = getAffineTransform(rotateArg, scaleArg, shiftArg, anchorArg);
|
|
52320
|
-
if (opts.fit_bbox) {
|
|
52321
|
-
transform = getFitBoxTransform(opts.fit_bbox, targetPoints, targetShapes, arcs);
|
|
52322
|
-
}
|
|
52323
|
-
if (targetShapes.length > 0) {
|
|
52324
|
-
targetFlags = new Uint8Array(arcs.size());
|
|
52325
|
-
otherFlags = new Uint8Array(arcs.size());
|
|
52326
|
-
countArcsInShapes(targetShapes, targetFlags);
|
|
52327
|
-
if (otherShapes.length > 0) {
|
|
52328
|
-
countArcsInShapes(otherShapes, otherFlags);
|
|
52329
|
-
applyArrayMask(otherFlags, targetFlags);
|
|
52330
|
-
noteDatasetWillChange(dataset, {operation: 'affine', unit: 'arcs'});
|
|
52331
|
-
dataset.arcs = duplicateSelectedArcs(otherShapes, arcs, otherFlags);
|
|
52332
|
-
markDatasetChanged(dataset, {operation: 'affine', unit: 'arcs'});
|
|
52333
|
-
}
|
|
52334
|
-
dataset.arcs.transformPoints(function(x, y, arcId) {
|
|
52335
|
-
if (arcId < targetFlags.length && targetFlags[arcId] > 0) {
|
|
52336
|
-
return transform(x, y);
|
|
52337
|
-
}
|
|
52338
|
-
});
|
|
52339
|
-
}
|
|
52340
|
-
targetPointLayers.forEach(function(lyr) {
|
|
52341
|
-
noteLayerWillChange(lyr, {operation: 'affine', unit: 'shapes'});
|
|
52342
|
-
});
|
|
52343
|
-
forEachPoint(targetPoints, function(p) {
|
|
52344
|
-
var p2 = transform(p[0], p[1]);
|
|
52345
|
-
p[0] = p2[0];
|
|
52346
|
-
p[1] = p2[1];
|
|
52347
|
-
});
|
|
52348
|
-
targetPointLayers.forEach(function(lyr) {
|
|
52349
|
-
markLayerChanged(lyr, {operation: 'affine', unit: 'shapes'});
|
|
52350
|
-
});
|
|
52351
|
-
};
|
|
52352
|
-
|
|
52353
|
-
function getAffineAnchor(dataset, opts) {
|
|
52354
|
-
var anchor, bounds;
|
|
52355
|
-
if (opts.anchor) {
|
|
52356
|
-
anchor = opts.anchor;
|
|
52357
|
-
} else {
|
|
52358
|
-
// get bounds of selected shapes to calculate center of rotation/scale
|
|
52359
|
-
bounds = getDatasetBounds(dataset);
|
|
52360
|
-
anchor = [bounds.centerX(), bounds.centerY()];
|
|
52361
|
-
}
|
|
52362
|
-
return anchor;
|
|
52363
|
-
}
|
|
52364
|
-
|
|
52365
|
-
// TODO: handle problems with unprojected datasets
|
|
52366
|
-
// option 1: don't allow affine transformation of unprojected data
|
|
52367
|
-
// option 2: error if transformed data exceeds valid coordinate range
|
|
52368
|
-
// source: http://mathworld.wolfram.com/AffineTransformation.html
|
|
52369
|
-
function getAffineTransform(rotation, scale, shift, anchor) {
|
|
52370
|
-
var angle = rotation * Math.PI / 180;
|
|
52371
|
-
var a = scale * Math.cos(angle);
|
|
52372
|
-
var b = -scale * Math.sin(angle);
|
|
52373
|
-
return function(x, y) {
|
|
52374
|
-
var x2 = a * (x - anchor[0]) - b * (y - anchor[1]) + shift[0] + anchor[0];
|
|
52375
|
-
var y2 = b * (x - anchor[0]) + a * (y - anchor[1]) + shift[1] + anchor[1];
|
|
52376
|
-
return [x2, y2];
|
|
52377
|
-
};
|
|
52378
|
-
}
|
|
52379
|
-
|
|
52380
|
-
function getFitBoxTransform(bbox, points, shapes, arcs) {
|
|
52381
|
-
var dataset = {
|
|
52382
|
-
arcs: arcs,
|
|
52383
|
-
layers: []
|
|
52384
|
-
};
|
|
52385
|
-
if (points && points.length) {
|
|
52386
|
-
dataset.layers.push({
|
|
52387
|
-
geometry_type: 'point',
|
|
52388
|
-
shapes: points
|
|
52389
|
-
});
|
|
52390
|
-
}
|
|
52391
|
-
if (shapes && shapes.length) {
|
|
52392
|
-
dataset.layers.push({
|
|
52393
|
-
geometry_type: 'polyline',
|
|
52394
|
-
shapes: shapes
|
|
52395
|
-
});
|
|
52396
|
-
}
|
|
52397
|
-
var frame = calcFrameData(dataset, {fit_bbox: bbox});
|
|
52398
|
-
var fromBounds = new Bounds(frame.bbox);
|
|
52399
|
-
var toBounds = new Bounds(frame.bbox2);
|
|
52400
|
-
var fwd = fromBounds.getTransform(toBounds, false);
|
|
52401
|
-
return function(x, y) {
|
|
52402
|
-
return fwd.transform(x, y);
|
|
52403
|
-
};
|
|
52404
|
-
}
|
|
52405
|
-
|
|
52406
|
-
function applyArrayMask(destArr, maskArr) {
|
|
52407
|
-
for (var i=0, n=destArr.length; i<n; i++) {
|
|
52408
|
-
if (maskArr[i] === 0) destArr[i] = 0;
|
|
52409
|
-
}
|
|
52410
|
-
}
|
|
52411
|
-
|
|
52412
|
-
function duplicateSelectedArcs(shapes, arcs, flags) {
|
|
52413
|
-
var arcCount = 0;
|
|
52414
|
-
var vertexCount = 0;
|
|
52415
|
-
var data = arcs.getVertexData();
|
|
52416
|
-
var xx = [], yy = [], nn = [], map = [], n;
|
|
52417
|
-
for (var i=0, len=flags.length; i<len; i++) {
|
|
52418
|
-
if (flags[i] > 0) {
|
|
52419
|
-
map[i] = arcs.size() + arcCount;
|
|
52420
|
-
n = data.nn[i];
|
|
52421
|
-
utils.copyElements(data.xx, data.ii[i], xx, vertexCount, n);
|
|
52422
|
-
utils.copyElements(data.yy, data.ii[i], yy, vertexCount, n);
|
|
52423
|
-
nn.push(n);
|
|
52424
|
-
vertexCount += n;
|
|
52425
|
-
arcCount++;
|
|
52426
|
-
}
|
|
52427
|
-
}
|
|
52428
|
-
forEachArcId(shapes, function(id) {
|
|
52429
|
-
var absId = absArcId(id);
|
|
52430
|
-
if (flags[absId] > 0) {
|
|
52431
|
-
return id < 0 ? ~map[absId] : map[absId];
|
|
52432
|
-
}
|
|
52433
|
-
});
|
|
52434
|
-
return mergeArcs([arcs, new ArcCollection(nn, xx, yy)]);
|
|
52435
|
-
}
|
|
52436
|
-
|
|
52437
53038
|
const epsilon = 1.1102230246251565e-16;
|
|
52438
53039
|
const splitter = 134217729;
|
|
52439
53040
|
const resulterrbound = (3 + 8 * epsilon) * epsilon;
|
|
@@ -75976,7 +76577,7 @@ ${svg}
|
|
|
75976
76577
|
|
|
75977
76578
|
function createMeridianPart(x, ymin, ymax) {
|
|
75978
76579
|
var coords = densifyPathByInterval([[x, ymin], [x, ymax]], precision);
|
|
75979
|
-
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord
|
|
76580
|
+
meridians.push(graticuleFeature(coords, {type: 'meridian', value: roundCoord(x)}));
|
|
75980
76581
|
}
|
|
75981
76582
|
|
|
75982
76583
|
function createParallel(y) {
|
|
@@ -76010,7 +76611,7 @@ ${svg}
|
|
|
76010
76611
|
}
|
|
76011
76612
|
|
|
76012
76613
|
// remove tiny offsets
|
|
76013
|
-
function roundCoord
|
|
76614
|
+
function roundCoord(x) {
|
|
76014
76615
|
return +x.toFixed(3) || 0;
|
|
76015
76616
|
}
|
|
76016
76617
|
|
|
@@ -79050,160 +79651,6 @@ ${svg}
|
|
|
79050
79651
|
};
|
|
79051
79652
|
}
|
|
79052
79653
|
|
|
79053
|
-
var roundCoord = getRoundingFunction(0.01);
|
|
79054
|
-
|
|
79055
|
-
function getSymbolFillColor(d) {
|
|
79056
|
-
return d.fill || 'magenta';
|
|
79057
|
-
}
|
|
79058
|
-
|
|
79059
|
-
function getSymbolStrokeColor(d) {
|
|
79060
|
-
return d.stroke || d.fill || 'magenta';
|
|
79061
|
-
}
|
|
79062
|
-
|
|
79063
|
-
function applySymbolStyles(sym, d) {
|
|
79064
|
-
if (sym.type == 'polyline') {
|
|
79065
|
-
sym.stroke = getSymbolStrokeColor(d);
|
|
79066
|
-
} else {
|
|
79067
|
-
sym.fill = getSymbolFillColor(d);
|
|
79068
|
-
}
|
|
79069
|
-
if (d.opacity) {
|
|
79070
|
-
sym.opacity = d.opacity;
|
|
79071
|
-
}
|
|
79072
|
-
return sym;
|
|
79073
|
-
}
|
|
79074
|
-
|
|
79075
|
-
function getSymbolRadius(d) {
|
|
79076
|
-
if (d.radius === 0 || d.length === 0 || d.r === 0) return 0;
|
|
79077
|
-
return d.radius || d.length || d.r || 5; // use a default value
|
|
79078
|
-
}
|
|
79079
|
-
|
|
79080
|
-
// Converts the value of a list-valued option into an array of items. The value
|
|
79081
|
-
// arrives either as a whole string (from a direct call, rather than from the
|
|
79082
|
-
// -symbols option accessor) or as an array of items already resolved one by
|
|
79083
|
-
// one. An item that holds an array of its own, as an expression like
|
|
79084
|
-
// [R, R * 0.5] does, is flattened into the list. Items themselves are left
|
|
79085
|
-
// unsplit, because a single item can contain a comma, as rgba() colors do.
|
|
79086
|
-
function toItemList(val) {
|
|
79087
|
-
var items = utils.isString(val) ? splitListItems(val) : [].concat(val);
|
|
79088
|
-
var list = [];
|
|
79089
|
-
for (var i=0; i<items.length; i++) {
|
|
79090
|
-
if (Array.isArray(items[i])) {
|
|
79091
|
-
list = list.concat(items[i]);
|
|
79092
|
-
} else {
|
|
79093
|
-
list.push(items[i]);
|
|
79094
|
-
}
|
|
79095
|
-
}
|
|
79096
|
-
return list;
|
|
79097
|
-
}
|
|
79098
|
-
|
|
79099
|
-
// Like toItemList(), for options that take numbers. An item that is a string
|
|
79100
|
-
// containing commas is split, so that a data field holding a value like "2,4"
|
|
79101
|
-
// works as a list. Values that aren't numbers become NaN, for the caller to
|
|
79102
|
-
// report or ignore.
|
|
79103
|
-
function toNumberList(val) {
|
|
79104
|
-
var list = [];
|
|
79105
|
-
toItemList(val).forEach(function(item) {
|
|
79106
|
-
if (utils.isString(item) && item.indexOf(',') > -1) {
|
|
79107
|
-
list = list.concat(parseNumberList(item));
|
|
79108
|
-
} else {
|
|
79109
|
-
list.push(Number(item));
|
|
79110
|
-
}
|
|
79111
|
-
});
|
|
79112
|
-
return list;
|
|
79113
|
-
}
|
|
79114
|
-
|
|
79115
|
-
// Returns the radius of the smallest circle centered on a symbol's point that
|
|
79116
|
-
// covers the symbol, or null if the symbol contains a part whose reach can't be
|
|
79117
|
-
// measured.
|
|
79118
|
-
//
|
|
79119
|
-
// Only a symbol drawn around its point can be described this way: a circle, or
|
|
79120
|
-
// a group of the circles and polygons that make up ring, pie and donut symbols.
|
|
79121
|
-
// An arrow is drawn from its point outward, so the circle around its point that
|
|
79122
|
-
// covers it says nothing useful about the space it occupies -- arrows are plain
|
|
79123
|
-
// polygon symbols, which reach this function only as group parts, which they
|
|
79124
|
-
// never are.
|
|
79125
|
-
function getSymbolBoundingRadius(sym) {
|
|
79126
|
-
var max = 0;
|
|
79127
|
-
var parts, r, i;
|
|
79128
|
-
if (!sym) return null;
|
|
79129
|
-
if (sym.type == 'circle') {
|
|
79130
|
-
// Half of a stroke lies outside the circle it follows, which is how a ring
|
|
79131
|
-
// symbol paints its bands.
|
|
79132
|
-
return getPositiveNumber(sym.r) + getStrokeOutset(sym);
|
|
79133
|
-
}
|
|
79134
|
-
if (sym.type == 'polygon') {
|
|
79135
|
-
forEachSymbolCoord(sym.coordinates || [], function(p) {
|
|
79136
|
-
var dist = Math.sqrt(p[0] * p[0] + p[1] * p[1]);
|
|
79137
|
-
if (dist > max) max = dist;
|
|
79138
|
-
});
|
|
79139
|
-
return max + getStrokeOutset(sym);
|
|
79140
|
-
}
|
|
79141
|
-
if (sym.type == 'group') {
|
|
79142
|
-
parts = sym.parts || [];
|
|
79143
|
-
for (i=0; i<parts.length; i++) {
|
|
79144
|
-
r = getSymbolBoundingRadius(parts[i]);
|
|
79145
|
-
if (r === null) return null;
|
|
79146
|
-
if (r > max) max = r;
|
|
79147
|
-
}
|
|
79148
|
-
return max;
|
|
79149
|
-
}
|
|
79150
|
-
return null;
|
|
79151
|
-
}
|
|
79152
|
-
|
|
79153
|
-
// A stroke straddles the path it is drawn on, so half of its width extends
|
|
79154
|
-
// beyond the shape.
|
|
79155
|
-
function getStrokeOutset(sym) {
|
|
79156
|
-
var width = sym['stroke-width'];
|
|
79157
|
-
if (!sym.stroke || sym.stroke == 'none') return 0;
|
|
79158
|
-
// an SVG stroke is one pixel wide unless stroke-width says otherwise
|
|
79159
|
-
if (width === undefined || width === null || width === '') return 0.5;
|
|
79160
|
-
return getPositiveNumber(width) / 2;
|
|
79161
|
-
}
|
|
79162
|
-
|
|
79163
|
-
function getPositiveNumber(val) {
|
|
79164
|
-
var num = +val;
|
|
79165
|
-
return num > 0 ? num : 0;
|
|
79166
|
-
}
|
|
79167
|
-
|
|
79168
|
-
function forEachSymbolCoord(coords, cb) {
|
|
79169
|
-
var isPoint = coords && utils.isNumber(coords[0]);
|
|
79170
|
-
var isNested = !isPoint && coords && Array.isArray(coords[0]);
|
|
79171
|
-
if (isPoint) return cb(coords);
|
|
79172
|
-
for (var i=0; i<coords.length; i++) {
|
|
79173
|
-
if (isNested) forEachSymbolCoord(coords[i], cb);
|
|
79174
|
-
}
|
|
79175
|
-
}
|
|
79176
|
-
|
|
79177
|
-
function flipY(coords) {
|
|
79178
|
-
forEachSymbolCoord(coords, function(p) {
|
|
79179
|
-
p[1] = -p[1];
|
|
79180
|
-
});
|
|
79181
|
-
}
|
|
79182
|
-
|
|
79183
|
-
function scaleAndShiftCoords(coords, scale, shift) {
|
|
79184
|
-
forEachSymbolCoord(coords, function(xy) {
|
|
79185
|
-
xy[0] = xy[0] * scale + shift[0];
|
|
79186
|
-
xy[1] = xy[1] * scale + shift[1];
|
|
79187
|
-
});
|
|
79188
|
-
}
|
|
79189
|
-
|
|
79190
|
-
function roundCoordsForSVG(coords) {
|
|
79191
|
-
forEachSymbolCoord(coords, function(p) {
|
|
79192
|
-
p[0] = roundCoord(p[0]);
|
|
79193
|
-
p[1] = roundCoord(p[1]);
|
|
79194
|
-
});
|
|
79195
|
-
}
|
|
79196
|
-
|
|
79197
|
-
function rotateCoords(coords, rotation) {
|
|
79198
|
-
if (!rotation) return;
|
|
79199
|
-
var f = getAffineTransform(rotation, 1, [0, 0], [0, 0]);
|
|
79200
|
-
forEachSymbolCoord(coords, function(p) {
|
|
79201
|
-
var p2 = f(p[0], p[1]);
|
|
79202
|
-
p[0] = p2[0];
|
|
79203
|
-
p[1] = p2[1];
|
|
79204
|
-
});
|
|
79205
|
-
}
|
|
79206
|
-
|
|
79207
79654
|
// Collision reduction for circular symbols, in the spirit of d3-force's
|
|
79208
79655
|
// forceCollide. Overlapping symbols are pushed apart, but each one is kept
|
|
79209
79656
|
// within a fixed distance of its true position.
|
|
@@ -83097,7 +83544,8 @@ ${svg}
|
|
|
83097
83544
|
job.catalog.addDataset(cmd.addLayer(targetDataset, opts));
|
|
83098
83545
|
|
|
83099
83546
|
} else if (name == 'update-frame') {
|
|
83100
|
-
cmd.updateFrame(targetLayers, targetDataset, opts
|
|
83547
|
+
cmd.updateFrame(targetLayers, targetDataset, opts,
|
|
83548
|
+
opts.fit ? expandCommandTargets(job.catalog.findCommandTargets(opts.fit)) : null);
|
|
83101
83549
|
|
|
83102
83550
|
} else if (name == 'update-label') {
|
|
83103
83551
|
cmd.updateLabel(targetLayers, targetDataset, opts);
|
|
@@ -83663,7 +84111,7 @@ ${svg}
|
|
|
83663
84111
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
83664
84112
|
}
|
|
83665
84113
|
|
|
83666
|
-
var version = "0.7.
|
|
84114
|
+
var version = "0.7.68";
|
|
83667
84115
|
|
|
83668
84116
|
// Parse command line args into commands and run them
|
|
83669
84117
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|