using ArcGIS.Core.CIM;
using ArcGIS.Core.Data;
using ArcGIS.Core.Geometry;
using ArcGIS.Core.Internal.Geometry;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
namespace PolylineDistanceTool
{
internal class ShowDistanceTool : MapTool
{
private IDisposable _graphic = null;
private Timer _hoverTimer;
private MapPoint _lastMousePosition = null;
private readonly object _timerLock = new object();
private const int HoverDelay = 3000;
public ShowDistanceTool()
{
IsSketchTool = false;
Cursor = Cursors.Cross;
}
protected override Task OnToolDeactivateAsync(bool haskwargs)
{
ClearOverlay();
StopHoverTimer();
return base.OnToolDeactivateAsync(haskwargs);
}
protected override void OnToolMouseMove(MapViewMouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
ClearOverlay();
StopHoverTimer();
return;
}
Task.Run(() =>
{
_lastMousePosition = MapView.Active?.ClientToMap(e.ClientPoint);
ResetHoverTimer();
});
}
private void ResetHoverTimer()
{
StopHoverTimer();
_hoverTimer = new Timer(OnTimerElapsed, null, HoverDelay, Timeout.Infinite);
}
private void StopHoverTimer()
{
lock (_timerLock)
{
_hoverTimer?.Dispose();
_hoverTimer = null;
}
}
private void OnTimerElapsed(object state)
{
lock (_timerLock)
{
if (_hoverTimer == null) return;
StopHoverTimer();
}
QueuedTask.Run(() => PerformHitTestAndCalculateDistance(_lastMousePosition));
}
private async Task PerformHitTestAndCalculateDistance(MapPoint currentMapPoint)
{
if (currentMapPoint == null || MapView.Active == null) return;
ClearOverlay();
var point1_screen = new System.Windows.Point(0, 0);
var point2_screen = new System.Windows.Point(10, 0);
var point1_map = MapView.Active.ClientToMap(point1_screen);
var point2_map = MapView.Active.ClientToMap(point2_screen);
var searchTolerance = GeometryEngine.Instance.Distance(point1_map, point2_map);
if (searchTolerance <= 0)
{
searchTolerance = MapView.Active.Extent.Width / 1000;
}
var envelopeBuilder = new EnvelopeBuilder(currentMapPoint.Extent);
envelopeBuilder.Expand(searchTolerance, searchTolerance, false);
var searchEnvelope = envelopeBuilder.ToGeometry();
var polylineLayers = MapView.Active.Map.GetLayersAsFlattenedList()
.OfType<FeatureLayer>()
.Where(l => l.ShapeType == esriGeometryType.esriGeometryPolyline && l.IsVisible);
foreach (var layer in polylineLayers)
{
var spatialQueryFilter = new SpatialQueryFilter
{
FilterGeometry = searchEnvelope,
SpatialRelationship = SpatialRelationship.Intersects
};
using (var selection = layer.Select(spatialQueryFilter))
{
if (selection.GetCount() == 0)
continue;
using (var cursor = selection.Search(null, false))
{
if (cursor.MoveNext())
{
using (var feature = cursor.Current as Feature)
{
if (feature?.GetShape() is Polyline polyline)
{
// **==================== 最终修正于此 ====================**
// 步骤 1: 获取鼠标点在折线上的最近点
var nearestPointResult = GeometryEngine.Instance.NearestPoint(polyline, currentMapPoint);
var pointOnLine = nearestPointResult.Point;
// 步骤 2: 定位该点所在的线段,并找到该线段的起始顶点索引
var nearestVertexResult = GeometryEngine.Instance.NearestVertex(polyline, pointOnLine);
int startingVertexIndexOfSegment = (int)nearestVertexResult.PointIndex;
// 步骤 3: 构建一个新的点集合,用于创建部分折线
var partialPoints = new List<MapPoint>();
var originalPoints = polyline.Points;
// 添加从起点到目标线段起始点的所有顶点
for (int i = 0; i <= startingVertexIndexOfSegment; i++)
{
partialPoints.Add(originalPoints[i]);
}
// 最后,添加我们计算出的精确点
partialPoints.Add(pointOnLine);
// 步骤 4: 从这个点集合创建新的折线,并获取其长度
var partialPolyline = PolylineBuilderEx.CreatePolyline(partialPoints, polyline.SpatialReference);
double distanceAlongCurve = partialPolyline.Length;
// **=======================================================**
double distanceInKm = distanceAlongCurve / 1000.0;
string displayText = $"距离起点: {distanceInKm:F2} 公里";
ShowDistanceAsOverlay(currentMapPoint, displayText);
return;
}
}
}
}
}
}
}
private void ShowDistanceAsOverlay(MapPoint location, string text)
{
QueuedTask.Run(() =>
{
CIMTextSymbol textSymbol = SymbolFactory.Instance.ConstructTextSymbol(
ColorFactory.Instance.BlackRGB, 12, "Arial", "Regular");
// 正确地创建光晕符号
var haloPolygonSymbol = new CIMPolygonSymbol();
var haloFill = new CIMSolidFill { Color = ColorFactory.Instance.WhiteRGB };
haloPolygonSymbol.SymbolLayers = new CIMSymbolLayer[] { haloFill };
textSymbol.HaloSymbol = haloPolygonSymbol;
textSymbol.HaloSize = 2;
var textGraphic = new CIMTextGraphic
{
Text = text,
Symbol = textSymbol.MakeSymbolReference(),
Shape = location
};
_graphic = MapView.Active.AddOverlay(textGraphic);
});
}
private void ClearOverlay()
{
if (_graphic != null)
{
_graphic.Dispose();
_graphic = null;
}
}
}
}