mapshaper 0.5.83 → 0.5.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/mapshaper.js +167 -104
- package/package.json +1 -1
- package/www/mapshaper-gui.js +2300 -2112
- package/www/mapshaper.js +167 -104
package/www/mapshaper-gui.js
CHANGED
|
@@ -3574,2540 +3574,2695 @@
|
|
|
3574
3574
|
}
|
|
3575
3575
|
}
|
|
3576
3576
|
|
|
3577
|
-
function
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
var _hidden = true;
|
|
3581
|
-
gui.on('active', updateVisibility);
|
|
3582
|
-
gui.on('inactive', updateVisibility);
|
|
3583
|
-
|
|
3584
|
-
// @iconRef: selector for an (svg) button icon
|
|
3585
|
-
this.addButton = function(iconRef) {
|
|
3586
|
-
var btn = initButton(iconRef).addClass('nav-btn');
|
|
3587
|
-
btn.appendTo(buttons);
|
|
3588
|
-
return btn;
|
|
3589
|
-
};
|
|
3590
|
-
|
|
3591
|
-
this.show = function() {
|
|
3592
|
-
_hidden = false;
|
|
3593
|
-
updateVisibility();
|
|
3594
|
-
};
|
|
3577
|
+
function getSymbolNodeId(node) {
|
|
3578
|
+
return parseInt(node.getAttribute('data-id'));
|
|
3579
|
+
}
|
|
3595
3580
|
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3581
|
+
function getSvgSymbolTransform(xy, ext) {
|
|
3582
|
+
var scale = ext.getSymbolScale();
|
|
3583
|
+
var p = ext.translateCoords(xy[0], xy[1]);
|
|
3584
|
+
return internal.svg.getTransform(p, scale);
|
|
3585
|
+
}
|
|
3600
3586
|
|
|
3601
|
-
var initButton = this.initButton = function(iconRef) {
|
|
3602
|
-
var icon = El('body').findChild(iconRef).node().cloneNode(true);
|
|
3603
|
-
var btn = El('div')
|
|
3604
|
-
.on('dblclick', function(e) {e.stopPropagation();}); // block dblclick zoom
|
|
3605
|
-
btn.appendChild(icon);
|
|
3606
|
-
if (icon.hasAttribute('id')) icon.removeAttribute('id');
|
|
3607
|
-
return btn;
|
|
3608
|
-
};
|
|
3609
3587
|
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3588
|
+
function repositionSymbols(elements, layer, ext) {
|
|
3589
|
+
var el, idx, shp, p, displayOn, inView, displayBounds;
|
|
3590
|
+
for (var i=0, n=elements.length; i<n; i++) {
|
|
3591
|
+
el = elements[i];
|
|
3592
|
+
idx = getSymbolNodeId(el);
|
|
3593
|
+
shp = layer.shapes[idx];
|
|
3594
|
+
if (!shp) continue;
|
|
3595
|
+
p = shp[0];
|
|
3596
|
+
// OPTIMIZATION: only display symbols that are in view
|
|
3597
|
+
// quick-and-dirty hit-test: expand the extent rectangle by a percentage.
|
|
3598
|
+
// very large symbols will disappear before they're completely out of view
|
|
3599
|
+
displayBounds = ext.getBounds(1.15);
|
|
3600
|
+
displayOn = !el.hasAttribute('display') || el.getAttribute('display') == 'block';
|
|
3601
|
+
inView = displayBounds.containsPoint(p[0], p[1]);
|
|
3602
|
+
if (inView) {
|
|
3603
|
+
if (!displayOn) el.setAttribute('display', 'block');
|
|
3604
|
+
el.setAttribute('transform', getSvgSymbolTransform(p, ext));
|
|
3613
3605
|
} else {
|
|
3614
|
-
|
|
3606
|
+
if (displayOn) el.setAttribute('display', 'none');
|
|
3615
3607
|
}
|
|
3616
3608
|
}
|
|
3617
3609
|
}
|
|
3618
3610
|
|
|
3619
|
-
function
|
|
3620
|
-
var
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
});
|
|
3630
|
-
|
|
3631
|
-
btn.on('click', function() {
|
|
3632
|
-
modes.enterMode(active ? null : name);
|
|
3611
|
+
function renderSymbols(lyr, ext, type) {
|
|
3612
|
+
var records = lyr.data.getRecords();
|
|
3613
|
+
var symbols = lyr.shapes.map(function(shp, i) {
|
|
3614
|
+
var d = records[i];
|
|
3615
|
+
var obj = type == 'label' ? internal.svg.importStyledLabel(d) :
|
|
3616
|
+
internal.svg.importSymbol(d['svg-symbol']);
|
|
3617
|
+
if (!obj || !shp) return null;
|
|
3618
|
+
obj.properties.transform = getSvgSymbolTransform(shp[0], ext);
|
|
3619
|
+
obj.properties['data-id'] = i;
|
|
3620
|
+
return obj;
|
|
3633
3621
|
});
|
|
3622
|
+
var obj = internal.getEmptyLayerForSVG(lyr, {});
|
|
3623
|
+
obj.children = symbols;
|
|
3624
|
+
return internal.svg.stringify(obj);
|
|
3634
3625
|
}
|
|
3635
3626
|
|
|
3636
|
-
function
|
|
3637
|
-
|
|
3638
|
-
|
|
3627
|
+
function isMultilineLabel(textNode) {
|
|
3628
|
+
return textNode.childNodes.length > 1;
|
|
3629
|
+
}
|
|
3639
3630
|
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3631
|
+
function toggleTextAlign(textNode, rec) {
|
|
3632
|
+
var curr = rec['text-anchor'] || 'middle';
|
|
3633
|
+
var value = curr == 'middle' && 'start' || curr == 'start' && 'end' || 'middle';
|
|
3634
|
+
updateTextAnchor(value, textNode, rec);
|
|
3635
|
+
}
|
|
3643
3636
|
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
if (
|
|
3655
|
-
|
|
3637
|
+
// Set an attribute on a <text> node and any child <tspan> elements
|
|
3638
|
+
// (mapshaper's svg labels require tspans to have the same x and dx values
|
|
3639
|
+
// as the enclosing text node)
|
|
3640
|
+
function setMultilineAttribute(textNode, name, value) {
|
|
3641
|
+
var n = textNode.childNodes.length;
|
|
3642
|
+
var i = -1;
|
|
3643
|
+
var child;
|
|
3644
|
+
textNode.setAttribute(name, value);
|
|
3645
|
+
while (++i < n) {
|
|
3646
|
+
child = textNode.childNodes[i];
|
|
3647
|
+
if (child.tagName == 'tspan') {
|
|
3648
|
+
child.setAttribute(name, value);
|
|
3656
3649
|
}
|
|
3657
|
-
}
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3658
3652
|
|
|
3659
|
-
|
|
3653
|
+
function findSvgRoot(el) {
|
|
3654
|
+
while (el && el.tagName != 'html' && el.tagName != 'body') {
|
|
3655
|
+
if (el.tagName == 'svg') return el;
|
|
3656
|
+
el = el.parentNode;
|
|
3657
|
+
}
|
|
3658
|
+
return null;
|
|
3659
|
+
}
|
|
3660
3660
|
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3661
|
+
// p: pixel coordinates of label anchor
|
|
3662
|
+
function autoUpdateTextAnchor(textNode, rec, p) {
|
|
3663
|
+
var svg = findSvgRoot(textNode);
|
|
3664
|
+
var rect = textNode.getBoundingClientRect();
|
|
3665
|
+
var labelCenterX = rect.left - svg.getBoundingClientRect().left + rect.width / 2;
|
|
3666
|
+
var xpct = (labelCenterX - p[0]) / rect.width; // offset of label center from anchor center
|
|
3667
|
+
var value = xpct < -0.25 && 'end' || xpct > 0.25 && 'start' || 'middle';
|
|
3668
|
+
updateTextAnchor(value, textNode, rec);
|
|
3669
|
+
}
|
|
3664
3670
|
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3671
|
+
// @value: optional position to set; if missing, auto-set
|
|
3672
|
+
function updateTextAnchor(value, textNode, rec) {
|
|
3673
|
+
var rect = textNode.getBoundingClientRect();
|
|
3674
|
+
var width = rect.width;
|
|
3675
|
+
var curr = rec['text-anchor'] || 'middle';
|
|
3676
|
+
var xshift = 0;
|
|
3677
|
+
|
|
3678
|
+
// console.log("anchor() curr:", curr, "xpct:", xpct, "left:", rect.left, "anchorX:", anchorX, "targ:", targ, "dx:", xshift)
|
|
3679
|
+
if (curr == 'middle' && value == 'end' || curr == 'start' && value == 'middle') {
|
|
3680
|
+
xshift = width / 2;
|
|
3681
|
+
} else if (curr == 'middle' && value == 'start' || curr == 'end' && value == 'middle') {
|
|
3682
|
+
xshift = -width / 2;
|
|
3683
|
+
} else if (curr == 'start' && value == 'end') {
|
|
3684
|
+
xshift = width;
|
|
3685
|
+
} else if (curr == 'end' && value == 'start') {
|
|
3686
|
+
xshift = -width;
|
|
3687
|
+
}
|
|
3688
|
+
if (xshift) {
|
|
3689
|
+
rec['text-anchor'] = value;
|
|
3690
|
+
applyDelta(rec, 'dx', Math.round(xshift));
|
|
3691
|
+
}
|
|
3672
3692
|
}
|
|
3673
3693
|
|
|
3674
|
-
|
|
3694
|
+
// handle either numeric strings or numbers in fields
|
|
3695
|
+
function applyDelta(rec, key, delta) {
|
|
3696
|
+
var currVal = rec[key];
|
|
3697
|
+
var isString = utils.isString(currVal);
|
|
3698
|
+
var newVal = (+currVal + delta) || 0;
|
|
3699
|
+
rec[key] = isString ? String(newVal) : newVal;
|
|
3700
|
+
}
|
|
3675
3701
|
|
|
3676
|
-
function
|
|
3677
|
-
var
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
if (!GUI.isActiveInstance(gui)) return;
|
|
3681
|
-
if (e.keyCode == 16) shiftDown = false;
|
|
3682
|
-
});
|
|
3702
|
+
function getDisplayCoordsById(id, layer, ext) {
|
|
3703
|
+
var coords = getPointCoordsById(id, layer);
|
|
3704
|
+
return ext.translateCoords(coords[0], coords[1]);
|
|
3705
|
+
}
|
|
3683
3706
|
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
self.dispatchEvent('keydown', {originalEvent: e});
|
|
3707
|
+
function snapVerticesToPoint(ids, p, arcs, final) {
|
|
3708
|
+
ids.forEach(function(idx) {
|
|
3709
|
+
internal.setVertexCoords(p[0], p[1], idx, arcs);
|
|
3688
3710
|
});
|
|
3711
|
+
if (final) {
|
|
3712
|
+
// kludge to get dataset to recalculate internal bounding boxes
|
|
3713
|
+
arcs.transformPoints(function() {});
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3689
3716
|
|
|
3690
|
-
|
|
3717
|
+
function getPointCoordsById(id, layer) {
|
|
3718
|
+
var coords = layer && layer.geometry_type == 'point' && layer.shapes[id];
|
|
3719
|
+
if (!coords || coords.length != 1) {
|
|
3720
|
+
return null;
|
|
3721
|
+
}
|
|
3722
|
+
return coords[0];
|
|
3723
|
+
}
|
|
3691
3724
|
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
cb();
|
|
3697
|
-
}
|
|
3698
|
-
});
|
|
3699
|
-
};
|
|
3725
|
+
function translateDeltaDisplayCoords(dx, dy, ext) {
|
|
3726
|
+
var a = ext.translatePixelCoords(0, 0);
|
|
3727
|
+
var b = ext.translatePixelCoords(dx, dy);
|
|
3728
|
+
return [b[0] - a[0], b[1] - a[1]];
|
|
3700
3729
|
}
|
|
3701
3730
|
|
|
3702
|
-
utils.inherit(KeyboardEvents, EventDispatcher);
|
|
3703
3731
|
|
|
3704
|
-
function
|
|
3732
|
+
function SymbolDragging2(gui, ext, hit) {
|
|
3733
|
+
// var targetTextNode; // text node currently being dragged
|
|
3734
|
+
var dragging = false;
|
|
3735
|
+
var activeRecord;
|
|
3736
|
+
var activeId = -1;
|
|
3737
|
+
var self = new EventDispatcher();
|
|
3738
|
+
var activeVertexIds = null; // for vertex dragging
|
|
3705
3739
|
|
|
3706
|
-
|
|
3707
|
-
standard: ['info', 'selection', 'data', 'box'],
|
|
3708
|
-
lines: ['info', 'selection', 'data', 'box', 'vertices'],
|
|
3709
|
-
table: ['info', 'selection', 'data'],
|
|
3710
|
-
labels: ['info', 'selection', 'data', 'box', 'labels', 'location'],
|
|
3711
|
-
points: ['info', 'selection', 'data', 'box', 'location']
|
|
3712
|
-
};
|
|
3740
|
+
initDragging();
|
|
3713
3741
|
|
|
3714
|
-
|
|
3715
|
-
box: 'Shift-drag to draw a box',
|
|
3716
|
-
data: 'Click-select features to edit their attributes',
|
|
3717
|
-
selection: 'Click-select or shift-drag to select features'
|
|
3718
|
-
};
|
|
3742
|
+
return self;
|
|
3719
3743
|
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
box: 'shift-drag box tool',
|
|
3724
|
-
data: 'edit attributes',
|
|
3725
|
-
labels: 'position labels',
|
|
3726
|
-
location: 'drag points',
|
|
3727
|
-
vertices: 'drag vertices',
|
|
3728
|
-
selection: 'select features',
|
|
3729
|
-
off: 'turn off'
|
|
3730
|
-
};
|
|
3731
|
-
var btn, menu;
|
|
3732
|
-
var _menuTimeout;
|
|
3744
|
+
function labelEditingEnabled() {
|
|
3745
|
+
return gui.interaction && gui.interaction.getMode() == 'labels' ? true : false;
|
|
3746
|
+
}
|
|
3733
3747
|
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3748
|
+
function locationEditingEnabled() {
|
|
3749
|
+
return gui.interaction && gui.interaction.getMode() == 'location' ? true : false;
|
|
3750
|
+
}
|
|
3737
3751
|
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
menu = El('div').addClass('nav-sub-menu').appendTo(btn.node());
|
|
3752
|
+
function vertexEditingEnabled() {
|
|
3753
|
+
return gui.interaction && gui.interaction.getMode() == 'vertices' ? true : false;
|
|
3754
|
+
}
|
|
3742
3755
|
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3756
|
+
// update symbol by setting attributes
|
|
3757
|
+
function updateSymbol(node, d) {
|
|
3758
|
+
var a = d['text-anchor'];
|
|
3759
|
+
if (a) node.setAttribute('text-anchor', a);
|
|
3760
|
+
setMultilineAttribute(node, 'dx', d.dx || 0);
|
|
3761
|
+
node.setAttribute('y', d.dy || 0);
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
// update symbol by re-rendering it
|
|
3765
|
+
function updateSymbol2(node, d, id) {
|
|
3766
|
+
var o = internal.svg.importStyledLabel(d); // TODO: symbol support
|
|
3767
|
+
var activeLayer = hit.getHitTarget().layer;
|
|
3768
|
+
var xy = activeLayer.shapes[id][0];
|
|
3769
|
+
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
3770
|
+
var node2;
|
|
3771
|
+
o.properties.transform = getSvgSymbolTransform(xy, ext);
|
|
3772
|
+
o.properties['data-id'] = id;
|
|
3773
|
+
// o.properties['class'] = 'selected';
|
|
3774
|
+
g.innerHTML = internal.svg.stringify(o);
|
|
3775
|
+
node2 = g.firstChild;
|
|
3776
|
+
node.parentNode.replaceChild(node2, node);
|
|
3777
|
+
gui.dispatchEvent('popup-needs-refresh');
|
|
3778
|
+
return node2;
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
function initDragging() {
|
|
3782
|
+
var downEvt;
|
|
3783
|
+
var eventPriority = 1;
|
|
3784
|
+
|
|
3785
|
+
// inspector and label editing aren't fully synced - stop editing if inspector opens
|
|
3786
|
+
// gui.on('inspector_on', function() {
|
|
3787
|
+
// stopEditing();
|
|
3788
|
+
// });
|
|
3789
|
+
|
|
3790
|
+
gui.on('interaction_mode_change', function(e) {
|
|
3791
|
+
if (e.mode != 'labels') {
|
|
3792
|
+
stopDragging();
|
|
3748
3793
|
}
|
|
3794
|
+
gui.undo.clear(); // TODO: put this elsewhere?
|
|
3749
3795
|
});
|
|
3750
3796
|
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3797
|
+
// down event on svg
|
|
3798
|
+
// a: off text
|
|
3799
|
+
// -> stop editing
|
|
3800
|
+
// b: on text
|
|
3801
|
+
// 1: not editing -> nop
|
|
3802
|
+
// 2: on selected text -> start dragging
|
|
3803
|
+
// 3: on other text -> stop dragging, select new text
|
|
3804
|
+
|
|
3805
|
+
hit.on('dragstart', function(e) {
|
|
3806
|
+
if (e.id >= 0 === false) return;
|
|
3807
|
+
if (labelEditingEnabled() && onLabelDragStart(e)) {
|
|
3808
|
+
triggerGlobalEvent('label_dragstart', e);
|
|
3809
|
+
startDragging();
|
|
3810
|
+
} else if (locationEditingEnabled()) {
|
|
3811
|
+
triggerGlobalEvent('symbol_dragstart', e);
|
|
3812
|
+
startDragging();
|
|
3813
|
+
} else if (vertexEditingEnabled()) {
|
|
3814
|
+
onVertexDragStart(e);
|
|
3815
|
+
triggerGlobalEvent('vertex_dragstart', e);
|
|
3816
|
+
startDragging();
|
|
3757
3817
|
}
|
|
3758
|
-
// if (_editMode != 'off') {
|
|
3759
|
-
// openMenu();
|
|
3760
|
-
// }
|
|
3761
3818
|
});
|
|
3762
3819
|
|
|
3763
|
-
|
|
3764
|
-
if (
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
} else {
|
|
3771
|
-
openMenu();
|
|
3820
|
+
hit.on('drag', function(e) {
|
|
3821
|
+
if (labelEditingEnabled()) {
|
|
3822
|
+
onLabelDrag(e);
|
|
3823
|
+
} else if (locationEditingEnabled()) {
|
|
3824
|
+
onLocationDrag(e);
|
|
3825
|
+
} else if (vertexEditingEnabled()) {
|
|
3826
|
+
onVertexDrag(e);
|
|
3772
3827
|
}
|
|
3773
|
-
e.stopPropagation();
|
|
3774
3828
|
});
|
|
3775
|
-
}
|
|
3776
3829
|
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3830
|
+
hit.on('dragend', function(e) {
|
|
3831
|
+
if (locationEditingEnabled()) {
|
|
3832
|
+
triggerGlobalEvent('symbol_dragend', e);
|
|
3833
|
+
stopDragging();
|
|
3834
|
+
} else if (labelEditingEnabled()) {
|
|
3835
|
+
triggerGlobalEvent('label_dragend', e);
|
|
3836
|
+
stopDragging();
|
|
3837
|
+
} else if (vertexEditingEnabled()) {
|
|
3838
|
+
// kludge to get dataset to recalculate internal bounding boxes
|
|
3839
|
+
hit.getHitTarget().arcs.transformPoints(function() {});
|
|
3840
|
+
triggerGlobalEvent('vertex_dragend', e);
|
|
3841
|
+
stopDragging();
|
|
3842
|
+
}
|
|
3843
|
+
});
|
|
3780
3844
|
|
|
3781
|
-
|
|
3845
|
+
hit.on('click', function(e) {
|
|
3846
|
+
if (labelEditingEnabled()) {
|
|
3847
|
+
onLabelClick(e);
|
|
3848
|
+
}
|
|
3849
|
+
});
|
|
3782
3850
|
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3851
|
+
function getVertexEventData(e) {
|
|
3852
|
+
return {
|
|
3853
|
+
FID: activeId,
|
|
3854
|
+
vertexIds: activeVertexIds
|
|
3855
|
+
};
|
|
3787
3856
|
}
|
|
3788
|
-
};
|
|
3789
3857
|
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3858
|
+
function onLocationDrag(e) {
|
|
3859
|
+
var lyr = hit.getHitTarget().layer;
|
|
3860
|
+
var p = getPointCoordsById(e.id, lyr);
|
|
3861
|
+
if (!p) return;
|
|
3862
|
+
var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
|
|
3863
|
+
p[0] += diff[0];
|
|
3864
|
+
p[1] += diff[1];
|
|
3865
|
+
self.dispatchEvent('location_change'); // signal map to redraw
|
|
3866
|
+
triggerGlobalEvent('symbol_drag', e);
|
|
3795
3867
|
}
|
|
3796
|
-
}, null, -1); // low priority?
|
|
3797
|
-
|
|
3798
|
-
function active() {
|
|
3799
|
-
return _editMode && _editMode != 'off';
|
|
3800
|
-
}
|
|
3801
3868
|
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
if (!internal.layerHasGeometry(o.layer)) {
|
|
3808
|
-
return menus.table;
|
|
3809
|
-
}
|
|
3810
|
-
if (internal.layerHasLabels(o.layer)) {
|
|
3811
|
-
return menus.labels;
|
|
3812
|
-
}
|
|
3813
|
-
if (internal.layerHasPoints(o.layer)) {
|
|
3814
|
-
return menus.points;
|
|
3815
|
-
}
|
|
3816
|
-
if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polyline') {
|
|
3817
|
-
return menus.lines;
|
|
3869
|
+
function onVertexDragStart(e) {
|
|
3870
|
+
var target = hit.getHitTarget();
|
|
3871
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
3872
|
+
activeVertexIds = internal.findNearestVertices(p, target.layer.shapes[e.id], target.arcs);
|
|
3873
|
+
activeId = e.id;
|
|
3818
3874
|
}
|
|
3819
|
-
return menus.standard;
|
|
3820
|
-
}
|
|
3821
3875
|
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3876
|
+
function onVertexDrag(e) {
|
|
3877
|
+
var target = hit.getHitTarget();
|
|
3878
|
+
if (!activeVertexIds) return; // ignore error condition
|
|
3879
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
3880
|
+
if (gui.keyboard.shiftIsPressed()) {
|
|
3881
|
+
internal.snapPointToArcEndpoint(p, activeVertexIds, target.arcs);
|
|
3882
|
+
}
|
|
3883
|
+
snapVerticesToPoint(activeVertexIds, p, target.arcs);
|
|
3884
|
+
self.dispatchEvent('location_change'); // signal map to redraw
|
|
3885
|
+
}
|
|
3825
3886
|
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
if (_editMode == mode) {
|
|
3836
|
-
// closeMenu();
|
|
3837
|
-
setMode('off');
|
|
3838
|
-
} else if (_editMode != mode) {
|
|
3839
|
-
setMode(mode);
|
|
3840
|
-
if (mode == 'off') closeMenu(120); // only close if turning off
|
|
3841
|
-
// closeMenu(mode == 'off' ? 120 : 400); // close after selecting
|
|
3842
|
-
}
|
|
3843
|
-
e.stopPropagation();
|
|
3844
|
-
});
|
|
3845
|
-
});
|
|
3846
|
-
updateSelectionHighlight();
|
|
3847
|
-
}
|
|
3887
|
+
function onLabelClick(e) {
|
|
3888
|
+
var textNode = getTextTarget3(e);
|
|
3889
|
+
var rec = getLabelRecordById(e.id);
|
|
3890
|
+
if (textNode && rec && isMultilineLabel(textNode)) {
|
|
3891
|
+
toggleTextAlign(textNode, rec);
|
|
3892
|
+
updateSymbol2(textNode, rec, e.id);
|
|
3893
|
+
// e.stopPropagation(); // prevent pin/unpin on popup
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3848
3896
|
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3897
|
+
function triggerGlobalEvent(type, e) {
|
|
3898
|
+
if (e.id >= 0 === false) return;
|
|
3899
|
+
var o = {
|
|
3900
|
+
FID: e.id,
|
|
3901
|
+
layer_name: hit.getHitTarget().layer.name,
|
|
3902
|
+
vertex_ids: activeVertexIds
|
|
3903
|
+
};
|
|
3904
|
+
// fire event to signal external editor that symbol coords have changed
|
|
3905
|
+
gui.dispatchEvent(type, o);
|
|
3854
3906
|
}
|
|
3855
|
-
}
|
|
3856
3907
|
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3908
|
+
function getLabelRecordById(id) {
|
|
3909
|
+
var table = hit.getTargetDataTable();
|
|
3910
|
+
if (id >= 0 === false || !table) return null;
|
|
3911
|
+
// add dx and dy properties, if not available
|
|
3912
|
+
if (!table.fieldExists('dx')) {
|
|
3913
|
+
table.addField('dx', 0);
|
|
3914
|
+
}
|
|
3915
|
+
if (!table.fieldExists('dy')) {
|
|
3916
|
+
table.addField('dy', 0);
|
|
3917
|
+
}
|
|
3918
|
+
if (!table.fieldExists('text-anchor')) {
|
|
3919
|
+
table.addField('text-anchor', '');
|
|
3920
|
+
}
|
|
3921
|
+
return table.getRecordAt(id);
|
|
3863
3922
|
}
|
|
3864
|
-
}
|
|
3865
3923
|
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
updateArrowButton();
|
|
3876
|
-
}, delay || 0);
|
|
3877
|
-
}
|
|
3924
|
+
function onLabelDragStart(e) {
|
|
3925
|
+
var textNode = getTextTarget3(e);
|
|
3926
|
+
var table = hit.getTargetDataTable();
|
|
3927
|
+
if (!textNode || !table) return false;
|
|
3928
|
+
activeId = e.id;
|
|
3929
|
+
activeRecord = getLabelRecordById(activeId);
|
|
3930
|
+
downEvt = e;
|
|
3931
|
+
return true;
|
|
3932
|
+
}
|
|
3878
3933
|
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3934
|
+
function onLabelDrag(e) {
|
|
3935
|
+
var scale = ext.getSymbolScale() || 1;
|
|
3936
|
+
var textNode;
|
|
3937
|
+
if (!dragging) return;
|
|
3938
|
+
if (e.id != activeId) {
|
|
3939
|
+
error("Mismatched hit ids:", e.id, activeId);
|
|
3940
|
+
}
|
|
3941
|
+
applyDelta(activeRecord, 'dx', e.dx / scale);
|
|
3942
|
+
applyDelta(activeRecord, 'dy', e.dy / scale);
|
|
3943
|
+
textNode = getTextTarget3(e);
|
|
3944
|
+
if (!isMultilineLabel(textNode)) {
|
|
3945
|
+
// update anchor position of single-line labels based on label position
|
|
3946
|
+
// relative to anchor point, for better placement when eventual display font is
|
|
3947
|
+
// different from mapshaper's font.
|
|
3948
|
+
autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget().layer, ext));
|
|
3949
|
+
}
|
|
3950
|
+
// updateSymbol(targetTextNode, activeRecord);
|
|
3951
|
+
updateSymbol2(textNode, activeRecord, activeId);
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
function getSymbolNodeById(id, parent) {
|
|
3955
|
+
// TODO: optimize selector
|
|
3956
|
+
var sel = '[data-id="' + id + '"]';
|
|
3957
|
+
return parent.querySelector(sel);
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
function getTextTarget3(e) {
|
|
3961
|
+
if (e.id > -1 === false || !e.container) return null;
|
|
3962
|
+
return getSymbolNodeById(e.id, e.container);
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
function getTextTarget2(e) {
|
|
3966
|
+
var el = e && e.targetSymbol || null;
|
|
3967
|
+
if (el && el.tagName == 'tspan') {
|
|
3968
|
+
el = el.parentNode;
|
|
3969
|
+
}
|
|
3970
|
+
return el && el.tagName == 'text' ? el : null;
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
function getTextTarget(e) {
|
|
3974
|
+
var el = e.target;
|
|
3975
|
+
if (el.tagName == 'tspan') {
|
|
3976
|
+
el = el.parentNode;
|
|
3977
|
+
}
|
|
3978
|
+
return el.tagName == 'text' ? el : null;
|
|
3887
3979
|
}
|
|
3980
|
+
|
|
3981
|
+
// svg.addEventListener('mousedown', function(e) {
|
|
3982
|
+
// var textTarget = getTextTarget(e);
|
|
3983
|
+
// downEvt = e;
|
|
3984
|
+
// if (!textTarget) {
|
|
3985
|
+
// stopEditing();
|
|
3986
|
+
// } else if (!editing) {
|
|
3987
|
+
// // nop
|
|
3988
|
+
// } else if (textTarget == targetTextNode) {
|
|
3989
|
+
// startDragging();
|
|
3990
|
+
// } else {
|
|
3991
|
+
// startDragging();
|
|
3992
|
+
// editTextNode(textTarget);
|
|
3993
|
+
// }
|
|
3994
|
+
// });
|
|
3995
|
+
|
|
3996
|
+
// up event on svg
|
|
3997
|
+
// a: currently dragging text
|
|
3998
|
+
// -> stop dragging
|
|
3999
|
+
// b: clicked on a text feature
|
|
4000
|
+
// -> start editing it
|
|
4001
|
+
|
|
4002
|
+
|
|
4003
|
+
// svg.addEventListener('mouseup', function(e) {
|
|
4004
|
+
// var textTarget = getTextTarget(e);
|
|
4005
|
+
// var isClick = isClickEvent(e, downEvt);
|
|
4006
|
+
// if (isClick && textTarget && textTarget == targetTextNode &&
|
|
4007
|
+
// activeRecord && isMultilineLabel(targetTextNode)) {
|
|
4008
|
+
// toggleTextAlign(targetTextNode, activeRecord);
|
|
4009
|
+
// updateSymbol();
|
|
4010
|
+
// }
|
|
4011
|
+
// if (dragging) {
|
|
4012
|
+
// stopDragging();
|
|
4013
|
+
// } else if (isClick && textTarget) {
|
|
4014
|
+
// editTextNode(textTarget);
|
|
4015
|
+
// }
|
|
4016
|
+
// });
|
|
4017
|
+
|
|
4018
|
+
// block dbl-click navigation when editing
|
|
4019
|
+
// mouse.on('dblclick', function(e) {
|
|
4020
|
+
// if (editing) e.stopPropagation();
|
|
4021
|
+
// }, null, eventPriority);
|
|
4022
|
+
|
|
4023
|
+
// mouse.on('dragstart', function(e) {
|
|
4024
|
+
// onLabelDrag(e);
|
|
4025
|
+
// }, null, eventPriority);
|
|
4026
|
+
|
|
4027
|
+
// mouse.on('drag', function(e) {
|
|
4028
|
+
// var scale = ext.getSymbolScale() || 1;
|
|
4029
|
+
// onLabelDrag(e);
|
|
4030
|
+
// if (!dragging || !activeRecord) return;
|
|
4031
|
+
// applyDelta(activeRecord, 'dx', e.dx / scale);
|
|
4032
|
+
// applyDelta(activeRecord, 'dy', e.dy / scale);
|
|
4033
|
+
// if (!isMultilineLabel(targetTextNode)) {
|
|
4034
|
+
// // update anchor position of single-line labels based on label position
|
|
4035
|
+
// // relative to anchor point, for better placement when eventual display font is
|
|
4036
|
+
// // different from mapshaper's font.
|
|
4037
|
+
// updateTextAnchor(targetTextNode, activeRecord);
|
|
4038
|
+
// }
|
|
4039
|
+
// // updateSymbol(targetTextNode, activeRecord);
|
|
4040
|
+
// targetTextNode = updateSymbol2(targetTextNode, activeRecord, activeId);
|
|
4041
|
+
// }, null, eventPriority);
|
|
4042
|
+
|
|
4043
|
+
// mouse.on('dragend', function(e) {
|
|
4044
|
+
// onLabelDrag(e);
|
|
4045
|
+
// stopDragging();
|
|
4046
|
+
// }, null, eventPriority);
|
|
4047
|
+
|
|
4048
|
+
|
|
4049
|
+
// function onLabelDrag(e) {
|
|
4050
|
+
// if (dragging) {
|
|
4051
|
+
// e.stopPropagation();
|
|
4052
|
+
// }
|
|
4053
|
+
// }
|
|
3888
4054
|
}
|
|
3889
4055
|
|
|
3890
|
-
function
|
|
3891
|
-
|
|
4056
|
+
function startDragging() {
|
|
4057
|
+
dragging = true;
|
|
3892
4058
|
}
|
|
3893
4059
|
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
} else {
|
|
3900
|
-
btn.removeClass('open');
|
|
3901
|
-
}
|
|
3902
|
-
btn.classed('hover', _menuOpen);
|
|
3903
|
-
// btn.classed('selected', active() && !_menuOpen);
|
|
3904
|
-
btn.classed('selected', active());
|
|
4060
|
+
function stopDragging() {
|
|
4061
|
+
dragging = false;
|
|
4062
|
+
activeId = -1;
|
|
4063
|
+
activeRecord = null;
|
|
4064
|
+
activeVertexIds = null;
|
|
3905
4065
|
}
|
|
3906
4066
|
|
|
3907
|
-
function
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
4067
|
+
function isClickEvent(up, down) {
|
|
4068
|
+
var elapsed = Math.abs(down.timeStamp - up.timeStamp);
|
|
4069
|
+
var dx = up.screenX - down.screenX;
|
|
4070
|
+
var dy = up.screenY - down.screenY;
|
|
4071
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
4072
|
+
return dist <= 4 && elapsed < 300;
|
|
3912
4073
|
}
|
|
4074
|
+
|
|
3913
4075
|
}
|
|
3914
4076
|
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4077
|
+
// import { cloneShape } from '../paths/mapshaper-shape-utils';
|
|
4078
|
+
// import { copyRecord } from '../datatable/mapshaper-data-utils';
|
|
4079
|
+
var cloneShape = internal.cloneShape;
|
|
4080
|
+
var copyRecord = internal.copyRecord;
|
|
3919
4081
|
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
deleteLayer.call(self, lyr, dataset);
|
|
3924
|
-
if (self.isEmpty()) {
|
|
3925
|
-
// refresh browser if deleted layer was the last layer
|
|
3926
|
-
window.location.href = window.location.href.toString();
|
|
3927
|
-
} else {
|
|
3928
|
-
// trigger event to update layer list and, if needed, the map view
|
|
3929
|
-
flags = {};
|
|
3930
|
-
active = self.getActiveLayer();
|
|
3931
|
-
if (active.layer != lyr) {
|
|
3932
|
-
flags.select = true;
|
|
3933
|
-
}
|
|
3934
|
-
internal.cleanupArcs(active.dataset);
|
|
3935
|
-
if (internal.layerHasPaths(lyr)) {
|
|
3936
|
-
flags.arc_count = true; // looks like a kludge, try to remove
|
|
3937
|
-
}
|
|
3938
|
-
self.updated(flags, active.layer, active.dataset);
|
|
3939
|
-
}
|
|
3940
|
-
};
|
|
4082
|
+
function Undo(gui) {
|
|
4083
|
+
var history, offset, stashedUndo;
|
|
4084
|
+
reset();
|
|
3941
4085
|
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
4086
|
+
function reset() {
|
|
4087
|
+
history = [];
|
|
4088
|
+
stashedUndo = null;
|
|
4089
|
+
offset = 0;
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
function refreshMap() {
|
|
4093
|
+
gui.dispatchEvent('undo_redo');
|
|
4094
|
+
}
|
|
4095
|
+
|
|
4096
|
+
function isUndoEvt(e) {
|
|
4097
|
+
return (e.ctrlKey || e.metaKey) && !e.shiftKey && e.key == 'z';
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
function isRedoEvt(e) {
|
|
4101
|
+
return (e.ctrlKey || e.metaKey) && (e.shiftKey && e.key == 'z' || !e.shiftKey && e.key == 'y');
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
gui.keyboard.on('keydown', function(evt) {
|
|
4105
|
+
var e = evt.originalEvent,
|
|
4106
|
+
kc = e.keyCode;
|
|
4107
|
+
if (isUndoEvt(e)) {
|
|
4108
|
+
this.undo();
|
|
4109
|
+
e.stopPropagation();
|
|
4110
|
+
e.preventDefault();
|
|
3948
4111
|
}
|
|
3949
|
-
if (
|
|
3950
|
-
|
|
4112
|
+
if (isRedoEvt(e)) {
|
|
4113
|
+
this.redo();
|
|
4114
|
+
e.stopPropagation();
|
|
4115
|
+
e.preventDefault();
|
|
3951
4116
|
}
|
|
3952
|
-
|
|
4117
|
+
|
|
4118
|
+
}, this, 10);
|
|
4119
|
+
|
|
4120
|
+
// undo/redo point/symbol dragging
|
|
4121
|
+
//
|
|
4122
|
+
gui.on('symbol_dragstart', function(e) {
|
|
4123
|
+
stashedUndo = this.makePointSetter(e.FID);
|
|
4124
|
+
}, this);
|
|
4125
|
+
|
|
4126
|
+
gui.on('symbol_dragend', function(e) {
|
|
4127
|
+
var redo = this.makePointSetter(e.FID);
|
|
4128
|
+
this.addHistoryState(stashedUndo, redo);
|
|
4129
|
+
}, this);
|
|
4130
|
+
|
|
4131
|
+
// undo/redo label dragging
|
|
4132
|
+
//
|
|
4133
|
+
gui.on('label_dragstart', function(e) {
|
|
4134
|
+
stashedUndo = this.makeDataSetter(e.FID);
|
|
4135
|
+
}, this);
|
|
4136
|
+
|
|
4137
|
+
gui.on('label_dragend', function(e) {
|
|
4138
|
+
var redo = this.makeDataSetter(e.FID);
|
|
4139
|
+
this.addHistoryState(stashedUndo, redo);
|
|
4140
|
+
}, this);
|
|
4141
|
+
|
|
4142
|
+
// undo/redo data editing
|
|
4143
|
+
// TODO: consider setting selected feature to the undo/redo target feature
|
|
4144
|
+
//
|
|
4145
|
+
gui.on('data_preupdate', function(e) {
|
|
4146
|
+
stashedUndo = this.makeDataSetter(e.FID);
|
|
4147
|
+
}, this);
|
|
4148
|
+
|
|
4149
|
+
gui.on('data_postupdate', function(e) {
|
|
4150
|
+
var redo = this.makeDataSetter(e.FID);
|
|
4151
|
+
this.addHistoryState(stashedUndo, redo);
|
|
4152
|
+
}, this);
|
|
4153
|
+
|
|
4154
|
+
// undo/redo vertex dragging
|
|
4155
|
+
gui.on('vertex_dragstart', function(e) {
|
|
4156
|
+
stashedUndo = this.makeVertexSetter(e.FID, e.vertex_ids);
|
|
4157
|
+
}, this);
|
|
4158
|
+
|
|
4159
|
+
gui.on('vertex_dragend', function(e) {
|
|
4160
|
+
var redo = this.makeVertexSetter(e.FID, e.vertex_ids);
|
|
4161
|
+
this.addHistoryState(stashedUndo, redo);
|
|
4162
|
+
}, this);
|
|
4163
|
+
|
|
4164
|
+
this.clear = function() {
|
|
4165
|
+
reset();
|
|
3953
4166
|
};
|
|
3954
4167
|
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
4168
|
+
this.makePointSetter = function(i) {
|
|
4169
|
+
var target = gui.model.getActiveLayer();
|
|
4170
|
+
var shp = cloneShape(target.layer.shapes[i]);
|
|
4171
|
+
return function() {
|
|
4172
|
+
target.layer.shapes[i] = shp;
|
|
4173
|
+
};
|
|
3960
4174
|
};
|
|
3961
4175
|
|
|
3962
|
-
|
|
3963
|
-
var
|
|
3964
|
-
|
|
4176
|
+
this.makeDataSetter = function(id) {
|
|
4177
|
+
var target = gui.model.getActiveLayer();
|
|
4178
|
+
var rec = copyRecord(target.layer.data.getRecordAt(id));
|
|
4179
|
+
return function() {
|
|
4180
|
+
target.layer.data.getRecords()[id] = rec;
|
|
4181
|
+
gui.dispatchEvent('popup-needs-refresh');
|
|
4182
|
+
};
|
|
3965
4183
|
};
|
|
3966
4184
|
|
|
3967
|
-
|
|
3968
|
-
var
|
|
3969
|
-
|
|
4185
|
+
this.makeVertexSetter = function(fid, ids) {
|
|
4186
|
+
var target = gui.model.getActiveLayer();
|
|
4187
|
+
var arcs = target.dataset.arcs;
|
|
4188
|
+
var p = arcs.getVertex2(ids[0]);
|
|
4189
|
+
return function() {
|
|
4190
|
+
snapVerticesToPoint(ids, p, arcs, true);
|
|
4191
|
+
};
|
|
3970
4192
|
};
|
|
3971
4193
|
|
|
3972
|
-
|
|
3973
|
-
|
|
4194
|
+
this.addHistoryState = function(undo, redo) {
|
|
4195
|
+
if (offset > 0) {
|
|
4196
|
+
history.splice(-offset);
|
|
4197
|
+
offset = 0;
|
|
4198
|
+
}
|
|
4199
|
+
history.push({undo, redo});
|
|
4200
|
+
};
|
|
3974
4201
|
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
}
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
4202
|
+
this.undo = function() {
|
|
4203
|
+
var item = getHistoryItem();
|
|
4204
|
+
if (item) {
|
|
4205
|
+
offset++;
|
|
4206
|
+
item.undo();
|
|
4207
|
+
refreshMap();
|
|
4208
|
+
}
|
|
4209
|
+
};
|
|
4210
|
+
|
|
4211
|
+
this.redo = function() {
|
|
4212
|
+
if (offset <= 0) return;
|
|
4213
|
+
offset--;
|
|
4214
|
+
var item = getHistoryItem();
|
|
4215
|
+
item.redo();
|
|
4216
|
+
refreshMap();
|
|
4217
|
+
};
|
|
4218
|
+
|
|
4219
|
+
function getHistoryItem() {
|
|
4220
|
+
var item = history[history.length - offset - 1];
|
|
4221
|
+
return item || null;
|
|
3988
4222
|
}
|
|
3989
|
-
return test;
|
|
3990
4223
|
|
|
3991
|
-
|
|
3992
|
-
function getHitBuffer(pix) {
|
|
3993
|
-
return pix / ext.getTransform().mx;
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
// reduce hit threshold when zoomed out
|
|
3997
|
-
function getZoomAdjustedHitBuffer(pix, minPix) {
|
|
3998
|
-
var scale = ext.scale();
|
|
3999
|
-
if (scale < 1) {
|
|
4000
|
-
pix *= scale;
|
|
4001
|
-
}
|
|
4002
|
-
if (minPix > 0 && pix < minPix) pix = minPix;
|
|
4003
|
-
return getHitBuffer(pix);
|
|
4004
|
-
}
|
|
4005
|
-
|
|
4006
|
-
function polygonTest(x, y) {
|
|
4007
|
-
var maxDist = getZoomAdjustedHitBuffer(5, 1),
|
|
4008
|
-
cands = findHitCandidates(x, y, maxDist),
|
|
4009
|
-
hits = [],
|
|
4010
|
-
cand, hitId;
|
|
4011
|
-
for (var i=0; i<cands.length; i++) {
|
|
4012
|
-
cand = cands[i];
|
|
4013
|
-
if (geom.testPointInPolygon(x, y, cand.shape, displayLayer.arcs)) {
|
|
4014
|
-
hits.push(cand.id);
|
|
4015
|
-
}
|
|
4016
|
-
}
|
|
4017
|
-
if (cands.length > 0 && hits.length === 0) {
|
|
4018
|
-
// secondary detection: proximity, if not inside a polygon
|
|
4019
|
-
sortByDistance(x, y, cands, displayLayer.arcs);
|
|
4020
|
-
hits = pickNearestCandidates(cands, 0, maxDist);
|
|
4021
|
-
}
|
|
4022
|
-
return hits;
|
|
4023
|
-
}
|
|
4224
|
+
}
|
|
4024
4225
|
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
break;
|
|
4032
|
-
} else if (i === 0) {
|
|
4033
|
-
minDist = cand.dist;
|
|
4034
|
-
} else if (cand.dist - minDist > bufDist) {
|
|
4035
|
-
break;
|
|
4036
|
-
}
|
|
4037
|
-
hits.push(cand.id);
|
|
4038
|
-
}
|
|
4039
|
-
return hits;
|
|
4040
|
-
}
|
|
4226
|
+
function SidebarButtons(gui) {
|
|
4227
|
+
var root = gui.container.findChild('.mshp-main-map');
|
|
4228
|
+
var buttons = El('div').addClass('nav-buttons').appendTo(root).hide();
|
|
4229
|
+
var _hidden = true;
|
|
4230
|
+
gui.on('active', updateVisibility);
|
|
4231
|
+
gui.on('inactive', updateVisibility);
|
|
4041
4232
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
}
|
|
4233
|
+
// @iconRef: selector for an (svg) button icon
|
|
4234
|
+
this.addButton = function(iconRef) {
|
|
4235
|
+
var btn = initButton(iconRef).addClass('nav-btn');
|
|
4236
|
+
btn.appendTo(buttons);
|
|
4237
|
+
return btn;
|
|
4238
|
+
};
|
|
4049
4239
|
|
|
4050
|
-
function
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
utils.sortOn(cands, 'dist');
|
|
4055
|
-
}
|
|
4240
|
+
this.show = function() {
|
|
4241
|
+
_hidden = false;
|
|
4242
|
+
updateVisibility();
|
|
4243
|
+
};
|
|
4056
4244
|
|
|
4057
|
-
function
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
hits = [],
|
|
4062
|
-
hitThreshold = 25,
|
|
4063
|
-
newThreshold = Infinity;
|
|
4245
|
+
this.hide = function() {
|
|
4246
|
+
_hidden = true;
|
|
4247
|
+
updateVisibility();
|
|
4248
|
+
};
|
|
4064
4249
|
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
//
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
newThreshold = dist < bullseyeDist ? -1 : dist - tinyDist;
|
|
4074
|
-
} else {
|
|
4075
|
-
// add to hits if inside bullseye or is same dist as previous hit
|
|
4076
|
-
hits.push(id);
|
|
4077
|
-
}
|
|
4078
|
-
});
|
|
4079
|
-
// console.log(hitThreshold, bullseye);
|
|
4080
|
-
return utils.uniq(hits); // multipoint features can register multiple hits
|
|
4081
|
-
}
|
|
4250
|
+
var initButton = this.initButton = function(iconRef) {
|
|
4251
|
+
var icon = El('body').findChild(iconRef).node().cloneNode(true);
|
|
4252
|
+
var btn = El('div')
|
|
4253
|
+
.on('dblclick', function(e) {e.stopPropagation();}); // block dblclick zoom
|
|
4254
|
+
btn.appendChild(icon);
|
|
4255
|
+
if (icon.hasAttribute('id')) icon.removeAttribute('id');
|
|
4256
|
+
return btn;
|
|
4257
|
+
};
|
|
4082
4258
|
|
|
4083
|
-
function
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
return o.radius || 0;
|
|
4089
|
-
};
|
|
4259
|
+
function updateVisibility() {
|
|
4260
|
+
if (GUI.isActiveInstance(gui) && !_hidden) {
|
|
4261
|
+
buttons.show();
|
|
4262
|
+
} else {
|
|
4263
|
+
buttons.hide();
|
|
4090
4264
|
}
|
|
4091
|
-
return function() {return style.radius || 0;};
|
|
4092
|
-
}
|
|
4093
|
-
|
|
4094
|
-
function getGraduatedCircleTest(radius) {
|
|
4095
|
-
return function(x, y) {
|
|
4096
|
-
var hits = [],
|
|
4097
|
-
margin = getHitBuffer(12),
|
|
4098
|
-
limit = getHitBuffer(50), // short-circuit hit test beyond this threshold
|
|
4099
|
-
directHit = false,
|
|
4100
|
-
hitRadius = 0,
|
|
4101
|
-
hitDist;
|
|
4102
|
-
internal.forEachPoint(displayLayer.layer.shapes, function(p, id) {
|
|
4103
|
-
var distSq = geom.distanceSq(x, y, p[0], p[1]);
|
|
4104
|
-
var isHit = false;
|
|
4105
|
-
var isOver, isNear, r, d, rpix;
|
|
4106
|
-
if (distSq > limit * limit) return;
|
|
4107
|
-
rpix = radius(id);
|
|
4108
|
-
r = getHitBuffer(rpix + 1); // increase effective radius to make small bubbles easier to hit in clusters
|
|
4109
|
-
d = Math.sqrt(distSq) - r; // pointer distance from edge of circle (negative = inside)
|
|
4110
|
-
isOver = d < 0;
|
|
4111
|
-
isNear = d < margin;
|
|
4112
|
-
if (!isNear || rpix > 0 === false) {
|
|
4113
|
-
isHit = false;
|
|
4114
|
-
} else if (hits.length === 0) {
|
|
4115
|
-
isHit = isNear;
|
|
4116
|
-
} else if (!directHit && isOver) {
|
|
4117
|
-
isHit = true;
|
|
4118
|
-
} else if (directHit && isOver) {
|
|
4119
|
-
isHit = r == hitRadius ? d <= hitDist : r < hitRadius; // smallest bubble wins if multiple direct hits
|
|
4120
|
-
} else if (!directHit && !isOver) {
|
|
4121
|
-
// closest to bubble edge wins
|
|
4122
|
-
isHit = hitDist == d ? r <= hitRadius : d < hitDist; // closest bubble wins if multiple indirect hits
|
|
4123
|
-
}
|
|
4124
|
-
if (isHit) {
|
|
4125
|
-
if (hits.length > 0 && (r != hitRadius || d != hitDist)) {
|
|
4126
|
-
hits = [];
|
|
4127
|
-
}
|
|
4128
|
-
hitRadius = r;
|
|
4129
|
-
hitDist = d;
|
|
4130
|
-
directHit = isOver;
|
|
4131
|
-
hits.push(id);
|
|
4132
|
-
}
|
|
4133
|
-
});
|
|
4134
|
-
return hits;
|
|
4135
|
-
};
|
|
4136
|
-
}
|
|
4137
|
-
|
|
4138
|
-
function findHitCandidates(x, y, dist) {
|
|
4139
|
-
var arcs = displayLayer.arcs,
|
|
4140
|
-
index = {},
|
|
4141
|
-
cands = [],
|
|
4142
|
-
bbox = [];
|
|
4143
|
-
displayLayer.layer.shapes.forEach(function(shp, shpId) {
|
|
4144
|
-
var cand;
|
|
4145
|
-
for (var i = 0, n = shp && shp.length; i < n; i++) {
|
|
4146
|
-
arcs.getSimpleShapeBounds2(shp[i], bbox);
|
|
4147
|
-
if (x + dist < bbox[0] || x - dist > bbox[2] ||
|
|
4148
|
-
y + dist < bbox[1] || y - dist > bbox[3]) {
|
|
4149
|
-
continue; // bbox non-intersection
|
|
4150
|
-
}
|
|
4151
|
-
cand = index[shpId];
|
|
4152
|
-
if (!cand) {
|
|
4153
|
-
cand = index[shpId] = {shape: [], id: shpId, dist: 0};
|
|
4154
|
-
cands.push(cand);
|
|
4155
|
-
}
|
|
4156
|
-
cand.shape.push(shp[i]);
|
|
4157
|
-
}
|
|
4158
|
-
});
|
|
4159
|
-
return cands;
|
|
4160
4265
|
}
|
|
4161
4266
|
}
|
|
4162
4267
|
|
|
4163
|
-
function
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
return internal.svg.getTransform(p, scale);
|
|
4171
|
-
}
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
function repositionSymbols(elements, layer, ext) {
|
|
4175
|
-
var el, idx, shp, p, displayOn, inView, displayBounds;
|
|
4176
|
-
for (var i=0, n=elements.length; i<n; i++) {
|
|
4177
|
-
el = elements[i];
|
|
4178
|
-
idx = getSymbolNodeId(el);
|
|
4179
|
-
shp = layer.shapes[idx];
|
|
4180
|
-
if (!shp) continue;
|
|
4181
|
-
p = shp[0];
|
|
4182
|
-
// OPTIMIZATION: only display symbols that are in view
|
|
4183
|
-
// quick-and-dirty hit-test: expand the extent rectangle by a percentage.
|
|
4184
|
-
// very large symbols will disappear before they're completely out of view
|
|
4185
|
-
displayBounds = ext.getBounds(1.15);
|
|
4186
|
-
displayOn = !el.hasAttribute('display') || el.getAttribute('display') == 'block';
|
|
4187
|
-
inView = displayBounds.containsPoint(p[0], p[1]);
|
|
4188
|
-
if (inView) {
|
|
4189
|
-
if (!displayOn) el.setAttribute('display', 'block');
|
|
4190
|
-
el.setAttribute('transform', getSvgSymbolTransform(p, ext));
|
|
4268
|
+
function ModeButton(modes, el, name) {
|
|
4269
|
+
var btn = El(el),
|
|
4270
|
+
active = false;
|
|
4271
|
+
modes.on('mode', function(e) {
|
|
4272
|
+
active = e.name == name;
|
|
4273
|
+
if (active) {
|
|
4274
|
+
btn.addClass('active');
|
|
4191
4275
|
} else {
|
|
4192
|
-
|
|
4276
|
+
btn.removeClass('active');
|
|
4193
4277
|
}
|
|
4194
|
-
}
|
|
4195
|
-
}
|
|
4196
|
-
|
|
4197
|
-
function renderSymbols(lyr, ext, type) {
|
|
4198
|
-
var records = lyr.data.getRecords();
|
|
4199
|
-
var symbols = lyr.shapes.map(function(shp, i) {
|
|
4200
|
-
var d = records[i];
|
|
4201
|
-
var obj = type == 'label' ? internal.svg.importStyledLabel(d) :
|
|
4202
|
-
internal.svg.importSymbol(d['svg-symbol']);
|
|
4203
|
-
if (!obj || !shp) return null;
|
|
4204
|
-
obj.properties.transform = getSvgSymbolTransform(shp[0], ext);
|
|
4205
|
-
obj.properties['data-id'] = i;
|
|
4206
|
-
return obj;
|
|
4207
4278
|
});
|
|
4208
|
-
var obj = internal.getEmptyLayerForSVG(lyr, {});
|
|
4209
|
-
obj.children = symbols;
|
|
4210
|
-
return internal.svg.stringify(obj);
|
|
4211
|
-
}
|
|
4212
4279
|
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
// target could be a part of an SVG symbol, or the SVG element, or something else
|
|
4217
|
-
var target = pointerEvent.originalEvent.target;
|
|
4218
|
-
var symbolNode = getSymbolNode(target);
|
|
4219
|
-
if (!symbolNode) {
|
|
4220
|
-
return null;
|
|
4221
|
-
}
|
|
4222
|
-
return {
|
|
4223
|
-
targetId: getSymbolNodeId(symbolNode), // TODO: some validation on id
|
|
4224
|
-
targetSymbol: symbolNode,
|
|
4225
|
-
targetNode: target,
|
|
4226
|
-
container: symbolNode.parentNode
|
|
4227
|
-
};
|
|
4228
|
-
};
|
|
4229
|
-
|
|
4230
|
-
// target: event target (could be any DOM element)
|
|
4231
|
-
function getSymbolNode(target) {
|
|
4232
|
-
var node = target;
|
|
4233
|
-
while (node && nodeHasSymbolTagType(node)) {
|
|
4234
|
-
if (isSymbolNode(node)) {
|
|
4235
|
-
return node;
|
|
4236
|
-
}
|
|
4237
|
-
node = node.parentElement;
|
|
4238
|
-
}
|
|
4239
|
-
return null;
|
|
4240
|
-
}
|
|
4241
|
-
|
|
4242
|
-
// TODO: switch to attribute detection
|
|
4243
|
-
function nodeHasSymbolTagType(node) {
|
|
4244
|
-
var tag = node.tagName;
|
|
4245
|
-
return tag == 'g' || tag == 'tspan' || tag == 'text' || tag == 'image' ||
|
|
4246
|
-
tag == 'path' || tag == 'circle' || tag == 'rect' || tag == 'line';
|
|
4247
|
-
}
|
|
4248
|
-
|
|
4249
|
-
function isSymbolNode(node) {
|
|
4250
|
-
return node.hasAttribute('data-id') && (node.tagName == 'text' || node.tagName == 'g');
|
|
4251
|
-
}
|
|
4252
|
-
|
|
4253
|
-
function isSymbolChildNode(node) {
|
|
4254
|
-
|
|
4255
|
-
}
|
|
4256
|
-
|
|
4257
|
-
function getChildId(childNode) {
|
|
4258
|
-
|
|
4259
|
-
}
|
|
4260
|
-
|
|
4261
|
-
function getSymbolId(symbolNode) {
|
|
4262
|
-
|
|
4263
|
-
}
|
|
4264
|
-
|
|
4265
|
-
function getFeatureId(symbolNode) {
|
|
4266
|
-
|
|
4267
|
-
}
|
|
4268
|
-
|
|
4269
|
-
}
|
|
4270
|
-
|
|
4271
|
-
function getPointerHitTest(mapLayer, ext) {
|
|
4272
|
-
var shapeTest, svgTest, targetLayer;
|
|
4273
|
-
if (!mapLayer || !internal.layerHasGeometry(mapLayer.layer)) {
|
|
4274
|
-
return null;
|
|
4275
|
-
}
|
|
4276
|
-
shapeTest = getShapeHitTest(mapLayer, ext);
|
|
4277
|
-
svgTest = getSvgHitTest(mapLayer);
|
|
4278
|
-
|
|
4279
|
-
// e: pointer event
|
|
4280
|
-
return function(e) {
|
|
4281
|
-
var p = ext.translatePixelCoords(e.x, e.y);
|
|
4282
|
-
var data = {
|
|
4283
|
-
ids: shapeTest(p[0], p[1]) || []
|
|
4284
|
-
};
|
|
4285
|
-
var svgData = svgTest(e); // null or a data object
|
|
4286
|
-
if (svgData) { // mouse is over an SVG symbol
|
|
4287
|
-
utils.extend(data, svgData);
|
|
4288
|
-
// placing symbol id in front of any other hits
|
|
4289
|
-
data.ids = utils.uniq([svgData.targetId].concat(data.ids));
|
|
4290
|
-
}
|
|
4291
|
-
data.id = data.ids.length > 0 ? data.ids[0] : -1;
|
|
4292
|
-
return data;
|
|
4293
|
-
};
|
|
4280
|
+
btn.on('click', function() {
|
|
4281
|
+
modes.enterMode(active ? null : name);
|
|
4282
|
+
});
|
|
4294
4283
|
}
|
|
4295
4284
|
|
|
4296
|
-
function
|
|
4297
|
-
var self =
|
|
4298
|
-
var
|
|
4299
|
-
var selectionIds = [];
|
|
4300
|
-
var transientIds = []; // e.g. hit ids while dragging a box
|
|
4301
|
-
var active = false;
|
|
4302
|
-
var interactionMode;
|
|
4303
|
-
var targetLayer;
|
|
4304
|
-
var hitTest;
|
|
4305
|
-
// event priority is higher than navigation, so stopping propagation disables
|
|
4306
|
-
// pan navigation
|
|
4307
|
-
var priority = 2;
|
|
4308
|
-
|
|
4309
|
-
// init keyboard controls for pinned features
|
|
4310
|
-
gui.keyboard.on('keydown', function(evt) {
|
|
4311
|
-
var e = evt.originalEvent;
|
|
4312
|
-
|
|
4313
|
-
if (gui.interaction.getMode() == 'off' || !targetLayer) return;
|
|
4314
|
-
|
|
4315
|
-
// esc key clears selection (unless in an editing mode -- esc key also exits current mode)
|
|
4316
|
-
if (e.keyCode == 27 && !gui.getMode()) {
|
|
4317
|
-
self.clearSelection();
|
|
4318
|
-
return;
|
|
4319
|
-
}
|
|
4320
|
-
|
|
4321
|
-
// ignore keypress if no feature is selected or user is editing text
|
|
4322
|
-
if (pinnedId() == -1 || GUI.getInputElement()) return;
|
|
4323
|
-
|
|
4324
|
-
if (e.keyCode == 37 || e.keyCode == 39) {
|
|
4325
|
-
// L/R arrow keys
|
|
4326
|
-
// advance pinned feature
|
|
4327
|
-
advanceSelectedFeature(e.keyCode == 37 ? -1 : 1);
|
|
4328
|
-
e.stopPropagation();
|
|
4285
|
+
function ModeSwitcher() {
|
|
4286
|
+
var self = this;
|
|
4287
|
+
var mode = null;
|
|
4329
4288
|
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4289
|
+
self.getMode = function() {
|
|
4290
|
+
return mode;
|
|
4291
|
+
};
|
|
4292
|
+
|
|
4293
|
+
// return a function to trigger this mode
|
|
4294
|
+
self.addMode = function(name, enter, exit, btn) {
|
|
4295
|
+
self.on('mode', function(e) {
|
|
4296
|
+
if (e.prev == name) {
|
|
4297
|
+
exit();
|
|
4298
|
+
}
|
|
4299
|
+
if (e.name == name) {
|
|
4300
|
+
enter();
|
|
4339
4301
|
}
|
|
4302
|
+
});
|
|
4303
|
+
if (btn) {
|
|
4304
|
+
new ModeButton(self, btn, name);
|
|
4340
4305
|
}
|
|
4341
|
-
}
|
|
4306
|
+
};
|
|
4342
4307
|
|
|
4343
|
-
self.
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4308
|
+
self.addMode(null, function() {}, function() {}); // null mode
|
|
4309
|
+
|
|
4310
|
+
self.clearMode = function() {
|
|
4311
|
+
self.enterMode(null);
|
|
4312
|
+
};
|
|
4313
|
+
|
|
4314
|
+
self.enterMode = function(next) {
|
|
4315
|
+
var prev = mode;
|
|
4316
|
+
if (next != prev) {
|
|
4317
|
+
mode = next;
|
|
4318
|
+
self.dispatchEvent('mode', {name: next, prev: prev});
|
|
4347
4319
|
}
|
|
4348
|
-
targetLayer = mapLayer;
|
|
4349
4320
|
};
|
|
4321
|
+
}
|
|
4350
4322
|
|
|
4351
|
-
|
|
4352
|
-
interactionMode = mode;
|
|
4353
|
-
active = true;
|
|
4354
|
-
}
|
|
4323
|
+
utils.inherit(ModeSwitcher, EventDispatcher);
|
|
4355
4324
|
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4325
|
+
function KeyboardEvents(gui) {
|
|
4326
|
+
var self = this;
|
|
4327
|
+
var shiftDown = false;
|
|
4328
|
+
document.addEventListener('keyup', function(e) {
|
|
4329
|
+
if (!GUI.isActiveInstance(gui)) return;
|
|
4330
|
+
if (e.keyCode == 16) shiftDown = false;
|
|
4331
|
+
});
|
|
4362
4332
|
|
|
4363
|
-
function
|
|
4364
|
-
|
|
4365
|
-
|
|
4333
|
+
document.addEventListener('keydown', function(e) {
|
|
4334
|
+
if (!GUI.isActiveInstance(gui)) return;
|
|
4335
|
+
if (e.keyCode == 16) shiftDown = true;
|
|
4336
|
+
self.dispatchEvent('keydown', {originalEvent: e});
|
|
4337
|
+
});
|
|
4366
4338
|
|
|
4367
|
-
function
|
|
4368
|
-
return clickable() && interactionMode != 'selection';
|
|
4369
|
-
}
|
|
4339
|
+
this.shiftIsPressed = function() { return shiftDown; };
|
|
4370
4340
|
|
|
4371
|
-
function
|
|
4372
|
-
|
|
4373
|
-
|
|
4341
|
+
this.onMenuSubmit = function(menuEl, cb) {
|
|
4342
|
+
gui.on('enter_key', function(e) {
|
|
4343
|
+
if (menuEl.visible()) {
|
|
4344
|
+
e.originalEvent.stopPropagation();
|
|
4345
|
+
cb();
|
|
4346
|
+
}
|
|
4347
|
+
});
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4374
4350
|
|
|
4375
|
-
|
|
4376
|
-
// click used to pin popup and select features
|
|
4377
|
-
return interactionMode == 'data' || interactionMode == 'info' || interactionMode == 'selection';
|
|
4378
|
-
}
|
|
4351
|
+
utils.inherit(KeyboardEvents, EventDispatcher);
|
|
4379
4352
|
|
|
4380
|
-
|
|
4353
|
+
function InteractionMode(gui) {
|
|
4381
4354
|
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4355
|
+
var menus = {
|
|
4356
|
+
standard: ['info', 'selection', 'data', 'box'],
|
|
4357
|
+
lines: ['info', 'selection', 'data', 'box', 'vertices'],
|
|
4358
|
+
table: ['info', 'selection', 'data'],
|
|
4359
|
+
labels: ['info', 'selection', 'data', 'box', 'labels', 'location'],
|
|
4360
|
+
points: ['info', 'selection', 'data', 'box', 'location']
|
|
4386
4361
|
};
|
|
4387
4362
|
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
updateSelectionState({ids: ids});
|
|
4363
|
+
var prompts = {
|
|
4364
|
+
box: 'Shift-drag to draw a box',
|
|
4365
|
+
data: 'Click-select features to edit their attributes',
|
|
4366
|
+
selection: 'Click-select or shift-drag to select features'
|
|
4393
4367
|
};
|
|
4394
4368
|
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4369
|
+
// mode name -> menu text lookup
|
|
4370
|
+
var labels = {
|
|
4371
|
+
info: 'inspect features',
|
|
4372
|
+
box: 'shift-drag box tool',
|
|
4373
|
+
data: 'edit attributes',
|
|
4374
|
+
labels: 'position labels',
|
|
4375
|
+
location: 'drag points',
|
|
4376
|
+
vertices: 'drag vertices',
|
|
4377
|
+
selection: 'select features',
|
|
4378
|
+
off: 'turn off'
|
|
4401
4379
|
};
|
|
4380
|
+
var btn, menu;
|
|
4381
|
+
var _menuTimeout;
|
|
4402
4382
|
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4383
|
+
// state variables
|
|
4384
|
+
var _editMode = 'off';
|
|
4385
|
+
var _menuOpen = false;
|
|
4406
4386
|
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4387
|
+
// Only render edit mode button/menu if this option is present
|
|
4388
|
+
if (gui.options.inspectorControl) {
|
|
4389
|
+
btn = gui.buttons.addButton('#pointer-icon').addClass('menu-btn');
|
|
4390
|
+
menu = El('div').addClass('nav-sub-menu').appendTo(btn.node());
|
|
4410
4391
|
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4392
|
+
btn.on('mouseleave', function() {
|
|
4393
|
+
if (!_menuOpen) {
|
|
4394
|
+
btn.removeClass('hover');
|
|
4395
|
+
} else {
|
|
4396
|
+
closeMenu(200);
|
|
4397
|
+
}
|
|
4398
|
+
});
|
|
4414
4399
|
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4400
|
+
btn.on('mouseenter', function() {
|
|
4401
|
+
btn.addClass('hover');
|
|
4402
|
+
if (_menuOpen) {
|
|
4403
|
+
clearTimeout(_menuTimeout); // prevent timed closing
|
|
4404
|
+
} else {
|
|
4405
|
+
openMenu();
|
|
4406
|
+
}
|
|
4407
|
+
// if (_editMode != 'off') {
|
|
4408
|
+
// openMenu();
|
|
4409
|
+
// }
|
|
4410
|
+
});
|
|
4411
|
+
|
|
4412
|
+
btn.on('click', function(e) {
|
|
4413
|
+
if (active()) {
|
|
4414
|
+
setMode('off');
|
|
4415
|
+
closeMenu();
|
|
4416
|
+
} else if (_menuOpen) {
|
|
4417
|
+
setMode('info'); // select info (inspect) as the default
|
|
4418
|
+
// closeMenu(350);
|
|
4419
|
+
} else {
|
|
4420
|
+
openMenu();
|
|
4421
|
+
}
|
|
4422
|
+
e.stopPropagation();
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
|
|
4426
|
+
this.turnOff = function() {
|
|
4427
|
+
setMode('off');
|
|
4418
4428
|
};
|
|
4419
4429
|
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4430
|
+
this.getMode = getInteractionMode;
|
|
4431
|
+
|
|
4432
|
+
this.setMode = function(mode) {
|
|
4433
|
+
// TODO: check that this mode is valid for the current dataset
|
|
4434
|
+
if (mode in labels) {
|
|
4435
|
+
setMode(mode);
|
|
4436
|
+
}
|
|
4426
4437
|
};
|
|
4427
4438
|
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
if (
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4439
|
+
gui.model.on('update', function(e) {
|
|
4440
|
+
// change mode if active layer doesn't support the current mode
|
|
4441
|
+
updateCurrentMode();
|
|
4442
|
+
if (_menuOpen) {
|
|
4443
|
+
renderMenu();
|
|
4444
|
+
}
|
|
4445
|
+
}, null, -1); // low priority?
|
|
4446
|
+
|
|
4447
|
+
function active() {
|
|
4448
|
+
return _editMode && _editMode != 'off';
|
|
4435
4449
|
}
|
|
4436
4450
|
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
if (
|
|
4443
|
-
|
|
4444
|
-
|
|
4451
|
+
function getAvailableModes() {
|
|
4452
|
+
var o = gui.model.getActiveLayer();
|
|
4453
|
+
if (!o || !o.layer) {
|
|
4454
|
+
return menus.standard; // TODO: more sensible handling of missing layer
|
|
4455
|
+
}
|
|
4456
|
+
if (!internal.layerHasGeometry(o.layer)) {
|
|
4457
|
+
return menus.table;
|
|
4458
|
+
}
|
|
4459
|
+
if (internal.layerHasLabels(o.layer)) {
|
|
4460
|
+
return menus.labels;
|
|
4461
|
+
}
|
|
4462
|
+
if (internal.layerHasPoints(o.layer)) {
|
|
4463
|
+
return menus.points;
|
|
4464
|
+
}
|
|
4465
|
+
if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polyline') {
|
|
4466
|
+
return menus.lines;
|
|
4467
|
+
}
|
|
4468
|
+
return menus.standard;
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
function getInteractionMode() {
|
|
4472
|
+
return active() ? _editMode : 'off';
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
function renderMenu() {
|
|
4476
|
+
if (!menu) return;
|
|
4477
|
+
var modes = getAvailableModes();
|
|
4478
|
+
menu.empty();
|
|
4479
|
+
modes.forEach(function(mode) {
|
|
4480
|
+
// don't show "turn off" link if not currently editing
|
|
4481
|
+
if (_editMode == 'off' && mode == 'off') return;
|
|
4482
|
+
var link = El('div').addClass('nav-menu-item').attr('data-name', mode).text(labels[mode]).appendTo(menu);
|
|
4483
|
+
link.on('click', function(e) {
|
|
4484
|
+
if (_editMode == mode) {
|
|
4485
|
+
// closeMenu();
|
|
4486
|
+
setMode('off');
|
|
4487
|
+
} else if (_editMode != mode) {
|
|
4488
|
+
setMode(mode);
|
|
4489
|
+
if (mode == 'off') closeMenu(120); // only close if turning off
|
|
4490
|
+
// closeMenu(mode == 'off' ? 120 : 400); // close after selecting
|
|
4491
|
+
}
|
|
4492
|
+
e.stopPropagation();
|
|
4493
|
+
});
|
|
4494
|
+
});
|
|
4495
|
+
updateSelectionHighlight();
|
|
4445
4496
|
}
|
|
4446
4497
|
|
|
4447
|
-
//
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
turnOff();
|
|
4453
|
-
} else {
|
|
4454
|
-
turnOn(e.mode);
|
|
4498
|
+
// if current editing mode is not available, turn off the tool
|
|
4499
|
+
function updateCurrentMode() {
|
|
4500
|
+
var modes = getAvailableModes();
|
|
4501
|
+
if (modes.indexOf(_editMode) == -1) {
|
|
4502
|
+
setMode('off');
|
|
4455
4503
|
}
|
|
4456
|
-
}
|
|
4457
|
-
|
|
4458
|
-
gui.on('box_drag_start', function() {
|
|
4459
|
-
self.clearHover();
|
|
4460
|
-
});
|
|
4504
|
+
}
|
|
4461
4505
|
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4506
|
+
function openMenu() {
|
|
4507
|
+
clearTimeout(_menuTimeout);
|
|
4508
|
+
if (!_menuOpen) {
|
|
4509
|
+
_menuOpen = true;
|
|
4510
|
+
renderMenu();
|
|
4511
|
+
updateArrowButton();
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4466
4514
|
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4515
|
+
// Calling with a delay lets users see the menu update after clicking a selection,
|
|
4516
|
+
// and prevents the menu from closing immediately if the pointer briefly drifts
|
|
4517
|
+
// off the menu while hovering.
|
|
4518
|
+
//
|
|
4519
|
+
function closeMenu(delay) {
|
|
4520
|
+
if (!_menuOpen) return;
|
|
4521
|
+
clearTimeout(_menuTimeout);
|
|
4522
|
+
_menuTimeout = setTimeout(function() {
|
|
4523
|
+
_menuOpen = false;
|
|
4524
|
+
updateArrowButton();
|
|
4525
|
+
}, delay || 0);
|
|
4526
|
+
}
|
|
4470
4527
|
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4528
|
+
function setMode(mode) {
|
|
4529
|
+
var changed = mode != _editMode;
|
|
4530
|
+
if (changed) {
|
|
4531
|
+
menu.classed('active', mode != 'off');
|
|
4532
|
+
_editMode = mode;
|
|
4533
|
+
onModeChange();
|
|
4534
|
+
updateArrowButton();
|
|
4535
|
+
updateSelectionHighlight();
|
|
4474
4536
|
}
|
|
4475
|
-
|
|
4476
|
-
}, null, priority);
|
|
4537
|
+
}
|
|
4477
4538
|
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4539
|
+
function onModeChange() {
|
|
4540
|
+
var mode = getInteractionMode();
|
|
4541
|
+
gui.state.interaction_mode = mode;
|
|
4542
|
+
gui.dispatchEvent('interaction_mode_change', {mode: mode});
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
// Update button highlight and selected menu item highlight (if any)
|
|
4546
|
+
function updateArrowButton() {
|
|
4547
|
+
if (!menu) return;
|
|
4548
|
+
if (_menuOpen) {
|
|
4549
|
+
btn.addClass('open');
|
|
4487
4550
|
} else {
|
|
4488
|
-
|
|
4551
|
+
btn.removeClass('open');
|
|
4489
4552
|
}
|
|
4490
|
-
|
|
4553
|
+
btn.classed('hover', _menuOpen);
|
|
4554
|
+
// btn.classed('selected', active() && !_menuOpen);
|
|
4555
|
+
btn.classed('selected', active());
|
|
4556
|
+
}
|
|
4491
4557
|
|
|
4492
|
-
function
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
target = target.parentNode;
|
|
4498
|
-
}
|
|
4499
|
-
return false;
|
|
4558
|
+
function updateSelectionHighlight() {
|
|
4559
|
+
El.findAll('.nav-menu-item').forEach(function(el) {
|
|
4560
|
+
el = El(el);
|
|
4561
|
+
el.classed('selected', el.attr('data-name') == _editMode);
|
|
4562
|
+
});
|
|
4500
4563
|
}
|
|
4564
|
+
}
|
|
4501
4565
|
|
|
4502
|
-
|
|
4566
|
+
function Model(gui) {
|
|
4567
|
+
var self = new internal.Catalog();
|
|
4568
|
+
var deleteLayer = self.deleteLayer;
|
|
4569
|
+
utils.extend(self, EventDispatcher.prototype);
|
|
4503
4570
|
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4571
|
+
// override Catalog method (so -drop command will work in web console)
|
|
4572
|
+
self.deleteLayer = function(lyr, dataset) {
|
|
4573
|
+
var active, flags;
|
|
4574
|
+
deleteLayer.call(self, lyr, dataset);
|
|
4575
|
+
if (self.isEmpty()) {
|
|
4576
|
+
// refresh browser if deleted layer was the last layer
|
|
4577
|
+
window.location.href = window.location.href.toString();
|
|
4578
|
+
} else {
|
|
4579
|
+
// trigger event to update layer list and, if needed, the map view
|
|
4580
|
+
flags = {};
|
|
4581
|
+
active = self.getActiveLayer();
|
|
4582
|
+
if (active.layer != lyr) {
|
|
4583
|
+
flags.select = true;
|
|
4517
4584
|
}
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
selectionIds = toggleId(id, selectionIds);
|
|
4585
|
+
internal.cleanupArcs(active.dataset);
|
|
4586
|
+
if (internal.layerHasPaths(lyr)) {
|
|
4587
|
+
flags.arc_count = true; // looks like a kludge, try to remove
|
|
4522
4588
|
}
|
|
4523
|
-
|
|
4589
|
+
self.updated(flags, active.layer, active.dataset);
|
|
4524
4590
|
}
|
|
4525
|
-
|
|
4526
|
-
}
|
|
4591
|
+
};
|
|
4527
4592
|
|
|
4528
|
-
function
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4593
|
+
self.updated = function(flags) {
|
|
4594
|
+
var targets = self.getDefaultTargets();
|
|
4595
|
+
var active = self.getActiveLayer();
|
|
4596
|
+
if (internal.countTargetLayers(targets) > 1) {
|
|
4597
|
+
self.setDefaultTarget([active.layer], active.dataset);
|
|
4598
|
+
gui.session.setTargetLayer(active.layer); // add -target command to target single layer
|
|
4534
4599
|
}
|
|
4535
|
-
if (
|
|
4536
|
-
|
|
4537
|
-
// kludge to inhibit hover effect while dragging a box
|
|
4538
|
-
if (gui.keydown) hitData.id = -1;
|
|
4600
|
+
if (flags.select) {
|
|
4601
|
+
self.dispatchEvent('select', active);
|
|
4539
4602
|
}
|
|
4540
|
-
|
|
4541
|
-
}
|
|
4603
|
+
self.dispatchEvent('update', utils.extend({flags: flags}, active));
|
|
4604
|
+
};
|
|
4542
4605
|
|
|
4543
|
-
function
|
|
4544
|
-
|
|
4545
|
-
|
|
4606
|
+
self.selectLayer = function(lyr, dataset) {
|
|
4607
|
+
if (self.getActiveLayer().layer == lyr) return;
|
|
4608
|
+
self.setDefaultTarget([lyr], dataset);
|
|
4609
|
+
self.updated({select: true});
|
|
4610
|
+
gui.session.setTargetLayer(lyr);
|
|
4611
|
+
};
|
|
4546
4612
|
|
|
4547
|
-
function
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
return [id].concat(ids);
|
|
4552
|
-
}
|
|
4613
|
+
self.selectNextLayer = function() {
|
|
4614
|
+
var next = self.findNextLayer(self.getActiveLayer().layer);
|
|
4615
|
+
if (next) self.selectLayer(next.layer, next.dataset);
|
|
4616
|
+
};
|
|
4553
4617
|
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4618
|
+
self.selectPrevLayer = function() {
|
|
4619
|
+
var prev = self.findPrevLayer(self.getActiveLayer().layer);
|
|
4620
|
+
if (prev) self.selectLayer(prev.layer, prev.dataset);
|
|
4621
|
+
};
|
|
4622
|
+
|
|
4623
|
+
return self;
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4626
|
+
function getShapeHitTest(displayLayer, ext) {
|
|
4627
|
+
var geoType = displayLayer.layer.geometry_type;
|
|
4628
|
+
var test;
|
|
4629
|
+
if (geoType == 'point' && displayLayer.style.type == 'styled') {
|
|
4630
|
+
test = getGraduatedCircleTest(getRadiusFunction(displayLayer.style));
|
|
4631
|
+
} else if (geoType == 'point') {
|
|
4632
|
+
test = pointTest;
|
|
4633
|
+
} else if (geoType == 'polyline') {
|
|
4634
|
+
test = polylineTest;
|
|
4635
|
+
} else if (geoType == 'polygon') {
|
|
4636
|
+
test = polygonTest;
|
|
4637
|
+
} else {
|
|
4638
|
+
error("Unexpected geometry type:", geoType);
|
|
4571
4639
|
}
|
|
4640
|
+
return test;
|
|
4572
4641
|
|
|
4573
|
-
//
|
|
4574
|
-
function
|
|
4575
|
-
|
|
4576
|
-
return false;
|
|
4577
|
-
}
|
|
4578
|
-
if ((type == 'drag' || type == 'dragstart' || type == 'dragend') && !draggable()) {
|
|
4579
|
-
return false;
|
|
4580
|
-
}
|
|
4581
|
-
return true;
|
|
4642
|
+
// Convert pixel distance to distance in coordinate units.
|
|
4643
|
+
function getHitBuffer(pix) {
|
|
4644
|
+
return pix / ext.getTransform().mx;
|
|
4582
4645
|
}
|
|
4583
4646
|
|
|
4584
|
-
|
|
4585
|
-
|
|
4647
|
+
// reduce hit threshold when zoomed out
|
|
4648
|
+
function getZoomAdjustedHitBuffer(pix, minPix) {
|
|
4649
|
+
var scale = ext.scale();
|
|
4650
|
+
if (scale < 1) {
|
|
4651
|
+
pix *= scale;
|
|
4652
|
+
}
|
|
4653
|
+
if (minPix > 0 && pix < minPix) pix = minPix;
|
|
4654
|
+
return getHitBuffer(pix);
|
|
4586
4655
|
}
|
|
4587
4656
|
|
|
4588
|
-
function
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4657
|
+
function polygonTest(x, y) {
|
|
4658
|
+
var maxDist = getZoomAdjustedHitBuffer(5, 1),
|
|
4659
|
+
cands = findHitCandidates(x, y, maxDist),
|
|
4660
|
+
hits = [],
|
|
4661
|
+
cand, hitId;
|
|
4662
|
+
for (var i=0; i<cands.length; i++) {
|
|
4663
|
+
cand = cands[i];
|
|
4664
|
+
if (geom.testPointInPolygon(x, y, cand.shape, displayLayer.arcs)) {
|
|
4665
|
+
hits.push(cand.id);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
if (cands.length > 0 && hits.length === 0) {
|
|
4669
|
+
// secondary detection: proximity, if not inside a polygon
|
|
4670
|
+
sortByDistance(x, y, cands, displayLayer.arcs);
|
|
4671
|
+
hits = pickNearestCandidates(cands, 0, maxDist);
|
|
4595
4672
|
}
|
|
4673
|
+
return hits;
|
|
4596
4674
|
}
|
|
4597
4675
|
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
var
|
|
4602
|
-
|
|
4603
|
-
|
|
4676
|
+
function pickNearestCandidates(sorted, bufDist, maxDist) {
|
|
4677
|
+
var hits = [],
|
|
4678
|
+
cand, minDist;
|
|
4679
|
+
for (var i=0; i<sorted.length; i++) {
|
|
4680
|
+
cand = sorted[i];
|
|
4681
|
+
if (cand.dist < maxDist !== true) {
|
|
4682
|
+
break;
|
|
4683
|
+
} else if (i === 0) {
|
|
4684
|
+
minDist = cand.dist;
|
|
4685
|
+
} else if (cand.dist - minDist > bufDist) {
|
|
4686
|
+
break;
|
|
4687
|
+
}
|
|
4688
|
+
hits.push(cand.id);
|
|
4604
4689
|
}
|
|
4605
|
-
|
|
4690
|
+
return hits;
|
|
4606
4691
|
}
|
|
4607
4692
|
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
}
|
|
4615
|
-
return true;
|
|
4693
|
+
function polylineTest(x, y) {
|
|
4694
|
+
var maxDist = getZoomAdjustedHitBuffer(15, 2),
|
|
4695
|
+
bufDist = getZoomAdjustedHitBuffer(0.05), // tiny threshold for hitting almost-identical lines
|
|
4696
|
+
cands = findHitCandidates(x, y, maxDist);
|
|
4697
|
+
sortByDistance(x, y, cands, displayLayer.arcs);
|
|
4698
|
+
return pickNearestCandidates(cands, bufDist, maxDist);
|
|
4616
4699
|
}
|
|
4617
4700
|
|
|
4618
|
-
function
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
if (a[i] !== b[i]) return false;
|
|
4701
|
+
function sortByDistance(x, y, cands, arcs) {
|
|
4702
|
+
for (var i=0; i<cands.length; i++) {
|
|
4703
|
+
cands[i].dist = geom.getPointToShapeDistance(x, y, cands[i].shape, arcs);
|
|
4622
4704
|
}
|
|
4623
|
-
|
|
4705
|
+
utils.sortOn(cands, 'dist');
|
|
4624
4706
|
}
|
|
4625
4707
|
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
gui.model.on('select', function(e) {
|
|
4634
|
-
enabled = !!e.layer.geometry_type; // no display on tabular layers
|
|
4635
|
-
readout.hide();
|
|
4636
|
-
});
|
|
4637
|
-
|
|
4638
|
-
readout.on('copy', function(e) {
|
|
4639
|
-
// remove selection on copy (using timeout or else copy is cancelled)
|
|
4640
|
-
setTimeout(function() {
|
|
4641
|
-
window.getSelection().removeAllRanges();
|
|
4642
|
-
}, 50);
|
|
4643
|
-
});
|
|
4644
|
-
|
|
4645
|
-
// clear coords when map pans
|
|
4646
|
-
ext.on('change', function() {
|
|
4647
|
-
clearCoords();
|
|
4648
|
-
// shapes may change along with map scale
|
|
4649
|
-
// target = lyr ? lyr.getDisplayLayer() : null;
|
|
4650
|
-
});
|
|
4651
|
-
|
|
4652
|
-
mouse.on('leave', clearCoords);
|
|
4653
|
-
|
|
4654
|
-
mouse.on('click', function(e) {
|
|
4655
|
-
if (!enabled) return;
|
|
4656
|
-
GUI.selectElement(readout.node());
|
|
4657
|
-
});
|
|
4658
|
-
|
|
4659
|
-
mouse.on('hover', onMouseChange);
|
|
4660
|
-
mouse.on('drag', onMouseChange, null, 10); // high priority so editor doesn't block propagation
|
|
4708
|
+
function pointTest(x, y) {
|
|
4709
|
+
var bullseyeDist = 2, // hit all points w/in 2 px
|
|
4710
|
+
tinyDist = 0.5,
|
|
4711
|
+
toPx = ext.getTransform().mx,
|
|
4712
|
+
hits = [],
|
|
4713
|
+
hitThreshold = 25,
|
|
4714
|
+
newThreshold = Infinity;
|
|
4661
4715
|
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4716
|
+
internal.forEachPoint(displayLayer.layer.shapes, function(p, id) {
|
|
4717
|
+
var dist = geom.distance2D(x, y, p[0], p[1]) * toPx;
|
|
4718
|
+
if (dist > hitThreshold) return;
|
|
4719
|
+
// got a hit
|
|
4720
|
+
if (dist < newThreshold) {
|
|
4721
|
+
// start a collection of hits
|
|
4722
|
+
hits = [id];
|
|
4723
|
+
hitThreshold = Math.max(bullseyeDist, dist + tinyDist);
|
|
4724
|
+
newThreshold = dist < bullseyeDist ? -1 : dist - tinyDist;
|
|
4725
|
+
} else {
|
|
4726
|
+
// add to hits if inside bullseye or is same dist as previous hit
|
|
4727
|
+
hits.push(id);
|
|
4728
|
+
}
|
|
4729
|
+
});
|
|
4730
|
+
// console.log(hitThreshold, bullseye);
|
|
4731
|
+
return utils.uniq(hits); // multipoint features can register multiple hits
|
|
4669
4732
|
}
|
|
4670
4733
|
|
|
4671
|
-
function
|
|
4672
|
-
var
|
|
4673
|
-
|
|
4674
|
-
|
|
4734
|
+
function getRadiusFunction(style) {
|
|
4735
|
+
var o = {};
|
|
4736
|
+
if (style.styler) {
|
|
4737
|
+
return function(i) {
|
|
4738
|
+
style.styler(o, i);
|
|
4739
|
+
return o.radius || 0;
|
|
4740
|
+
};
|
|
4741
|
+
}
|
|
4742
|
+
return function() {return style.radius || 0;};
|
|
4675
4743
|
}
|
|
4676
4744
|
|
|
4677
|
-
function
|
|
4678
|
-
|
|
4745
|
+
function getGraduatedCircleTest(radius) {
|
|
4746
|
+
return function(x, y) {
|
|
4747
|
+
var hits = [],
|
|
4748
|
+
margin = getHitBuffer(12),
|
|
4749
|
+
limit = getHitBuffer(50), // short-circuit hit test beyond this threshold
|
|
4750
|
+
directHit = false,
|
|
4751
|
+
hitRadius = 0,
|
|
4752
|
+
hitDist;
|
|
4753
|
+
internal.forEachPoint(displayLayer.layer.shapes, function(p, id) {
|
|
4754
|
+
var distSq = geom.distanceSq(x, y, p[0], p[1]);
|
|
4755
|
+
var isHit = false;
|
|
4756
|
+
var isOver, isNear, r, d, rpix;
|
|
4757
|
+
if (distSq > limit * limit) return;
|
|
4758
|
+
rpix = radius(id);
|
|
4759
|
+
r = getHitBuffer(rpix + 1); // increase effective radius to make small bubbles easier to hit in clusters
|
|
4760
|
+
d = Math.sqrt(distSq) - r; // pointer distance from edge of circle (negative = inside)
|
|
4761
|
+
isOver = d < 0;
|
|
4762
|
+
isNear = d < margin;
|
|
4763
|
+
if (!isNear || rpix > 0 === false) {
|
|
4764
|
+
isHit = false;
|
|
4765
|
+
} else if (hits.length === 0) {
|
|
4766
|
+
isHit = isNear;
|
|
4767
|
+
} else if (!directHit && isOver) {
|
|
4768
|
+
isHit = true;
|
|
4769
|
+
} else if (directHit && isOver) {
|
|
4770
|
+
isHit = r == hitRadius ? d <= hitDist : r < hitRadius; // smallest bubble wins if multiple direct hits
|
|
4771
|
+
} else if (!directHit && !isOver) {
|
|
4772
|
+
// closest to bubble edge wins
|
|
4773
|
+
isHit = hitDist == d ? r <= hitRadius : d < hitDist; // closest bubble wins if multiple indirect hits
|
|
4774
|
+
}
|
|
4775
|
+
if (isHit) {
|
|
4776
|
+
if (hits.length > 0 && (r != hitRadius || d != hitDist)) {
|
|
4777
|
+
hits = [];
|
|
4778
|
+
}
|
|
4779
|
+
hitRadius = r;
|
|
4780
|
+
hitDist = d;
|
|
4781
|
+
directHit = isOver;
|
|
4782
|
+
hits.push(id);
|
|
4783
|
+
}
|
|
4784
|
+
});
|
|
4785
|
+
return hits;
|
|
4786
|
+
};
|
|
4679
4787
|
}
|
|
4680
4788
|
|
|
4681
|
-
function
|
|
4682
|
-
|
|
4789
|
+
function findHitCandidates(x, y, dist) {
|
|
4790
|
+
var arcs = displayLayer.arcs,
|
|
4791
|
+
index = {},
|
|
4792
|
+
cands = [],
|
|
4793
|
+
bbox = [];
|
|
4794
|
+
displayLayer.layer.shapes.forEach(function(shp, shpId) {
|
|
4795
|
+
var cand;
|
|
4796
|
+
for (var i = 0, n = shp && shp.length; i < n; i++) {
|
|
4797
|
+
arcs.getSimpleShapeBounds2(shp[i], bbox);
|
|
4798
|
+
if (x + dist < bbox[0] || x - dist > bbox[2] ||
|
|
4799
|
+
y + dist < bbox[1] || y - dist > bbox[3]) {
|
|
4800
|
+
continue; // bbox non-intersection
|
|
4801
|
+
}
|
|
4802
|
+
cand = index[shpId];
|
|
4803
|
+
if (!cand) {
|
|
4804
|
+
cand = index[shpId] = {shape: [], id: shpId, dist: 0};
|
|
4805
|
+
cands.push(cand);
|
|
4806
|
+
}
|
|
4807
|
+
cand.shape.push(shp[i]);
|
|
4808
|
+
}
|
|
4809
|
+
});
|
|
4810
|
+
return cands;
|
|
4683
4811
|
}
|
|
4684
4812
|
}
|
|
4685
4813
|
|
|
4686
|
-
function
|
|
4687
|
-
return typeof requestAnimationFrame == 'function' ?
|
|
4688
|
-
requestAnimationFrame : function(cb) {setTimeout(cb, 25);};
|
|
4689
|
-
}
|
|
4690
|
-
|
|
4691
|
-
function Timer() {
|
|
4692
|
-
var self = this,
|
|
4693
|
-
running = false,
|
|
4694
|
-
busy = false,
|
|
4695
|
-
tickTime, startTime, duration;
|
|
4696
|
-
|
|
4697
|
-
this.start = function(ms) {
|
|
4698
|
-
var now = +new Date();
|
|
4699
|
-
duration = ms || Infinity;
|
|
4700
|
-
startTime = now;
|
|
4701
|
-
running = true;
|
|
4702
|
-
if (!busy) startTick(now);
|
|
4703
|
-
};
|
|
4814
|
+
function getSvgHitTest(displayLayer) {
|
|
4704
4815
|
|
|
4705
|
-
|
|
4706
|
-
|
|
4816
|
+
return function(pointerEvent) {
|
|
4817
|
+
// target could be a part of an SVG symbol, or the SVG element, or something else
|
|
4818
|
+
var target = pointerEvent.originalEvent.target;
|
|
4819
|
+
var symbolNode = getSymbolNode(target);
|
|
4820
|
+
if (!symbolNode) {
|
|
4821
|
+
return null;
|
|
4822
|
+
}
|
|
4823
|
+
return {
|
|
4824
|
+
targetId: getSymbolNodeId(symbolNode), // TODO: some validation on id
|
|
4825
|
+
targetSymbol: symbolNode,
|
|
4826
|
+
targetNode: target,
|
|
4827
|
+
container: symbolNode.parentNode
|
|
4828
|
+
};
|
|
4707
4829
|
};
|
|
4708
4830
|
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
elapsed = now - startTime,
|
|
4718
|
-
pct = Math.min((elapsed + 10) / duration, 1),
|
|
4719
|
-
done = pct >= 1;
|
|
4720
|
-
if (!running) { // interrupted
|
|
4721
|
-
busy = false;
|
|
4722
|
-
return;
|
|
4831
|
+
// target: event target (could be any DOM element)
|
|
4832
|
+
function getSymbolNode(target) {
|
|
4833
|
+
var node = target;
|
|
4834
|
+
while (node && nodeHasSymbolTagType(node)) {
|
|
4835
|
+
if (isSymbolNode(node)) {
|
|
4836
|
+
return node;
|
|
4837
|
+
}
|
|
4838
|
+
node = node.parentElement;
|
|
4723
4839
|
}
|
|
4724
|
-
|
|
4725
|
-
self.dispatchEvent('tick', {
|
|
4726
|
-
elapsed: elapsed,
|
|
4727
|
-
pct: pct,
|
|
4728
|
-
done: done,
|
|
4729
|
-
time: now,
|
|
4730
|
-
tickTime: now - tickTime
|
|
4731
|
-
});
|
|
4732
|
-
busy = false;
|
|
4733
|
-
if (running) startTick(now);
|
|
4840
|
+
return null;
|
|
4734
4841
|
}
|
|
4735
|
-
}
|
|
4736
|
-
|
|
4737
|
-
utils.inherit(Timer, EventDispatcher);
|
|
4738
4842
|
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4843
|
+
// TODO: switch to attribute detection
|
|
4844
|
+
function nodeHasSymbolTagType(node) {
|
|
4845
|
+
var tag = node.tagName;
|
|
4846
|
+
return tag == 'g' || tag == 'tspan' || tag == 'text' || tag == 'image' ||
|
|
4847
|
+
tag == 'path' || tag == 'circle' || tag == 'rect' || tag == 'line';
|
|
4848
|
+
}
|
|
4743
4849
|
|
|
4744
|
-
|
|
4850
|
+
function isSymbolNode(node) {
|
|
4851
|
+
return node.hasAttribute('data-id') && (node.tagName == 'text' || node.tagName == 'g');
|
|
4852
|
+
}
|
|
4745
4853
|
|
|
4746
|
-
|
|
4747
|
-
start = a;
|
|
4748
|
-
end = b;
|
|
4749
|
-
timer.start(duration || 500);
|
|
4750
|
-
};
|
|
4854
|
+
function isSymbolChildNode(node) {
|
|
4751
4855
|
|
|
4752
|
-
function onTick(e) {
|
|
4753
|
-
var pct = ease ? ease(e.pct) : e.pct,
|
|
4754
|
-
val = end * pct + start * (1 - pct);
|
|
4755
|
-
self.dispatchEvent('change', {value: val});
|
|
4756
4856
|
}
|
|
4757
|
-
}
|
|
4758
4857
|
|
|
4759
|
-
|
|
4858
|
+
function getChildId(childNode) {
|
|
4760
4859
|
|
|
4761
|
-
|
|
4762
|
-
return 0.5 - Math.cos(n * Math.PI) / 2;
|
|
4763
|
-
};
|
|
4860
|
+
}
|
|
4764
4861
|
|
|
4765
|
-
|
|
4766
|
-
return 1 - Math.pow((1 - n), 2);
|
|
4767
|
-
};
|
|
4862
|
+
function getSymbolId(symbolNode) {
|
|
4768
4863
|
|
|
4769
|
-
|
|
4770
|
-
var self = this,
|
|
4771
|
-
el = El(ref),
|
|
4772
|
-
pageX = 0,
|
|
4773
|
-
pageY = 0,
|
|
4774
|
-
width = 0,
|
|
4775
|
-
height = 0;
|
|
4864
|
+
}
|
|
4776
4865
|
|
|
4777
|
-
|
|
4778
|
-
if (window.onorientationchange) window.addEventListener('orientationchange', update);
|
|
4779
|
-
window.addEventListener('scroll', update);
|
|
4780
|
-
window.addEventListener('resize', update);
|
|
4866
|
+
function getFeatureId(symbolNode) {
|
|
4781
4867
|
|
|
4782
|
-
|
|
4783
|
-
this.update = function() {
|
|
4784
|
-
update();
|
|
4785
|
-
};
|
|
4868
|
+
}
|
|
4786
4869
|
|
|
4787
|
-
|
|
4788
|
-
el.css('width', w).css('height', h);
|
|
4789
|
-
update();
|
|
4790
|
-
};
|
|
4870
|
+
}
|
|
4791
4871
|
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
return
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
width: width,
|
|
4800
|
-
height: height
|
|
4801
|
-
};
|
|
4802
|
-
};
|
|
4872
|
+
function getPointerHitTest(mapLayer, ext) {
|
|
4873
|
+
var shapeTest, svgTest, targetLayer;
|
|
4874
|
+
if (!mapLayer || !internal.layerHasGeometry(mapLayer.layer)) {
|
|
4875
|
+
return null;
|
|
4876
|
+
}
|
|
4877
|
+
shapeTest = getShapeHitTest(mapLayer, ext);
|
|
4878
|
+
svgTest = getSvgHitTest(mapLayer);
|
|
4803
4879
|
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
pageY = y;
|
|
4816
|
-
width = w;
|
|
4817
|
-
height = h;
|
|
4818
|
-
self.dispatchEvent('change', self.position());
|
|
4819
|
-
if (resized) {
|
|
4820
|
-
self.dispatchEvent('resize', self.position());
|
|
4821
|
-
}
|
|
4880
|
+
// e: pointer event
|
|
4881
|
+
return function(e) {
|
|
4882
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
4883
|
+
var data = {
|
|
4884
|
+
ids: shapeTest(p[0], p[1]) || []
|
|
4885
|
+
};
|
|
4886
|
+
var svgData = svgTest(e); // null or a data object
|
|
4887
|
+
if (svgData) { // mouse is over an SVG symbol
|
|
4888
|
+
utils.extend(data, svgData);
|
|
4889
|
+
// placing symbol id in front of any other hits
|
|
4890
|
+
data.ids = utils.uniq([svgData.targetId].concat(data.ids));
|
|
4822
4891
|
}
|
|
4823
|
-
|
|
4824
|
-
|
|
4892
|
+
data.id = data.ids.length > 0 ? data.ids[0] : -1;
|
|
4893
|
+
return data;
|
|
4894
|
+
};
|
|
4825
4895
|
}
|
|
4826
4896
|
|
|
4827
|
-
|
|
4897
|
+
function InteractiveSelection(gui, ext, mouse) {
|
|
4898
|
+
var self = new EventDispatcher();
|
|
4899
|
+
var storedData = noHitData(); // may include additional data from SVG symbol hit (e.g. hit node)
|
|
4900
|
+
var selectionIds = [];
|
|
4901
|
+
var transientIds = []; // e.g. hit ids while dragging a box
|
|
4902
|
+
var active = false;
|
|
4903
|
+
var interactionMode;
|
|
4904
|
+
var targetLayer;
|
|
4905
|
+
var hitTest;
|
|
4906
|
+
// event priority is higher than navigation, so stopping propagation disables
|
|
4907
|
+
// pan navigation
|
|
4908
|
+
var priority = 2;
|
|
4828
4909
|
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4910
|
+
// init keyboard controls for pinned features
|
|
4911
|
+
gui.keyboard.on('keydown', function(evt) {
|
|
4912
|
+
var e = evt.originalEvent;
|
|
4832
4913
|
|
|
4833
|
-
|
|
4834
|
-
// use avg of three values, as a buffer against single anomalous values
|
|
4835
|
-
return function(e, now) {
|
|
4836
|
-
var delta = e.wheelDelta || -e.detail || 0;
|
|
4837
|
-
var dir = delta > 0 && 1 || delta < 0 && -1 || 0;
|
|
4838
|
-
var avg;
|
|
4839
|
-
if (!getAvgDir || now - prevTime > 300) {
|
|
4840
|
-
getAvgDir = LimitedAverage(3); // reset if wheel has paused
|
|
4841
|
-
}
|
|
4842
|
-
prevTime = now;
|
|
4843
|
-
avg = getAvgDir(dir) || dir; // handle average == 0
|
|
4844
|
-
return avg > 0 && 1 || avg < 0 && -1 || 0;
|
|
4845
|
-
};
|
|
4846
|
-
}
|
|
4914
|
+
if (gui.interaction.getMode() == 'off' || !targetLayer) return;
|
|
4847
4915
|
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
i = -1;
|
|
4853
|
-
arr.push(val);
|
|
4854
|
-
if (arr.length > maxSize) arr.shift();
|
|
4855
|
-
while (++i < arr.length) {
|
|
4856
|
-
sum += arr[i];
|
|
4916
|
+
// esc key clears selection (unless in an editing mode -- esc key also exits current mode)
|
|
4917
|
+
if (e.keyCode == 27 && !gui.getMode()) {
|
|
4918
|
+
self.clearSelection();
|
|
4919
|
+
return;
|
|
4857
4920
|
}
|
|
4858
|
-
return sum / arr.length;
|
|
4859
|
-
};
|
|
4860
|
-
}
|
|
4861
|
-
|
|
4862
|
-
// @mouse: MouseArea object
|
|
4863
|
-
function MouseWheel(mouse) {
|
|
4864
|
-
var self = this,
|
|
4865
|
-
active = false,
|
|
4866
|
-
timer = new Timer().addEventListener('tick', onTick),
|
|
4867
|
-
sustainInterval = 150,
|
|
4868
|
-
fadeDelay = 70,
|
|
4869
|
-
eventTime = 0,
|
|
4870
|
-
getAverageRate = LimitedAverage(10),
|
|
4871
|
-
getWheelDirection = MouseWheelDirection(),
|
|
4872
|
-
wheelDirection;
|
|
4873
4921
|
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
} else { // firefox
|
|
4877
|
-
window.addEventListener('DOMMouseScroll', handleWheel);
|
|
4878
|
-
}
|
|
4922
|
+
// ignore keypress if no feature is selected or user is editing text
|
|
4923
|
+
if (pinnedId() == -1 || GUI.getInputElement()) return;
|
|
4879
4924
|
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4925
|
+
if (e.keyCode == 37 || e.keyCode == 39) {
|
|
4926
|
+
// L/R arrow keys
|
|
4927
|
+
// advance pinned feature
|
|
4928
|
+
advanceSelectedFeature(e.keyCode == 37 ? -1 : 1);
|
|
4929
|
+
e.stopPropagation();
|
|
4885
4930
|
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
//
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
// first event may not have a direction, e.g. if 'smooth scrolling' is on
|
|
4897
|
-
return;
|
|
4931
|
+
} else if (e.keyCode == 8) {
|
|
4932
|
+
// DELETE key
|
|
4933
|
+
// delete pinned feature
|
|
4934
|
+
// to help protect against inadvertent deletion, don't delete
|
|
4935
|
+
// when console is open or a popup menu is open
|
|
4936
|
+
if (!gui.getMode() && !gui.consoleIsOpen()) {
|
|
4937
|
+
internal.deleteFeatureById(targetLayer.layer, pinnedId());
|
|
4938
|
+
self.clearSelection();
|
|
4939
|
+
gui.model.updated({flags: 'filter'}); // signal map to update
|
|
4940
|
+
}
|
|
4898
4941
|
}
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4942
|
+
}, !!'capture'); // preempt the layer control's arrow key handler
|
|
4943
|
+
|
|
4944
|
+
self.setLayer = function(mapLayer) {
|
|
4945
|
+
hitTest = getPointerHitTest(mapLayer, ext);
|
|
4946
|
+
if (!hitTest) {
|
|
4947
|
+
hitTest = function() {return {ids: []};};
|
|
4905
4948
|
}
|
|
4906
|
-
|
|
4907
|
-
|
|
4949
|
+
targetLayer = mapLayer;
|
|
4950
|
+
};
|
|
4951
|
+
|
|
4952
|
+
function turnOn(mode) {
|
|
4953
|
+
interactionMode = mode;
|
|
4954
|
+
active = true;
|
|
4908
4955
|
}
|
|
4909
4956
|
|
|
4910
|
-
function
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
fadeFactor = 0,
|
|
4914
|
-
obj;
|
|
4915
|
-
if (tickInterval > fadeDelay) {
|
|
4916
|
-
fadeFactor = Math.min(1, (tickInterval - fadeDelay) / (sustainInterval - fadeDelay));
|
|
4917
|
-
}
|
|
4918
|
-
if (evt.done) {
|
|
4957
|
+
function turnOff() {
|
|
4958
|
+
if (active) {
|
|
4959
|
+
updateSelectionState(null); // no hit data, no event
|
|
4919
4960
|
active = false;
|
|
4920
|
-
} else {
|
|
4921
|
-
if (fadeFactor > 0) {
|
|
4922
|
-
// Decelerate towards the end of the sustain interval (for smoother zooming)
|
|
4923
|
-
multiplier *= Tween.quadraticOut(1 - fadeFactor);
|
|
4924
|
-
}
|
|
4925
|
-
obj = utils.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
|
|
4926
|
-
self.dispatchEvent('mousewheel', obj);
|
|
4927
4961
|
}
|
|
4928
4962
|
}
|
|
4929
|
-
}
|
|
4930
4963
|
|
|
4931
|
-
|
|
4964
|
+
function selectable() {
|
|
4965
|
+
return interactionMode == 'selection';
|
|
4966
|
+
}
|
|
4932
4967
|
|
|
4968
|
+
function pinnable() {
|
|
4969
|
+
return clickable() && interactionMode != 'selection';
|
|
4970
|
+
}
|
|
4933
4971
|
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
_self = this,
|
|
4938
|
-
_dragging = false,
|
|
4939
|
-
_isOver = false,
|
|
4940
|
-
_disabled = false,
|
|
4941
|
-
_prevEvt,
|
|
4942
|
-
_downEvt;
|
|
4972
|
+
function draggable() {
|
|
4973
|
+
return interactionMode == 'vertices' || interactionMode == 'location' || interactionMode == 'labels';
|
|
4974
|
+
}
|
|
4943
4975
|
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
document.addEventListener('mouseup', onMouseUp);
|
|
4949
|
-
element.addEventListener('mouseover', onAreaEnter);
|
|
4950
|
-
element.addEventListener('mousemove', onAreaEnter);
|
|
4951
|
-
element.addEventListener('mouseout', onAreaOut);
|
|
4952
|
-
element.addEventListener('mousedown', onAreaDown);
|
|
4953
|
-
element.addEventListener('dblclick', onAreaDblClick);
|
|
4976
|
+
function clickable() {
|
|
4977
|
+
// click used to pin popup and select features
|
|
4978
|
+
return interactionMode == 'data' || interactionMode == 'info' || interactionMode == 'selection';
|
|
4979
|
+
}
|
|
4954
4980
|
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4981
|
+
self.getHitId = function() {return storedData.id;};
|
|
4982
|
+
|
|
4983
|
+
// Get a reference to the active layer, so listeners to hit events can interact
|
|
4984
|
+
// with data and shapes
|
|
4985
|
+
self.getHitTarget = function() {
|
|
4986
|
+
return targetLayer;
|
|
4959
4987
|
};
|
|
4960
4988
|
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4989
|
+
self.addSelectionIds = function(ids) {
|
|
4990
|
+
turnOn('selection');
|
|
4991
|
+
selectionIds = utils.uniq(selectionIds.concat(ids));
|
|
4992
|
+
ids = utils.uniq(storedData.ids.concat(ids));
|
|
4993
|
+
updateSelectionState({ids: ids});
|
|
4994
|
+
};
|
|
4995
|
+
|
|
4996
|
+
self.setTransientIds = function(ids) {
|
|
4997
|
+
// turnOn('selection');
|
|
4998
|
+
transientIds = ids || [];
|
|
4999
|
+
if (active) {
|
|
5000
|
+
triggerHitEvent('change');
|
|
4965
5001
|
}
|
|
4966
5002
|
};
|
|
4967
5003
|
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
_disabled = true;
|
|
4971
|
-
if (_isOver) onAreaOut();
|
|
4972
|
-
this.stopDragging();
|
|
4973
|
-
element.style.pointerEvents = 'none';
|
|
5004
|
+
self.clearSelection = function() {
|
|
5005
|
+
updateSelectionState(null);
|
|
4974
5006
|
};
|
|
4975
5007
|
|
|
4976
|
-
|
|
4977
|
-
|
|
5008
|
+
self.clearHover = function() {
|
|
5009
|
+
updateSelectionState(mergeHoverData({ids: []}));
|
|
4978
5010
|
};
|
|
4979
5011
|
|
|
4980
|
-
|
|
4981
|
-
return
|
|
5012
|
+
self.getSelectionIds = function() {
|
|
5013
|
+
return selectionIds.concat();
|
|
4982
5014
|
};
|
|
4983
5015
|
|
|
4984
|
-
|
|
4985
|
-
|
|
5016
|
+
self.getTargetDataTable = function() {
|
|
5017
|
+
var targ = self.getHitTarget();
|
|
5018
|
+
return targ && targ.layer.data || null;
|
|
4986
5019
|
};
|
|
4987
5020
|
|
|
4988
|
-
function
|
|
4989
|
-
|
|
5021
|
+
// get function for selecting next or prev feature within the current set of
|
|
5022
|
+
// selected features
|
|
5023
|
+
self.getSwitchTrigger = function(diff) {
|
|
5024
|
+
return function() {
|
|
5025
|
+
switchWithinSelection(diff);
|
|
5026
|
+
};
|
|
5027
|
+
};
|
|
5028
|
+
|
|
5029
|
+
// diff: 1 or -1
|
|
5030
|
+
function advanceSelectedFeature(diff) {
|
|
5031
|
+
var n = internal.getFeatureCount(targetLayer.layer);
|
|
5032
|
+
if (n < 2 || pinnedId() == -1) return;
|
|
5033
|
+
storedData.id = (pinnedId() + n + diff) % n;
|
|
5034
|
+
storedData.ids = [storedData.id];
|
|
5035
|
+
triggerHitEvent('change');
|
|
4990
5036
|
}
|
|
4991
5037
|
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5038
|
+
// diff: 1 or -1
|
|
5039
|
+
function switchWithinSelection(diff) {
|
|
5040
|
+
var id = pinnedId();
|
|
5041
|
+
var i = storedData.ids.indexOf(id);
|
|
5042
|
+
var n = storedData.ids.length;
|
|
5043
|
+
if (i < 0 || n < 2) return;
|
|
5044
|
+
storedData.id = storedData.ids[(i + diff + n) % n];
|
|
5045
|
+
triggerHitEvent('change');
|
|
4997
5046
|
}
|
|
4998
5047
|
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5048
|
+
// make sure popup is unpinned and turned off when switching editing modes
|
|
5049
|
+
// (some modes do not support pinning)
|
|
5050
|
+
gui.on('interaction_mode_change', function(e) {
|
|
5051
|
+
updateSelectionState(null);
|
|
5052
|
+
if (e.mode == 'off' || e.mode == 'box') {
|
|
5053
|
+
turnOff();
|
|
5054
|
+
} else {
|
|
5055
|
+
turnOn(e.mode);
|
|
5056
|
+
}
|
|
5057
|
+
});
|
|
5058
|
+
|
|
5059
|
+
gui.on('box_drag_start', function() {
|
|
5060
|
+
self.clearHover();
|
|
5061
|
+
});
|
|
5062
|
+
|
|
5063
|
+
mouse.on('dblclick', handlePointerEvent, null, priority);
|
|
5064
|
+
mouse.on('dragstart', handlePointerEvent, null, priority);
|
|
5065
|
+
mouse.on('drag', handlePointerEvent, null, priority);
|
|
5066
|
+
mouse.on('dragend', handlePointerEvent, null, priority);
|
|
5067
|
+
|
|
5068
|
+
mouse.on('click', function(e) {
|
|
5069
|
+
if (!hitTest || !active) return;
|
|
5070
|
+
e.stopPropagation();
|
|
5071
|
+
|
|
5072
|
+
// TODO: move pinning to inspection control?
|
|
5073
|
+
if (clickable()) {
|
|
5074
|
+
updateSelectionState(mergeClickData(hitTest(e)));
|
|
5075
|
+
}
|
|
5076
|
+
triggerHitEvent('click', e.data);
|
|
5077
|
+
}, null, priority);
|
|
5078
|
+
|
|
5079
|
+
// Hits are re-detected on 'hover' (if hit detection is active)
|
|
5080
|
+
mouse.on('hover', function(e) {
|
|
5081
|
+
if (storedData.pinned || !hitTest || !active) return;
|
|
5082
|
+
if (e.hover && isOverMap(e)) {
|
|
5083
|
+
// mouse is hovering directly over map area -- update hit detection
|
|
5084
|
+
updateSelectionState(mergeHoverData(hitTest(e)));
|
|
5085
|
+
} else if (targetIsRollover(e.originalEvent.target)) {
|
|
5086
|
+
// don't update hit detection if mouse is over the rollover (to prevent
|
|
5087
|
+
// on-off flickering)
|
|
5088
|
+
} else {
|
|
5089
|
+
updateSelectionState(mergeHoverData({ids:[]}));
|
|
5090
|
+
}
|
|
5091
|
+
}, null, priority);
|
|
5092
|
+
|
|
5093
|
+
function targetIsRollover(target) {
|
|
5094
|
+
while (target.parentNode && target != target.parentNode) {
|
|
5095
|
+
if (target.className && String(target.className).indexOf('rollover') > -1) {
|
|
5096
|
+
return true;
|
|
5097
|
+
}
|
|
5098
|
+
target = target.parentNode;
|
|
5099
|
+
}
|
|
5100
|
+
return false;
|
|
5002
5101
|
}
|
|
5003
5102
|
|
|
5004
|
-
function
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5103
|
+
function noHitData() {return {ids: [], id: -1, pinned: false};}
|
|
5104
|
+
|
|
5105
|
+
function mergeClickData(hitData) {
|
|
5106
|
+
// mergeCurrentState(hitData);
|
|
5107
|
+
// TOGGLE pinned state under some conditions
|
|
5108
|
+
var id = hitData.ids.length > 0 ? hitData.ids[0] : -1;
|
|
5109
|
+
hitData.id = id;
|
|
5110
|
+
if (pinnable()) {
|
|
5111
|
+
if (!storedData.pinned && id > -1) {
|
|
5112
|
+
hitData.pinned = true; // add pin
|
|
5113
|
+
} else if (storedData.pinned && storedData.id == id) {
|
|
5114
|
+
delete hitData.pinned; // remove pin
|
|
5115
|
+
// hitData.id = -1; // keep highlighting (pointer is still hovering)
|
|
5116
|
+
} else if (storedData.pinned && id > -1) {
|
|
5117
|
+
hitData.pinned = true; // stay pinned, switch id
|
|
5118
|
+
}
|
|
5009
5119
|
}
|
|
5010
|
-
if (
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
dy = evt.pageY - _downEvt.pageY;
|
|
5014
|
-
if (_isOver && elapsed < 500 && Math.sqrt(dx * dx + dy * dy) < 6) {
|
|
5015
|
-
_self.dispatchEvent('click', evt);
|
|
5120
|
+
if (selectable()) {
|
|
5121
|
+
if (id > -1) {
|
|
5122
|
+
selectionIds = toggleId(id, selectionIds);
|
|
5016
5123
|
}
|
|
5017
|
-
|
|
5124
|
+
hitData.ids = selectionIds;
|
|
5125
|
+
}
|
|
5126
|
+
return hitData;
|
|
5127
|
+
}
|
|
5128
|
+
|
|
5129
|
+
function mergeHoverData(hitData) {
|
|
5130
|
+
if (storedData.pinned) {
|
|
5131
|
+
hitData.id = storedData.id;
|
|
5132
|
+
hitData.pinned = true;
|
|
5133
|
+
} else {
|
|
5134
|
+
hitData.id = hitData.ids.length > 0 ? hitData.ids[0] : -1;
|
|
5135
|
+
}
|
|
5136
|
+
if (selectable()) {
|
|
5137
|
+
hitData.ids = selectionIds;
|
|
5138
|
+
// kludge to inhibit hover effect while dragging a box
|
|
5139
|
+
if (gui.keydown) hitData.id = -1;
|
|
5140
|
+
}
|
|
5141
|
+
return hitData;
|
|
5142
|
+
}
|
|
5143
|
+
|
|
5144
|
+
function pinnedId() {
|
|
5145
|
+
return storedData.pinned ? storedData.id : -1;
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
function toggleId(id, ids) {
|
|
5149
|
+
if (ids.indexOf(id) > -1) {
|
|
5150
|
+
return utils.difference(ids, [id]);
|
|
5151
|
+
}
|
|
5152
|
+
return [id].concat(ids);
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
// If hit ids have changed, update stored hit ids and fire 'hover' event
|
|
5156
|
+
// evt: (optional) mouse event
|
|
5157
|
+
function updateSelectionState(newData) {
|
|
5158
|
+
var nonEmpty = newData && (newData.ids.length || newData.id > -1);
|
|
5159
|
+
transientIds = [];
|
|
5160
|
+
if (!newData) {
|
|
5161
|
+
newData = noHitData();
|
|
5162
|
+
selectionIds = [];
|
|
5163
|
+
}
|
|
5164
|
+
if (!testHitChange(storedData, newData)) {
|
|
5165
|
+
return;
|
|
5166
|
+
}
|
|
5167
|
+
storedData = newData;
|
|
5168
|
+
gui.container.findChild('.map-layers').classed('symbol-hit', nonEmpty);
|
|
5169
|
+
if (active) {
|
|
5170
|
+
triggerHitEvent('change');
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
|
|
5174
|
+
// check if an event is used in the current interaction mode
|
|
5175
|
+
function eventIsEnabled(type) {
|
|
5176
|
+
if (type == 'click' && !clickable()) {
|
|
5177
|
+
return false;
|
|
5178
|
+
}
|
|
5179
|
+
if ((type == 'drag' || type == 'dragstart' || type == 'dragend') && !draggable()) {
|
|
5180
|
+
return false;
|
|
5018
5181
|
}
|
|
5182
|
+
return true;
|
|
5019
5183
|
}
|
|
5020
5184
|
|
|
5021
|
-
function
|
|
5022
|
-
|
|
5023
|
-
_self.dispatchEvent('dragend', evt);
|
|
5185
|
+
function isOverMap(e) {
|
|
5186
|
+
return e.x >= 0 && e.y >= 0 && e.x < ext.width() && e.y < ext.height();
|
|
5024
5187
|
}
|
|
5025
5188
|
|
|
5026
|
-
function
|
|
5027
|
-
|
|
5028
|
-
|
|
5189
|
+
function handlePointerEvent(e) {
|
|
5190
|
+
if (!hitTest || !active) return;
|
|
5191
|
+
if (self.getHitId() == -1) return; // ignore pointer events when no features are being hit
|
|
5192
|
+
// don't block pan and other navigation in modes when they are not being used
|
|
5193
|
+
if (eventIsEnabled(e.type)) {
|
|
5194
|
+
e.stopPropagation(); // block navigation
|
|
5195
|
+
triggerHitEvent(e.type, e.data);
|
|
5029
5196
|
}
|
|
5030
5197
|
}
|
|
5031
5198
|
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
if (evt.dx === 0 && evt.dy === 0) return; // seen in Chrome
|
|
5039
|
-
if (_dragging) {
|
|
5040
|
-
var obj = {
|
|
5041
|
-
dragX: evt.pageX - _downEvt.pageX,
|
|
5042
|
-
dragY: evt.pageY - _downEvt.pageY
|
|
5043
|
-
};
|
|
5044
|
-
_self.dispatchEvent('drag', utils.extend(obj, evt));
|
|
5045
|
-
} else {
|
|
5046
|
-
_self.dispatchEvent('hover', evt);
|
|
5199
|
+
// d: event data (may be a pointer event object, an ordinary object or null)
|
|
5200
|
+
function triggerHitEvent(type, d) {
|
|
5201
|
+
// Merge stored hit data into the event data
|
|
5202
|
+
var eventData = utils.extend({mode: interactionMode}, d || {}, storedData);
|
|
5203
|
+
if (transientIds.length) {
|
|
5204
|
+
eventData.ids = utils.uniq(transientIds.concat(eventData.ids || []));
|
|
5047
5205
|
}
|
|
5206
|
+
self.dispatchEvent(type, eventData);
|
|
5048
5207
|
}
|
|
5049
5208
|
|
|
5050
|
-
|
|
5051
|
-
|
|
5209
|
+
// Test if two hit data objects are equivalent
|
|
5210
|
+
function testHitChange(a, b) {
|
|
5211
|
+
// check change in 'container', e.g. so moving from anchor hit to label hit
|
|
5212
|
+
// is detected
|
|
5213
|
+
if (sameIds(a.ids, b.ids) && a.container == b.container && a.pinned == b.pinned && a.id == b.id) {
|
|
5214
|
+
return false;
|
|
5215
|
+
}
|
|
5216
|
+
return true;
|
|
5052
5217
|
}
|
|
5053
5218
|
|
|
5054
|
-
function
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
shiftKey: e.shiftKey,
|
|
5061
|
-
metaKey: e.metaKey,
|
|
5062
|
-
ctrlKey: e.ctrlKey,
|
|
5063
|
-
time: +new Date(),
|
|
5064
|
-
pageX: pageX,
|
|
5065
|
-
pageY: pageY,
|
|
5066
|
-
hover: _isOver,
|
|
5067
|
-
x: pageX - _areaPos.pageX,
|
|
5068
|
-
y: pageY - _areaPos.pageY,
|
|
5069
|
-
dx: prev ? pageX - prev.pageX : 0,
|
|
5070
|
-
dy: prev ? pageY - prev.pageY : 0
|
|
5071
|
-
};
|
|
5072
|
-
return _prevEvt;
|
|
5219
|
+
function sameIds(a, b) {
|
|
5220
|
+
if (a.length != b.length) return false;
|
|
5221
|
+
for (var i=0; i<a.length; i++) {
|
|
5222
|
+
if (a[i] !== b[i]) return false;
|
|
5223
|
+
}
|
|
5224
|
+
return true;
|
|
5073
5225
|
}
|
|
5226
|
+
|
|
5227
|
+
return self;
|
|
5074
5228
|
}
|
|
5075
5229
|
|
|
5076
|
-
|
|
5230
|
+
function CoordinatesDisplay(gui, ext, mouse) {
|
|
5231
|
+
var readout = gui.container.findChild('.coordinate-info').hide();
|
|
5232
|
+
var enabled = false;
|
|
5077
5233
|
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5234
|
+
gui.model.on('select', function(e) {
|
|
5235
|
+
enabled = !!e.layer.geometry_type; // no display on tabular layers
|
|
5236
|
+
readout.hide();
|
|
5237
|
+
});
|
|
5081
5238
|
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5239
|
+
readout.on('copy', function(e) {
|
|
5240
|
+
// remove selection on copy (using timeout or else copy is cancelled)
|
|
5241
|
+
setTimeout(function() {
|
|
5242
|
+
window.getSelection().removeAllRanges();
|
|
5243
|
+
}, 50);
|
|
5085
5244
|
});
|
|
5086
5245
|
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
if (shift > 5 || elapsed > 1000) return;
|
|
5093
|
-
downEvent = null;
|
|
5094
|
-
cb({time: elapsed});
|
|
5246
|
+
// clear coords when map pans
|
|
5247
|
+
ext.on('change', function() {
|
|
5248
|
+
clearCoords();
|
|
5249
|
+
// shapes may change along with map scale
|
|
5250
|
+
// target = lyr ? lyr.getDisplayLayer() : null;
|
|
5095
5251
|
});
|
|
5096
|
-
}
|
|
5097
5252
|
|
|
5098
|
-
|
|
5099
|
-
var wheel = new MouseWheel(mouse),
|
|
5100
|
-
zoomTween = new Tween(Tween.sineInOut),
|
|
5101
|
-
boxDrag = false,
|
|
5102
|
-
zoomScaleMultiplier = 1,
|
|
5103
|
-
inBtn, outBtn,
|
|
5104
|
-
dragStartEvt,
|
|
5105
|
-
_fx, _fy; // zoom foci, [0,1]
|
|
5253
|
+
mouse.on('leave', clearCoords);
|
|
5106
5254
|
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5255
|
+
mouse.on('click', function(e) {
|
|
5256
|
+
if (!enabled) return;
|
|
5257
|
+
GUI.selectElement(readout.node());
|
|
5258
|
+
});
|
|
5110
5259
|
|
|
5111
|
-
|
|
5260
|
+
mouse.on('hover', onMouseChange);
|
|
5261
|
+
mouse.on('drag', onMouseChange, null, 10); // high priority so editor doesn't block propagation
|
|
5112
5262
|
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
}
|
|
5263
|
+
function onMouseChange(e) {
|
|
5264
|
+
if (!enabled) return;
|
|
5265
|
+
if (isOverMap(e)) {
|
|
5266
|
+
displayCoords(ext.translatePixelCoords(e.x, e.y));
|
|
5267
|
+
} else {
|
|
5268
|
+
clearCoords();
|
|
5269
|
+
}
|
|
5118
5270
|
}
|
|
5119
5271
|
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
initVariableClick(outBtn.node(), zoomOut);
|
|
5125
|
-
ext.on('change', function() {
|
|
5126
|
-
inBtn.classed('disabled', ext.scale() >= ext.maxScale());
|
|
5127
|
-
});
|
|
5272
|
+
function displayCoords(p) {
|
|
5273
|
+
var decimals = internal.getBoundsPrecisionForDisplay(ext.getBounds().toArray());
|
|
5274
|
+
var str = internal.getRoundedCoordString(p, decimals);
|
|
5275
|
+
readout.text(str).show();
|
|
5128
5276
|
}
|
|
5129
5277
|
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
}
|
|
5133
|
-
|
|
5134
|
-
zoomTween.on('change', function(e) {
|
|
5135
|
-
ext.zoomToExtent(e.value, _fx, _fy);
|
|
5136
|
-
});
|
|
5278
|
+
function clearCoords() {
|
|
5279
|
+
readout.hide();
|
|
5280
|
+
}
|
|
5137
5281
|
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5282
|
+
function isOverMap(e) {
|
|
5283
|
+
return e.x >= 0 && e.y >= 0 && e.x < ext.width() && e.y < ext.height();
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5142
5286
|
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
boxDrag = !!e.shiftKey;
|
|
5148
|
-
if (boxDrag) {
|
|
5149
|
-
dragStartEvt = e;
|
|
5150
|
-
gui.dispatchEvent('box_drag_start');
|
|
5151
|
-
}
|
|
5152
|
-
});
|
|
5287
|
+
function getTimerFunction() {
|
|
5288
|
+
return typeof requestAnimationFrame == 'function' ?
|
|
5289
|
+
requestAnimationFrame : function(cb) {setTimeout(cb, 25);};
|
|
5290
|
+
}
|
|
5153
5291
|
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
ext.pan(e.dx, e.dy);
|
|
5160
|
-
}
|
|
5161
|
-
});
|
|
5292
|
+
function Timer() {
|
|
5293
|
+
var self = this,
|
|
5294
|
+
running = false,
|
|
5295
|
+
busy = false,
|
|
5296
|
+
tickTime, startTime, duration;
|
|
5162
5297
|
|
|
5163
|
-
|
|
5164
|
-
var
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
});
|
|
5298
|
+
this.start = function(ms) {
|
|
5299
|
+
var now = +new Date();
|
|
5300
|
+
duration = ms || Infinity;
|
|
5301
|
+
startTime = now;
|
|
5302
|
+
running = true;
|
|
5303
|
+
if (!busy) startTick(now);
|
|
5304
|
+
};
|
|
5171
5305
|
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
delta = e.direction > 0 ? k : 1 / k;
|
|
5176
|
-
if (disabled()) return;
|
|
5177
|
-
ext.zoomByPct(delta, e.x / ext.width(), e.y / ext.height());
|
|
5178
|
-
});
|
|
5306
|
+
this.stop = function() {
|
|
5307
|
+
running = false;
|
|
5308
|
+
};
|
|
5179
5309
|
|
|
5180
|
-
function
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5310
|
+
function startTick(now) {
|
|
5311
|
+
busy = true;
|
|
5312
|
+
tickTime = now;
|
|
5313
|
+
getTimerFunction()(onTick);
|
|
5184
5314
|
}
|
|
5185
5315
|
|
|
5186
|
-
function
|
|
5187
|
-
var
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
if (pageBox[1] > pageBox[3]) {
|
|
5195
|
-
swapElements(pageBox, 1, 3);
|
|
5196
|
-
swapElements(mapBox, 1, 3);
|
|
5316
|
+
function onTick() {
|
|
5317
|
+
var now = +new Date(),
|
|
5318
|
+
elapsed = now - startTime,
|
|
5319
|
+
pct = Math.min((elapsed + 10) / duration, 1),
|
|
5320
|
+
done = pct >= 1;
|
|
5321
|
+
if (!running) { // interrupted
|
|
5322
|
+
busy = false;
|
|
5323
|
+
return;
|
|
5197
5324
|
}
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5325
|
+
if (done) running = false;
|
|
5326
|
+
self.dispatchEvent('tick', {
|
|
5327
|
+
elapsed: elapsed,
|
|
5328
|
+
pct: pct,
|
|
5329
|
+
done: done,
|
|
5330
|
+
time: now,
|
|
5331
|
+
tickTime: now - tickTime
|
|
5332
|
+
});
|
|
5333
|
+
busy = false;
|
|
5334
|
+
if (running) startTick(now);
|
|
5206
5335
|
}
|
|
5336
|
+
}
|
|
5207
5337
|
|
|
5208
|
-
|
|
5209
|
-
if (disabled()) return;
|
|
5210
|
-
zoomByPct(getZoomInPct(e.time), 0.5, 0.5);
|
|
5211
|
-
}
|
|
5338
|
+
utils.inherit(Timer, EventDispatcher);
|
|
5212
5339
|
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5340
|
+
function Tween(ease) {
|
|
5341
|
+
var self = this,
|
|
5342
|
+
timer = new Timer(),
|
|
5343
|
+
start, end;
|
|
5217
5344
|
|
|
5218
|
-
|
|
5219
|
-
var minScale = 0.2,
|
|
5220
|
-
maxScale = 4,
|
|
5221
|
-
minTime = 100,
|
|
5222
|
-
maxTime = 800,
|
|
5223
|
-
time = utils.clamp(clickTime || 200, minTime, maxTime),
|
|
5224
|
-
k = (time - minTime) / (maxTime - minTime),
|
|
5225
|
-
scale = minScale + k * (maxScale - minScale);
|
|
5226
|
-
return 1 + scale * zoomScaleMultiplier;
|
|
5227
|
-
}
|
|
5345
|
+
timer.on('tick', onTick);
|
|
5228
5346
|
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
fy = bounds.centerY() / ext.height() * (1 + pct) - pct / 2;
|
|
5235
|
-
zoomByPct(1 / pct, fx, fy);
|
|
5236
|
-
}
|
|
5347
|
+
this.start = function(a, b, duration) {
|
|
5348
|
+
start = a;
|
|
5349
|
+
end = b;
|
|
5350
|
+
timer.start(duration || 500);
|
|
5351
|
+
};
|
|
5237
5352
|
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
_fx = fx;
|
|
5243
|
-
_fy = fy;
|
|
5244
|
-
zoomTween.start(w, w / pct, 400);
|
|
5353
|
+
function onTick(e) {
|
|
5354
|
+
var pct = ease ? ease(e.pct) : e.pct,
|
|
5355
|
+
val = end * pct + start * (1 - pct);
|
|
5356
|
+
self.dispatchEvent('change', {value: val});
|
|
5245
5357
|
}
|
|
5246
5358
|
}
|
|
5247
5359
|
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5360
|
+
utils.inherit(Tween, EventDispatcher);
|
|
5361
|
+
|
|
5362
|
+
Tween.sineInOut = function(n) {
|
|
5363
|
+
return 0.5 - Math.cos(n * Math.PI) / 2;
|
|
5364
|
+
};
|
|
5365
|
+
|
|
5366
|
+
Tween.quadraticOut = function(n) {
|
|
5367
|
+
return 1 - Math.pow((1 - n), 2);
|
|
5368
|
+
};
|
|
5369
|
+
|
|
5370
|
+
function ElementPosition(ref) {
|
|
5371
|
+
var self = this,
|
|
5372
|
+
el = El(ref),
|
|
5373
|
+
pageX = 0,
|
|
5374
|
+
pageY = 0,
|
|
5375
|
+
width = 0,
|
|
5376
|
+
height = 0;
|
|
5377
|
+
|
|
5378
|
+
el.on('mouseover', update);
|
|
5379
|
+
if (window.onorientationchange) window.addEventListener('orientationchange', update);
|
|
5380
|
+
window.addEventListener('scroll', update);
|
|
5381
|
+
window.addEventListener('resize', update);
|
|
5382
|
+
|
|
5383
|
+
// trigger an update, e.g. when map container is resized
|
|
5384
|
+
this.update = function() {
|
|
5385
|
+
update();
|
|
5263
5386
|
};
|
|
5264
|
-
return box;
|
|
5265
|
-
}
|
|
5266
5387
|
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5388
|
+
this.resize = function(w, h) {
|
|
5389
|
+
el.css('width', w).css('height', h);
|
|
5390
|
+
update();
|
|
5391
|
+
};
|
|
5271
5392
|
|
|
5272
|
-
|
|
5393
|
+
this.width = function() { return width; };
|
|
5394
|
+
this.height = function() { return height; };
|
|
5395
|
+
this.position = function() {
|
|
5396
|
+
return {
|
|
5397
|
+
element: el.node(),
|
|
5398
|
+
pageX: pageX,
|
|
5399
|
+
pageY: pageY,
|
|
5400
|
+
width: width,
|
|
5401
|
+
height: height
|
|
5402
|
+
};
|
|
5403
|
+
};
|
|
5273
5404
|
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5405
|
+
function update() {
|
|
5406
|
+
var div = el.node(),
|
|
5407
|
+
xy = getPageXY(div),
|
|
5408
|
+
w = div.clientWidth,
|
|
5409
|
+
h = div.clientHeight,
|
|
5410
|
+
x = xy.x,
|
|
5411
|
+
y = xy.y,
|
|
5412
|
+
resized = w != width || h != height,
|
|
5413
|
+
moved = x != pageX || y != pageY;
|
|
5414
|
+
if (resized || moved) {
|
|
5415
|
+
pageX = x;
|
|
5416
|
+
pageY = y;
|
|
5417
|
+
width = w;
|
|
5418
|
+
height = h;
|
|
5419
|
+
self.dispatchEvent('change', self.position());
|
|
5420
|
+
if (resized) {
|
|
5421
|
+
self.dispatchEvent('resize', self.position());
|
|
5422
|
+
}
|
|
5279
5423
|
}
|
|
5280
|
-
}
|
|
5424
|
+
}
|
|
5425
|
+
update();
|
|
5426
|
+
}
|
|
5281
5427
|
|
|
5282
|
-
|
|
5283
|
-
if (!_on) return;
|
|
5284
|
-
var b = e.page_bbox;
|
|
5285
|
-
box.show(b[0], b[1], b[2], b[3]);
|
|
5286
|
-
updateSelection(e.map_bbox, true);
|
|
5287
|
-
});
|
|
5428
|
+
utils.inherit(ElementPosition, EventDispatcher);
|
|
5288
5429
|
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
updateSelection(e.map_bbox);
|
|
5293
|
-
});
|
|
5430
|
+
function MouseWheelDirection() {
|
|
5431
|
+
var prevTime = 0;
|
|
5432
|
+
var getAvgDir;
|
|
5294
5433
|
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
var
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5434
|
+
// returns 1, -1 or 0 to indicate direction of scroll
|
|
5435
|
+
// use avg of three values, as a buffer against single anomalous values
|
|
5436
|
+
return function(e, now) {
|
|
5437
|
+
var delta = e.wheelDelta || -e.detail || 0;
|
|
5438
|
+
var dir = delta > 0 && 1 || delta < 0 && -1 || 0;
|
|
5439
|
+
var avg;
|
|
5440
|
+
if (!getAvgDir || now - prevTime > 300) {
|
|
5441
|
+
getAvgDir = LimitedAverage(3); // reset if wheel has paused
|
|
5303
5442
|
}
|
|
5304
|
-
|
|
5443
|
+
prevTime = now;
|
|
5444
|
+
avg = getAvgDir(dir) || dir; // handle average == 0
|
|
5445
|
+
return avg > 0 && 1 || avg < 0 && -1 || 0;
|
|
5446
|
+
};
|
|
5447
|
+
}
|
|
5305
5448
|
|
|
5306
|
-
|
|
5307
|
-
|
|
5449
|
+
function LimitedAverage(maxSize) {
|
|
5450
|
+
var arr = [];
|
|
5451
|
+
return function(val) {
|
|
5452
|
+
var sum = 0,
|
|
5453
|
+
i = -1;
|
|
5454
|
+
arr.push(val);
|
|
5455
|
+
if (arr.length > maxSize) arr.shift();
|
|
5456
|
+
while (++i < arr.length) {
|
|
5457
|
+
sum += arr[i];
|
|
5458
|
+
}
|
|
5459
|
+
return sum / arr.length;
|
|
5460
|
+
};
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
// @mouse: MouseArea object
|
|
5464
|
+
function MouseWheel(mouse) {
|
|
5465
|
+
var self = this,
|
|
5466
|
+
active = false,
|
|
5467
|
+
timer = new Timer().addEventListener('tick', onTick),
|
|
5468
|
+
sustainInterval = 150,
|
|
5469
|
+
fadeDelay = 70,
|
|
5470
|
+
eventTime = 0,
|
|
5471
|
+
getAverageRate = LimitedAverage(10),
|
|
5472
|
+
getWheelDirection = MouseWheelDirection(),
|
|
5473
|
+
wheelDirection;
|
|
5474
|
+
|
|
5475
|
+
if (window.onmousewheel !== undefined) { // ie, webkit
|
|
5476
|
+
window.addEventListener('mousewheel', handleWheel, {passive: false});
|
|
5477
|
+
} else { // firefox
|
|
5478
|
+
window.addEventListener('DOMMouseScroll', handleWheel);
|
|
5308
5479
|
}
|
|
5309
5480
|
|
|
5310
|
-
function
|
|
5311
|
-
var
|
|
5312
|
-
|
|
5313
|
-
|
|
5481
|
+
function updateSustainInterval(eventRate) {
|
|
5482
|
+
var fadeInterval = 80;
|
|
5483
|
+
fadeDelay = eventRate + 50; // adding a little extra time helps keep trackpad scrolling smooth in Firefox
|
|
5484
|
+
sustainInterval = fadeDelay + fadeInterval;
|
|
5314
5485
|
}
|
|
5315
5486
|
|
|
5316
|
-
function
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
if (
|
|
5320
|
-
//
|
|
5321
|
-
|
|
5487
|
+
function handleWheel(evt) {
|
|
5488
|
+
var now = +new Date();
|
|
5489
|
+
wheelDirection = getWheelDirection(evt, now);
|
|
5490
|
+
if (evt.ctrlKey) {
|
|
5491
|
+
// Prevent pinch-zoom in Chrome (doesn't work in Safari, though)
|
|
5492
|
+
evt.preventDefault();
|
|
5493
|
+
evt.stopImmediatePropagation();
|
|
5494
|
+
}
|
|
5495
|
+
if (!mouse.isOver()) return;
|
|
5496
|
+
if (wheelDirection === 0) {
|
|
5497
|
+
// first event may not have a direction, e.g. if 'smooth scrolling' is on
|
|
5498
|
+
return;
|
|
5499
|
+
}
|
|
5500
|
+
evt.preventDefault();
|
|
5501
|
+
if (!active) {
|
|
5502
|
+
active = true;
|
|
5503
|
+
self.dispatchEvent('mousewheelstart');
|
|
5504
|
+
} else {
|
|
5505
|
+
updateSustainInterval(getAverageRate(now - eventTime));
|
|
5322
5506
|
}
|
|
5507
|
+
eventTime = now;
|
|
5508
|
+
timer.start(sustainInterval);
|
|
5323
5509
|
}
|
|
5324
5510
|
|
|
5325
|
-
function
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
if (e.mode != 'selection') return;
|
|
5336
|
-
var ids = hit.getSelectionIds();
|
|
5337
|
-
if (ids.length > 0) {
|
|
5338
|
-
// enter this mode when we're ready to show the selection options
|
|
5339
|
-
// (this closes any other active mode, e.g. box_tool)
|
|
5340
|
-
gui.enterMode('selection_tool');
|
|
5341
|
-
popup.show();
|
|
5511
|
+
function onTick(evt) {
|
|
5512
|
+
var tickInterval = evt.time - eventTime,
|
|
5513
|
+
multiplier = evt.tickTime / 25,
|
|
5514
|
+
fadeFactor = 0,
|
|
5515
|
+
obj;
|
|
5516
|
+
if (tickInterval > fadeDelay) {
|
|
5517
|
+
fadeFactor = Math.min(1, (tickInterval - fadeDelay) / (sustainInterval - fadeDelay));
|
|
5518
|
+
}
|
|
5519
|
+
if (evt.done) {
|
|
5520
|
+
active = false;
|
|
5342
5521
|
} else {
|
|
5343
|
-
|
|
5522
|
+
if (fadeFactor > 0) {
|
|
5523
|
+
// Decelerate towards the end of the sustain interval (for smoother zooming)
|
|
5524
|
+
multiplier *= Tween.quadraticOut(1 - fadeFactor);
|
|
5525
|
+
}
|
|
5526
|
+
obj = utils.extend({direction: wheelDirection, multiplier: multiplier}, mouse.mouseData());
|
|
5527
|
+
self.dispatchEvent('mousewheel', obj);
|
|
5344
5528
|
}
|
|
5345
|
-
});
|
|
5346
|
-
|
|
5347
|
-
new SimpleButton(popup.findChild('.delete-btn')).on('click', function() {
|
|
5348
|
-
var cmd = '-filter invert ids=' + getIdsOpt();
|
|
5349
|
-
runCommand(cmd);
|
|
5350
|
-
});
|
|
5351
|
-
|
|
5352
|
-
new SimpleButton(popup.findChild('.filter-btn')).on('click', function() {
|
|
5353
|
-
var cmd = '-filter ids=' + getIdsOpt();
|
|
5354
|
-
runCommand(cmd);
|
|
5355
|
-
});
|
|
5356
|
-
|
|
5357
|
-
new SimpleButton(popup.findChild('.split-btn')).on('click', function() {
|
|
5358
|
-
var cmd = '-split ids=' + getIdsOpt();
|
|
5359
|
-
runCommand(cmd);
|
|
5360
|
-
});
|
|
5361
|
-
|
|
5362
|
-
new SimpleButton(popup.findChild('.cancel-btn')).on('click', function() {
|
|
5363
|
-
hit.clearSelection();
|
|
5364
|
-
});
|
|
5365
|
-
|
|
5366
|
-
function runCommand(cmd) {
|
|
5367
|
-
popup.hide();
|
|
5368
|
-
if (gui.console) gui.console.runMapshaperCommands(cmd, function(err) {
|
|
5369
|
-
reset();
|
|
5370
|
-
});
|
|
5371
5529
|
}
|
|
5372
5530
|
}
|
|
5373
5531
|
|
|
5374
|
-
|
|
5375
|
-
function Popup(gui, toNext, toPrev) {
|
|
5376
|
-
var self = new EventDispatcher();
|
|
5377
|
-
var parent = gui.container.findChild('.mshp-main-map');
|
|
5378
|
-
var el = El('div').addClass('popup').appendTo(parent).hide();
|
|
5379
|
-
var content = El('div').addClass('popup-content').appendTo(el);
|
|
5380
|
-
// multi-hit display and navigation
|
|
5381
|
-
var tab = El('div').addClass('popup-tab').appendTo(el).hide();
|
|
5382
|
-
var nav = El('div').addClass('popup-nav').appendTo(tab);
|
|
5383
|
-
var prevLink = El('span').addClass('popup-nav-arrow colored-text').appendTo(nav).text('◀');
|
|
5384
|
-
var navInfo = El('span').addClass('popup-nav-info').appendTo(nav);
|
|
5385
|
-
var nextLink = El('span').addClass('popup-nav-arrow colored-text').appendTo(nav).text('▶');
|
|
5386
|
-
var refresh = null;
|
|
5387
|
-
var currId = -1;
|
|
5532
|
+
utils.inherit(MouseWheel, EventDispatcher);
|
|
5388
5533
|
|
|
5389
|
-
el.addClass('rollover'); // used as a sentinel for the hover function
|
|
5390
5534
|
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5535
|
+
function MouseArea(element, pos) {
|
|
5536
|
+
var _pos = pos || new ElementPosition(element),
|
|
5537
|
+
_areaPos = _pos.position(),
|
|
5538
|
+
_self = this,
|
|
5539
|
+
_dragging = false,
|
|
5540
|
+
_isOver = false,
|
|
5541
|
+
_disabled = false,
|
|
5542
|
+
_prevEvt,
|
|
5543
|
+
_downEvt;
|
|
5396
5544
|
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
};
|
|
5408
|
-
refresh();
|
|
5409
|
-
if (ids && ids.length > 1) {
|
|
5410
|
-
showNav(id, ids, pinned);
|
|
5411
|
-
} else {
|
|
5412
|
-
tab.hide();
|
|
5413
|
-
}
|
|
5414
|
-
el.show();
|
|
5415
|
-
if (content.node().clientHeight > maxHeight) {
|
|
5416
|
-
content.css('height:' + maxHeight + 'px');
|
|
5417
|
-
}
|
|
5418
|
-
};
|
|
5545
|
+
_pos.on('change', function() {_areaPos = _pos.position();});
|
|
5546
|
+
// TODO: think about touch events
|
|
5547
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
5548
|
+
document.addEventListener('mousedown', onMouseDown);
|
|
5549
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
5550
|
+
element.addEventListener('mouseover', onAreaEnter);
|
|
5551
|
+
element.addEventListener('mousemove', onAreaEnter);
|
|
5552
|
+
element.addEventListener('mouseout', onAreaOut);
|
|
5553
|
+
element.addEventListener('mousedown', onAreaDown);
|
|
5554
|
+
element.addEventListener('dblclick', onAreaDblClick);
|
|
5419
5555
|
|
|
5420
|
-
|
|
5421
|
-
if (!
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
// make sure any pending edits are made before re-rendering popup
|
|
5425
|
-
GUI.blurActiveElement(); // this should be more selective -- could cause a glitch if typing in console
|
|
5426
|
-
content.empty();
|
|
5427
|
-
content.node().removeAttribute('style'); // remove inline height
|
|
5428
|
-
el.hide();
|
|
5556
|
+
this.enable = function() {
|
|
5557
|
+
if (!_disabled) return;
|
|
5558
|
+
_disabled = false;
|
|
5559
|
+
element.style.pointerEvents = 'auto';
|
|
5429
5560
|
};
|
|
5430
5561
|
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5562
|
+
this.stopDragging = function() {
|
|
5563
|
+
if (_downEvt) {
|
|
5564
|
+
if (_dragging) stopDragging(_downEvt);
|
|
5565
|
+
_downEvt = null;
|
|
5566
|
+
}
|
|
5567
|
+
};
|
|
5436
5568
|
|
|
5437
|
-
function
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
}
|
|
5569
|
+
this.disable = function() {
|
|
5570
|
+
if (_disabled) return;
|
|
5571
|
+
_disabled = true;
|
|
5572
|
+
if (_isOver) onAreaOut();
|
|
5573
|
+
this.stopDragging();
|
|
5574
|
+
element.style.pointerEvents = 'none';
|
|
5575
|
+
};
|
|
5444
5576
|
|
|
5445
|
-
function
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
// self.hide(); // clean up if panel is already open
|
|
5449
|
-
el.empty(); // clean up if panel is already open
|
|
5450
|
-
utils.forEachProperty(rec, function(v, k) {
|
|
5451
|
-
var type;
|
|
5452
|
-
// missing GeoJSON fields are set to undefined on import; skip these
|
|
5453
|
-
if (v !== undefined) {
|
|
5454
|
-
type = getFieldType(v, k, table);
|
|
5455
|
-
renderRow(tableEl, rec, k, type, editable);
|
|
5456
|
-
rows++;
|
|
5457
|
-
}
|
|
5458
|
-
});
|
|
5577
|
+
this.isOver = function() {
|
|
5578
|
+
return _isOver;
|
|
5579
|
+
};
|
|
5459
5580
|
|
|
5460
|
-
|
|
5461
|
-
|
|
5581
|
+
this.isDown = function() {
|
|
5582
|
+
return !!_downEvt;
|
|
5583
|
+
};
|
|
5462
5584
|
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
var pasted = window.getSelection().toString();
|
|
5467
|
-
var cleaned = pasted.replace(/^\t/, '').replace(/\t$/, '');
|
|
5468
|
-
if (pasted != cleaned && !window.clipboardData) { // ignore ie
|
|
5469
|
-
(e.clipboardData || e.originalEvent.clipboardData).setData("text", cleaned);
|
|
5470
|
-
e.preventDefault(); // don't copy original string with tabs
|
|
5471
|
-
}
|
|
5472
|
-
});
|
|
5585
|
+
this.mouseData = function() {
|
|
5586
|
+
return utils.extend({}, _prevEvt);
|
|
5587
|
+
};
|
|
5473
5588
|
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
// their data properties (properties are set to undefined when an input JSON file
|
|
5477
|
-
// has inconsistent fields, or after force-merging layers with inconsistent fields).
|
|
5478
|
-
el.html(utils.format('<div class="note">This %s is missing attribute data.</div>',
|
|
5479
|
-
table && table.getFields().length > 0 ? 'feature': 'layer'));
|
|
5480
|
-
}
|
|
5589
|
+
function onAreaDown(e) {
|
|
5590
|
+
e.preventDefault(); // prevent text selection cursor on drag
|
|
5481
5591
|
}
|
|
5482
5592
|
|
|
5483
|
-
function
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
var cell = El('tr')
|
|
5488
|
-
.appendTo(table)
|
|
5489
|
-
.html(utils.format(rowHtml, key, utils.htmlEscape(str)))
|
|
5490
|
-
.findChild('.value');
|
|
5491
|
-
setFieldClass(cell, val, type);
|
|
5492
|
-
if (editable) {
|
|
5493
|
-
editItem(cell, rec, key, type);
|
|
5593
|
+
function onAreaEnter() {
|
|
5594
|
+
if (!_isOver) {
|
|
5595
|
+
_isOver = true;
|
|
5596
|
+
_self.dispatchEvent('enter');
|
|
5494
5597
|
}
|
|
5495
5598
|
}
|
|
5496
5599
|
|
|
5497
|
-
function
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
var isEmpty = val === '';
|
|
5501
|
-
el.classed('num-field', isNum);
|
|
5502
|
-
el.classed('object-field', type == 'object');
|
|
5503
|
-
el.classed('null-value', isNully);
|
|
5504
|
-
el.classed('empty', isEmpty);
|
|
5600
|
+
function onAreaOut() {
|
|
5601
|
+
_isOver = false;
|
|
5602
|
+
_self.dispatchEvent('leave');
|
|
5505
5603
|
}
|
|
5506
5604
|
|
|
5507
|
-
function
|
|
5508
|
-
var
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
// invalid value; revert to previous value
|
|
5520
|
-
input.value(strval);
|
|
5521
|
-
} else {
|
|
5522
|
-
// field content has changed
|
|
5523
|
-
strval = strval2;
|
|
5524
|
-
rec[key] = val2;
|
|
5525
|
-
input.value(strval);
|
|
5526
|
-
setFieldClass(el, val2, type);
|
|
5527
|
-
self.dispatchEvent('update', {field: key, value: val2, id: currId});
|
|
5605
|
+
function onMouseUp(e) {
|
|
5606
|
+
var evt = procMouseEvent(e),
|
|
5607
|
+
elapsed, dx, dy;
|
|
5608
|
+
if (_dragging) {
|
|
5609
|
+
stopDragging(evt);
|
|
5610
|
+
}
|
|
5611
|
+
if (_downEvt) {
|
|
5612
|
+
elapsed = evt.time - _downEvt.time;
|
|
5613
|
+
dx = evt.pageX - _downEvt.pageX;
|
|
5614
|
+
dy = evt.pageY - _downEvt.pageY;
|
|
5615
|
+
if (_isOver && elapsed < 500 && Math.sqrt(dx * dx + dy * dy) < 6) {
|
|
5616
|
+
_self.dispatchEvent('click', evt);
|
|
5528
5617
|
}
|
|
5529
|
-
|
|
5618
|
+
_downEvt = null;
|
|
5619
|
+
}
|
|
5530
5620
|
}
|
|
5531
|
-
}
|
|
5532
5621
|
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
str = utils.formatDateISO(val);
|
|
5537
|
-
} else if (type == 'object') {
|
|
5538
|
-
str = val ? JSON.stringify(val) : "";
|
|
5539
|
-
} else {
|
|
5540
|
-
str = String(val);
|
|
5622
|
+
function stopDragging(evt) {
|
|
5623
|
+
_dragging = false;
|
|
5624
|
+
_self.dispatchEvent('dragend', evt);
|
|
5541
5625
|
}
|
|
5542
|
-
return str;
|
|
5543
|
-
}
|
|
5544
5626
|
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
return isNaN(+d) ? null : d;
|
|
5549
|
-
},
|
|
5550
|
-
string: function(raw) {
|
|
5551
|
-
return raw;
|
|
5552
|
-
},
|
|
5553
|
-
number: function(raw) {
|
|
5554
|
-
var val = Number(raw);
|
|
5555
|
-
if (raw == 'NaN') {
|
|
5556
|
-
val = NaN;
|
|
5557
|
-
} else if (isNaN(val)) {
|
|
5558
|
-
val = null;
|
|
5627
|
+
function onMouseDown(e) {
|
|
5628
|
+
if (e.button != 2 && e.which != 3) { // ignore right-click
|
|
5629
|
+
_downEvt = procMouseEvent(e);
|
|
5559
5630
|
}
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
var
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5631
|
+
}
|
|
5632
|
+
|
|
5633
|
+
function onMouseMove(e) {
|
|
5634
|
+
var evt = procMouseEvent(e);
|
|
5635
|
+
if (!_dragging && _downEvt && _downEvt.hover) {
|
|
5636
|
+
_dragging = true;
|
|
5637
|
+
_self.dispatchEvent('dragstart', evt);
|
|
5638
|
+
}
|
|
5639
|
+
if (evt.dx === 0 && evt.dy === 0) return; // seen in Chrome
|
|
5640
|
+
if (_dragging) {
|
|
5641
|
+
var obj = {
|
|
5642
|
+
dragX: evt.pageX - _downEvt.pageX,
|
|
5643
|
+
dragY: evt.pageY - _downEvt.pageY
|
|
5644
|
+
};
|
|
5645
|
+
_self.dispatchEvent('drag', utils.extend(obj, evt));
|
|
5646
|
+
} else {
|
|
5647
|
+
_self.dispatchEvent('hover', evt);
|
|
5575
5648
|
}
|
|
5576
|
-
return val;
|
|
5577
|
-
},
|
|
5578
|
-
multiple: function(raw) {
|
|
5579
|
-
var val = Number(raw);
|
|
5580
|
-
return isNaN(val) ? raw : val;
|
|
5581
5649
|
}
|
|
5582
|
-
};
|
|
5583
5650
|
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5651
|
+
function onAreaDblClick(e) {
|
|
5652
|
+
if (_isOver) _self.dispatchEvent('dblclick', procMouseEvent(e));
|
|
5653
|
+
}
|
|
5587
5654
|
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5655
|
+
function procMouseEvent(e) {
|
|
5656
|
+
var pageX = e.pageX,
|
|
5657
|
+
pageY = e.pageY,
|
|
5658
|
+
prev = _prevEvt;
|
|
5659
|
+
_prevEvt = {
|
|
5660
|
+
originalEvent: e,
|
|
5661
|
+
shiftKey: e.shiftKey,
|
|
5662
|
+
metaKey: e.metaKey,
|
|
5663
|
+
ctrlKey: e.ctrlKey,
|
|
5664
|
+
time: +new Date(),
|
|
5665
|
+
pageX: pageX,
|
|
5666
|
+
pageY: pageY,
|
|
5667
|
+
hover: _isOver,
|
|
5668
|
+
x: pageX - _areaPos.pageX,
|
|
5669
|
+
y: pageY - _areaPos.pageY,
|
|
5670
|
+
dx: prev ? pageX - prev.pageX : 0,
|
|
5671
|
+
dy: prev ? pageY - prev.pageY : 0
|
|
5672
|
+
};
|
|
5673
|
+
return _prevEvt;
|
|
5674
|
+
}
|
|
5591
5675
|
}
|
|
5592
5676
|
|
|
5593
|
-
|
|
5594
|
-
var _popup = new Popup(gui, hit.getSwitchTrigger(1), hit.getSwitchTrigger(-1));
|
|
5595
|
-
var _self = new EventDispatcher();
|
|
5677
|
+
utils.inherit(MouseArea, EventDispatcher);
|
|
5596
5678
|
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
}
|
|
5601
|
-
});
|
|
5679
|
+
function initVariableClick(node, cb) {
|
|
5680
|
+
var downEvent = null;
|
|
5681
|
+
var downTime = 0;
|
|
5602
5682
|
|
|
5603
|
-
|
|
5604
|
-
|
|
5683
|
+
node.addEventListener('mousedown', function(e) {
|
|
5684
|
+
downEvent = e;
|
|
5685
|
+
downTime = Date.now();
|
|
5605
5686
|
});
|
|
5606
5687
|
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5688
|
+
node.addEventListener('mouseup', function(upEvent) {
|
|
5689
|
+
if (!downEvent) return;
|
|
5690
|
+
var shift = Math.abs(downEvent.pageX - upEvent.pageX) +
|
|
5691
|
+
Math.abs(downEvent.pageY - upEvent.pageY);
|
|
5692
|
+
var elapsed = Date.now() - downTime;
|
|
5693
|
+
if (shift > 5 || elapsed > 1000) return;
|
|
5694
|
+
downEvent = null;
|
|
5695
|
+
cb({time: elapsed});
|
|
5612
5696
|
});
|
|
5697
|
+
}
|
|
5613
5698
|
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5699
|
+
function MapNav(gui, ext, mouse) {
|
|
5700
|
+
var wheel = new MouseWheel(mouse),
|
|
5701
|
+
zoomTween = new Tween(Tween.sineInOut),
|
|
5702
|
+
boxDrag = false,
|
|
5703
|
+
zoomScaleMultiplier = 1,
|
|
5704
|
+
inBtn, outBtn,
|
|
5705
|
+
dragStartEvt,
|
|
5706
|
+
_fx, _fy; // zoom foci, [0,1]
|
|
5707
|
+
|
|
5708
|
+
this.setZoomFactor = function(k) {
|
|
5709
|
+
zoomScaleMultiplier = k || 1;
|
|
5710
|
+
};
|
|
5711
|
+
|
|
5712
|
+
this.zoomToBbox = zoomToBbox;
|
|
5713
|
+
|
|
5714
|
+
if (gui.options.homeControl) {
|
|
5715
|
+
gui.buttons.addButton("#home-icon").on('click', function() {
|
|
5716
|
+
if (disabled()) return;
|
|
5717
|
+
gui.dispatchEvent('map_reset');
|
|
5718
|
+
});
|
|
5622
5719
|
}
|
|
5623
5720
|
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5721
|
+
if (gui.options.zoomControl) {
|
|
5722
|
+
inBtn = gui.buttons.addButton("#zoom-in-icon");
|
|
5723
|
+
outBtn = gui.buttons.addButton("#zoom-out-icon");
|
|
5724
|
+
initVariableClick(inBtn.node(), zoomIn);
|
|
5725
|
+
initVariableClick(outBtn.node(), zoomOut);
|
|
5726
|
+
ext.on('change', function() {
|
|
5727
|
+
inBtn.classed('disabled', ext.scale() >= ext.maxScale());
|
|
5728
|
+
});
|
|
5627
5729
|
}
|
|
5628
5730
|
|
|
5629
|
-
|
|
5630
|
-
|
|
5731
|
+
gui.on('map_reset', function() {
|
|
5732
|
+
ext.home();
|
|
5733
|
+
});
|
|
5631
5734
|
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
var contentPct = getIntersectionPct(viewportBounds, newBounds);
|
|
5636
|
-
var boundsChanged = !prevBounds.equals(newBounds);
|
|
5637
|
-
var inView = newBounds.intersects(viewportBounds);
|
|
5638
|
-
var areaChg = newBounds.area() / prevBounds.area();
|
|
5639
|
-
var chgThreshold = flags.proj ? 1e3 : 1e8;
|
|
5640
|
-
// don't reset if layer extent hasn't changed
|
|
5641
|
-
if (!boundsChanged) return false;
|
|
5642
|
-
// reset if layer is out-of-view
|
|
5643
|
-
if (!inView) return true;
|
|
5644
|
-
// reset if content is mostly offscreen
|
|
5645
|
-
if (viewportPct < 0.3 && contentPct < 0.9) return true;
|
|
5646
|
-
// reset if content bounds have changed a lot (e.g. after projection)
|
|
5647
|
-
if (areaChg > chgThreshold || areaChg < 1/chgThreshold) return true;
|
|
5648
|
-
return false;
|
|
5649
|
-
}
|
|
5735
|
+
zoomTween.on('change', function(e) {
|
|
5736
|
+
ext.zoomToExtent(e.value, _fx, _fy);
|
|
5737
|
+
});
|
|
5650
5738
|
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5739
|
+
mouse.on('dblclick', function(e) {
|
|
5740
|
+
if (disabled()) return;
|
|
5741
|
+
zoomByPct(getZoomInPct(), e.x / ext.width(), e.y / ext.height());
|
|
5742
|
+
});
|
|
5655
5743
|
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5744
|
+
mouse.on('dragstart', function(e) {
|
|
5745
|
+
if (disabled()) return;
|
|
5746
|
+
if (!internal.layerHasGeometry(gui.model.getActiveLayer().layer)) return;
|
|
5747
|
+
// zoomDrag = !!e.metaKey || !!e.ctrlKey; // meta is command on mac, windows key on windows
|
|
5748
|
+
boxDrag = !!e.shiftKey;
|
|
5749
|
+
if (boxDrag) {
|
|
5750
|
+
dragStartEvt = e;
|
|
5751
|
+
gui.dispatchEvent('box_drag_start');
|
|
5752
|
+
}
|
|
5753
|
+
});
|
|
5664
5754
|
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5755
|
+
mouse.on('drag', function(e) {
|
|
5756
|
+
if (disabled()) return;
|
|
5757
|
+
if (boxDrag) {
|
|
5758
|
+
gui.dispatchEvent('box_drag', getBoxData(e));
|
|
5759
|
+
} else {
|
|
5760
|
+
ext.pan(e.dx, e.dy);
|
|
5761
|
+
}
|
|
5762
|
+
});
|
|
5763
|
+
|
|
5764
|
+
mouse.on('dragend', function(e) {
|
|
5765
|
+
var bbox;
|
|
5766
|
+
if (disabled()) return;
|
|
5767
|
+
if (boxDrag) {
|
|
5768
|
+
boxDrag = false;
|
|
5769
|
+
gui.dispatchEvent('box_drag_end', getBoxData(e));
|
|
5770
|
+
}
|
|
5771
|
+
});
|
|
5772
|
+
|
|
5773
|
+
wheel.on('mousewheel', function(e) {
|
|
5774
|
+
var tickFraction = 0.11; // 0.15; // fraction of zoom step per wheel event;
|
|
5775
|
+
var k = 1 + (tickFraction * e.multiplier * zoomScaleMultiplier),
|
|
5776
|
+
delta = e.direction > 0 ? k : 1 / k;
|
|
5777
|
+
if (disabled()) return;
|
|
5778
|
+
ext.zoomByPct(delta, e.x / ext.width(), e.y / ext.height());
|
|
5779
|
+
});
|
|
5668
5780
|
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5781
|
+
function swapElements(arr, i, j) {
|
|
5782
|
+
var tmp = arr[i];
|
|
5783
|
+
arr[i] = arr[j];
|
|
5784
|
+
arr[j] = tmp;
|
|
5785
|
+
}
|
|
5674
5786
|
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
child.setAttribute(name, value);
|
|
5787
|
+
function getBoxData(e) {
|
|
5788
|
+
var pageBox = [e.pageX, e.pageY, dragStartEvt.pageX, dragStartEvt.pageY];
|
|
5789
|
+
var mapBox = [e.x, e.y, dragStartEvt.x, dragStartEvt.y];
|
|
5790
|
+
var tmp;
|
|
5791
|
+
if (pageBox[0] > pageBox[2]) {
|
|
5792
|
+
swapElements(pageBox, 0, 2);
|
|
5793
|
+
swapElements(mapBox, 0, 2);
|
|
5794
|
+
}
|
|
5795
|
+
if (pageBox[1] > pageBox[3]) {
|
|
5796
|
+
swapElements(pageBox, 1, 3);
|
|
5797
|
+
swapElements(mapBox, 1, 3);
|
|
5687
5798
|
}
|
|
5799
|
+
return {
|
|
5800
|
+
map_bbox: mapBox,
|
|
5801
|
+
page_bbox: pageBox
|
|
5802
|
+
};
|
|
5688
5803
|
}
|
|
5689
|
-
}
|
|
5690
5804
|
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
if (el.tagName == 'svg') return el;
|
|
5694
|
-
el = el.parentNode;
|
|
5805
|
+
function disabled() {
|
|
5806
|
+
return !!gui.options.disableNavigation;
|
|
5695
5807
|
}
|
|
5696
|
-
return null;
|
|
5697
|
-
}
|
|
5698
|
-
|
|
5699
|
-
// p: pixel coordinates of label anchor
|
|
5700
|
-
function autoUpdateTextAnchor(textNode, rec, p) {
|
|
5701
|
-
var svg = findSvgRoot(textNode);
|
|
5702
|
-
var rect = textNode.getBoundingClientRect();
|
|
5703
|
-
var labelCenterX = rect.left - svg.getBoundingClientRect().left + rect.width / 2;
|
|
5704
|
-
var xpct = (labelCenterX - p[0]) / rect.width; // offset of label center from anchor center
|
|
5705
|
-
var value = xpct < -0.25 && 'end' || xpct > 0.25 && 'start' || 'middle';
|
|
5706
|
-
updateTextAnchor(value, textNode, rec);
|
|
5707
|
-
}
|
|
5708
|
-
|
|
5709
|
-
// @value: optional position to set; if missing, auto-set
|
|
5710
|
-
function updateTextAnchor(value, textNode, rec) {
|
|
5711
|
-
var rect = textNode.getBoundingClientRect();
|
|
5712
|
-
var width = rect.width;
|
|
5713
|
-
var curr = rec['text-anchor'] || 'middle';
|
|
5714
|
-
var xshift = 0;
|
|
5715
5808
|
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
} else if (curr == 'middle' && value == 'start' || curr == 'end' && value == 'middle') {
|
|
5720
|
-
xshift = -width / 2;
|
|
5721
|
-
} else if (curr == 'start' && value == 'end') {
|
|
5722
|
-
xshift = width;
|
|
5723
|
-
} else if (curr == 'end' && value == 'start') {
|
|
5724
|
-
xshift = -width;
|
|
5809
|
+
function zoomIn(e) {
|
|
5810
|
+
if (disabled()) return;
|
|
5811
|
+
zoomByPct(getZoomInPct(e.time), 0.5, 0.5);
|
|
5725
5812
|
}
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5813
|
+
|
|
5814
|
+
function zoomOut(e) {
|
|
5815
|
+
if (disabled()) return;
|
|
5816
|
+
zoomByPct(1/getZoomInPct(e.time), 0.5, 0.5);
|
|
5729
5817
|
}
|
|
5730
|
-
}
|
|
5731
5818
|
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5819
|
+
function getZoomInPct(clickTime) {
|
|
5820
|
+
var minScale = 0.2,
|
|
5821
|
+
maxScale = 4,
|
|
5822
|
+
minTime = 100,
|
|
5823
|
+
maxTime = 800,
|
|
5824
|
+
time = utils.clamp(clickTime || 200, minTime, maxTime),
|
|
5825
|
+
k = (time - minTime) / (maxTime - minTime),
|
|
5826
|
+
scale = minScale + k * (maxScale - minScale);
|
|
5827
|
+
return 1 + scale * zoomScaleMultiplier;
|
|
5828
|
+
}
|
|
5739
5829
|
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5830
|
+
// @box Bounds with pixels from t,l corner of map area.
|
|
5831
|
+
function zoomToBbox(bbox) {
|
|
5832
|
+
var bounds = new Bounds(bbox),
|
|
5833
|
+
pct = Math.max(bounds.width() / ext.width(), bounds.height() / ext.height()),
|
|
5834
|
+
fx = bounds.centerX() / ext.width() * (1 + pct) - pct / 2,
|
|
5835
|
+
fy = bounds.centerY() / ext.height() * (1 + pct) - pct / 2;
|
|
5836
|
+
zoomByPct(1 / pct, fx, fy);
|
|
5837
|
+
}
|
|
5744
5838
|
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5839
|
+
// @pct Change in scale (2 = 2x zoom)
|
|
5840
|
+
// @fx, @fy zoom focus, [0, 1]
|
|
5841
|
+
function zoomByPct(pct, fx, fy) {
|
|
5842
|
+
var w = ext.getBounds().width();
|
|
5843
|
+
_fx = fx;
|
|
5844
|
+
_fy = fy;
|
|
5845
|
+
zoomTween.start(w, w / pct, 400);
|
|
5749
5846
|
}
|
|
5750
|
-
return coords[0];
|
|
5751
5847
|
}
|
|
5752
5848
|
|
|
5753
|
-
function
|
|
5754
|
-
var
|
|
5755
|
-
|
|
5756
|
-
|
|
5849
|
+
function HighlightBox() {
|
|
5850
|
+
var box = El('div').addClass('zoom-box').appendTo('body'),
|
|
5851
|
+
show = box.show.bind(box), // original show() function
|
|
5852
|
+
stroke = 2;
|
|
5853
|
+
box.hide();
|
|
5854
|
+
box.show = function(x1, y1, x2, y2) {
|
|
5855
|
+
var w = Math.abs(x1 - x2),
|
|
5856
|
+
h = Math.abs(y1 - y2);
|
|
5857
|
+
box.css({
|
|
5858
|
+
top: Math.min(y1, y2),
|
|
5859
|
+
left: Math.min(x1, x2),
|
|
5860
|
+
width: Math.max(w - stroke * 2, 1),
|
|
5861
|
+
height: Math.max(h - stroke * 2, 1)
|
|
5862
|
+
});
|
|
5863
|
+
show();
|
|
5864
|
+
};
|
|
5865
|
+
return box;
|
|
5757
5866
|
}
|
|
5758
5867
|
|
|
5868
|
+
function SelectionTool(gui, ext, hit) {
|
|
5869
|
+
var popup = gui.container.findChild('.selection-tool-options');
|
|
5870
|
+
var box = new HighlightBox();
|
|
5871
|
+
var _on = false;
|
|
5759
5872
|
|
|
5760
|
-
|
|
5761
|
-
// var targetTextNode; // text node currently being dragged
|
|
5762
|
-
var dragging = false;
|
|
5763
|
-
var activeRecord;
|
|
5764
|
-
var activeId = -1;
|
|
5765
|
-
var self = new EventDispatcher();
|
|
5766
|
-
var activeVertexIds = null; // for vertex dragging
|
|
5873
|
+
gui.addMode('selection_tool', turnOn, turnOff);
|
|
5767
5874
|
|
|
5768
|
-
|
|
5875
|
+
gui.on('interaction_mode_change', function(e) {
|
|
5876
|
+
if (e.mode === 'selection') {
|
|
5877
|
+
gui.enterMode('selection_tool');
|
|
5878
|
+
} else if (gui.getMode() == 'selection_tool') {
|
|
5879
|
+
gui.clearMode();
|
|
5880
|
+
}
|
|
5881
|
+
});
|
|
5769
5882
|
|
|
5770
|
-
|
|
5883
|
+
gui.on('box_drag', function(e) {
|
|
5884
|
+
if (!_on) return;
|
|
5885
|
+
var b = e.page_bbox;
|
|
5886
|
+
box.show(b[0], b[1], b[2], b[3]);
|
|
5887
|
+
updateSelection(e.map_bbox, true);
|
|
5888
|
+
});
|
|
5771
5889
|
|
|
5772
|
-
function
|
|
5773
|
-
|
|
5890
|
+
gui.on('box_drag_end', function(e) {
|
|
5891
|
+
if (!_on) return;
|
|
5892
|
+
box.hide();
|
|
5893
|
+
updateSelection(e.map_bbox);
|
|
5894
|
+
});
|
|
5895
|
+
|
|
5896
|
+
function updateSelection(bboxPixels, transient) {
|
|
5897
|
+
var bbox = bboxToCoords(bboxPixels);
|
|
5898
|
+
var active = gui.model.getActiveLayer();
|
|
5899
|
+
var ids = internal.findShapesIntersectingBBox(bbox, active.layer, active.dataset.arcs);
|
|
5900
|
+
if (transient) {
|
|
5901
|
+
hit.setTransientIds(ids);
|
|
5902
|
+
} else if (ids.length) {
|
|
5903
|
+
hit.addSelectionIds(ids);
|
|
5904
|
+
}
|
|
5774
5905
|
}
|
|
5775
5906
|
|
|
5776
|
-
function
|
|
5777
|
-
|
|
5907
|
+
function turnOn() {
|
|
5908
|
+
_on = true;
|
|
5778
5909
|
}
|
|
5779
5910
|
|
|
5780
|
-
function
|
|
5781
|
-
|
|
5911
|
+
function bboxToCoords(bbox) {
|
|
5912
|
+
var a = ext.translatePixelCoords(bbox[0], bbox[1]);
|
|
5913
|
+
var b = ext.translatePixelCoords(bbox[2], bbox[3]);
|
|
5914
|
+
return [a[0], b[1], b[0], a[1]];
|
|
5782
5915
|
}
|
|
5783
5916
|
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
if (
|
|
5788
|
-
|
|
5789
|
-
|
|
5917
|
+
function turnOff() {
|
|
5918
|
+
reset();
|
|
5919
|
+
_on = false;
|
|
5920
|
+
if (gui.interaction.getMode() == 'selection') {
|
|
5921
|
+
// mode change was not initiated by interactive menu -- turn off interactivity
|
|
5922
|
+
gui.interaction.turnOff();
|
|
5923
|
+
}
|
|
5790
5924
|
}
|
|
5791
5925
|
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
var activeLayer = hit.getHitTarget().layer;
|
|
5796
|
-
var xy = activeLayer.shapes[id][0];
|
|
5797
|
-
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
5798
|
-
var node2;
|
|
5799
|
-
o.properties.transform = getSvgSymbolTransform(xy, ext);
|
|
5800
|
-
o.properties['data-id'] = id;
|
|
5801
|
-
// o.properties['class'] = 'selected';
|
|
5802
|
-
g.innerHTML = internal.svg.stringify(o);
|
|
5803
|
-
node2 = g.firstChild;
|
|
5804
|
-
node.parentNode.replaceChild(node2, node);
|
|
5805
|
-
gui.dispatchEvent('popup-needs-refresh');
|
|
5806
|
-
return node2;
|
|
5926
|
+
function reset() {
|
|
5927
|
+
popup.hide();
|
|
5928
|
+
hit.clearSelection();
|
|
5807
5929
|
}
|
|
5808
5930
|
|
|
5809
|
-
function
|
|
5810
|
-
|
|
5811
|
-
|
|
5931
|
+
function getIdsOpt() {
|
|
5932
|
+
return hit.getSelectionIds().join(',');
|
|
5933
|
+
}
|
|
5812
5934
|
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5935
|
+
hit.on('change', function(e) {
|
|
5936
|
+
if (e.mode != 'selection') return;
|
|
5937
|
+
var ids = hit.getSelectionIds();
|
|
5938
|
+
if (ids.length > 0) {
|
|
5939
|
+
// enter this mode when we're ready to show the selection options
|
|
5940
|
+
// (this closes any other active mode, e.g. box_tool)
|
|
5941
|
+
gui.enterMode('selection_tool');
|
|
5942
|
+
popup.show();
|
|
5943
|
+
} else {
|
|
5944
|
+
popup.hide();
|
|
5945
|
+
}
|
|
5946
|
+
});
|
|
5817
5947
|
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
});
|
|
5948
|
+
new SimpleButton(popup.findChild('.delete-btn')).on('click', function() {
|
|
5949
|
+
var cmd = '-filter invert ids=' + getIdsOpt();
|
|
5950
|
+
runCommand(cmd);
|
|
5951
|
+
});
|
|
5823
5952
|
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
// 1: not editing -> nop
|
|
5829
|
-
// 2: on selected text -> start dragging
|
|
5830
|
-
// 3: on other text -> stop dragging, select new text
|
|
5953
|
+
new SimpleButton(popup.findChild('.filter-btn')).on('click', function() {
|
|
5954
|
+
var cmd = '-filter ids=' + getIdsOpt();
|
|
5955
|
+
runCommand(cmd);
|
|
5956
|
+
});
|
|
5831
5957
|
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
onLocationDragStart(e);
|
|
5837
|
-
} else if (vertexEditingEnabled()) {
|
|
5838
|
-
onVertexDragStart(e);
|
|
5839
|
-
}
|
|
5840
|
-
});
|
|
5958
|
+
new SimpleButton(popup.findChild('.split-btn')).on('click', function() {
|
|
5959
|
+
var cmd = '-split ids=' + getIdsOpt();
|
|
5960
|
+
runCommand(cmd);
|
|
5961
|
+
});
|
|
5841
5962
|
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
} else if (locationEditingEnabled()) {
|
|
5846
|
-
onLocationDrag(e);
|
|
5847
|
-
} else if (vertexEditingEnabled()) {
|
|
5848
|
-
onVertexDrag(e);
|
|
5849
|
-
}
|
|
5850
|
-
});
|
|
5963
|
+
new SimpleButton(popup.findChild('.cancel-btn')).on('click', function() {
|
|
5964
|
+
hit.clearSelection();
|
|
5965
|
+
});
|
|
5851
5966
|
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
} else if (labelEditingEnabled()) {
|
|
5857
|
-
stopDragging();
|
|
5858
|
-
} else if (vertexEditingEnabled()) {
|
|
5859
|
-
onVertexDragEnd(e);
|
|
5860
|
-
stopDragging();
|
|
5861
|
-
}
|
|
5967
|
+
function runCommand(cmd) {
|
|
5968
|
+
popup.hide();
|
|
5969
|
+
if (gui.console) gui.console.runMapshaperCommands(cmd, function(err) {
|
|
5970
|
+
reset();
|
|
5862
5971
|
});
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5863
5974
|
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5975
|
+
// toNext, toPrev: trigger functions for switching between multiple records
|
|
5976
|
+
function Popup(gui, toNext, toPrev) {
|
|
5977
|
+
var self = new EventDispatcher();
|
|
5978
|
+
var parent = gui.container.findChild('.mshp-main-map');
|
|
5979
|
+
var el = El('div').addClass('popup').appendTo(parent).hide();
|
|
5980
|
+
var content = El('div').addClass('popup-content').appendTo(el);
|
|
5981
|
+
// multi-hit display and navigation
|
|
5982
|
+
var tab = El('div').addClass('popup-tab').appendTo(el).hide();
|
|
5983
|
+
var nav = El('div').addClass('popup-nav').appendTo(tab);
|
|
5984
|
+
var prevLink = El('span').addClass('popup-nav-arrow colored-text').appendTo(nav).text('◀');
|
|
5985
|
+
var navInfo = El('span').addClass('popup-nav-info').appendTo(nav);
|
|
5986
|
+
var nextLink = El('span').addClass('popup-nav-arrow colored-text').appendTo(nav).text('▶');
|
|
5987
|
+
var refresh = null;
|
|
5988
|
+
var currId = -1;
|
|
5869
5989
|
|
|
5870
|
-
|
|
5871
|
-
if (e.id >= 0) {
|
|
5872
|
-
dragging = true;
|
|
5873
|
-
triggerGlobalEvent('symbol_dragstart', e);
|
|
5874
|
-
}
|
|
5875
|
-
}
|
|
5990
|
+
el.addClass('rollover'); // used as a sentinel for the hover function
|
|
5876
5991
|
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5992
|
+
nextLink.on('click', toNext);
|
|
5993
|
+
prevLink.on('click', toPrev);
|
|
5994
|
+
gui.on('popup-needs-refresh', function() {
|
|
5995
|
+
if (refresh) refresh();
|
|
5996
|
+
});
|
|
5882
5997
|
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5998
|
+
self.show = function(id, ids, lyr, pinned) {
|
|
5999
|
+
var table = lyr.data; // table can be null (e.g. if layer has no attribute data)
|
|
6000
|
+
var editable = pinned && gui.interaction.getMode() == 'data';
|
|
6001
|
+
var maxHeight = parent.node().clientHeight - 36;
|
|
6002
|
+
currId = id;
|
|
6003
|
+
// stash a function for refreshing the current popup when data changes
|
|
6004
|
+
// while the popup is being displayed (e.g. while dragging a label)
|
|
6005
|
+
refresh = function() {
|
|
6006
|
+
var rec = table && (editable ? table.getRecordAt(id) : table.getReadOnlyRecordAt(id)) || {};
|
|
6007
|
+
render(content, rec, table, editable);
|
|
6008
|
+
};
|
|
6009
|
+
refresh();
|
|
6010
|
+
if (ids && ids.length > 1) {
|
|
6011
|
+
showNav(id, ids, pinned);
|
|
6012
|
+
} else {
|
|
6013
|
+
tab.hide();
|
|
5892
6014
|
}
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
var p = ext.translatePixelCoords(e.x, e.y);
|
|
5897
|
-
if (!activeVertexIds) {
|
|
5898
|
-
activeVertexIds = internal.findNearestVertices(p, target.layer.shapes[e.id], target.arcs);
|
|
5899
|
-
}
|
|
5900
|
-
if (!activeVertexIds) return; // ignore error condition
|
|
5901
|
-
if (gui.keyboard.shiftIsPressed()) {
|
|
5902
|
-
internal.snapPointToArcEndpoint(p, activeVertexIds, target.arcs);
|
|
5903
|
-
}
|
|
5904
|
-
activeVertexIds.forEach(function(idx) {
|
|
5905
|
-
internal.setVertexCoords(p[0], p[1], idx, target.arcs);
|
|
5906
|
-
});
|
|
5907
|
-
self.dispatchEvent('location_change'); // signal map to redraw
|
|
6015
|
+
el.show();
|
|
6016
|
+
if (content.node().clientHeight > maxHeight) {
|
|
6017
|
+
content.css('height:' + maxHeight + 'px');
|
|
5908
6018
|
}
|
|
6019
|
+
};
|
|
5909
6020
|
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
6021
|
+
self.hide = function() {
|
|
6022
|
+
if (!isOpen()) return;
|
|
6023
|
+
refresh = null;
|
|
6024
|
+
currId = -1;
|
|
6025
|
+
// make sure any pending edits are made before re-rendering popup
|
|
6026
|
+
GUI.blurActiveElement(); // this should be more selective -- could cause a glitch if typing in console
|
|
6027
|
+
content.empty();
|
|
6028
|
+
content.node().removeAttribute('style'); // remove inline height
|
|
6029
|
+
el.hide();
|
|
6030
|
+
};
|
|
5913
6031
|
|
|
5914
|
-
|
|
5915
|
-
// kludge to get dataset to recalculate internal bounding boxes
|
|
5916
|
-
hit.getHitTarget().arcs.transformPoints(function() {});
|
|
5917
|
-
activeVertexIds = null;
|
|
5918
|
-
}
|
|
6032
|
+
return self;
|
|
5919
6033
|
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
if (textNode && rec && isMultilineLabel(textNode)) {
|
|
5924
|
-
toggleTextAlign(textNode, rec);
|
|
5925
|
-
updateSymbol2(textNode, rec, e.id);
|
|
5926
|
-
// e.stopPropagation(); // prevent pin/unpin on popup
|
|
5927
|
-
}
|
|
5928
|
-
}
|
|
6034
|
+
function isOpen() {
|
|
6035
|
+
return el.visible();
|
|
6036
|
+
}
|
|
5929
6037
|
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
6038
|
+
function showNav(id, ids, pinned) {
|
|
6039
|
+
var num = ids.indexOf(id) + 1;
|
|
6040
|
+
navInfo.text(' ' + num + ' / ' + ids.length + ' ');
|
|
6041
|
+
nextLink.css('display', pinned ? 'inline-block' : 'none');
|
|
6042
|
+
prevLink.css('display', pinned && ids.length > 2 ? 'inline-block' : 'none');
|
|
6043
|
+
tab.show();
|
|
6044
|
+
}
|
|
5936
6045
|
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
6046
|
+
function render(el, rec, table, editable) {
|
|
6047
|
+
var tableEl = El('table').addClass('selectable'),
|
|
6048
|
+
rows = 0;
|
|
6049
|
+
// self.hide(); // clean up if panel is already open
|
|
6050
|
+
el.empty(); // clean up if panel is already open
|
|
6051
|
+
utils.forEachProperty(rec, function(v, k) {
|
|
6052
|
+
var type;
|
|
6053
|
+
// missing GeoJSON fields are set to undefined on import; skip these
|
|
6054
|
+
if (v !== undefined) {
|
|
6055
|
+
type = getFieldType(v, k, table);
|
|
6056
|
+
renderRow(tableEl, rec, k, type, editable);
|
|
6057
|
+
rows++;
|
|
5949
6058
|
}
|
|
5950
|
-
|
|
5951
|
-
}
|
|
6059
|
+
});
|
|
5952
6060
|
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
var table = hit.getTargetDataTable();
|
|
5956
|
-
if (!textNode || !table) return;
|
|
5957
|
-
activeId = e.id;
|
|
5958
|
-
activeRecord = getLabelRecordById(activeId);
|
|
5959
|
-
dragging = true;
|
|
5960
|
-
downEvt = e;
|
|
5961
|
-
}
|
|
6061
|
+
if (rows > 0) {
|
|
6062
|
+
tableEl.appendTo(el);
|
|
5962
6063
|
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
if (!isMultilineLabel(textNode)) {
|
|
5974
|
-
// update anchor position of single-line labels based on label position
|
|
5975
|
-
// relative to anchor point, for better placement when eventual display font is
|
|
5976
|
-
// different from mapshaper's font.
|
|
5977
|
-
autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget().layer, ext));
|
|
5978
|
-
}
|
|
5979
|
-
// updateSymbol(targetTextNode, activeRecord);
|
|
5980
|
-
updateSymbol2(textNode, activeRecord, activeId);
|
|
5981
|
-
}
|
|
6064
|
+
tableEl.on('copy', function(e) {
|
|
6065
|
+
// remove leading or trailing tabs that sometimes get copied when
|
|
6066
|
+
// selecting from a table
|
|
6067
|
+
var pasted = window.getSelection().toString();
|
|
6068
|
+
var cleaned = pasted.replace(/^\t/, '').replace(/\t$/, '');
|
|
6069
|
+
if (pasted != cleaned && !window.clipboardData) { // ignore ie
|
|
6070
|
+
(e.clipboardData || e.originalEvent.clipboardData).setData("text", cleaned);
|
|
6071
|
+
e.preventDefault(); // don't copy original string with tabs
|
|
6072
|
+
}
|
|
6073
|
+
});
|
|
5982
6074
|
|
|
5983
|
-
|
|
5984
|
-
//
|
|
5985
|
-
|
|
5986
|
-
|
|
6075
|
+
} else {
|
|
6076
|
+
// Some individual features can have undefined values for some or all of
|
|
6077
|
+
// their data properties (properties are set to undefined when an input JSON file
|
|
6078
|
+
// has inconsistent fields, or after force-merging layers with inconsistent fields).
|
|
6079
|
+
el.html(utils.format('<div class="note">This %s is missing attribute data.</div>',
|
|
6080
|
+
table && table.getFields().length > 0 ? 'feature': 'layer'));
|
|
5987
6081
|
}
|
|
6082
|
+
}
|
|
5988
6083
|
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
6084
|
+
function renderRow(table, rec, key, type, editable) {
|
|
6085
|
+
var rowHtml = '<td class="field-name">%s</td><td><span class="value">%s</span> </td>';
|
|
6086
|
+
var val = rec[key];
|
|
6087
|
+
var str = formatInspectorValue(val, type);
|
|
6088
|
+
var cell = El('tr')
|
|
6089
|
+
.appendTo(table)
|
|
6090
|
+
.html(utils.format(rowHtml, key, utils.htmlEscape(str)))
|
|
6091
|
+
.findChild('.value');
|
|
6092
|
+
setFieldClass(cell, val, type);
|
|
6093
|
+
if (editable) {
|
|
6094
|
+
editItem(cell, rec, key, type);
|
|
5992
6095
|
}
|
|
6096
|
+
}
|
|
5993
6097
|
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6098
|
+
function setFieldClass(el, val, type) {
|
|
6099
|
+
var isNum = type ? type == 'number' : utils.isNumber(val);
|
|
6100
|
+
var isNully = val === undefined || val === null || val !== val;
|
|
6101
|
+
var isEmpty = val === '';
|
|
6102
|
+
el.classed('num-field', isNum);
|
|
6103
|
+
el.classed('object-field', type == 'object');
|
|
6104
|
+
el.classed('null-value', isNully);
|
|
6105
|
+
el.classed('empty', isEmpty);
|
|
6106
|
+
}
|
|
6001
6107
|
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6108
|
+
function editItem(el, rec, key, type) {
|
|
6109
|
+
var input = new ClickText2(el),
|
|
6110
|
+
strval = formatInspectorValue(rec[key], type),
|
|
6111
|
+
parser = getInputParser(type);
|
|
6112
|
+
el.parent().addClass('editable-cell');
|
|
6113
|
+
el.addClass('colored-text dot-underline');
|
|
6114
|
+
input.on('change', function(e) {
|
|
6115
|
+
var val2 = parser(input.value()),
|
|
6116
|
+
strval2 = formatInspectorValue(val2, type);
|
|
6117
|
+
if (strval == strval2) {
|
|
6118
|
+
// contents unchanged
|
|
6119
|
+
} else if (val2 === null && type != 'object') { // allow null objects
|
|
6120
|
+
// invalid value; revert to previous value
|
|
6121
|
+
input.value(strval);
|
|
6122
|
+
} else {
|
|
6123
|
+
// field content has changed
|
|
6124
|
+
strval = strval2;
|
|
6125
|
+
gui.dispatchEvent('data_preupdate', {FID: currId}); // for undo/redo
|
|
6126
|
+
rec[key] = val2;
|
|
6127
|
+
gui.dispatchEvent('data_postupdate', {FID: currId});
|
|
6128
|
+
input.value(strval);
|
|
6129
|
+
setFieldClass(el, val2, type);
|
|
6130
|
+
self.dispatchEvent('update', {field: key, value: val2, id: currId});
|
|
6006
6131
|
}
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
// svg.addEventListener('mousedown', function(e) {
|
|
6011
|
-
// var textTarget = getTextTarget(e);
|
|
6012
|
-
// downEvt = e;
|
|
6013
|
-
// if (!textTarget) {
|
|
6014
|
-
// stopEditing();
|
|
6015
|
-
// } else if (!editing) {
|
|
6016
|
-
// // nop
|
|
6017
|
-
// } else if (textTarget == targetTextNode) {
|
|
6018
|
-
// startDragging();
|
|
6019
|
-
// } else {
|
|
6020
|
-
// startDragging();
|
|
6021
|
-
// editTextNode(textTarget);
|
|
6022
|
-
// }
|
|
6023
|
-
// });
|
|
6024
|
-
|
|
6025
|
-
// up event on svg
|
|
6026
|
-
// a: currently dragging text
|
|
6027
|
-
// -> stop dragging
|
|
6028
|
-
// b: clicked on a text feature
|
|
6029
|
-
// -> start editing it
|
|
6132
|
+
});
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
6030
6135
|
|
|
6136
|
+
function formatInspectorValue(val, type) {
|
|
6137
|
+
var str;
|
|
6138
|
+
if (type == 'date') {
|
|
6139
|
+
str = utils.formatDateISO(val);
|
|
6140
|
+
} else if (type == 'object') {
|
|
6141
|
+
str = val ? JSON.stringify(val) : "";
|
|
6142
|
+
} else {
|
|
6143
|
+
str = String(val);
|
|
6144
|
+
}
|
|
6145
|
+
return str;
|
|
6146
|
+
}
|
|
6031
6147
|
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6148
|
+
var inputParsers = {
|
|
6149
|
+
date: function(raw) {
|
|
6150
|
+
var d = new Date(raw);
|
|
6151
|
+
return isNaN(+d) ? null : d;
|
|
6152
|
+
},
|
|
6153
|
+
string: function(raw) {
|
|
6154
|
+
return raw;
|
|
6155
|
+
},
|
|
6156
|
+
number: function(raw) {
|
|
6157
|
+
var val = Number(raw);
|
|
6158
|
+
if (raw == 'NaN') {
|
|
6159
|
+
val = NaN;
|
|
6160
|
+
} else if (isNaN(val)) {
|
|
6161
|
+
val = null;
|
|
6162
|
+
}
|
|
6163
|
+
return val;
|
|
6164
|
+
},
|
|
6165
|
+
object: function(raw) {
|
|
6166
|
+
var val = null;
|
|
6167
|
+
try {
|
|
6168
|
+
val = JSON.parse(raw);
|
|
6169
|
+
} catch(e) {}
|
|
6170
|
+
return val;
|
|
6171
|
+
},
|
|
6172
|
+
boolean: function(raw) {
|
|
6173
|
+
var val = null;
|
|
6174
|
+
if (raw == 'true') {
|
|
6175
|
+
val = true;
|
|
6176
|
+
} else if (raw == 'false') {
|
|
6177
|
+
val = false;
|
|
6178
|
+
}
|
|
6179
|
+
return val;
|
|
6180
|
+
},
|
|
6181
|
+
multiple: function(raw) {
|
|
6182
|
+
var val = Number(raw);
|
|
6183
|
+
return isNaN(val) ? raw : val;
|
|
6184
|
+
}
|
|
6185
|
+
};
|
|
6046
6186
|
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
// }, null, eventPriority);
|
|
6187
|
+
function getInputParser(type) {
|
|
6188
|
+
return inputParsers[type || 'multiple'];
|
|
6189
|
+
}
|
|
6051
6190
|
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6191
|
+
function getFieldType(val, key, table) {
|
|
6192
|
+
// if a field has a null value, look at entire column to identify type
|
|
6193
|
+
return internal.getValueType(val) || internal.getColumnType(key, table.getRecords());
|
|
6194
|
+
}
|
|
6055
6195
|
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
// if (!dragging || !activeRecord) return;
|
|
6060
|
-
// applyDelta(activeRecord, 'dx', e.dx / scale);
|
|
6061
|
-
// applyDelta(activeRecord, 'dy', e.dy / scale);
|
|
6062
|
-
// if (!isMultilineLabel(targetTextNode)) {
|
|
6063
|
-
// // update anchor position of single-line labels based on label position
|
|
6064
|
-
// // relative to anchor point, for better placement when eventual display font is
|
|
6065
|
-
// // different from mapshaper's font.
|
|
6066
|
-
// updateTextAnchor(targetTextNode, activeRecord);
|
|
6067
|
-
// }
|
|
6068
|
-
// // updateSymbol(targetTextNode, activeRecord);
|
|
6069
|
-
// targetTextNode = updateSymbol2(targetTextNode, activeRecord, activeId);
|
|
6070
|
-
// }, null, eventPriority);
|
|
6196
|
+
function InspectionControl2(gui, hit) {
|
|
6197
|
+
var _popup = new Popup(gui, hit.getSwitchTrigger(1), hit.getSwitchTrigger(-1));
|
|
6198
|
+
var _self = new EventDispatcher();
|
|
6071
6199
|
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6200
|
+
gui.on('interaction_mode_change', function(e) {
|
|
6201
|
+
if (e.mode == 'off') {
|
|
6202
|
+
inspect(-1); // clear the popup
|
|
6203
|
+
}
|
|
6204
|
+
});
|
|
6076
6205
|
|
|
6206
|
+
_popup.on('update', function(e) {
|
|
6207
|
+
_self.dispatchEvent('data_change', e.data); // let map know which field has changed
|
|
6208
|
+
});
|
|
6077
6209
|
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
}
|
|
6210
|
+
hit.on('change', function(e) {
|
|
6211
|
+
var ids;
|
|
6212
|
+
if (!inspecting()) return;
|
|
6213
|
+
ids = e.mode == 'selection' ? null : e.ids;
|
|
6214
|
+
inspect(e.id, e.pinned, ids);
|
|
6215
|
+
});
|
|
6084
6216
|
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6217
|
+
// id: Id of a feature in the active layer, or -1
|
|
6218
|
+
function inspect(id, pin, ids) {
|
|
6219
|
+
var target = hit.getHitTarget();
|
|
6220
|
+
if (id > -1 && inspecting() && target && target.layer) {
|
|
6221
|
+
_popup.show(id, ids, target.layer, pin);
|
|
6222
|
+
} else {
|
|
6223
|
+
_popup.hide();
|
|
6224
|
+
}
|
|
6091
6225
|
}
|
|
6092
6226
|
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
var dy = up.screenY - down.screenY;
|
|
6097
|
-
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
6098
|
-
return dist <= 4 && elapsed < 300;
|
|
6227
|
+
// does the attribute inspector appear on rollover
|
|
6228
|
+
function inspecting() {
|
|
6229
|
+
return gui.interaction && gui.interaction.getMode() != 'off';
|
|
6099
6230
|
}
|
|
6100
6231
|
|
|
6232
|
+
return _self;
|
|
6233
|
+
}
|
|
6101
6234
|
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6235
|
+
// Test if map should be re-framed to show updated layer
|
|
6236
|
+
function mapNeedsReset(newBounds, prevBounds, viewportBounds, flags) {
|
|
6237
|
+
var viewportPct = getIntersectionPct(newBounds, viewportBounds);
|
|
6238
|
+
var contentPct = getIntersectionPct(viewportBounds, newBounds);
|
|
6239
|
+
var boundsChanged = !prevBounds.equals(newBounds);
|
|
6240
|
+
var inView = newBounds.intersects(viewportBounds);
|
|
6241
|
+
var areaChg = newBounds.area() / prevBounds.area();
|
|
6242
|
+
var chgThreshold = flags.proj ? 1e3 : 1e8;
|
|
6243
|
+
// don't reset if layer extent hasn't changed
|
|
6244
|
+
if (!boundsChanged) return false;
|
|
6245
|
+
// reset if layer is out-of-view
|
|
6246
|
+
if (!inView) return true;
|
|
6247
|
+
// reset if content is mostly offscreen
|
|
6248
|
+
if (viewportPct < 0.3 && contentPct < 0.9) return true;
|
|
6249
|
+
// reset if content bounds have changed a lot (e.g. after projection)
|
|
6250
|
+
if (areaChg > chgThreshold || areaChg < 1/chgThreshold) return true;
|
|
6251
|
+
return false;
|
|
6252
|
+
}
|
|
6109
6253
|
|
|
6254
|
+
// Returns proportion of bb2 occupied by bb1
|
|
6255
|
+
function getIntersectionPct(bb1, bb2) {
|
|
6256
|
+
return getBoundsIntersection(bb1, bb2).area() / bb2.area() || 0;
|
|
6257
|
+
}
|
|
6110
6258
|
|
|
6259
|
+
function getBoundsIntersection(a, b) {
|
|
6260
|
+
var c = new Bounds();
|
|
6261
|
+
if (a.intersects(b)) {
|
|
6262
|
+
c.setBounds(Math.max(a.xmin, b.xmin), Math.max(a.ymin, b.ymin),
|
|
6263
|
+
Math.min(a.xmax, b.xmax), Math.min(a.ymax, b.ymax));
|
|
6264
|
+
}
|
|
6265
|
+
return c;
|
|
6111
6266
|
}
|
|
6112
6267
|
|
|
6113
6268
|
var darkStroke = "#334",
|
|
@@ -6151,7 +6306,7 @@
|
|
|
6151
6306
|
dotSize: 2.5
|
|
6152
6307
|
}, polyline: {
|
|
6153
6308
|
strokeColor: black,
|
|
6154
|
-
strokeWidth: 2.5
|
|
6309
|
+
strokeWidth: 2.5,
|
|
6155
6310
|
}
|
|
6156
6311
|
},
|
|
6157
6312
|
unselectedHoverStyles = {
|
|
@@ -6656,7 +6811,10 @@
|
|
|
6656
6811
|
} else {
|
|
6657
6812
|
arcs = getArcsForRendering(obj, ext);
|
|
6658
6813
|
filter = getShapeFilter(arcs, ext);
|
|
6659
|
-
canv.
|
|
6814
|
+
canv.drawStyledPaths(layer.shapes, arcs, style, filter);
|
|
6815
|
+
if (style.vertices) {
|
|
6816
|
+
canv.drawVertices(layer.shapes, arcs, style, filter);
|
|
6817
|
+
}
|
|
6660
6818
|
}
|
|
6661
6819
|
}
|
|
6662
6820
|
|
|
@@ -6730,7 +6888,7 @@
|
|
|
6730
6888
|
|
|
6731
6889
|
/*
|
|
6732
6890
|
// Original function, not optimized
|
|
6733
|
-
_self.
|
|
6891
|
+
_self.drawStyledPaths = function(shapes, arcs, style) {
|
|
6734
6892
|
var startPath = getPathStart(_ext),
|
|
6735
6893
|
drawPath = getShapePencil(arcs, _ext),
|
|
6736
6894
|
styler = style.styler || null;
|
|
@@ -6743,8 +6901,28 @@
|
|
|
6743
6901
|
};
|
|
6744
6902
|
*/
|
|
6745
6903
|
|
|
6904
|
+
_self.drawVertices = function(shapes, arcs, style, filter) {
|
|
6905
|
+
var iter = new internal.ShapeIter(arcs);
|
|
6906
|
+
var t = getScaledTransform(_ext);
|
|
6907
|
+
var radius = (style.strokeWidth * 0.8 || 2.2) * GUI.getPixelRatio() * getScaledLineScale(_ext);
|
|
6908
|
+
var color = style.strokeColor || 'black';
|
|
6909
|
+
var shp;
|
|
6910
|
+
_ctx.beginPath();
|
|
6911
|
+
_ctx.fillStyle = color;
|
|
6912
|
+
for (var i=0; i<shapes.length; i++) {
|
|
6913
|
+
shp = shapes[i];
|
|
6914
|
+
if (!shp || filter && !filter(shp)) continue;
|
|
6915
|
+
iter.init(shp);
|
|
6916
|
+
while (iter.hasNext()) {
|
|
6917
|
+
drawCircle(iter.x * t.mx + t.bx, iter.y * t.my + t.by, radius, _ctx);
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
_ctx.fill();
|
|
6921
|
+
_ctx.closePath();
|
|
6922
|
+
};
|
|
6923
|
+
|
|
6746
6924
|
// Optimized to draw paths in same-style batches (faster Canvas drawing)
|
|
6747
|
-
_self.
|
|
6925
|
+
_self.drawStyledPaths = function(shapes, arcs, style, filter) {
|
|
6748
6926
|
var styleIndex = {};
|
|
6749
6927
|
var batchSize = 1500;
|
|
6750
6928
|
var startPath = getPathStart(_ext, getScaledLineScale(_ext));
|
|
@@ -6872,7 +7050,7 @@
|
|
|
6872
7050
|
}
|
|
6873
7051
|
}
|
|
6874
7052
|
|
|
6875
|
-
// TODO: consider using
|
|
7053
|
+
// TODO: consider using drawStyledPaths(), which draws paths in batches
|
|
6876
7054
|
// for faster Canvas rendering. Downside: changes stacking order, which
|
|
6877
7055
|
// is bad if circles are graduated.
|
|
6878
7056
|
_self.drawPoints = function(shapes, style) {
|
|
@@ -7812,6 +7990,10 @@
|
|
|
7812
7990
|
_mouse.disable();
|
|
7813
7991
|
});
|
|
7814
7992
|
|
|
7993
|
+
gui.on('undo_redo', function() {
|
|
7994
|
+
drawLayers();
|
|
7995
|
+
});
|
|
7996
|
+
|
|
7815
7997
|
model.on('update', onUpdate);
|
|
7816
7998
|
|
|
7817
7999
|
// Update display of segment intersections
|
|
@@ -7996,6 +8178,10 @@
|
|
|
7996
8178
|
function updateOverlayLayer(e) {
|
|
7997
8179
|
var style = getOverlayStyle(_activeLyr.layer, e);
|
|
7998
8180
|
if (style) {
|
|
8181
|
+
// kludge to show vertices when editing path shapes
|
|
8182
|
+
if (gui.state.interaction_mode == 'vertices') {
|
|
8183
|
+
style.vertices = true;
|
|
8184
|
+
}
|
|
7999
8185
|
_overlayLyr = utils.defaults({
|
|
8000
8186
|
layer: filterLayerByIds(_activeLyr.layer, style.ids),
|
|
8001
8187
|
style: style
|
|
@@ -8235,6 +8421,8 @@
|
|
|
8235
8421
|
gui.map = new MshpMap(gui);
|
|
8236
8422
|
gui.interaction = new InteractionMode(gui);
|
|
8237
8423
|
gui.session = new SessionHistory(gui);
|
|
8424
|
+
gui.undo = new Undo(gui);
|
|
8425
|
+
gui.state = {};
|
|
8238
8426
|
|
|
8239
8427
|
gui.showProgressMessage = function(msg) {
|
|
8240
8428
|
if (!gui.progressMessage) {
|