A few weeks back I was approached by @Tony Price from Powys County Council. Tony wanted to merge his cycle path and footpath dataset with his road dataset. The idea was to get a single dataset that could be used for routing purposes.
As you can imagine, combining these two datasets and ensuring they can be used for routing requires a bit more work than just combining them.
The image above highlights a few of the issues. The back lines are footpaths and the purple lines are roads.
There can be overshoots where the footpaths cross the road and don't end right on the road; shown with the cyan circle above.
The other problem is undershoots. That's where the footpath doesn't go far enough to reach the roads. These are illustrated with the red lines above.
In this #MapInfoMonday article, we will focus on the undershoots.
Tony had been looking into this and had investigated the Distance Calculator tool. The problem with this tool is that it finds the shortest distance between the footpath and the roads. Tony was looking for a way to extend the footpath to an intersection.
I shared this advice with Tony:
As you can see I pass the function several parameters: the polyline to create the extension line for, whether I want to create the line from the start or the end, the length of the extension line, and finally if I want to use Cartesian or Spherical calculation methods.
The function covers steps 2 to 5 from my advice above even though I haven't done it exactly as I proposed but quite similar. These functions are custom functions from the OBJLib: OBJReverse()
, OBJDirection()
, and OBJCreatePlineFromPoints()
.
The other function, OBJCreateLineToIntersection()
, can create a polyline from a given polyline to an intersection with another given polyline.
Function OBJCreateLineToIntersection( ByVal oFromPline As Object '2-node polyline
, ByVal nFromStartOrEnd As Integer 'OBJ_FIRST_SEGMENT (1), OBJ_LAST_SEGMENT (-1)
, ByVal oToPline As Object 'multi-point polyline
, ByVal fDistanceTolerance As Float 'Search distance
, ByVal sDistanceUnits As String 'Distance units
, ByVal nCalculationMethod As Integer 'CALC_METHOD_CARTESIAN / CALC_METHOD_SPHERICAL
) As Object 'Returns a point at (0,0) if no intersection was found
Dim oNewLine, oIntersection As Object,
nPnts, nNumPnts As Integer,
fX, fY, fAngle As Float
OnError GoTo ErrorOccured
OBJCreateLineToIntersection = CreatePoint(0,0)
oNewLine = OBJCreateLineExtension(oFromPline, nFromStartOrEnd, fDistanceTolerance, sDistanceUnits, nCalculationMethod)
If Not oNewLine Intersects oToPline Then
Call ERRCreate(0, "Extended line doesn't intersect line to extend to", "OBJCreateLineToIntersection")
Exit Function
End If
oIntersection = IntersectNodes(oNewLine, oToPline, INCL_CROSSINGS)
nNumPnts = ObjectInfo(oIntersection, OBJ_INFO_NPNTS)
If nNumPnts = 0 Then
Call ERRCreate(0, "Extended line doesn't intersect line to extend to", "OBJCreateLineToIntersection")
Exit Function
End If
fX = ObjectNodeX(oNewLine, 1, 1)
fY = ObjectNodeY(oNewLine, 1, 1)
Create Pline
Into Variable oNewLine
1 (fX, fY)
For nPnts = 1 To nNumPnts
Alter Object oNewLine
Node Add Position 1, (nPnts + 1) (ObjectNodeX(oIntersection, 1, nPnts), ObjectNodeY(oIntersection, 1, nPnts))
Next
OBJCreateLineToIntersection = oNewLine
Exit Function
'-------------------------
ErrorOccured:
Call ERRCreate(Err(), Error$(), "OBJCreateLineToIntersection")
Call ERRShow()
End Function
As you can see, this function uses the first custom function for creating the extension line. Then it calculates the intersection point and creates a new polyline from the first node of the extension line and the intersection point.
And finally, let's inspect the code for the custom tool that extends the drawn line to potential intersections.
Sub DTDrawAndExtendPolyline
Dim oDrawnPline, oSegment, oToPline, oNewLine, oShortestLine As Object,
fX, fY, fTolerance, fShortestLength As Float,
nWID, nNumPnts, nFromStartOrEnd, nNode As Integer,
sTab As String
OnError GoTo ErrorOccured
nWID = FrontWindow()
If nWID = 0 Then
Note "Please use this tool in a map window!"
Exit Sub
End If
If Not WindowInfo(nWID, WIN_INFO_TYPE) = WIN_MAPPER Then
Note "Please use this tool in a map window!"
Exit Sub
End If
sTab = MAPGetEditLayerName(nWID)
If sTab = "" Then
Note "Please make sure that you have made a layer editable!"
Exit Sub
End If
Set CoordSys Table sTab
fTolerance = 10 'meters
oDrawnPline = CommandInfo(CMD_INFO_CUSTOM_OBJ)
For nFromStartOrEnd = 1 To 2
oNewLine = OBJCreateLineExtension(oDrawnPline, IIf(nFromStartOrEnd = 1, OBJ_FIRST_SEGMENT, OBJ_LAST_SEGMENT), fTolerance, "m", CALC_METHOD_CARTESIAN)
Select * From sTab
Where OBJ Intersects oNewLine
Into __Extent_To NoSelect Hide
fShortestLength = fTolerance * 10
Fetch First From __Extent_To
Do Until EOT(__Extent_To)
oToPline = __Extent_To.OBJ
oNewLine = OBJCreateLineToIntersection(oDrawnPline, IIf(nFromStartOrEnd = 1, OBJ_FIRST_SEGMENT, OBJ_LAST_SEGMENT), oToPline, fTolerance, "m", CALC_METHOD_CARTESIAN)
If not ObjectInfo(oNewLine, OBJ_INFO_TYPE) = OBJ_TYPE_POINT Then
If CartesianObjectLen(oNewLine, "m") < fShortestLength Then
fShortestLength = CartesianObjectLen(oNewLine, "m")
oShortestLine = oNewLine
End If
End If
Fetch Next From __Extent_To
Loop
If fShortestLength < (fTolerance * 10) Then
fX = ObjectNodeX(oShortestLine, 1, 2)
fY = ObjectNodeY(oShortestLine, 1, 2)
nNode = IIf(nFromStartOrEnd = 1, 1, ObjectInfo(oDrawnPline, OBJ_INFO_NPOLYGONS + 1))
Alter Object oDrawnPline
Node Set Position 1, nNode (fX, fY)
End If
Close Table __Extent_To
Next
Insert Into sTab (OBJ) Values (oDrawnPline)
Exit Sub
'-------------------------
ErrorOccured:
Call ERRCreate(Err(), Error$(), "DTDrawAndExtendPolyline")
Call ERRShow()
End Sub
With the drawn polyline, oDrawnPline
, I run through a process twice; once for each end of the polyline.
I create an extended line at the first end of the polyline and using this I query the editable layer for other polylines intersecting this extended line. If some are found, I create the line between the drawn polyline and then the nearby polyline using the function OBJCreateLineToIntersection()
.
In case more polylines have been found, I measure the length of this line allowing me to pick the shortest extension line.
Finally, I modify the drawn polyline by moving the end node for the current end to the location of the end node of the shortest line extension.
Then I repeat this process for the other end of the polyline.
Here you can see an example of a polyline drawn with the tool
And here is how it looks once it has been extended to intersection points. Notice how the nodes of the polyline align with the existing polylines. Note that the line below isn't identical to the one above as I had to repeat the process.
Now my tool modifies the drawn polyline. Tony, on the other hand, wants these extension lines as separate polylines so he doesn't need to modify the existing polyline. He can add these extension polylines to another table and so end up with polylines connecting the footpaths to the roads.
You can find the source code on GitHub:
------------------------------
Peter Horsbøll Møller
Principal Presales Consultant | Distinguished Engineer
Precisely | Trust in Data
------------------------------