mapshaper 0.5.87 → 0.5.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +299 -60
- package/package.json +1 -1
- package/www/mapshaper-gui.js +996 -1050
- package/www/mapshaper.js +299 -60
- package/www/nacis/Makefile +22 -0
- package/www/nacis/Makefile.txt +22 -0
- package/www/nacis/images/close.png +0 -0
- package/www/nacis/images/eye.png +0 -0
- package/www/nacis/images/eye2.png +0 -0
- package/www/nacis/index.html +262 -0
- package/www/nacis/manifest.js +27 -0
- package/www/nacis/map.svg +12308 -0
- package/CHANGELOG.md +0 -1294
package/www/mapshaper-gui.js
CHANGED
|
@@ -182,6 +182,10 @@
|
|
|
182
182
|
return (el && (el.tagName == 'INPUT' || el.contentEditable == 'true')) ? el : null;
|
|
183
183
|
};
|
|
184
184
|
|
|
185
|
+
GUI.textIsSelected = function() {
|
|
186
|
+
return !!GUI.getInputElement();
|
|
187
|
+
};
|
|
188
|
+
|
|
185
189
|
GUI.selectElement = function(el) {
|
|
186
190
|
var range = document.createRange(),
|
|
187
191
|
sel = window.getSelection();
|
|
@@ -1027,6 +1031,10 @@
|
|
|
1027
1031
|
var types = Array.from(e.clipboardData.types || []).join(',');
|
|
1028
1032
|
var items = Array.from(e.clipboardData.items || []);
|
|
1029
1033
|
var files;
|
|
1034
|
+
if (GUI.textIsSelected()) {
|
|
1035
|
+
// user is probably pasting text into an editable text field
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1030
1038
|
block(e);
|
|
1031
1039
|
// Browser compatibility (tested on MacOS only):
|
|
1032
1040
|
// Chrome and Safari: full support
|
|
@@ -3395,7 +3403,7 @@
|
|
|
3395
3403
|
GUI.onClick(entry, function() {
|
|
3396
3404
|
var target = findLayerById(id);
|
|
3397
3405
|
// don't select if user is typing or dragging
|
|
3398
|
-
if (!GUI.
|
|
3406
|
+
if (!GUI.textIsSelected() && !dragging) {
|
|
3399
3407
|
gui.clearMode();
|
|
3400
3408
|
if (!map.isActiveLayer(target.layer)) {
|
|
3401
3409
|
model.selectLayer(target.layer, target.dataset);
|
|
@@ -3574,637 +3582,135 @@
|
|
|
3574
3582
|
}
|
|
3575
3583
|
}
|
|
3576
3584
|
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
var scale = ext.getSymbolScale();
|
|
3583
|
-
var p = ext.translateCoords(xy[0], xy[1]);
|
|
3584
|
-
return internal.svg.getTransform(p, scale);
|
|
3585
|
-
}
|
|
3585
|
+
// import { cloneShape } from '../paths/mapshaper-shape-utils';
|
|
3586
|
+
// import { copyRecord } from '../datatable/mapshaper-data-utils';
|
|
3587
|
+
var snapVerticesToPoint$1 = internal.snapVerticesToPoint;
|
|
3588
|
+
var cloneShape = internal.cloneShape;
|
|
3589
|
+
var copyRecord = internal.copyRecord;
|
|
3586
3590
|
|
|
3591
|
+
function Undo(gui) {
|
|
3592
|
+
var history, offset, stashedUndo;
|
|
3593
|
+
reset();
|
|
3587
3594
|
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
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));
|
|
3605
|
-
} else {
|
|
3606
|
-
if (displayOn) el.setAttribute('display', 'none');
|
|
3607
|
-
}
|
|
3595
|
+
function reset() {
|
|
3596
|
+
history = [];
|
|
3597
|
+
stashedUndo = null;
|
|
3598
|
+
offset = 0;
|
|
3608
3599
|
}
|
|
3609
|
-
}
|
|
3610
|
-
|
|
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;
|
|
3621
|
-
});
|
|
3622
|
-
var obj = internal.getEmptyLayerForSVG(lyr, {});
|
|
3623
|
-
obj.children = symbols;
|
|
3624
|
-
return internal.svg.stringify(obj);
|
|
3625
|
-
}
|
|
3626
|
-
|
|
3627
|
-
function isMultilineLabel(textNode) {
|
|
3628
|
-
return textNode.childNodes.length > 1;
|
|
3629
|
-
}
|
|
3630
3600
|
|
|
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
|
-
}
|
|
3636
3601
|
|
|
3637
|
-
|
|
3638
|
-
|
|
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);
|
|
3649
|
-
}
|
|
3602
|
+
function isUndoEvt(e) {
|
|
3603
|
+
return (e.ctrlKey || e.metaKey) && !e.shiftKey && e.key == 'z';
|
|
3650
3604
|
}
|
|
3651
|
-
}
|
|
3652
3605
|
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
if (el.tagName == 'svg') return el;
|
|
3656
|
-
el = el.parentNode;
|
|
3606
|
+
function isRedoEvt(e) {
|
|
3607
|
+
return (e.ctrlKey || e.metaKey) && (e.shiftKey && e.key == 'z' || !e.shiftKey && e.key == 'y');
|
|
3657
3608
|
}
|
|
3658
|
-
return null;
|
|
3659
|
-
}
|
|
3660
|
-
|
|
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
|
-
}
|
|
3670
3609
|
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3610
|
+
gui.keyboard.on('keydown', function(evt) {
|
|
3611
|
+
var e = evt.originalEvent,
|
|
3612
|
+
kc = e.keyCode;
|
|
3613
|
+
if (isUndoEvt(e)) {
|
|
3614
|
+
this.undo();
|
|
3615
|
+
e.stopPropagation();
|
|
3616
|
+
e.preventDefault();
|
|
3617
|
+
}
|
|
3618
|
+
if (isRedoEvt(e)) {
|
|
3619
|
+
this.redo();
|
|
3620
|
+
e.stopPropagation();
|
|
3621
|
+
e.preventDefault();
|
|
3622
|
+
}
|
|
3677
3623
|
|
|
3678
|
-
|
|
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
|
-
}
|
|
3692
|
-
}
|
|
3624
|
+
}, this, 10);
|
|
3693
3625
|
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
rec[key] = isString ? String(newVal) : newVal;
|
|
3700
|
-
}
|
|
3626
|
+
// undo/redo point/symbol dragging
|
|
3627
|
+
//
|
|
3628
|
+
gui.on('symbol_dragstart', function(e) {
|
|
3629
|
+
stashedUndo = this.makePointSetter(e.FID);
|
|
3630
|
+
}, this);
|
|
3701
3631
|
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3632
|
+
gui.on('symbol_dragend', function(e) {
|
|
3633
|
+
var redo = this.makePointSetter(e.FID);
|
|
3634
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3635
|
+
}, this);
|
|
3706
3636
|
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
// kludge to get dataset to recalculate internal bounding boxes
|
|
3713
|
-
arcs.transformPoints(function() {});
|
|
3714
|
-
}
|
|
3715
|
-
}
|
|
3637
|
+
// undo/redo label dragging
|
|
3638
|
+
//
|
|
3639
|
+
gui.on('label_dragstart', function(e) {
|
|
3640
|
+
stashedUndo = this.makeDataSetter(e.FID);
|
|
3641
|
+
}, this);
|
|
3716
3642
|
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
}
|
|
3722
|
-
return coords[0];
|
|
3723
|
-
}
|
|
3643
|
+
gui.on('label_dragend', function(e) {
|
|
3644
|
+
var redo = this.makeDataSetter(e.FID);
|
|
3645
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3646
|
+
}, this);
|
|
3724
3647
|
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3648
|
+
// undo/redo data editing
|
|
3649
|
+
// TODO: consider setting selected feature to the undo/redo target feature
|
|
3650
|
+
//
|
|
3651
|
+
gui.on('data_preupdate', function(e) {
|
|
3652
|
+
stashedUndo = this.makeDataSetter(e.FID);
|
|
3653
|
+
}, this);
|
|
3730
3654
|
|
|
3655
|
+
gui.on('data_postupdate', function(e) {
|
|
3656
|
+
var redo = this.makeDataSetter(e.FID);
|
|
3657
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3658
|
+
}, this);
|
|
3731
3659
|
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
var activeId = -1;
|
|
3737
|
-
var self = new EventDispatcher();
|
|
3738
|
-
var activeVertexIds = null; // for vertex dragging
|
|
3660
|
+
// undo/redo vertex dragging
|
|
3661
|
+
gui.on('vertex_dragstart', function(e) {
|
|
3662
|
+
stashedUndo = this.makeVertexSetter(e.FID, e.vertex_ids);
|
|
3663
|
+
}, this);
|
|
3739
3664
|
|
|
3740
|
-
|
|
3665
|
+
gui.on('vertex_dragend', function(e) {
|
|
3666
|
+
var redo = this.makeVertexSetter(e.FID, e.vertex_ids);
|
|
3667
|
+
this.addHistoryState(stashedUndo, redo);
|
|
3668
|
+
}, this);
|
|
3741
3669
|
|
|
3742
|
-
|
|
3670
|
+
this.clear = function() {
|
|
3671
|
+
reset();
|
|
3672
|
+
};
|
|
3743
3673
|
|
|
3744
|
-
function
|
|
3745
|
-
|
|
3746
|
-
|
|
3674
|
+
this.makePointSetter = function(i) {
|
|
3675
|
+
var target = gui.model.getActiveLayer();
|
|
3676
|
+
var shp = cloneShape(target.layer.shapes[i]);
|
|
3677
|
+
return function() {
|
|
3678
|
+
target.layer.shapes[i] = shp;
|
|
3679
|
+
};
|
|
3680
|
+
};
|
|
3747
3681
|
|
|
3748
|
-
function
|
|
3749
|
-
|
|
3750
|
-
|
|
3682
|
+
this.makeDataSetter = function(id) {
|
|
3683
|
+
var target = gui.model.getActiveLayer();
|
|
3684
|
+
var rec = copyRecord(target.layer.data.getRecordAt(id));
|
|
3685
|
+
return function() {
|
|
3686
|
+
target.layer.data.getRecords()[id] = rec;
|
|
3687
|
+
gui.dispatchEvent('popup-needs-refresh');
|
|
3688
|
+
};
|
|
3689
|
+
};
|
|
3751
3690
|
|
|
3752
|
-
function
|
|
3753
|
-
|
|
3754
|
-
|
|
3691
|
+
this.makeVertexSetter = function(fid, ids) {
|
|
3692
|
+
var target = gui.model.getActiveLayer();
|
|
3693
|
+
var arcs = target.dataset.arcs;
|
|
3694
|
+
var p = internal.getVertexCoords(ids[0], arcs);
|
|
3695
|
+
return function() {
|
|
3696
|
+
snapVerticesToPoint$1(ids, p, arcs, true);
|
|
3697
|
+
};
|
|
3698
|
+
};
|
|
3755
3699
|
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
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();
|
|
3793
|
-
}
|
|
3794
|
-
gui.undo.clear(); // TODO: put this elsewhere?
|
|
3795
|
-
});
|
|
3796
|
-
|
|
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();
|
|
3817
|
-
}
|
|
3818
|
-
});
|
|
3819
|
-
|
|
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);
|
|
3827
|
-
}
|
|
3828
|
-
});
|
|
3829
|
-
|
|
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
|
-
});
|
|
3844
|
-
|
|
3845
|
-
hit.on('click', function(e) {
|
|
3846
|
-
if (labelEditingEnabled()) {
|
|
3847
|
-
onLabelClick(e);
|
|
3848
|
-
}
|
|
3849
|
-
});
|
|
3850
|
-
|
|
3851
|
-
function getVertexEventData(e) {
|
|
3852
|
-
return {
|
|
3853
|
-
FID: activeId,
|
|
3854
|
-
vertexIds: activeVertexIds
|
|
3855
|
-
};
|
|
3856
|
-
}
|
|
3857
|
-
|
|
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);
|
|
3867
|
-
}
|
|
3868
|
-
|
|
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;
|
|
3874
|
-
}
|
|
3875
|
-
|
|
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
|
-
}
|
|
3886
|
-
|
|
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
|
-
}
|
|
3896
|
-
|
|
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);
|
|
3906
|
-
}
|
|
3907
|
-
|
|
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);
|
|
3922
|
-
}
|
|
3923
|
-
|
|
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
|
-
}
|
|
3933
|
-
|
|
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;
|
|
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
|
-
// }
|
|
4054
|
-
}
|
|
4055
|
-
|
|
4056
|
-
function startDragging() {
|
|
4057
|
-
dragging = true;
|
|
4058
|
-
}
|
|
4059
|
-
|
|
4060
|
-
function stopDragging() {
|
|
4061
|
-
dragging = false;
|
|
4062
|
-
activeId = -1;
|
|
4063
|
-
activeRecord = null;
|
|
4064
|
-
activeVertexIds = null;
|
|
4065
|
-
}
|
|
4066
|
-
|
|
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;
|
|
4073
|
-
}
|
|
4074
|
-
|
|
4075
|
-
}
|
|
4076
|
-
|
|
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;
|
|
4081
|
-
|
|
4082
|
-
function Undo(gui) {
|
|
4083
|
-
var history, offset, stashedUndo;
|
|
4084
|
-
reset();
|
|
4085
|
-
|
|
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();
|
|
4111
|
-
}
|
|
4112
|
-
if (isRedoEvt(e)) {
|
|
4113
|
-
this.redo();
|
|
4114
|
-
e.stopPropagation();
|
|
4115
|
-
e.preventDefault();
|
|
4116
|
-
}
|
|
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();
|
|
4166
|
-
};
|
|
4167
|
-
|
|
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
|
-
};
|
|
4174
|
-
};
|
|
4175
|
-
|
|
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
|
-
};
|
|
4183
|
-
};
|
|
4184
|
-
|
|
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
|
-
};
|
|
4192
|
-
};
|
|
4193
|
-
|
|
4194
|
-
this.addHistoryState = function(undo, redo) {
|
|
4195
|
-
if (offset > 0) {
|
|
4196
|
-
history.splice(-offset);
|
|
4197
|
-
offset = 0;
|
|
4198
|
-
}
|
|
4199
|
-
history.push({undo, redo});
|
|
4200
|
-
};
|
|
3700
|
+
this.addHistoryState = function(undo, redo) {
|
|
3701
|
+
if (offset > 0) {
|
|
3702
|
+
history.splice(-offset);
|
|
3703
|
+
offset = 0;
|
|
3704
|
+
}
|
|
3705
|
+
history.push({undo, redo});
|
|
3706
|
+
};
|
|
4201
3707
|
|
|
4202
3708
|
this.undo = function() {
|
|
4203
3709
|
var item = getHistoryItem();
|
|
4204
3710
|
if (item) {
|
|
4205
3711
|
offset++;
|
|
4206
3712
|
item.undo();
|
|
4207
|
-
|
|
3713
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
4208
3714
|
}
|
|
4209
3715
|
};
|
|
4210
3716
|
|
|
@@ -4213,7 +3719,7 @@
|
|
|
4213
3719
|
offset--;
|
|
4214
3720
|
var item = getHistoryItem();
|
|
4215
3721
|
item.redo();
|
|
4216
|
-
|
|
3722
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
4217
3723
|
};
|
|
4218
3724
|
|
|
4219
3725
|
function getHistoryItem() {
|
|
@@ -4354,6 +3860,7 @@
|
|
|
4354
3860
|
|
|
4355
3861
|
var menus = {
|
|
4356
3862
|
standard: ['info', 'selection', 'data', 'box'],
|
|
3863
|
+
polygons: ['info', 'selection', 'data', 'box', 'vertices'],
|
|
4357
3864
|
lines: ['info', 'selection', 'data', 'box', 'vertices'],
|
|
4358
3865
|
table: ['info', 'selection', 'data'],
|
|
4359
3866
|
labels: ['info', 'selection', 'data', 'box', 'labels', 'location'],
|
|
@@ -4465,6 +3972,9 @@
|
|
|
4465
3972
|
if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polyline') {
|
|
4466
3973
|
return menus.lines;
|
|
4467
3974
|
}
|
|
3975
|
+
if (internal.layerHasPaths(o.layer) && o.layer.geometry_type == 'polygon') {
|
|
3976
|
+
return menus.polygons;
|
|
3977
|
+
}
|
|
4468
3978
|
return menus.standard;
|
|
4469
3979
|
}
|
|
4470
3980
|
|
|
@@ -4811,6 +4321,56 @@
|
|
|
4811
4321
|
}
|
|
4812
4322
|
}
|
|
4813
4323
|
|
|
4324
|
+
function getSymbolNodeId(node) {
|
|
4325
|
+
return parseInt(node.getAttribute('data-id'));
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
function getSvgSymbolTransform(xy, ext) {
|
|
4329
|
+
var scale = ext.getSymbolScale();
|
|
4330
|
+
var p = ext.translateCoords(xy[0], xy[1]);
|
|
4331
|
+
return internal.svg.getTransform(p, scale);
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
|
|
4335
|
+
function repositionSymbols(elements, layer, ext) {
|
|
4336
|
+
var el, idx, shp, p, displayOn, inView, displayBounds;
|
|
4337
|
+
for (var i=0, n=elements.length; i<n; i++) {
|
|
4338
|
+
el = elements[i];
|
|
4339
|
+
idx = getSymbolNodeId(el);
|
|
4340
|
+
shp = layer.shapes[idx];
|
|
4341
|
+
if (!shp) continue;
|
|
4342
|
+
p = shp[0];
|
|
4343
|
+
// OPTIMIZATION: only display symbols that are in view
|
|
4344
|
+
// quick-and-dirty hit-test: expand the extent rectangle by a percentage.
|
|
4345
|
+
// very large symbols will disappear before they're completely out of view
|
|
4346
|
+
displayBounds = ext.getBounds(1.15);
|
|
4347
|
+
displayOn = !el.hasAttribute('display') || el.getAttribute('display') == 'block';
|
|
4348
|
+
inView = displayBounds.containsPoint(p[0], p[1]);
|
|
4349
|
+
if (inView) {
|
|
4350
|
+
if (!displayOn) el.setAttribute('display', 'block');
|
|
4351
|
+
el.setAttribute('transform', getSvgSymbolTransform(p, ext));
|
|
4352
|
+
} else {
|
|
4353
|
+
if (displayOn) el.setAttribute('display', 'none');
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4358
|
+
function renderSymbols(lyr, ext, type) {
|
|
4359
|
+
var records = lyr.data.getRecords();
|
|
4360
|
+
var symbols = lyr.shapes.map(function(shp, i) {
|
|
4361
|
+
var d = records[i];
|
|
4362
|
+
var obj = type == 'label' ? internal.svg.importStyledLabel(d) :
|
|
4363
|
+
internal.svg.importSymbol(d['svg-symbol']);
|
|
4364
|
+
if (!obj || !shp) return null;
|
|
4365
|
+
obj.properties.transform = getSvgSymbolTransform(shp[0], ext);
|
|
4366
|
+
obj.properties['data-id'] = i;
|
|
4367
|
+
return obj;
|
|
4368
|
+
});
|
|
4369
|
+
var obj = internal.getEmptyLayerForSVG(lyr, {});
|
|
4370
|
+
obj.children = symbols;
|
|
4371
|
+
return internal.svg.stringify(obj);
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4814
4374
|
function getSvgHitTest(displayLayer) {
|
|
4815
4375
|
|
|
4816
4376
|
return function(pointerEvent) {
|
|
@@ -4920,7 +4480,7 @@
|
|
|
4920
4480
|
}
|
|
4921
4481
|
|
|
4922
4482
|
// ignore keypress if no feature is selected or user is editing text
|
|
4923
|
-
if (pinnedId() == -1 || GUI.
|
|
4483
|
+
if (pinnedId() == -1 || GUI.textIsSelected()) return;
|
|
4924
4484
|
|
|
4925
4485
|
if (e.keyCode == 37 || e.keyCode == 39) {
|
|
4926
4486
|
// L/R arrow keys
|
|
@@ -5078,6 +4638,7 @@
|
|
|
5078
4638
|
|
|
5079
4639
|
// Hits are re-detected on 'hover' (if hit detection is active)
|
|
5080
4640
|
mouse.on('hover', function(e) {
|
|
4641
|
+
handlePointerEvent(e);
|
|
5081
4642
|
if (storedData.pinned || !hitTest || !active) return;
|
|
5082
4643
|
if (e.hover && isOverMap(e)) {
|
|
5083
4644
|
// mouse is hovering directly over map area -- update hit detection
|
|
@@ -5728,541 +5289,931 @@
|
|
|
5728
5289
|
});
|
|
5729
5290
|
}
|
|
5730
5291
|
|
|
5731
|
-
gui.on('map_reset', function() {
|
|
5732
|
-
ext.home();
|
|
5292
|
+
gui.on('map_reset', function() {
|
|
5293
|
+
ext.home();
|
|
5294
|
+
});
|
|
5295
|
+
|
|
5296
|
+
zoomTween.on('change', function(e) {
|
|
5297
|
+
ext.zoomToExtent(e.value, _fx, _fy);
|
|
5298
|
+
});
|
|
5299
|
+
|
|
5300
|
+
mouse.on('dblclick', function(e) {
|
|
5301
|
+
if (disabled()) return;
|
|
5302
|
+
zoomByPct(getZoomInPct(), e.x / ext.width(), e.y / ext.height());
|
|
5303
|
+
});
|
|
5304
|
+
|
|
5305
|
+
mouse.on('dragstart', function(e) {
|
|
5306
|
+
if (disabled()) return;
|
|
5307
|
+
if (!internal.layerHasGeometry(gui.model.getActiveLayer().layer)) return;
|
|
5308
|
+
// zoomDrag = !!e.metaKey || !!e.ctrlKey; // meta is command on mac, windows key on windows
|
|
5309
|
+
boxDrag = !!e.shiftKey;
|
|
5310
|
+
if (boxDrag) {
|
|
5311
|
+
dragStartEvt = e;
|
|
5312
|
+
gui.dispatchEvent('box_drag_start');
|
|
5313
|
+
}
|
|
5314
|
+
});
|
|
5315
|
+
|
|
5316
|
+
mouse.on('drag', function(e) {
|
|
5317
|
+
if (disabled()) return;
|
|
5318
|
+
if (boxDrag) {
|
|
5319
|
+
gui.dispatchEvent('box_drag', getBoxData(e));
|
|
5320
|
+
} else {
|
|
5321
|
+
ext.pan(e.dx, e.dy);
|
|
5322
|
+
}
|
|
5323
|
+
});
|
|
5324
|
+
|
|
5325
|
+
mouse.on('dragend', function(e) {
|
|
5326
|
+
var bbox;
|
|
5327
|
+
if (disabled()) return;
|
|
5328
|
+
if (boxDrag) {
|
|
5329
|
+
boxDrag = false;
|
|
5330
|
+
gui.dispatchEvent('box_drag_end', getBoxData(e));
|
|
5331
|
+
}
|
|
5332
|
+
});
|
|
5333
|
+
|
|
5334
|
+
wheel.on('mousewheel', function(e) {
|
|
5335
|
+
var tickFraction = 0.11; // 0.15; // fraction of zoom step per wheel event;
|
|
5336
|
+
var k = 1 + (tickFraction * e.multiplier * zoomScaleMultiplier),
|
|
5337
|
+
delta = e.direction > 0 ? k : 1 / k;
|
|
5338
|
+
if (disabled()) return;
|
|
5339
|
+
ext.zoomByPct(delta, e.x / ext.width(), e.y / ext.height());
|
|
5340
|
+
});
|
|
5341
|
+
|
|
5342
|
+
function swapElements(arr, i, j) {
|
|
5343
|
+
var tmp = arr[i];
|
|
5344
|
+
arr[i] = arr[j];
|
|
5345
|
+
arr[j] = tmp;
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5348
|
+
function getBoxData(e) {
|
|
5349
|
+
var pageBox = [e.pageX, e.pageY, dragStartEvt.pageX, dragStartEvt.pageY];
|
|
5350
|
+
var mapBox = [e.x, e.y, dragStartEvt.x, dragStartEvt.y];
|
|
5351
|
+
var tmp;
|
|
5352
|
+
if (pageBox[0] > pageBox[2]) {
|
|
5353
|
+
swapElements(pageBox, 0, 2);
|
|
5354
|
+
swapElements(mapBox, 0, 2);
|
|
5355
|
+
}
|
|
5356
|
+
if (pageBox[1] > pageBox[3]) {
|
|
5357
|
+
swapElements(pageBox, 1, 3);
|
|
5358
|
+
swapElements(mapBox, 1, 3);
|
|
5359
|
+
}
|
|
5360
|
+
return {
|
|
5361
|
+
map_bbox: mapBox,
|
|
5362
|
+
page_bbox: pageBox
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
5365
|
+
|
|
5366
|
+
function disabled() {
|
|
5367
|
+
return !!gui.options.disableNavigation;
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5370
|
+
function zoomIn(e) {
|
|
5371
|
+
if (disabled()) return;
|
|
5372
|
+
zoomByPct(getZoomInPct(e.time), 0.5, 0.5);
|
|
5373
|
+
}
|
|
5374
|
+
|
|
5375
|
+
function zoomOut(e) {
|
|
5376
|
+
if (disabled()) return;
|
|
5377
|
+
zoomByPct(1/getZoomInPct(e.time), 0.5, 0.5);
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
function getZoomInPct(clickTime) {
|
|
5381
|
+
var minScale = 0.2,
|
|
5382
|
+
maxScale = 4,
|
|
5383
|
+
minTime = 100,
|
|
5384
|
+
maxTime = 800,
|
|
5385
|
+
time = utils.clamp(clickTime || 200, minTime, maxTime),
|
|
5386
|
+
k = (time - minTime) / (maxTime - minTime),
|
|
5387
|
+
scale = minScale + k * (maxScale - minScale);
|
|
5388
|
+
return 1 + scale * zoomScaleMultiplier;
|
|
5389
|
+
}
|
|
5390
|
+
|
|
5391
|
+
// @box Bounds with pixels from t,l corner of map area.
|
|
5392
|
+
function zoomToBbox(bbox) {
|
|
5393
|
+
var bounds = new Bounds(bbox),
|
|
5394
|
+
pct = Math.max(bounds.width() / ext.width(), bounds.height() / ext.height()),
|
|
5395
|
+
fx = bounds.centerX() / ext.width() * (1 + pct) - pct / 2,
|
|
5396
|
+
fy = bounds.centerY() / ext.height() * (1 + pct) - pct / 2;
|
|
5397
|
+
zoomByPct(1 / pct, fx, fy);
|
|
5398
|
+
}
|
|
5399
|
+
|
|
5400
|
+
// @pct Change in scale (2 = 2x zoom)
|
|
5401
|
+
// @fx, @fy zoom focus, [0, 1]
|
|
5402
|
+
function zoomByPct(pct, fx, fy) {
|
|
5403
|
+
var w = ext.getBounds().width();
|
|
5404
|
+
_fx = fx;
|
|
5405
|
+
_fy = fy;
|
|
5406
|
+
zoomTween.start(w, w / pct, 400);
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
function HighlightBox() {
|
|
5411
|
+
var box = El('div').addClass('zoom-box').appendTo('body'),
|
|
5412
|
+
show = box.show.bind(box), // original show() function
|
|
5413
|
+
stroke = 2;
|
|
5414
|
+
box.hide();
|
|
5415
|
+
box.show = function(x1, y1, x2, y2) {
|
|
5416
|
+
var w = Math.abs(x1 - x2),
|
|
5417
|
+
h = Math.abs(y1 - y2);
|
|
5418
|
+
box.css({
|
|
5419
|
+
top: Math.min(y1, y2),
|
|
5420
|
+
left: Math.min(x1, x2),
|
|
5421
|
+
width: Math.max(w - stroke * 2, 1),
|
|
5422
|
+
height: Math.max(h - stroke * 2, 1)
|
|
5423
|
+
});
|
|
5424
|
+
show();
|
|
5425
|
+
};
|
|
5426
|
+
return box;
|
|
5427
|
+
}
|
|
5428
|
+
|
|
5429
|
+
function SelectionTool(gui, ext, hit) {
|
|
5430
|
+
var popup = gui.container.findChild('.selection-tool-options');
|
|
5431
|
+
var box = new HighlightBox();
|
|
5432
|
+
var _on = false;
|
|
5433
|
+
|
|
5434
|
+
gui.addMode('selection_tool', turnOn, turnOff);
|
|
5435
|
+
|
|
5436
|
+
gui.on('interaction_mode_change', function(e) {
|
|
5437
|
+
if (e.mode === 'selection') {
|
|
5438
|
+
gui.enterMode('selection_tool');
|
|
5439
|
+
} else if (gui.getMode() == 'selection_tool') {
|
|
5440
|
+
gui.clearMode();
|
|
5441
|
+
}
|
|
5442
|
+
});
|
|
5443
|
+
|
|
5444
|
+
gui.on('box_drag', function(e) {
|
|
5445
|
+
if (!_on) return;
|
|
5446
|
+
var b = e.page_bbox;
|
|
5447
|
+
box.show(b[0], b[1], b[2], b[3]);
|
|
5448
|
+
updateSelection(e.map_bbox, true);
|
|
5449
|
+
});
|
|
5450
|
+
|
|
5451
|
+
gui.on('box_drag_end', function(e) {
|
|
5452
|
+
if (!_on) return;
|
|
5453
|
+
box.hide();
|
|
5454
|
+
updateSelection(e.map_bbox);
|
|
5455
|
+
});
|
|
5456
|
+
|
|
5457
|
+
function updateSelection(bboxPixels, transient) {
|
|
5458
|
+
var bbox = bboxToCoords(bboxPixels);
|
|
5459
|
+
var active = gui.model.getActiveLayer();
|
|
5460
|
+
var ids = internal.findShapesIntersectingBBox(bbox, active.layer, active.dataset.arcs);
|
|
5461
|
+
if (transient) {
|
|
5462
|
+
hit.setTransientIds(ids);
|
|
5463
|
+
} else if (ids.length) {
|
|
5464
|
+
hit.addSelectionIds(ids);
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5468
|
+
function turnOn() {
|
|
5469
|
+
_on = true;
|
|
5470
|
+
}
|
|
5471
|
+
|
|
5472
|
+
function bboxToCoords(bbox) {
|
|
5473
|
+
var a = ext.translatePixelCoords(bbox[0], bbox[1]);
|
|
5474
|
+
var b = ext.translatePixelCoords(bbox[2], bbox[3]);
|
|
5475
|
+
return [a[0], b[1], b[0], a[1]];
|
|
5476
|
+
}
|
|
5477
|
+
|
|
5478
|
+
function turnOff() {
|
|
5479
|
+
reset();
|
|
5480
|
+
_on = false;
|
|
5481
|
+
if (gui.interaction.getMode() == 'selection') {
|
|
5482
|
+
// mode change was not initiated by interactive menu -- turn off interactivity
|
|
5483
|
+
gui.interaction.turnOff();
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
function reset() {
|
|
5488
|
+
popup.hide();
|
|
5489
|
+
hit.clearSelection();
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5492
|
+
function getIdsOpt() {
|
|
5493
|
+
return hit.getSelectionIds().join(',');
|
|
5494
|
+
}
|
|
5495
|
+
|
|
5496
|
+
hit.on('change', function(e) {
|
|
5497
|
+
if (e.mode != 'selection') return;
|
|
5498
|
+
var ids = hit.getSelectionIds();
|
|
5499
|
+
if (ids.length > 0) {
|
|
5500
|
+
// enter this mode when we're ready to show the selection options
|
|
5501
|
+
// (this closes any other active mode, e.g. box_tool)
|
|
5502
|
+
gui.enterMode('selection_tool');
|
|
5503
|
+
popup.show();
|
|
5504
|
+
} else {
|
|
5505
|
+
popup.hide();
|
|
5506
|
+
}
|
|
5507
|
+
});
|
|
5508
|
+
|
|
5509
|
+
new SimpleButton(popup.findChild('.delete-btn')).on('click', function() {
|
|
5510
|
+
var cmd = '-filter invert ids=' + getIdsOpt();
|
|
5511
|
+
runCommand(cmd);
|
|
5512
|
+
});
|
|
5513
|
+
|
|
5514
|
+
new SimpleButton(popup.findChild('.filter-btn')).on('click', function() {
|
|
5515
|
+
var cmd = '-filter ids=' + getIdsOpt();
|
|
5516
|
+
runCommand(cmd);
|
|
5733
5517
|
});
|
|
5734
5518
|
|
|
5735
|
-
|
|
5736
|
-
|
|
5519
|
+
new SimpleButton(popup.findChild('.split-btn')).on('click', function() {
|
|
5520
|
+
var cmd = '-split ids=' + getIdsOpt();
|
|
5521
|
+
runCommand(cmd);
|
|
5737
5522
|
});
|
|
5738
5523
|
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
zoomByPct(getZoomInPct(), e.x / ext.width(), e.y / ext.height());
|
|
5524
|
+
new SimpleButton(popup.findChild('.cancel-btn')).on('click', function() {
|
|
5525
|
+
hit.clearSelection();
|
|
5742
5526
|
});
|
|
5743
5527
|
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
if (
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5528
|
+
function runCommand(cmd) {
|
|
5529
|
+
popup.hide();
|
|
5530
|
+
if (gui.console) gui.console.runMapshaperCommands(cmd, function(err) {
|
|
5531
|
+
reset();
|
|
5532
|
+
});
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
// toNext, toPrev: trigger functions for switching between multiple records
|
|
5537
|
+
function Popup(gui, toNext, toPrev) {
|
|
5538
|
+
var self = new EventDispatcher();
|
|
5539
|
+
var parent = gui.container.findChild('.mshp-main-map');
|
|
5540
|
+
var el = El('div').addClass('popup').appendTo(parent).hide();
|
|
5541
|
+
var content = El('div').addClass('popup-content').appendTo(el);
|
|
5542
|
+
// multi-hit display and navigation
|
|
5543
|
+
var tab = El('div').addClass('popup-tab').appendTo(el).hide();
|
|
5544
|
+
var nav = El('div').addClass('popup-nav').appendTo(tab);
|
|
5545
|
+
var prevLink = El('span').addClass('popup-nav-arrow colored-text').appendTo(nav).text('◀');
|
|
5546
|
+
var navInfo = El('span').addClass('popup-nav-info').appendTo(nav);
|
|
5547
|
+
var nextLink = El('span').addClass('popup-nav-arrow colored-text').appendTo(nav).text('▶');
|
|
5548
|
+
var refresh = null;
|
|
5549
|
+
var currId = -1;
|
|
5550
|
+
|
|
5551
|
+
el.addClass('rollover'); // used as a sentinel for the hover function
|
|
5552
|
+
|
|
5553
|
+
nextLink.on('click', toNext);
|
|
5554
|
+
prevLink.on('click', toPrev);
|
|
5555
|
+
gui.on('popup-needs-refresh', function() {
|
|
5556
|
+
if (refresh) refresh();
|
|
5753
5557
|
});
|
|
5754
5558
|
|
|
5755
|
-
|
|
5756
|
-
if
|
|
5757
|
-
|
|
5758
|
-
|
|
5559
|
+
self.show = function(id, ids, lyr, pinned) {
|
|
5560
|
+
var table = lyr.data; // table can be null (e.g. if layer has no attribute data)
|
|
5561
|
+
var editable = pinned && gui.interaction.getMode() == 'data';
|
|
5562
|
+
var maxHeight = parent.node().clientHeight - 36;
|
|
5563
|
+
currId = id;
|
|
5564
|
+
// stash a function for refreshing the current popup when data changes
|
|
5565
|
+
// while the popup is being displayed (e.g. while dragging a label)
|
|
5566
|
+
refresh = function() {
|
|
5567
|
+
var rec = table && (editable ? table.getRecordAt(id) : table.getReadOnlyRecordAt(id)) || {};
|
|
5568
|
+
render(content, rec, table, editable);
|
|
5569
|
+
};
|
|
5570
|
+
refresh();
|
|
5571
|
+
if (ids && ids.length > 1) {
|
|
5572
|
+
showNav(id, ids, pinned);
|
|
5759
5573
|
} else {
|
|
5760
|
-
|
|
5574
|
+
tab.hide();
|
|
5761
5575
|
}
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
var bbox;
|
|
5766
|
-
if (disabled()) return;
|
|
5767
|
-
if (boxDrag) {
|
|
5768
|
-
boxDrag = false;
|
|
5769
|
-
gui.dispatchEvent('box_drag_end', getBoxData(e));
|
|
5576
|
+
el.show();
|
|
5577
|
+
if (content.node().clientHeight > maxHeight) {
|
|
5578
|
+
content.css('height:' + maxHeight + 'px');
|
|
5770
5579
|
}
|
|
5771
|
-
}
|
|
5580
|
+
};
|
|
5772
5581
|
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5582
|
+
self.hide = function() {
|
|
5583
|
+
if (!isOpen()) return;
|
|
5584
|
+
refresh = null;
|
|
5585
|
+
currId = -1;
|
|
5586
|
+
// make sure any pending edits are made before re-rendering popup
|
|
5587
|
+
GUI.blurActiveElement(); // this should be more selective -- could cause a glitch if typing in console
|
|
5588
|
+
content.empty();
|
|
5589
|
+
content.node().removeAttribute('style'); // remove inline height
|
|
5590
|
+
el.hide();
|
|
5591
|
+
};
|
|
5780
5592
|
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5593
|
+
return self;
|
|
5594
|
+
|
|
5595
|
+
function isOpen() {
|
|
5596
|
+
return el.visible();
|
|
5785
5597
|
}
|
|
5786
5598
|
|
|
5787
|
-
function
|
|
5788
|
-
var
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5599
|
+
function showNav(id, ids, pinned) {
|
|
5600
|
+
var num = ids.indexOf(id) + 1;
|
|
5601
|
+
navInfo.text(' ' + num + ' / ' + ids.length + ' ');
|
|
5602
|
+
nextLink.css('display', pinned ? 'inline-block' : 'none');
|
|
5603
|
+
prevLink.css('display', pinned && ids.length > 2 ? 'inline-block' : 'none');
|
|
5604
|
+
tab.show();
|
|
5605
|
+
}
|
|
5606
|
+
|
|
5607
|
+
function render(el, rec, table, editable) {
|
|
5608
|
+
var tableEl = El('table').addClass('selectable'),
|
|
5609
|
+
rows = 0;
|
|
5610
|
+
// self.hide(); // clean up if panel is already open
|
|
5611
|
+
el.empty(); // clean up if panel is already open
|
|
5612
|
+
utils.forEachProperty(rec, function(v, k) {
|
|
5613
|
+
var type;
|
|
5614
|
+
// missing GeoJSON fields are set to undefined on import; skip these
|
|
5615
|
+
if (v !== undefined) {
|
|
5616
|
+
type = getFieldType(v, k, table);
|
|
5617
|
+
renderRow(tableEl, rec, k, type, editable);
|
|
5618
|
+
rows++;
|
|
5619
|
+
}
|
|
5620
|
+
});
|
|
5621
|
+
|
|
5622
|
+
if (rows > 0) {
|
|
5623
|
+
tableEl.appendTo(el);
|
|
5624
|
+
|
|
5625
|
+
tableEl.on('copy', function(e) {
|
|
5626
|
+
// remove leading or trailing tabs that sometimes get copied when
|
|
5627
|
+
// selecting from a table
|
|
5628
|
+
var pasted = window.getSelection().toString();
|
|
5629
|
+
var cleaned = pasted.replace(/^\t/, '').replace(/\t$/, '');
|
|
5630
|
+
if (pasted != cleaned && !window.clipboardData) { // ignore ie
|
|
5631
|
+
(e.clipboardData || e.originalEvent.clipboardData).setData("text", cleaned);
|
|
5632
|
+
e.preventDefault(); // don't copy original string with tabs
|
|
5633
|
+
}
|
|
5634
|
+
});
|
|
5635
|
+
|
|
5636
|
+
} else {
|
|
5637
|
+
// Some individual features can have undefined values for some or all of
|
|
5638
|
+
// their data properties (properties are set to undefined when an input JSON file
|
|
5639
|
+
// has inconsistent fields, or after force-merging layers with inconsistent fields).
|
|
5640
|
+
el.html(utils.format('<div class="note">This %s is missing attribute data.</div>',
|
|
5641
|
+
table && table.getFields().length > 0 ? 'feature': 'layer'));
|
|
5798
5642
|
}
|
|
5799
|
-
return {
|
|
5800
|
-
map_bbox: mapBox,
|
|
5801
|
-
page_bbox: pageBox
|
|
5802
|
-
};
|
|
5803
5643
|
}
|
|
5804
5644
|
|
|
5805
|
-
function
|
|
5806
|
-
|
|
5645
|
+
function renderRow(table, rec, key, type, editable) {
|
|
5646
|
+
var rowHtml = '<td class="field-name">%s</td><td><span class="value">%s</span> </td>';
|
|
5647
|
+
var val = rec[key];
|
|
5648
|
+
var str = formatInspectorValue(val, type);
|
|
5649
|
+
var cell = El('tr')
|
|
5650
|
+
.appendTo(table)
|
|
5651
|
+
.html(utils.format(rowHtml, key, utils.htmlEscape(str)))
|
|
5652
|
+
.findChild('.value');
|
|
5653
|
+
setFieldClass(cell, val, type);
|
|
5654
|
+
if (editable) {
|
|
5655
|
+
editItem(cell, rec, key, type);
|
|
5656
|
+
}
|
|
5807
5657
|
}
|
|
5808
5658
|
|
|
5809
|
-
function
|
|
5810
|
-
|
|
5811
|
-
|
|
5659
|
+
function setFieldClass(el, val, type) {
|
|
5660
|
+
var isNum = type ? type == 'number' : utils.isNumber(val);
|
|
5661
|
+
var isNully = val === undefined || val === null || val !== val;
|
|
5662
|
+
var isEmpty = val === '';
|
|
5663
|
+
el.classed('num-field', isNum);
|
|
5664
|
+
el.classed('object-field', type == 'object');
|
|
5665
|
+
el.classed('null-value', isNully);
|
|
5666
|
+
el.classed('empty', isEmpty);
|
|
5812
5667
|
}
|
|
5813
5668
|
|
|
5814
|
-
function
|
|
5815
|
-
|
|
5816
|
-
|
|
5669
|
+
function editItem(el, rec, key, type) {
|
|
5670
|
+
var input = new ClickText2(el),
|
|
5671
|
+
strval = formatInspectorValue(rec[key], type),
|
|
5672
|
+
parser = getInputParser(type);
|
|
5673
|
+
el.parent().addClass('editable-cell');
|
|
5674
|
+
el.addClass('colored-text dot-underline');
|
|
5675
|
+
input.on('change', function(e) {
|
|
5676
|
+
var val2 = parser(input.value()),
|
|
5677
|
+
strval2 = formatInspectorValue(val2, type);
|
|
5678
|
+
if (strval == strval2) {
|
|
5679
|
+
// contents unchanged
|
|
5680
|
+
} else if (val2 === null && type != 'object') { // allow null objects
|
|
5681
|
+
// invalid value; revert to previous value
|
|
5682
|
+
input.value(strval);
|
|
5683
|
+
} else {
|
|
5684
|
+
// field content has changed
|
|
5685
|
+
strval = strval2;
|
|
5686
|
+
gui.dispatchEvent('data_preupdate', {FID: currId}); // for undo/redo
|
|
5687
|
+
rec[key] = val2;
|
|
5688
|
+
gui.dispatchEvent('data_postupdate', {FID: currId});
|
|
5689
|
+
input.value(strval);
|
|
5690
|
+
setFieldClass(el, val2, type);
|
|
5691
|
+
self.dispatchEvent('update', {field: key, value: val2, id: currId});
|
|
5692
|
+
}
|
|
5693
|
+
});
|
|
5817
5694
|
}
|
|
5695
|
+
}
|
|
5818
5696
|
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
return 1 + scale * zoomScaleMultiplier;
|
|
5697
|
+
function formatInspectorValue(val, type) {
|
|
5698
|
+
var str;
|
|
5699
|
+
if (type == 'date') {
|
|
5700
|
+
str = utils.formatDateISO(val);
|
|
5701
|
+
} else if (type == 'object') {
|
|
5702
|
+
str = val ? JSON.stringify(val) : "";
|
|
5703
|
+
} else {
|
|
5704
|
+
str = String(val);
|
|
5828
5705
|
}
|
|
5706
|
+
return str;
|
|
5707
|
+
}
|
|
5829
5708
|
|
|
5830
|
-
|
|
5831
|
-
function
|
|
5832
|
-
var
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5709
|
+
var inputParsers = {
|
|
5710
|
+
date: function(raw) {
|
|
5711
|
+
var d = new Date(raw);
|
|
5712
|
+
return isNaN(+d) ? null : d;
|
|
5713
|
+
},
|
|
5714
|
+
string: function(raw) {
|
|
5715
|
+
return raw;
|
|
5716
|
+
},
|
|
5717
|
+
number: function(raw) {
|
|
5718
|
+
var val = Number(raw);
|
|
5719
|
+
if (raw == 'NaN') {
|
|
5720
|
+
val = NaN;
|
|
5721
|
+
} else if (isNaN(val)) {
|
|
5722
|
+
val = null;
|
|
5723
|
+
}
|
|
5724
|
+
return val;
|
|
5725
|
+
},
|
|
5726
|
+
object: function(raw) {
|
|
5727
|
+
var val = null;
|
|
5728
|
+
try {
|
|
5729
|
+
val = JSON.parse(raw);
|
|
5730
|
+
} catch(e) {}
|
|
5731
|
+
return val;
|
|
5732
|
+
},
|
|
5733
|
+
boolean: function(raw) {
|
|
5734
|
+
var val = null;
|
|
5735
|
+
if (raw == 'true') {
|
|
5736
|
+
val = true;
|
|
5737
|
+
} else if (raw == 'false') {
|
|
5738
|
+
val = false;
|
|
5739
|
+
}
|
|
5740
|
+
return val;
|
|
5741
|
+
},
|
|
5742
|
+
multiple: function(raw) {
|
|
5743
|
+
var val = Number(raw);
|
|
5744
|
+
return isNaN(val) ? raw : val;
|
|
5837
5745
|
}
|
|
5746
|
+
};
|
|
5838
5747
|
|
|
5839
|
-
|
|
5840
|
-
|
|
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);
|
|
5846
|
-
}
|
|
5748
|
+
function getInputParser(type) {
|
|
5749
|
+
return inputParsers[type || 'multiple'];
|
|
5847
5750
|
}
|
|
5848
5751
|
|
|
5849
|
-
function
|
|
5850
|
-
|
|
5851
|
-
|
|
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;
|
|
5752
|
+
function getFieldType(val, key, table) {
|
|
5753
|
+
// if a field has a null value, look at entire column to identify type
|
|
5754
|
+
return internal.getValueType(val) || internal.getColumnType(key, table.getRecords());
|
|
5866
5755
|
}
|
|
5867
5756
|
|
|
5868
|
-
function
|
|
5869
|
-
var
|
|
5870
|
-
var
|
|
5871
|
-
var _on = false;
|
|
5872
|
-
|
|
5873
|
-
gui.addMode('selection_tool', turnOn, turnOff);
|
|
5757
|
+
function InspectionControl2(gui, hit) {
|
|
5758
|
+
var _popup = new Popup(gui, hit.getSwitchTrigger(1), hit.getSwitchTrigger(-1));
|
|
5759
|
+
var _self = new EventDispatcher();
|
|
5874
5760
|
|
|
5875
5761
|
gui.on('interaction_mode_change', function(e) {
|
|
5876
|
-
if (e.mode
|
|
5877
|
-
|
|
5878
|
-
} else if (gui.getMode() == 'selection_tool') {
|
|
5879
|
-
gui.clearMode();
|
|
5762
|
+
if (e.mode == 'off') {
|
|
5763
|
+
inspect(-1); // clear the popup
|
|
5880
5764
|
}
|
|
5881
5765
|
});
|
|
5882
5766
|
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
var b = e.page_bbox;
|
|
5886
|
-
box.show(b[0], b[1], b[2], b[3]);
|
|
5887
|
-
updateSelection(e.map_bbox, true);
|
|
5767
|
+
_popup.on('update', function(e) {
|
|
5768
|
+
_self.dispatchEvent('data_change', e.data); // let map know which field has changed
|
|
5888
5769
|
});
|
|
5889
5770
|
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5771
|
+
hit.on('change', function(e) {
|
|
5772
|
+
var ids;
|
|
5773
|
+
if (!inspecting()) return;
|
|
5774
|
+
ids = e.mode == 'selection' ? null : e.ids;
|
|
5775
|
+
inspect(e.id, e.pinned, ids);
|
|
5894
5776
|
});
|
|
5895
5777
|
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
var
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
hit.addSelectionIds(ids);
|
|
5778
|
+
// id: Id of a feature in the active layer, or -1
|
|
5779
|
+
function inspect(id, pin, ids) {
|
|
5780
|
+
var target = hit.getHitTarget();
|
|
5781
|
+
if (id > -1 && inspecting() && target && target.layer) {
|
|
5782
|
+
_popup.show(id, ids, target.layer, pin);
|
|
5783
|
+
} else {
|
|
5784
|
+
_popup.hide();
|
|
5904
5785
|
}
|
|
5905
5786
|
}
|
|
5906
5787
|
|
|
5907
|
-
|
|
5908
|
-
|
|
5788
|
+
// does the attribute inspector appear on rollover
|
|
5789
|
+
function inspecting() {
|
|
5790
|
+
return gui.interaction && gui.interaction.getMode() != 'off';
|
|
5909
5791
|
}
|
|
5910
5792
|
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
var b = ext.translatePixelCoords(bbox[2], bbox[3]);
|
|
5914
|
-
return [a[0], b[1], b[0], a[1]];
|
|
5915
|
-
}
|
|
5793
|
+
return _self;
|
|
5794
|
+
}
|
|
5916
5795
|
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5796
|
+
// Test if map should be re-framed to show updated layer
|
|
5797
|
+
function mapNeedsReset(newBounds, prevBounds, viewportBounds, flags) {
|
|
5798
|
+
var viewportPct = getIntersectionPct(newBounds, viewportBounds);
|
|
5799
|
+
var contentPct = getIntersectionPct(viewportBounds, newBounds);
|
|
5800
|
+
var boundsChanged = !prevBounds.equals(newBounds);
|
|
5801
|
+
var inView = newBounds.intersects(viewportBounds);
|
|
5802
|
+
var areaChg = newBounds.area() / prevBounds.area();
|
|
5803
|
+
var chgThreshold = flags.proj ? 1e3 : 1e8;
|
|
5804
|
+
// don't reset if layer extent hasn't changed
|
|
5805
|
+
if (!boundsChanged) return false;
|
|
5806
|
+
// reset if layer is out-of-view
|
|
5807
|
+
if (!inView) return true;
|
|
5808
|
+
// reset if content is mostly offscreen
|
|
5809
|
+
if (viewportPct < 0.3 && contentPct < 0.9) return true;
|
|
5810
|
+
// reset if content bounds have changed a lot (e.g. after projection)
|
|
5811
|
+
if (areaChg > chgThreshold || areaChg < 1/chgThreshold) return true;
|
|
5812
|
+
return false;
|
|
5813
|
+
}
|
|
5925
5814
|
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5815
|
+
// Returns proportion of bb2 occupied by bb1
|
|
5816
|
+
function getIntersectionPct(bb1, bb2) {
|
|
5817
|
+
return getBoundsIntersection(bb1, bb2).area() / bb2.area() || 0;
|
|
5818
|
+
}
|
|
5930
5819
|
|
|
5931
|
-
|
|
5932
|
-
|
|
5820
|
+
function getBoundsIntersection(a, b) {
|
|
5821
|
+
var c = new Bounds();
|
|
5822
|
+
if (a.intersects(b)) {
|
|
5823
|
+
c.setBounds(Math.max(a.xmin, b.xmin), Math.max(a.ymin, b.ymin),
|
|
5824
|
+
Math.min(a.xmax, b.xmax), Math.min(a.ymax, b.ymax));
|
|
5933
5825
|
}
|
|
5826
|
+
return c;
|
|
5827
|
+
}
|
|
5934
5828
|
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5829
|
+
function isMultilineLabel(textNode) {
|
|
5830
|
+
return textNode.childNodes.length > 1;
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5833
|
+
function toggleTextAlign(textNode, rec) {
|
|
5834
|
+
var curr = rec['text-anchor'] || 'middle';
|
|
5835
|
+
var value = curr == 'middle' && 'start' || curr == 'start' && 'end' || 'middle';
|
|
5836
|
+
updateTextAnchor(value, textNode, rec);
|
|
5837
|
+
}
|
|
5838
|
+
|
|
5839
|
+
// Set an attribute on a <text> node and any child <tspan> elements
|
|
5840
|
+
// (mapshaper's svg labels require tspans to have the same x and dx values
|
|
5841
|
+
// as the enclosing text node)
|
|
5842
|
+
function setMultilineAttribute(textNode, name, value) {
|
|
5843
|
+
var n = textNode.childNodes.length;
|
|
5844
|
+
var i = -1;
|
|
5845
|
+
var child;
|
|
5846
|
+
textNode.setAttribute(name, value);
|
|
5847
|
+
while (++i < n) {
|
|
5848
|
+
child = textNode.childNodes[i];
|
|
5849
|
+
if (child.tagName == 'tspan') {
|
|
5850
|
+
child.setAttribute(name, value);
|
|
5945
5851
|
}
|
|
5946
|
-
}
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5947
5854
|
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5855
|
+
function findSvgRoot(el) {
|
|
5856
|
+
while (el && el.tagName != 'html' && el.tagName != 'body') {
|
|
5857
|
+
if (el.tagName == 'svg') return el;
|
|
5858
|
+
el = el.parentNode;
|
|
5859
|
+
}
|
|
5860
|
+
return null;
|
|
5861
|
+
}
|
|
5952
5862
|
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5863
|
+
// p: pixel coordinates of label anchor
|
|
5864
|
+
function autoUpdateTextAnchor(textNode, rec, p) {
|
|
5865
|
+
var svg = findSvgRoot(textNode);
|
|
5866
|
+
var rect = textNode.getBoundingClientRect();
|
|
5867
|
+
var labelCenterX = rect.left - svg.getBoundingClientRect().left + rect.width / 2;
|
|
5868
|
+
var xpct = (labelCenterX - p[0]) / rect.width; // offset of label center from anchor center
|
|
5869
|
+
var value = xpct < -0.25 && 'end' || xpct > 0.25 && 'start' || 'middle';
|
|
5870
|
+
updateTextAnchor(value, textNode, rec);
|
|
5871
|
+
}
|
|
5957
5872
|
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5873
|
+
// @value: optional position to set; if missing, auto-set
|
|
5874
|
+
function updateTextAnchor(value, textNode, rec) {
|
|
5875
|
+
var rect = textNode.getBoundingClientRect();
|
|
5876
|
+
var width = rect.width;
|
|
5877
|
+
var curr = rec['text-anchor'] || 'middle';
|
|
5878
|
+
var xshift = 0;
|
|
5962
5879
|
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5880
|
+
// console.log("anchor() curr:", curr, "xpct:", xpct, "left:", rect.left, "anchorX:", anchorX, "targ:", targ, "dx:", xshift)
|
|
5881
|
+
if (curr == 'middle' && value == 'end' || curr == 'start' && value == 'middle') {
|
|
5882
|
+
xshift = width / 2;
|
|
5883
|
+
} else if (curr == 'middle' && value == 'start' || curr == 'end' && value == 'middle') {
|
|
5884
|
+
xshift = -width / 2;
|
|
5885
|
+
} else if (curr == 'start' && value == 'end') {
|
|
5886
|
+
xshift = width;
|
|
5887
|
+
} else if (curr == 'end' && value == 'start') {
|
|
5888
|
+
xshift = -width;
|
|
5889
|
+
}
|
|
5890
|
+
if (xshift) {
|
|
5891
|
+
rec['text-anchor'] = value;
|
|
5892
|
+
applyDelta(rec, 'dx', Math.round(xshift));
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
|
|
5896
|
+
// handle either numeric strings or numbers in fields
|
|
5897
|
+
function applyDelta(rec, key, delta) {
|
|
5898
|
+
var currVal = rec[key];
|
|
5899
|
+
var isString = utils.isString(currVal);
|
|
5900
|
+
var newVal = (+currVal + delta) || 0;
|
|
5901
|
+
rec[key] = isString ? String(newVal) : newVal;
|
|
5902
|
+
}
|
|
5966
5903
|
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5904
|
+
var snapVerticesToPoint = internal.snapVerticesToPoint;
|
|
5905
|
+
|
|
5906
|
+
function getDisplayCoordsById(id, layer, ext) {
|
|
5907
|
+
var coords = getPointCoordsById(id, layer);
|
|
5908
|
+
return ext.translateCoords(coords[0], coords[1]);
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
function getPointCoordsById(id, layer) {
|
|
5912
|
+
var coords = layer && layer.geometry_type == 'point' && layer.shapes[id];
|
|
5913
|
+
if (!coords || coords.length != 1) {
|
|
5914
|
+
return null;
|
|
5972
5915
|
}
|
|
5916
|
+
return coords[0];
|
|
5973
5917
|
}
|
|
5974
5918
|
|
|
5975
|
-
|
|
5976
|
-
|
|
5919
|
+
function translateDeltaDisplayCoords(dx, dy, ext) {
|
|
5920
|
+
var a = ext.translatePixelCoords(0, 0);
|
|
5921
|
+
var b = ext.translatePixelCoords(dx, dy);
|
|
5922
|
+
return [b[0] - a[0], b[1] - a[1]];
|
|
5923
|
+
}
|
|
5924
|
+
|
|
5925
|
+
|
|
5926
|
+
function InteractiveEditor(gui, ext, hit) {
|
|
5927
|
+
// var targetTextNode; // text node currently being dragged
|
|
5928
|
+
var dragging = false;
|
|
5929
|
+
var activeRecord;
|
|
5930
|
+
var activeId = -1;
|
|
5977
5931
|
var self = new EventDispatcher();
|
|
5978
|
-
var
|
|
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;
|
|
5932
|
+
var activeVertexIds = null; // for vertex dragging
|
|
5989
5933
|
|
|
5990
|
-
|
|
5934
|
+
initDragging();
|
|
5991
5935
|
|
|
5992
|
-
|
|
5993
|
-
prevLink.on('click', toPrev);
|
|
5994
|
-
gui.on('popup-needs-refresh', function() {
|
|
5995
|
-
if (refresh) refresh();
|
|
5996
|
-
});
|
|
5936
|
+
return self;
|
|
5997
5937
|
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
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();
|
|
6014
|
-
}
|
|
6015
|
-
el.show();
|
|
6016
|
-
if (content.node().clientHeight > maxHeight) {
|
|
6017
|
-
content.css('height:' + maxHeight + 'px');
|
|
6018
|
-
}
|
|
6019
|
-
};
|
|
5938
|
+
function labelEditingEnabled() {
|
|
5939
|
+
return gui.interaction && gui.interaction.getMode() == 'labels' ? true : false;
|
|
5940
|
+
}
|
|
6020
5941
|
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
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
|
-
};
|
|
5942
|
+
function locationEditingEnabled() {
|
|
5943
|
+
return gui.interaction && gui.interaction.getMode() == 'location' ? true : false;
|
|
5944
|
+
}
|
|
6031
5945
|
|
|
6032
|
-
|
|
5946
|
+
function vertexEditingEnabled() {
|
|
5947
|
+
return gui.interaction && gui.interaction.getMode() == 'vertices' ? true : false;
|
|
5948
|
+
}
|
|
6033
5949
|
|
|
6034
|
-
|
|
6035
|
-
|
|
5950
|
+
// update symbol by setting attributes
|
|
5951
|
+
function updateSymbol(node, d) {
|
|
5952
|
+
var a = d['text-anchor'];
|
|
5953
|
+
if (a) node.setAttribute('text-anchor', a);
|
|
5954
|
+
setMultilineAttribute(node, 'dx', d.dx || 0);
|
|
5955
|
+
node.setAttribute('y', d.dy || 0);
|
|
6036
5956
|
}
|
|
6037
5957
|
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
5958
|
+
// update symbol by re-rendering it
|
|
5959
|
+
function updateSymbol2(node, d, id) {
|
|
5960
|
+
var o = internal.svg.importStyledLabel(d); // TODO: symbol support
|
|
5961
|
+
var activeLayer = hit.getHitTarget().layer;
|
|
5962
|
+
var xy = activeLayer.shapes[id][0];
|
|
5963
|
+
var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
5964
|
+
var node2;
|
|
5965
|
+
o.properties.transform = getSvgSymbolTransform(xy, ext);
|
|
5966
|
+
o.properties['data-id'] = id;
|
|
5967
|
+
// o.properties['class'] = 'selected';
|
|
5968
|
+
g.innerHTML = internal.svg.stringify(o);
|
|
5969
|
+
node2 = g.firstChild;
|
|
5970
|
+
node.parentNode.replaceChild(node2, node);
|
|
5971
|
+
gui.dispatchEvent('popup-needs-refresh');
|
|
5972
|
+
return node2;
|
|
6044
5973
|
}
|
|
6045
5974
|
|
|
6046
|
-
function
|
|
6047
|
-
var
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
5975
|
+
function initDragging() {
|
|
5976
|
+
var downEvt;
|
|
5977
|
+
var eventPriority = 1;
|
|
5978
|
+
|
|
5979
|
+
// inspector and label editing aren't fully synced - stop editing if inspector opens
|
|
5980
|
+
// gui.on('inspector_on', function() {
|
|
5981
|
+
// stopEditing();
|
|
5982
|
+
// });
|
|
5983
|
+
|
|
5984
|
+
gui.on('interaction_mode_change', function(e) {
|
|
5985
|
+
if (e.mode != 'labels') {
|
|
5986
|
+
stopDragging();
|
|
6058
5987
|
}
|
|
5988
|
+
gui.undo.clear(); // TODO: put this elsewhere?
|
|
6059
5989
|
});
|
|
6060
5990
|
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
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
|
-
});
|
|
5991
|
+
// down event on svg
|
|
5992
|
+
// a: off text
|
|
5993
|
+
// -> stop editing
|
|
5994
|
+
// b: on text
|
|
5995
|
+
// 1: not editing -> nop
|
|
5996
|
+
// 2: on selected text -> start dragging
|
|
5997
|
+
// 3: on other text -> stop dragging, select new text
|
|
6074
5998
|
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
5999
|
+
hit.on('dragstart', function(e) {
|
|
6000
|
+
if (e.id >= 0 === false) return;
|
|
6001
|
+
if (labelEditingEnabled() && onLabelDragStart(e)) {
|
|
6002
|
+
triggerGlobalEvent('label_dragstart', e);
|
|
6003
|
+
startDragging();
|
|
6004
|
+
} else if (locationEditingEnabled()) {
|
|
6005
|
+
triggerGlobalEvent('symbol_dragstart', e);
|
|
6006
|
+
startDragging();
|
|
6007
|
+
} else if (vertexEditingEnabled()) {
|
|
6008
|
+
onVertexDragStart(e);
|
|
6009
|
+
triggerGlobalEvent('vertex_dragstart', e);
|
|
6010
|
+
startDragging();
|
|
6011
|
+
}
|
|
6012
|
+
});
|
|
6083
6013
|
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
if (editable) {
|
|
6094
|
-
editItem(cell, rec, key, type);
|
|
6095
|
-
}
|
|
6096
|
-
}
|
|
6014
|
+
hit.on('drag', function(e) {
|
|
6015
|
+
if (labelEditingEnabled()) {
|
|
6016
|
+
onLabelDrag(e);
|
|
6017
|
+
} else if (locationEditingEnabled()) {
|
|
6018
|
+
onLocationDrag(e);
|
|
6019
|
+
} else if (vertexEditingEnabled()) {
|
|
6020
|
+
onVertexDrag(e);
|
|
6021
|
+
}
|
|
6022
|
+
});
|
|
6097
6023
|
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6024
|
+
hit.on('dragend', function(e) {
|
|
6025
|
+
if (locationEditingEnabled()) {
|
|
6026
|
+
triggerGlobalEvent('symbol_dragend', e);
|
|
6027
|
+
stopDragging();
|
|
6028
|
+
} else if (labelEditingEnabled()) {
|
|
6029
|
+
triggerGlobalEvent('label_dragend', e);
|
|
6030
|
+
stopDragging();
|
|
6031
|
+
} else if (vertexEditingEnabled()) {
|
|
6032
|
+
// kludge to get dataset to recalculate internal bounding boxes
|
|
6033
|
+
hit.getHitTarget().arcs.transformPoints(function() {});
|
|
6034
|
+
triggerGlobalEvent('vertex_dragend', e);
|
|
6035
|
+
stopDragging();
|
|
6036
|
+
}
|
|
6037
|
+
});
|
|
6107
6038
|
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
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});
|
|
6039
|
+
hit.on('click', function(e) {
|
|
6040
|
+
if (labelEditingEnabled()) {
|
|
6041
|
+
var target = hit.getHitTarget();
|
|
6042
|
+
onLabelClick(e);
|
|
6131
6043
|
}
|
|
6132
6044
|
});
|
|
6133
|
-
}
|
|
6134
|
-
}
|
|
6135
6045
|
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
} else {
|
|
6143
|
-
str = String(val);
|
|
6144
|
-
}
|
|
6145
|
-
return str;
|
|
6146
|
-
}
|
|
6046
|
+
// TODO: highlight hit vertex in path edit mode
|
|
6047
|
+
if (false) hit.on('hover', function(e) {
|
|
6048
|
+
if (vertexEditingEnabled() && !dragging) {
|
|
6049
|
+
onVertexHover(e);
|
|
6050
|
+
}
|
|
6051
|
+
}, null, 100);
|
|
6147
6052
|
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
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;
|
|
6053
|
+
function onVertexHover(e) {
|
|
6054
|
+
// hovering in vertex edit mode: find vertex insertion point
|
|
6055
|
+
var target = hit.getHitTarget();
|
|
6056
|
+
var shp = target.layer.shapes[e.id];
|
|
6057
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
6058
|
+
var o = internal.findInsertionPoint(p, shp, target.arcs, ext.getPixelSize());
|
|
6162
6059
|
}
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
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;
|
|
6060
|
+
|
|
6061
|
+
|
|
6062
|
+
function getVertexEventData(e) {
|
|
6063
|
+
return {
|
|
6064
|
+
FID: activeId,
|
|
6065
|
+
vertexIds: activeVertexIds
|
|
6066
|
+
};
|
|
6178
6067
|
}
|
|
6179
|
-
return val;
|
|
6180
|
-
},
|
|
6181
|
-
multiple: function(raw) {
|
|
6182
|
-
var val = Number(raw);
|
|
6183
|
-
return isNaN(val) ? raw : val;
|
|
6184
|
-
}
|
|
6185
|
-
};
|
|
6186
6068
|
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6069
|
+
function onLocationDrag(e) {
|
|
6070
|
+
var lyr = hit.getHitTarget().layer;
|
|
6071
|
+
var p = getPointCoordsById(e.id, lyr);
|
|
6072
|
+
if (!p) return;
|
|
6073
|
+
var diff = translateDeltaDisplayCoords(e.dx, e.dy, ext);
|
|
6074
|
+
p[0] += diff[0];
|
|
6075
|
+
p[1] += diff[1];
|
|
6076
|
+
triggerRedraw();
|
|
6077
|
+
triggerGlobalEvent('symbol_drag', e);
|
|
6078
|
+
}
|
|
6190
6079
|
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6080
|
+
function onVertexDragStart(e) {
|
|
6081
|
+
var target = hit.getHitTarget();
|
|
6082
|
+
var shp = target.layer.shapes[e.id];
|
|
6083
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
6084
|
+
activeVertexIds = internal.findNearestVertices(p, shp, target.arcs);
|
|
6085
|
+
activeId = e.id;
|
|
6086
|
+
}
|
|
6195
6087
|
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6088
|
+
function onVertexDrag(e) {
|
|
6089
|
+
var target = hit.getHitTarget();
|
|
6090
|
+
if (!activeVertexIds) return; // ignore error condition
|
|
6091
|
+
var p = ext.translatePixelCoords(e.x, e.y);
|
|
6092
|
+
if (gui.keyboard.shiftIsPressed()) {
|
|
6093
|
+
internal.snapPointToArcEndpoint(p, activeVertexIds, target.arcs);
|
|
6094
|
+
}
|
|
6095
|
+
snapVerticesToPoint(activeVertexIds, p, target.arcs);
|
|
6096
|
+
triggerRedraw();
|
|
6097
|
+
}
|
|
6199
6098
|
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6099
|
+
function onLabelClick(e) {
|
|
6100
|
+
var textNode = getTextTarget3(e);
|
|
6101
|
+
var rec = getLabelRecordById(e.id);
|
|
6102
|
+
if (textNode && rec && isMultilineLabel(textNode)) {
|
|
6103
|
+
toggleTextAlign(textNode, rec);
|
|
6104
|
+
updateSymbol2(textNode, rec, e.id);
|
|
6105
|
+
// e.stopPropagation(); // prevent pin/unpin on popup
|
|
6106
|
+
}
|
|
6203
6107
|
}
|
|
6204
|
-
});
|
|
6205
6108
|
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6109
|
+
function triggerRedraw() {
|
|
6110
|
+
gui.dispatchEvent('map-needs-refresh');
|
|
6111
|
+
}
|
|
6209
6112
|
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6113
|
+
function triggerGlobalEvent(type, e) {
|
|
6114
|
+
if (e.id >= 0 === false) return;
|
|
6115
|
+
var o = {
|
|
6116
|
+
FID: e.id,
|
|
6117
|
+
layer_name: hit.getHitTarget().layer.name,
|
|
6118
|
+
vertex_ids: activeVertexIds
|
|
6119
|
+
};
|
|
6120
|
+
// fire event to signal external editor that symbol coords have changed
|
|
6121
|
+
gui.dispatchEvent(type, o);
|
|
6122
|
+
}
|
|
6216
6123
|
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6124
|
+
function getLabelRecordById(id) {
|
|
6125
|
+
var table = hit.getTargetDataTable();
|
|
6126
|
+
if (id >= 0 === false || !table) return null;
|
|
6127
|
+
// add dx and dy properties, if not available
|
|
6128
|
+
if (!table.fieldExists('dx')) {
|
|
6129
|
+
table.addField('dx', 0);
|
|
6130
|
+
}
|
|
6131
|
+
if (!table.fieldExists('dy')) {
|
|
6132
|
+
table.addField('dy', 0);
|
|
6133
|
+
}
|
|
6134
|
+
if (!table.fieldExists('text-anchor')) {
|
|
6135
|
+
table.addField('text-anchor', '');
|
|
6136
|
+
}
|
|
6137
|
+
return table.getRecordAt(id);
|
|
6224
6138
|
}
|
|
6225
|
-
}
|
|
6226
6139
|
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6140
|
+
function onLabelDragStart(e) {
|
|
6141
|
+
var textNode = getTextTarget3(e);
|
|
6142
|
+
var table = hit.getTargetDataTable();
|
|
6143
|
+
if (!textNode || !table) return false;
|
|
6144
|
+
activeId = e.id;
|
|
6145
|
+
activeRecord = getLabelRecordById(activeId);
|
|
6146
|
+
downEvt = e;
|
|
6147
|
+
return true;
|
|
6148
|
+
}
|
|
6231
6149
|
|
|
6232
|
-
|
|
6233
|
-
|
|
6150
|
+
function onLabelDrag(e) {
|
|
6151
|
+
var scale = ext.getSymbolScale() || 1;
|
|
6152
|
+
var textNode;
|
|
6153
|
+
if (!dragging) return;
|
|
6154
|
+
if (e.id != activeId) {
|
|
6155
|
+
error("Mismatched hit ids:", e.id, activeId);
|
|
6156
|
+
}
|
|
6157
|
+
applyDelta(activeRecord, 'dx', e.dx / scale);
|
|
6158
|
+
applyDelta(activeRecord, 'dy', e.dy / scale);
|
|
6159
|
+
textNode = getTextTarget3(e);
|
|
6160
|
+
if (!isMultilineLabel(textNode)) {
|
|
6161
|
+
// update anchor position of single-line labels based on label position
|
|
6162
|
+
// relative to anchor point, for better placement when eventual display font is
|
|
6163
|
+
// different from mapshaper's font.
|
|
6164
|
+
autoUpdateTextAnchor(textNode, activeRecord, getDisplayCoordsById(activeId, hit.getHitTarget().layer, ext));
|
|
6165
|
+
}
|
|
6166
|
+
// updateSymbol(targetTextNode, activeRecord);
|
|
6167
|
+
updateSymbol2(textNode, activeRecord, activeId);
|
|
6168
|
+
}
|
|
6234
6169
|
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
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
|
-
}
|
|
6170
|
+
function getSymbolNodeById(id, parent) {
|
|
6171
|
+
// TODO: optimize selector
|
|
6172
|
+
var sel = '[data-id="' + id + '"]';
|
|
6173
|
+
return parent.querySelector(sel);
|
|
6174
|
+
}
|
|
6253
6175
|
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6176
|
+
function getTextTarget3(e) {
|
|
6177
|
+
if (e.id > -1 === false || !e.container) return null;
|
|
6178
|
+
return getSymbolNodeById(e.id, e.container);
|
|
6179
|
+
}
|
|
6258
6180
|
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6181
|
+
function getTextTarget2(e) {
|
|
6182
|
+
var el = e && e.targetSymbol || null;
|
|
6183
|
+
if (el && el.tagName == 'tspan') {
|
|
6184
|
+
el = el.parentNode;
|
|
6185
|
+
}
|
|
6186
|
+
return el && el.tagName == 'text' ? el : null;
|
|
6187
|
+
}
|
|
6188
|
+
|
|
6189
|
+
function getTextTarget(e) {
|
|
6190
|
+
var el = e.target;
|
|
6191
|
+
if (el.tagName == 'tspan') {
|
|
6192
|
+
el = el.parentNode;
|
|
6193
|
+
}
|
|
6194
|
+
return el.tagName == 'text' ? el : null;
|
|
6195
|
+
}
|
|
6264
6196
|
}
|
|
6265
|
-
|
|
6197
|
+
|
|
6198
|
+
function startDragging() {
|
|
6199
|
+
dragging = true;
|
|
6200
|
+
}
|
|
6201
|
+
|
|
6202
|
+
function stopDragging() {
|
|
6203
|
+
dragging = false;
|
|
6204
|
+
activeId = -1;
|
|
6205
|
+
activeRecord = null;
|
|
6206
|
+
activeVertexIds = null;
|
|
6207
|
+
}
|
|
6208
|
+
|
|
6209
|
+
function isClickEvent(up, down) {
|
|
6210
|
+
var elapsed = Math.abs(down.timeStamp - up.timeStamp);
|
|
6211
|
+
var dx = up.screenX - down.screenX;
|
|
6212
|
+
var dy = up.screenY - down.screenY;
|
|
6213
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
6214
|
+
return dist <= 4 && elapsed < 300;
|
|
6215
|
+
}
|
|
6216
|
+
|
|
6266
6217
|
}
|
|
6267
6218
|
|
|
6268
6219
|
var darkStroke = "#334",
|
|
@@ -6620,6 +6571,7 @@
|
|
|
6620
6571
|
|
|
6621
6572
|
this.maxScale = maxScale;
|
|
6622
6573
|
|
|
6574
|
+
// Display scale, e.g. meters per pixel or degrees per pixel
|
|
6623
6575
|
this.getPixelSize = function() {
|
|
6624
6576
|
return 1 / this.getTransform().mx;
|
|
6625
6577
|
};
|
|
@@ -6904,7 +6856,7 @@
|
|
|
6904
6856
|
_self.drawVertices = function(shapes, arcs, style, filter) {
|
|
6905
6857
|
var iter = new internal.ShapeIter(arcs);
|
|
6906
6858
|
var t = getScaledTransform(_ext);
|
|
6907
|
-
var radius = (style.strokeWidth * 0.
|
|
6859
|
+
var radius = (style.strokeWidth * 0.9 || 2.2) * GUI.getPixelRatio() * getScaledLineScale(_ext);
|
|
6908
6860
|
var color = style.strokeColor || 'black';
|
|
6909
6861
|
var shp;
|
|
6910
6862
|
_ctx.beginPath();
|
|
@@ -7990,7 +7942,7 @@
|
|
|
7990
7942
|
_mouse.disable();
|
|
7991
7943
|
});
|
|
7992
7944
|
|
|
7993
|
-
gui.on('
|
|
7945
|
+
gui.on('map-needs-refresh', function() {
|
|
7994
7946
|
drawLayers();
|
|
7995
7947
|
});
|
|
7996
7948
|
|
|
@@ -8152,13 +8104,7 @@
|
|
|
8152
8104
|
});
|
|
8153
8105
|
}
|
|
8154
8106
|
|
|
8155
|
-
|
|
8156
|
-
_editor = new SymbolDragging2(gui, _ext, _hit);
|
|
8157
|
-
_editor.on('location_change', function(e) {
|
|
8158
|
-
// TODO: look into optimizing, so only changed symbol is redrawn
|
|
8159
|
-
drawLayers();
|
|
8160
|
-
});
|
|
8161
|
-
}
|
|
8107
|
+
_editor = new InteractiveEditor(gui, _ext, _hit);
|
|
8162
8108
|
|
|
8163
8109
|
_ext.on('change', function(e) {
|
|
8164
8110
|
if (e.reset) return; // don't need to redraw map here if extent has been reset
|