Kilometer Intelligence on a Railway Polyline

873
6
Jump to solution
10-10-2025 02:17 AM
offi24
by
Emerging Contributor

Hello,

I’ve been trying—so far without success—to add some intelligence to a polyline in ArcGIS. I have a polyline representing a railway line, with an attribute field that stores the line’s starting kilometer point. I also have a point layer with kilometer markers placed every 1,000 meters along the railway. I want to transfer this information onto the polyline so that, no matter where I click on the line, I can retrieve the exact kilometer value at that location—like in the screenshot.Screenshot_KP.png

Does someone has an idea how to proceed?

 

Thanks in advance and best regards

Eric

Tags (1)
0 Kudos
1 Solution

Accepted Solutions
AyanPalit
Esri Regular Contributor

@offi24 Thanks for the notes along with screenshot - helps in understanding the use case. 

The workflow is better handled through Linear Referencing (simple functions) and Roads and Highways  (advanced functions).

You can get started with the following references:

The use case is discussed in the following posts:

Creating points at hatch intervals along LRS routes

Create Line segments from Point Locations referencing Linear feature

Ayan Palit | Principal Consultant Esri

View solution in original post

0 Kudos
6 Replies
RPGIS
by MVP Regular Contributor
MVP Regular Contributor

Hi @offi24,

Given the complexity of your question, the only things that I can recommend are as follows:

  1. Create a copy of the polyline and edit the copied line by dividing it into equal distances or parts
  2. Use/Create an attribute rule or arcade calculation to create points in another feature at set distances and then label those features.
  3. Add fields for starting kilometer distances and label them so that the labels are positioned along the line at set distances.
  4. Try linear referencing and seeing what tools may work best for you.

Despite those I don't think you will be able to click anywhere on a line and get the distance along that line unless you use linear referencing. 

BobBooth1
Esri Regular Contributor
AyanPalit
Esri Regular Contributor

@offi24 Thanks for the notes along with screenshot - helps in understanding the use case. 

The workflow is better handled through Linear Referencing (simple functions) and Roads and Highways  (advanced functions).

You can get started with the following references:

The use case is discussed in the following posts:

Creating points at hatch intervals along LRS routes

Create Line segments from Point Locations referencing Linear feature

Ayan Palit | Principal Consultant Esri
0 Kudos
guanjiu
Emerging Contributor

我最近在学习add-in的开发,也许我可以做一个小插件实现这个功能,😁

0 Kudos
guanjiu
Emerging Contributor

I can't publish my tool here, so I can only share the code. It can identify line features and automatically provide the distance from the initial point to the area where the mouse hovers (when the mouse hovers over the line for 3 seconds).

0 Kudos
guanjiu
Emerging Contributor

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;
}
}
}
}

0 Kudos