mapshaper 0.7.39 → 0.7.41
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 +17771 -17192
- package/package.json +1 -1
- package/www/mapshaper-gui.js +776 -19
- package/www/mapshaper.js +17771 -17192
- package/www/page.css +68 -1
package/www/mapshaper-gui.js
CHANGED
|
@@ -2447,7 +2447,7 @@
|
|
|
2447
2447
|
return obj === Object(obj); // via underscore
|
|
2448
2448
|
}
|
|
2449
2449
|
|
|
2450
|
-
function clamp$
|
|
2450
|
+
function clamp$2(val, min, max) {
|
|
2451
2451
|
return val < min ? min : (val > max ? max : val);
|
|
2452
2452
|
}
|
|
2453
2453
|
|
|
@@ -3128,7 +3128,7 @@
|
|
|
3128
3128
|
function findValueByRank(arr, rank) {
|
|
3129
3129
|
if (!arr.length || rank < 1 || rank > arr.length) error$1("[findValueByRank()] invalid input");
|
|
3130
3130
|
|
|
3131
|
-
rank = clamp$
|
|
3131
|
+
rank = clamp$2(rank | 0, 1, arr.length);
|
|
3132
3132
|
var k = rank - 1, // conv. rank to array index
|
|
3133
3133
|
n = arr.length,
|
|
3134
3134
|
l = 0,
|
|
@@ -3502,7 +3502,7 @@
|
|
|
3502
3502
|
// self-import and the resulting Rollup circular-dependency warning.
|
|
3503
3503
|
var utils = {
|
|
3504
3504
|
addThousandsSep, addslashes, arrayToIndex,
|
|
3505
|
-
clamp: clamp$
|
|
3505
|
+
clamp: clamp$2, cleanNumericString, contains, copyElements, countValues, createBuffer,
|
|
3506
3506
|
defaults, difference,
|
|
3507
3507
|
endsWith, every, expandoBuffer, extend, extendBuffer,
|
|
3508
3508
|
find, findMedian, findQuantile, findRankByValue, findStringPrefix,
|
|
@@ -12860,15 +12860,15 @@
|
|
|
12860
12860
|
function InteractionMode(gui) {
|
|
12861
12861
|
|
|
12862
12862
|
var menus = {
|
|
12863
|
-
standard: ['info', 'selection', 'box'],
|
|
12864
|
-
empty: ['edit_polygons', 'edit_lines', 'edit_points', 'box'],
|
|
12865
|
-
polygons: ['info', 'selection', 'box', 'polygon_style', 'edit_polygons'],
|
|
12866
|
-
rectangles: ['info', 'selection', 'box', 'polygon_style', 'rectangles', 'edit_polygons'],
|
|
12867
|
-
lines: ['info', 'selection', 'box', 'line_style', 'edit_lines'], // 'snip_lines'
|
|
12863
|
+
standard: ['info', 'selection', 'box', 'ruler'],
|
|
12864
|
+
empty: ['edit_polygons', 'edit_lines', 'edit_points', 'box', 'ruler'],
|
|
12865
|
+
polygons: ['info', 'selection', 'box', 'polygon_style', 'edit_polygons', 'ruler'],
|
|
12866
|
+
rectangles: ['info', 'selection', 'box', 'polygon_style', 'rectangles', 'edit_polygons', 'ruler'],
|
|
12867
|
+
lines: ['info', 'selection', 'box', 'line_style', 'edit_lines', 'ruler'], // 'snip_lines'
|
|
12868
12868
|
table: ['info', 'selection'],
|
|
12869
|
-
raster: ['box'],
|
|
12870
|
-
labels: ['info', 'selection', 'box', 'point_style', 'labels', 'edit_points'],
|
|
12871
|
-
points: ['info', 'selection', 'box', 'point_style', 'edit_points'] // , 'add-points'
|
|
12869
|
+
raster: ['ruler', 'box'],
|
|
12870
|
+
labels: ['info', 'selection', 'box', 'point_style', 'labels', 'edit_points', 'ruler'],
|
|
12871
|
+
points: ['info', 'selection', 'box', 'point_style', 'edit_points', 'ruler'] // , 'add-points'
|
|
12872
12872
|
};
|
|
12873
12873
|
|
|
12874
12874
|
var prompts = {
|
|
@@ -12893,6 +12893,7 @@
|
|
|
12893
12893
|
snip_lines: 'snip polylines',
|
|
12894
12894
|
vertices: 'edit vertices',
|
|
12895
12895
|
selection: 'selection tool',
|
|
12896
|
+
ruler: 'measure distance',
|
|
12896
12897
|
'add-points': 'add points',
|
|
12897
12898
|
rectangles: 'drag-to-resize',
|
|
12898
12899
|
off: 'turn off'
|
|
@@ -14028,16 +14029,16 @@
|
|
|
14028
14029
|
function getCanvasPoint(canvas, evt) {
|
|
14029
14030
|
var rect = canvas.getBoundingClientRect();
|
|
14030
14031
|
return {
|
|
14031
|
-
x: clamp(Math.round((evt.clientX - rect.left) / rect.width * (canvas.width - 1)), 0, canvas.width - 1),
|
|
14032
|
-
y: clamp(Math.round((evt.clientY - rect.top) / rect.height * (canvas.height - 1)), 0, canvas.height - 1)
|
|
14032
|
+
x: clamp$1(Math.round((evt.clientX - rect.left) / rect.width * (canvas.width - 1)), 0, canvas.width - 1),
|
|
14033
|
+
y: clamp$1(Math.round((evt.clientY - rect.top) / rect.height * (canvas.height - 1)), 0, canvas.height - 1)
|
|
14033
14034
|
};
|
|
14034
14035
|
}
|
|
14035
14036
|
|
|
14036
14037
|
function setPickerColor(hsb) {
|
|
14037
14038
|
pickerColor = {
|
|
14038
|
-
h: clamp(Math.round(hsb.h), 0, 255),
|
|
14039
|
-
s: clamp(Math.round(hsb.s), 0, 255),
|
|
14040
|
-
b: clamp(Math.round(hsb.b), 0, 255)
|
|
14039
|
+
h: clamp$1(Math.round(hsb.h), 0, 255),
|
|
14040
|
+
s: clamp$1(Math.round(hsb.s), 0, 255),
|
|
14041
|
+
b: clamp$1(Math.round(hsb.b), 0, 255)
|
|
14041
14042
|
};
|
|
14042
14043
|
drawColorPicker();
|
|
14043
14044
|
updatePickerFields();
|
|
@@ -14178,7 +14179,7 @@
|
|
|
14178
14179
|
}
|
|
14179
14180
|
|
|
14180
14181
|
function pctToByte(val) {
|
|
14181
|
-
return Math.round(clamp(val, 0, 100) / 100 * 255);
|
|
14182
|
+
return Math.round(clamp$1(val, 0, 100) / 100 * 255);
|
|
14182
14183
|
}
|
|
14183
14184
|
|
|
14184
14185
|
function byteToDegrees(val) {
|
|
@@ -14186,14 +14187,14 @@
|
|
|
14186
14187
|
}
|
|
14187
14188
|
|
|
14188
14189
|
function degreesToByte(val) {
|
|
14189
|
-
return Math.round(clamp(val, 0, 360) / 360 * 255);
|
|
14190
|
+
return Math.round(clamp$1(val, 0, 360) / 360 * 255);
|
|
14190
14191
|
}
|
|
14191
14192
|
|
|
14192
14193
|
function parseNumberField(str) {
|
|
14193
14194
|
return Number(String(str).replace(/[°%]/g, '').trim());
|
|
14194
14195
|
}
|
|
14195
14196
|
|
|
14196
|
-
function clamp(val, min, max) {
|
|
14197
|
+
function clamp$1(val, min, max) {
|
|
14197
14198
|
return isFinite(val) ? Math.max(min, Math.min(max, val)) : min;
|
|
14198
14199
|
}
|
|
14199
14200
|
|
|
@@ -22554,6 +22555,760 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
22554
22555
|
}
|
|
22555
22556
|
}
|
|
22556
22557
|
|
|
22558
|
+
function getDistanceDisplay(distanceMeters, unitHint) {
|
|
22559
|
+
var unit = unitHint || (distanceMeters >= 1000 ? 'km' : 'm');
|
|
22560
|
+
var value = unit == 'km' ? distanceMeters / 1000 : distanceMeters;
|
|
22561
|
+
return {
|
|
22562
|
+
value,
|
|
22563
|
+
unit,
|
|
22564
|
+
label: formatDistanceValue(value) + ' ' + unit
|
|
22565
|
+
};
|
|
22566
|
+
}
|
|
22567
|
+
|
|
22568
|
+
function formatDistanceValue(value) {
|
|
22569
|
+
var decimals = getDistanceDecimals(value);
|
|
22570
|
+
return value.toFixed(decimals);
|
|
22571
|
+
}
|
|
22572
|
+
|
|
22573
|
+
function getDistanceUnit(distanceMeters) {
|
|
22574
|
+
return distanceMeters >= 1000 ? 'km' : 'm';
|
|
22575
|
+
}
|
|
22576
|
+
|
|
22577
|
+
function pointIsInLngLatRange(p) {
|
|
22578
|
+
return !!p && p[0] >= -180 && p[0] <= 180 && p[1] >= -90 && p[1] <= 90;
|
|
22579
|
+
}
|
|
22580
|
+
|
|
22581
|
+
function segmentCrossesAntimeridian(a, b) {
|
|
22582
|
+
return pointIsInLngLatRange(a) && pointIsInLngLatRange(b) && Math.abs(a[0] - b[0]) > 180;
|
|
22583
|
+
}
|
|
22584
|
+
|
|
22585
|
+
function pointIsNearPole(p) {
|
|
22586
|
+
return !!p && Math.abs(p[1]) >= 89.9;
|
|
22587
|
+
}
|
|
22588
|
+
|
|
22589
|
+
function interpolateGreatCirclePoint(a, b, k) {
|
|
22590
|
+
var v1 = lngLatToUnitVector(a[0], a[1]);
|
|
22591
|
+
var v2 = lngLatToUnitVector(b[0], b[1]);
|
|
22592
|
+
var dot = clamp(dotProduct(v1, v2), -1, 1);
|
|
22593
|
+
var omega = Math.acos(dot);
|
|
22594
|
+
var sinOmega = Math.sin(omega);
|
|
22595
|
+
var u, v;
|
|
22596
|
+
if (k === 0) return a.concat();
|
|
22597
|
+
if (k === 1) return b.concat();
|
|
22598
|
+
if (sinOmega < 1e-12) {
|
|
22599
|
+
return unitVectorToLngLat(normalizeVector([
|
|
22600
|
+
v1[0] * (1 - k) + v2[0] * k,
|
|
22601
|
+
v1[1] * (1 - k) + v2[1] * k,
|
|
22602
|
+
v1[2] * (1 - k) + v2[2] * k
|
|
22603
|
+
]));
|
|
22604
|
+
}
|
|
22605
|
+
u = Math.sin((1 - k) * omega) / sinOmega;
|
|
22606
|
+
v = Math.sin(k * omega) / sinOmega;
|
|
22607
|
+
return unitVectorToLngLat([
|
|
22608
|
+
v1[0] * u + v2[0] * v,
|
|
22609
|
+
v1[1] * u + v2[1] * v,
|
|
22610
|
+
v1[2] * u + v2[2] * v
|
|
22611
|
+
]);
|
|
22612
|
+
}
|
|
22613
|
+
|
|
22614
|
+
function getDistanceDecimals(value) {
|
|
22615
|
+
if (value >= 100) return 0;
|
|
22616
|
+
if (value >= 10) return 1;
|
|
22617
|
+
if (value >= 1) return 2;
|
|
22618
|
+
return 3;
|
|
22619
|
+
}
|
|
22620
|
+
|
|
22621
|
+
function lngLatToUnitVector(lng, lat) {
|
|
22622
|
+
var D2R = Math.PI / 180;
|
|
22623
|
+
var lngRad = lng * D2R;
|
|
22624
|
+
var latRad = lat * D2R;
|
|
22625
|
+
var cosLat = Math.cos(latRad);
|
|
22626
|
+
return [
|
|
22627
|
+
Math.cos(lngRad) * cosLat,
|
|
22628
|
+
Math.sin(lngRad) * cosLat,
|
|
22629
|
+
Math.sin(latRad)
|
|
22630
|
+
];
|
|
22631
|
+
}
|
|
22632
|
+
|
|
22633
|
+
function unitVectorToLngLat(v) {
|
|
22634
|
+
var R2D = 180 / Math.PI;
|
|
22635
|
+
return [
|
|
22636
|
+
Math.atan2(v[1], v[0]) * R2D,
|
|
22637
|
+
Math.atan2(v[2], Math.sqrt(v[0] * v[0] + v[1] * v[1])) * R2D
|
|
22638
|
+
];
|
|
22639
|
+
}
|
|
22640
|
+
|
|
22641
|
+
function normalizeVector(v) {
|
|
22642
|
+
var len = Math.sqrt(dotProduct(v, v));
|
|
22643
|
+
return len > 0 ? [v[0] / len, v[1] / len, v[2] / len] : v;
|
|
22644
|
+
}
|
|
22645
|
+
|
|
22646
|
+
function dotProduct(a, b) {
|
|
22647
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
22648
|
+
}
|
|
22649
|
+
|
|
22650
|
+
function clamp(val, min, max) {
|
|
22651
|
+
return Math.max(min, Math.min(max, val));
|
|
22652
|
+
}
|
|
22653
|
+
|
|
22654
|
+
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
22655
|
+
var OUT_OF_RANGE_MESSAGE = 'out of range';
|
|
22656
|
+
var GEODESIC_PIXEL_TOLERANCE = 1.5;
|
|
22657
|
+
var GEODESIC_MAX_DEPTH = 14;
|
|
22658
|
+
var GEODESIC_MAX_SEGMENT_PX = 18;
|
|
22659
|
+
var GEODESIC_MAX_SUBDIVISIONS = 1500;
|
|
22660
|
+
var ENDPOINT_HIT_RADIUS = 10;
|
|
22661
|
+
var WEB_MERCATOR_HALF_WORLD = 6378137 * Math.PI;
|
|
22662
|
+
var WEB_MERCATOR_MAX_Y = 6378137 * Math.log(Math.tan(Math.PI * 0.25 + 84 * Math.PI / 360));
|
|
22663
|
+
|
|
22664
|
+
function RulerTool(gui, ext) {
|
|
22665
|
+
var mapLayers = gui.container.findChild('.map-layers');
|
|
22666
|
+
var parent = gui.container.findChild('.mshp-main-map');
|
|
22667
|
+
var svg = createSvgNode('svg');
|
|
22668
|
+
var directPath = createSvgNode('path');
|
|
22669
|
+
var geodesicPath = createSvgNode('path');
|
|
22670
|
+
var startMarker = createSvgNode('circle');
|
|
22671
|
+
var endMarker = createSvgNode('circle');
|
|
22672
|
+
var popup = El('div').addClass('ruler-popup rollover').appendTo(parent).hide();
|
|
22673
|
+
var content = El('div').addClass('ruler-popup-content').appendTo(popup);
|
|
22674
|
+
var closeBtn = El('button').addClass('label-style-close ruler-close-btn').appendTo(popup).text('×');
|
|
22675
|
+
var wgs84 = internal.parseCrsString('wgs84');
|
|
22676
|
+
var _on = false;
|
|
22677
|
+
var startPoint = null;
|
|
22678
|
+
var pointerPoint = null;
|
|
22679
|
+
var placed = false;
|
|
22680
|
+
var draggingEndpoint = null;
|
|
22681
|
+
var lastValidDragPoint = null;
|
|
22682
|
+
|
|
22683
|
+
svg.classList.add('ruler-overlay');
|
|
22684
|
+
directPath.classList.add('ruler-line');
|
|
22685
|
+
geodesicPath.classList.add('ruler-line', 'ruler-great-circle');
|
|
22686
|
+
startMarker.classList.add('ruler-endpoint');
|
|
22687
|
+
endMarker.classList.add('ruler-endpoint');
|
|
22688
|
+
svg.appendChild(directPath);
|
|
22689
|
+
svg.appendChild(geodesicPath);
|
|
22690
|
+
svg.appendChild(startMarker);
|
|
22691
|
+
svg.appendChild(endMarker);
|
|
22692
|
+
mapLayers.node().appendChild(svg);
|
|
22693
|
+
|
|
22694
|
+
closeBtn.on('click', function(e) {
|
|
22695
|
+
e.stopPropagation();
|
|
22696
|
+
reset();
|
|
22697
|
+
if (gui.interaction.getMode() == 'ruler') {
|
|
22698
|
+
gui.interaction.turnOff();
|
|
22699
|
+
}
|
|
22700
|
+
});
|
|
22701
|
+
|
|
22702
|
+
gui.keyboard.on('keydown', function(e) {
|
|
22703
|
+
if (!_on || e.keyName != 'esc') return;
|
|
22704
|
+
clearRuler();
|
|
22705
|
+
ensureRulerMode();
|
|
22706
|
+
e.originalEvent.preventDefault();
|
|
22707
|
+
e.originalEvent.stopPropagation();
|
|
22708
|
+
e.stopPropagation();
|
|
22709
|
+
}, null, 10);
|
|
22710
|
+
|
|
22711
|
+
gui.addMode('ruler_tool', turnOn, turnOff);
|
|
22712
|
+
|
|
22713
|
+
gui.on('interaction_mode_change', function(e) {
|
|
22714
|
+
if (e.mode == 'ruler') {
|
|
22715
|
+
gui.enterMode('ruler_tool');
|
|
22716
|
+
} else if (_on) {
|
|
22717
|
+
gui.clearMode();
|
|
22718
|
+
}
|
|
22719
|
+
});
|
|
22720
|
+
|
|
22721
|
+
gui.on('map_click', function(e) {
|
|
22722
|
+
if (!_on) return;
|
|
22723
|
+
handleClick(e);
|
|
22724
|
+
});
|
|
22725
|
+
|
|
22726
|
+
gui.map.getMouse().on('hover', function(e) {
|
|
22727
|
+
if (!_on || !startPoint || placed) return;
|
|
22728
|
+
pointerPoint = getRulerPoint(e.x, e.y);
|
|
22729
|
+
render();
|
|
22730
|
+
});
|
|
22731
|
+
|
|
22732
|
+
gui.map.getMouse().on('dragstart', function(e) {
|
|
22733
|
+
if (!_on || !placed) return;
|
|
22734
|
+
draggingEndpoint = getHitEndpoint(e.x, e.y);
|
|
22735
|
+
if (!draggingEndpoint) return;
|
|
22736
|
+
lastValidDragPoint = getEndpoint(draggingEndpoint);
|
|
22737
|
+
mapLayers.addClass('ruler-dragging');
|
|
22738
|
+
e.stopPropagation();
|
|
22739
|
+
}, null, 10);
|
|
22740
|
+
|
|
22741
|
+
gui.map.getMouse().on('drag', function(e) {
|
|
22742
|
+
var point;
|
|
22743
|
+
if (!draggingEndpoint) return;
|
|
22744
|
+
point = getRulerPoint(e.x, e.y);
|
|
22745
|
+
setEndpoint(draggingEndpoint, point);
|
|
22746
|
+
if (point.valid) {
|
|
22747
|
+
lastValidDragPoint = point;
|
|
22748
|
+
}
|
|
22749
|
+
render();
|
|
22750
|
+
e.stopPropagation();
|
|
22751
|
+
}, null, 10);
|
|
22752
|
+
|
|
22753
|
+
gui.map.getMouse().on('dragend', function(e) {
|
|
22754
|
+
if (!draggingEndpoint) return;
|
|
22755
|
+
if (!getEndpoint(draggingEndpoint).valid && lastValidDragPoint) {
|
|
22756
|
+
setEndpoint(draggingEndpoint, lastValidDragPoint);
|
|
22757
|
+
render();
|
|
22758
|
+
}
|
|
22759
|
+
draggingEndpoint = null;
|
|
22760
|
+
lastValidDragPoint = null;
|
|
22761
|
+
mapLayers.removeClass('ruler-dragging');
|
|
22762
|
+
gui.dispatchEvent('map_interaction_end');
|
|
22763
|
+
e.stopPropagation();
|
|
22764
|
+
}, null, 10);
|
|
22765
|
+
|
|
22766
|
+
gui.on('map_rendered', function() {
|
|
22767
|
+
if (_on) render();
|
|
22768
|
+
});
|
|
22769
|
+
|
|
22770
|
+
gui.map.on('display_crs_change', function() {
|
|
22771
|
+
if (_on) reprojectStoredRulerPoints();
|
|
22772
|
+
});
|
|
22773
|
+
|
|
22774
|
+
gui.model.on('update', function() {
|
|
22775
|
+
if (_on) reset();
|
|
22776
|
+
});
|
|
22777
|
+
|
|
22778
|
+
function turnOn() {
|
|
22779
|
+
_on = true;
|
|
22780
|
+
mapLayers.addClass('ruler-tool');
|
|
22781
|
+
reset();
|
|
22782
|
+
}
|
|
22783
|
+
|
|
22784
|
+
function turnOff() {
|
|
22785
|
+
_on = false;
|
|
22786
|
+
mapLayers.removeClass('ruler-tool');
|
|
22787
|
+
reset();
|
|
22788
|
+
if (gui.interaction.getMode() == 'ruler') {
|
|
22789
|
+
gui.interaction.turnOff();
|
|
22790
|
+
}
|
|
22791
|
+
}
|
|
22792
|
+
|
|
22793
|
+
function handleClick(e) {
|
|
22794
|
+
var point = getRulerPoint(e.x, e.y);
|
|
22795
|
+
if (!point.valid) {
|
|
22796
|
+
if (startPoint && !placed) {
|
|
22797
|
+
pointerPoint = point;
|
|
22798
|
+
render();
|
|
22799
|
+
} else {
|
|
22800
|
+
showOutOfRangeNote();
|
|
22801
|
+
}
|
|
22802
|
+
return;
|
|
22803
|
+
}
|
|
22804
|
+
if (!startPoint || placed) {
|
|
22805
|
+
startPoint = point;
|
|
22806
|
+
pointerPoint = null;
|
|
22807
|
+
placed = false;
|
|
22808
|
+
} else {
|
|
22809
|
+
pointerPoint = point;
|
|
22810
|
+
placed = true;
|
|
22811
|
+
}
|
|
22812
|
+
render();
|
|
22813
|
+
}
|
|
22814
|
+
|
|
22815
|
+
function reset() {
|
|
22816
|
+
clearRuler();
|
|
22817
|
+
}
|
|
22818
|
+
|
|
22819
|
+
function clearRuler() {
|
|
22820
|
+
startPoint = null;
|
|
22821
|
+
pointerPoint = null;
|
|
22822
|
+
placed = false;
|
|
22823
|
+
draggingEndpoint = null;
|
|
22824
|
+
lastValidDragPoint = null;
|
|
22825
|
+
mapLayers.removeClass('ruler-dragging');
|
|
22826
|
+
clearSvgPath(directPath);
|
|
22827
|
+
clearSvgPath(geodesicPath);
|
|
22828
|
+
hideMarker(startMarker);
|
|
22829
|
+
hideMarker(endMarker);
|
|
22830
|
+
popup.hide();
|
|
22831
|
+
}
|
|
22832
|
+
|
|
22833
|
+
function ensureRulerMode() {
|
|
22834
|
+
if (gui.interaction.getMode() != 'ruler') {
|
|
22835
|
+
gui.interaction.setMode('ruler');
|
|
22836
|
+
}
|
|
22837
|
+
}
|
|
22838
|
+
|
|
22839
|
+
function render() {
|
|
22840
|
+
var endPoint = pointerPoint;
|
|
22841
|
+
var measurement;
|
|
22842
|
+
syncOverlaySize();
|
|
22843
|
+
if (!startPoint || !endPoint) {
|
|
22844
|
+
clearSvgPath(directPath);
|
|
22845
|
+
clearSvgPath(geodesicPath);
|
|
22846
|
+
drawMarker(startMarker, startPoint);
|
|
22847
|
+
hideMarker(endMarker);
|
|
22848
|
+
popup.hide();
|
|
22849
|
+
return;
|
|
22850
|
+
}
|
|
22851
|
+
|
|
22852
|
+
if (!startPoint.valid || !endPoint.valid) {
|
|
22853
|
+
clearSvgPath(directPath);
|
|
22854
|
+
clearSvgPath(geodesicPath);
|
|
22855
|
+
drawMarker(startMarker, startPoint.valid ? startPoint : null, false);
|
|
22856
|
+
drawMarker(endMarker, placed && endPoint.valid ? endPoint : null, false);
|
|
22857
|
+
showOutOfRangeNote();
|
|
22858
|
+
return;
|
|
22859
|
+
}
|
|
22860
|
+
|
|
22861
|
+
measurement = getMeasurement(startPoint, endPoint);
|
|
22862
|
+
drawRulerLines(startPoint, endPoint, measurement);
|
|
22863
|
+
drawMarker(startMarker, startPoint, measurement.isLatLng);
|
|
22864
|
+
if (placed) {
|
|
22865
|
+
drawMarker(endMarker, endPoint, measurement.isLatLng);
|
|
22866
|
+
} else {
|
|
22867
|
+
hideMarker(endMarker);
|
|
22868
|
+
}
|
|
22869
|
+
showPopup(measurement);
|
|
22870
|
+
}
|
|
22871
|
+
|
|
22872
|
+
function drawRulerLines(a, b, measurement) {
|
|
22873
|
+
if (measurement.isLatLng) {
|
|
22874
|
+
drawPath(geodesicPath, getGeodesicPixels(a, b));
|
|
22875
|
+
clearSvgPath(directPath);
|
|
22876
|
+
} else {
|
|
22877
|
+
drawPath(directPath, [a.display, b.display]);
|
|
22878
|
+
drawPath(geodesicPath, measurement.greatCircleMeters ? getGeodesicPixels(a, b) : null);
|
|
22879
|
+
}
|
|
22880
|
+
}
|
|
22881
|
+
|
|
22882
|
+
function showPopup(measurement) {
|
|
22883
|
+
content.empty();
|
|
22884
|
+
measurement.labels.forEach(function(label) {
|
|
22885
|
+
El('div').appendTo(content).text(label);
|
|
22886
|
+
});
|
|
22887
|
+
if (placed) {
|
|
22888
|
+
closeBtn.show();
|
|
22889
|
+
} else {
|
|
22890
|
+
closeBtn.hide();
|
|
22891
|
+
}
|
|
22892
|
+
popup.show();
|
|
22893
|
+
}
|
|
22894
|
+
|
|
22895
|
+
function showOutOfRangeNote() {
|
|
22896
|
+
content.empty();
|
|
22897
|
+
El('div').appendTo(content).text(OUT_OF_RANGE_MESSAGE);
|
|
22898
|
+
if (placed) {
|
|
22899
|
+
closeBtn.show();
|
|
22900
|
+
} else {
|
|
22901
|
+
closeBtn.hide();
|
|
22902
|
+
}
|
|
22903
|
+
popup.show();
|
|
22904
|
+
}
|
|
22905
|
+
|
|
22906
|
+
function getHitEndpoint(x, y) {
|
|
22907
|
+
var startDist = getEndpointPixelDistance(startPoint, x, y);
|
|
22908
|
+
var endDist = getEndpointPixelDistance(pointerPoint, x, y);
|
|
22909
|
+
if (startDist > ENDPOINT_HIT_RADIUS && endDist > ENDPOINT_HIT_RADIUS) return null;
|
|
22910
|
+
return startDist <= endDist ? 'start' : 'end';
|
|
22911
|
+
}
|
|
22912
|
+
|
|
22913
|
+
function getEndpointPixelDistance(point, x, y) {
|
|
22914
|
+
var p;
|
|
22915
|
+
if (!point || !point.valid) return Infinity;
|
|
22916
|
+
p = ext.translateCoords(point.display[0], point.display[1]);
|
|
22917
|
+
return geom.distance2D(p[0], p[1], x, y);
|
|
22918
|
+
}
|
|
22919
|
+
|
|
22920
|
+
function getEndpoint(endpoint) {
|
|
22921
|
+
return endpoint == 'start' ? startPoint : pointerPoint;
|
|
22922
|
+
}
|
|
22923
|
+
|
|
22924
|
+
function setEndpoint(endpoint, point) {
|
|
22925
|
+
if (endpoint == 'start') {
|
|
22926
|
+
startPoint = point;
|
|
22927
|
+
} else {
|
|
22928
|
+
pointerPoint = point;
|
|
22929
|
+
}
|
|
22930
|
+
}
|
|
22931
|
+
|
|
22932
|
+
function getRulerPoint(x, y) {
|
|
22933
|
+
var display = ext.pixCoordsToMapCoords(x, y);
|
|
22934
|
+
var lyr = gui.map.getActiveLayer();
|
|
22935
|
+
var displayCRS = gui.map.getDisplayCRS();
|
|
22936
|
+
var sourceCRS = getSourceCRS();
|
|
22937
|
+
if (!displayPointIsInRange(display, displayCRS)) {
|
|
22938
|
+
return {
|
|
22939
|
+
display,
|
|
22940
|
+
data: null,
|
|
22941
|
+
lngLat: null,
|
|
22942
|
+
sourceCRS,
|
|
22943
|
+
valid: false
|
|
22944
|
+
};
|
|
22945
|
+
}
|
|
22946
|
+
var data = lyr ? translateDisplayPoint(lyr, display) : display;
|
|
22947
|
+
var o = validateRulerPoint(data, sourceCRS);
|
|
22948
|
+
return {
|
|
22949
|
+
display,
|
|
22950
|
+
data,
|
|
22951
|
+
lngLat: o.lngLat,
|
|
22952
|
+
sourceCRS,
|
|
22953
|
+
valid: o.valid
|
|
22954
|
+
};
|
|
22955
|
+
}
|
|
22956
|
+
|
|
22957
|
+
function reprojectStoredRulerPoints() {
|
|
22958
|
+
startPoint = reprojectRulerPoint(startPoint);
|
|
22959
|
+
pointerPoint = reprojectRulerPoint(pointerPoint);
|
|
22960
|
+
if (startPoint && !startPoint.valid || pointerPoint && !pointerPoint.valid) {
|
|
22961
|
+
showOutOfRangeNote();
|
|
22962
|
+
}
|
|
22963
|
+
render();
|
|
22964
|
+
}
|
|
22965
|
+
|
|
22966
|
+
function reprojectRulerPoint(point) {
|
|
22967
|
+
var displayCRS = gui.map.getDisplayCRS();
|
|
22968
|
+
var sourceCRS = getSourceCRS();
|
|
22969
|
+
var data = point && point.data;
|
|
22970
|
+
var project, display, o;
|
|
22971
|
+
if (!point || !data) return point;
|
|
22972
|
+
if (displayCRS && sourceCRS && !internal.crsHaveSameTransform(sourceCRS, displayCRS)) {
|
|
22973
|
+
project = internal.getProjTransform2(sourceCRS, displayCRS);
|
|
22974
|
+
display = project && project(data[0], data[1]);
|
|
22975
|
+
} else {
|
|
22976
|
+
display = data;
|
|
22977
|
+
}
|
|
22978
|
+
if (!displayPointIsInRange(display, displayCRS)) {
|
|
22979
|
+
return Object.assign({}, point, {
|
|
22980
|
+
display,
|
|
22981
|
+
sourceCRS,
|
|
22982
|
+
valid: false
|
|
22983
|
+
});
|
|
22984
|
+
}
|
|
22985
|
+
o = validateRulerPoint(data, sourceCRS);
|
|
22986
|
+
return {
|
|
22987
|
+
display,
|
|
22988
|
+
data,
|
|
22989
|
+
lngLat: o.lngLat,
|
|
22990
|
+
sourceCRS,
|
|
22991
|
+
valid: o.valid
|
|
22992
|
+
};
|
|
22993
|
+
}
|
|
22994
|
+
|
|
22995
|
+
function displayPointIsInRange(display, displayCRS) {
|
|
22996
|
+
if (!pointHasFiniteCoords(display)) return false;
|
|
22997
|
+
if (!displayCRS || !internal.isWebMercator(displayCRS)) return true;
|
|
22998
|
+
return display[0] >= -WEB_MERCATOR_HALF_WORLD &&
|
|
22999
|
+
display[0] <= WEB_MERCATOR_HALF_WORLD &&
|
|
23000
|
+
display[1] >= -WEB_MERCATOR_MAX_Y &&
|
|
23001
|
+
display[1] <= WEB_MERCATOR_MAX_Y;
|
|
23002
|
+
}
|
|
23003
|
+
|
|
23004
|
+
function getSourceCRS() {
|
|
23005
|
+
var lyr = gui.map.getActiveLayer();
|
|
23006
|
+
var dataset = lyr && lyr.gui && lyr.gui.source && lyr.gui.source.dataset;
|
|
23007
|
+
var info = dataset ? getDatasetCrsInfo(dataset) : null;
|
|
23008
|
+
if (info && info.crs) return info.crs;
|
|
23009
|
+
if (!lyr) return gui.map.getDisplayCRS() || wgs84;
|
|
23010
|
+
return gui.map.getDisplayCRS() || null;
|
|
23011
|
+
}
|
|
23012
|
+
|
|
23013
|
+
function validateRulerPoint(data, sourceCRS) {
|
|
23014
|
+
var lngLat;
|
|
23015
|
+
var valid;
|
|
23016
|
+
if (!pointHasFiniteCoords(data)) {
|
|
23017
|
+
return {valid: false, lngLat: null};
|
|
23018
|
+
}
|
|
23019
|
+
if (!sourceCRS) {
|
|
23020
|
+
return {valid: true, lngLat: null};
|
|
23021
|
+
}
|
|
23022
|
+
if (internal.isLatLngCRS(sourceCRS)) {
|
|
23023
|
+
return {
|
|
23024
|
+
valid: pointIsInLngLatRange(data),
|
|
23025
|
+
lngLat: pointIsInLngLatRange(data) ? data : null
|
|
23026
|
+
};
|
|
23027
|
+
}
|
|
23028
|
+
if (!internal.isInvertibleCRS(sourceCRS)) {
|
|
23029
|
+
return {valid: true, lngLat: null};
|
|
23030
|
+
}
|
|
23031
|
+
lngLat = internal.toLngLat(data, sourceCRS);
|
|
23032
|
+
valid = pointIsInLngLatRange(lngLat) && inverseProjectionLooksStable(data, lngLat, sourceCRS);
|
|
23033
|
+
return {
|
|
23034
|
+
valid,
|
|
23035
|
+
lngLat: valid ? lngLat : null
|
|
23036
|
+
};
|
|
23037
|
+
}
|
|
23038
|
+
|
|
23039
|
+
function inverseProjectionLooksStable(data, lngLat, sourceCRS) {
|
|
23040
|
+
var fwd = internal.projectPoint(lngLat, wgs84, sourceCRS);
|
|
23041
|
+
var tolerance = sourceCRS.to_meter ? 10 / sourceCRS.to_meter : 10;
|
|
23042
|
+
return pointHasFiniteCoords(fwd) &&
|
|
23043
|
+
geom.distance2D(data[0], data[1], fwd[0], fwd[1]) <= tolerance;
|
|
23044
|
+
}
|
|
23045
|
+
|
|
23046
|
+
function getMeasurement(a, b) {
|
|
23047
|
+
var isLatLng = internal.isLatLngCRS(a.sourceCRS);
|
|
23048
|
+
var projectedMeters = isLatLng ? null : getProjectedDistanceMeters(a, b);
|
|
23049
|
+
var greatCircleMeters = getGreatCircleDistanceMeters(a, b);
|
|
23050
|
+
var unit = getDistanceUnit(projectedMeters || greatCircleMeters || 0);
|
|
23051
|
+
var labels = [];
|
|
23052
|
+
|
|
23053
|
+
if (projectedMeters !== null) {
|
|
23054
|
+
labels.push('Projected: ' + getDistanceDisplay(projectedMeters, unit).label);
|
|
23055
|
+
if (greatCircleMeters !== null) {
|
|
23056
|
+
labels.push('Great circle: ' + getDistanceDisplay(greatCircleMeters, unit).label);
|
|
23057
|
+
}
|
|
23058
|
+
} else if (greatCircleMeters !== null) {
|
|
23059
|
+
labels.push('Great circle: ' + getDistanceDisplay(greatCircleMeters, unit).label);
|
|
23060
|
+
}
|
|
23061
|
+
return {
|
|
23062
|
+
isLatLng,
|
|
23063
|
+
projectedMeters,
|
|
23064
|
+
greatCircleMeters,
|
|
23065
|
+
labels: labels.length ? labels : ['Distance unavailable']
|
|
23066
|
+
};
|
|
23067
|
+
}
|
|
23068
|
+
|
|
23069
|
+
function getProjectedDistanceMeters(a, b) {
|
|
23070
|
+
var toMeter = a.sourceCRS && a.sourceCRS.to_meter || 1;
|
|
23071
|
+
if (!a.data || !b.data) return null;
|
|
23072
|
+
return geom.distance2D(a.data[0], a.data[1], b.data[0], b.data[1]) * toMeter;
|
|
23073
|
+
}
|
|
23074
|
+
|
|
23075
|
+
function getGreatCircleDistanceMeters(a, b) {
|
|
23076
|
+
if (!a.lngLat || !b.lngLat) return null;
|
|
23077
|
+
return geom.greatCircleDistance(a.lngLat[0], a.lngLat[1], b.lngLat[0], b.lngLat[1]);
|
|
23078
|
+
}
|
|
23079
|
+
|
|
23080
|
+
function getGeodesicPixels(a, b) {
|
|
23081
|
+
var displayCRS = gui.map.getDisplayCRS() || a.sourceCRS;
|
|
23082
|
+
var project = displayCRS ? internal.getProjTransform2(wgs84, displayCRS) : null;
|
|
23083
|
+
var points = [];
|
|
23084
|
+
var start = getGeodesicEndpoint(a);
|
|
23085
|
+
var end = getGeodesicEndpoint(b);
|
|
23086
|
+
var state = {subdivisions: 0};
|
|
23087
|
+
|
|
23088
|
+
if (!a.lngLat || !b.lngLat || !project || !start || !end) return null;
|
|
23089
|
+
points.push(start.display);
|
|
23090
|
+
appendAdaptiveGeodesicSegment(points, start, end, project, 0, state);
|
|
23091
|
+
return countPathPoints(points) > 1 ? points : null;
|
|
23092
|
+
}
|
|
23093
|
+
|
|
23094
|
+
function getGeodesicEndpoint(point) {
|
|
23095
|
+
// Anchor the rendered great-circle line to the actual ruler endpoint.
|
|
23096
|
+
// Some projections have inverse/forward roundoff near their boundary, so
|
|
23097
|
+
// projecting point.lngLat back to display coords can visibly miss the marker.
|
|
23098
|
+
return point.lngLat ? {lngLat: point.lngLat, display: point.display} : null;
|
|
23099
|
+
}
|
|
23100
|
+
|
|
23101
|
+
function appendAdaptiveGeodesicSegment(points, a, b, project, depth, state) {
|
|
23102
|
+
var midLngLat = getGeodesicLngLatAt(a.lngLat, b.lngLat, 0.5);
|
|
23103
|
+
var mid = getProjectedGeodesicPoint(midLngLat, project);
|
|
23104
|
+
if (++state.subdivisions > GEODESIC_MAX_SUBDIVISIONS) {
|
|
23105
|
+
if (segmentCrossesAntimeridian(a.lngLat, b.lngLat)) {
|
|
23106
|
+
addPathBreak(points);
|
|
23107
|
+
}
|
|
23108
|
+
addPathPoint(points, b.display);
|
|
23109
|
+
return;
|
|
23110
|
+
}
|
|
23111
|
+
if (!mid) {
|
|
23112
|
+
handleInvalidGeodesicMidpoint(points, a, b, midLngLat, project, depth, state);
|
|
23113
|
+
return;
|
|
23114
|
+
}
|
|
23115
|
+
if (segmentCrossesAntimeridian(a.lngLat, b.lngLat)) {
|
|
23116
|
+
if (depth < GEODESIC_MAX_DEPTH) {
|
|
23117
|
+
appendAdaptiveGeodesicSegment(points, a, mid, project, depth + 1, state);
|
|
23118
|
+
appendAdaptiveGeodesicSegment(points, mid, b, project, depth + 1, state);
|
|
23119
|
+
} else {
|
|
23120
|
+
addPathBreak(points);
|
|
23121
|
+
addPathPoint(points, b.display);
|
|
23122
|
+
}
|
|
23123
|
+
} else if (depth < GEODESIC_MAX_DEPTH && segmentNeedsSubdivision(a, b, mid, project)) {
|
|
23124
|
+
appendAdaptiveGeodesicSegment(points, a, mid, project, depth + 1, state);
|
|
23125
|
+
appendAdaptiveGeodesicSegment(points, mid, b, project, depth + 1, state);
|
|
23126
|
+
} else {
|
|
23127
|
+
addPathPoint(points, b.display);
|
|
23128
|
+
}
|
|
23129
|
+
}
|
|
23130
|
+
|
|
23131
|
+
function segmentNeedsSubdivision(a, b, mid, project) {
|
|
23132
|
+
var samples = [
|
|
23133
|
+
getGeodesicPointAt(a.lngLat, b.lngLat, 0.25, project),
|
|
23134
|
+
mid,
|
|
23135
|
+
getGeodesicPointAt(a.lngLat, b.lngLat, 0.75, project)
|
|
23136
|
+
];
|
|
23137
|
+
return getScreenDistance(a.display, b.display) > GEODESIC_MAX_SEGMENT_PX ||
|
|
23138
|
+
getGeodesicPixelError(a.display, b.display, samples) > GEODESIC_PIXEL_TOLERANCE;
|
|
23139
|
+
}
|
|
23140
|
+
|
|
23141
|
+
function getGeodesicPointAt(a, b, k, project) {
|
|
23142
|
+
return getProjectedGeodesicPoint(getGeodesicLngLatAt(a, b, k), project);
|
|
23143
|
+
}
|
|
23144
|
+
|
|
23145
|
+
function getGeodesicLngLatAt(a, b, k) {
|
|
23146
|
+
return interpolateGreatCirclePoint(a, b, k);
|
|
23147
|
+
}
|
|
23148
|
+
|
|
23149
|
+
function handleInvalidGeodesicMidpoint(points, a, b, midLngLat, project, depth, state) {
|
|
23150
|
+
var left, right;
|
|
23151
|
+
if (depth >= GEODESIC_MAX_DEPTH) {
|
|
23152
|
+
addPathBreak(points);
|
|
23153
|
+
addPathPoint(points, b.display);
|
|
23154
|
+
return;
|
|
23155
|
+
}
|
|
23156
|
+
left = findNearestValidGeodesicPoint(a.lngLat, midLngLat, project);
|
|
23157
|
+
right = findNearestValidGeodesicPoint(b.lngLat, midLngLat, project);
|
|
23158
|
+
if (left && lngLatPointsDiffer(a.lngLat, left.lngLat)) {
|
|
23159
|
+
appendAdaptiveGeodesicSegment(points, a, left, project, depth + 1, state);
|
|
23160
|
+
}
|
|
23161
|
+
addPathBreak(points);
|
|
23162
|
+
if (right && lngLatPointsDiffer(b.lngLat, right.lngLat)) {
|
|
23163
|
+
addPathPoint(points, right.display);
|
|
23164
|
+
appendAdaptiveGeodesicSegment(points, right, b, project, depth + 1, state);
|
|
23165
|
+
} else {
|
|
23166
|
+
addPathPoint(points, b.display);
|
|
23167
|
+
}
|
|
23168
|
+
}
|
|
23169
|
+
|
|
23170
|
+
function findNearestValidGeodesicPoint(validLngLat, invalidLngLat, project) {
|
|
23171
|
+
var lo = 0;
|
|
23172
|
+
var hi = 1;
|
|
23173
|
+
var best = getProjectedGeodesicPoint(validLngLat, project, true);
|
|
23174
|
+
var p, k;
|
|
23175
|
+
if (!best) return null;
|
|
23176
|
+
for (var i = 0; i < 16; i++) {
|
|
23177
|
+
k = (lo + hi) / 2;
|
|
23178
|
+
p = getProjectedGeodesicPoint(getGeodesicLngLatAt(validLngLat, invalidLngLat, k), project);
|
|
23179
|
+
if (p) {
|
|
23180
|
+
best = p;
|
|
23181
|
+
lo = k;
|
|
23182
|
+
} else {
|
|
23183
|
+
hi = k;
|
|
23184
|
+
}
|
|
23185
|
+
}
|
|
23186
|
+
return best;
|
|
23187
|
+
}
|
|
23188
|
+
|
|
23189
|
+
function lngLatPointsDiffer(a, b) {
|
|
23190
|
+
return a[0] != b[0] || a[1] != b[1];
|
|
23191
|
+
}
|
|
23192
|
+
|
|
23193
|
+
function getProjectedGeodesicPoint(lngLat, project, allowNearPole) {
|
|
23194
|
+
var display;
|
|
23195
|
+
if (!pointIsInLngLatRange(lngLat) || !allowNearPole && pointIsNearPole(lngLat)) return null;
|
|
23196
|
+
display = project(lngLat[0], lngLat[1]);
|
|
23197
|
+
return pointHasFiniteCoords(display) ? {lngLat, display} : null;
|
|
23198
|
+
}
|
|
23199
|
+
|
|
23200
|
+
function addPathBreak(points) {
|
|
23201
|
+
if (points.length > 0 && points[points.length - 1] !== null) {
|
|
23202
|
+
points.push(null);
|
|
23203
|
+
}
|
|
23204
|
+
}
|
|
23205
|
+
|
|
23206
|
+
function addPathPoint(points, p) {
|
|
23207
|
+
if (points[points.length - 1] === null && points.length > 1) {
|
|
23208
|
+
points.push(p);
|
|
23209
|
+
} else if (points.length === 0 || points[points.length - 1] !== p) {
|
|
23210
|
+
points.push(p);
|
|
23211
|
+
}
|
|
23212
|
+
}
|
|
23213
|
+
|
|
23214
|
+
function countPathPoints(points) {
|
|
23215
|
+
var count = 0;
|
|
23216
|
+
points.forEach(function(p) {
|
|
23217
|
+
if (p) count++;
|
|
23218
|
+
});
|
|
23219
|
+
return count;
|
|
23220
|
+
}
|
|
23221
|
+
|
|
23222
|
+
function getGeodesicPixelError(a, b, samples) {
|
|
23223
|
+
var p1 = ext.translateCoords(a[0], a[1]);
|
|
23224
|
+
var p2 = ext.translateCoords(b[0], b[1]);
|
|
23225
|
+
return samples.reduce(function(max, sample) {
|
|
23226
|
+
var p3;
|
|
23227
|
+
if (!sample) return Infinity;
|
|
23228
|
+
p3 = ext.translateCoords(sample.display[0], sample.display[1]);
|
|
23229
|
+
return Math.max(max, pointSegDist(p3[0], p3[1], p1[0], p1[1], p2[0], p2[1]));
|
|
23230
|
+
}, 0);
|
|
23231
|
+
}
|
|
23232
|
+
|
|
23233
|
+
function getScreenDistance(a, b) {
|
|
23234
|
+
var p1 = ext.translateCoords(a[0], a[1]);
|
|
23235
|
+
var p2 = ext.translateCoords(b[0], b[1]);
|
|
23236
|
+
return geom.distance2D(p1[0], p1[1], p2[0], p2[1]);
|
|
23237
|
+
}
|
|
23238
|
+
|
|
23239
|
+
function syncOverlaySize() {
|
|
23240
|
+
svg.setAttribute('width', ext.width());
|
|
23241
|
+
svg.setAttribute('height', ext.height());
|
|
23242
|
+
}
|
|
23243
|
+
|
|
23244
|
+
function drawPath(path, points) {
|
|
23245
|
+
var d, started = false;
|
|
23246
|
+
if (!points || countPathPoints(points) < 2) {
|
|
23247
|
+
clearSvgPath(path);
|
|
23248
|
+
return;
|
|
23249
|
+
}
|
|
23250
|
+
d = points.map(function(p) {
|
|
23251
|
+
if (!p) {
|
|
23252
|
+
started = false;
|
|
23253
|
+
return '';
|
|
23254
|
+
}
|
|
23255
|
+
var q = ext.translateCoords(p[0], p[1]);
|
|
23256
|
+
var cmd = started ? 'L' : 'M';
|
|
23257
|
+
started = true;
|
|
23258
|
+
return cmd + round(q[0]) + ' ' + round(q[1]);
|
|
23259
|
+
}).filter(Boolean).join(' ');
|
|
23260
|
+
path.setAttribute('d', d);
|
|
23261
|
+
}
|
|
23262
|
+
|
|
23263
|
+
function clearSvgPath(path) {
|
|
23264
|
+
path.removeAttribute('d');
|
|
23265
|
+
}
|
|
23266
|
+
|
|
23267
|
+
function drawMarker(marker, point, useGreatCircleStyle) {
|
|
23268
|
+
var p;
|
|
23269
|
+
if (!point) {
|
|
23270
|
+
hideMarker(marker);
|
|
23271
|
+
return;
|
|
23272
|
+
}
|
|
23273
|
+
p = ext.translateCoords(point.display[0], point.display[1]);
|
|
23274
|
+
marker.setAttribute('cx', round(p[0]));
|
|
23275
|
+
marker.setAttribute('cy', round(p[1]));
|
|
23276
|
+
marker.setAttribute('r', 4);
|
|
23277
|
+
marker.classList.toggle('ruler-great-circle-endpoint', !!useGreatCircleStyle);
|
|
23278
|
+
marker.removeAttribute('display');
|
|
23279
|
+
}
|
|
23280
|
+
|
|
23281
|
+
function hideMarker(marker) {
|
|
23282
|
+
marker.classList.remove('ruler-great-circle-endpoint');
|
|
23283
|
+
marker.setAttribute('display', 'none');
|
|
23284
|
+
}
|
|
23285
|
+
}
|
|
23286
|
+
|
|
23287
|
+
function createSvgNode(name) {
|
|
23288
|
+
return document.createElementNS(SVG_NS, name);
|
|
23289
|
+
}
|
|
23290
|
+
|
|
23291
|
+
function round(n) {
|
|
23292
|
+
return Math.round(n * 10) / 10;
|
|
23293
|
+
}
|
|
23294
|
+
|
|
23295
|
+
function pointHasFiniteCoords(p) {
|
|
23296
|
+
return !!p && isFinite(p[0]) && isFinite(p[1]);
|
|
23297
|
+
}
|
|
23298
|
+
|
|
23299
|
+
function pointSegDist(px, py, ax, ay, bx, by) {
|
|
23300
|
+
var dx = bx - ax;
|
|
23301
|
+
var dy = by - ay;
|
|
23302
|
+
var lenSq = dx * dx + dy * dy;
|
|
23303
|
+
var k;
|
|
23304
|
+
if (lenSq === 0) {
|
|
23305
|
+
return geom.distance2D(px, py, ax, ay);
|
|
23306
|
+
}
|
|
23307
|
+
k = ((px - ax) * dx + (py - ay) * dy) / lenSq;
|
|
23308
|
+
k = Math.max(0, Math.min(1, k));
|
|
23309
|
+
return geom.distance2D(px, py, ax + dx * k, ay + dy * k);
|
|
23310
|
+
}
|
|
23311
|
+
|
|
22557
23312
|
utils$1.inherit(MshpMap, EventDispatcher);
|
|
22558
23313
|
|
|
22559
23314
|
function MshpMap(gui) {
|
|
@@ -22714,6 +23469,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
22714
23469
|
// Update map extent (also triggers redraw)
|
|
22715
23470
|
projectMapExtent(_ext, oldCRS, this.getDisplayCRS(), calcFullBounds());
|
|
22716
23471
|
updateFullBounds();
|
|
23472
|
+
map.dispatchEvent('display_crs_change', {crs: this.getDisplayCRS(), prev_crs: oldCRS});
|
|
22717
23473
|
};
|
|
22718
23474
|
|
|
22719
23475
|
// Initialization just before displaying the map for the first time
|
|
@@ -22729,6 +23485,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
22729
23485
|
new InspectionControl2(gui, _hit);
|
|
22730
23486
|
new SelectionTool(gui, _ext, _hit),
|
|
22731
23487
|
new BoxTool(gui, _ext, _nav),
|
|
23488
|
+
new RulerTool(gui, _ext),
|
|
22732
23489
|
new RectangleControl(gui, _hit),
|
|
22733
23490
|
initInteractiveEditing(gui, _ext, _hit);
|
|
22734
23491
|
_hit.on('change', function() { drawLayers('hover'); });
|