MapInfo Pro

 View Only

MapBasic Monday: Extending Polyline to Intersection

  • 1.  MapBasic Monday: Extending Polyline to Intersection

    Employee
    Posted 04-22-2024 05:43
    Edited by Peter Møller 04-22-2024 06:51

    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:

    Here's how I would approach it.

    1. You have your footpath polyline and you know at what end you need to create a new line to extend to the neighboring polyline.
    2. Now fetch the two last points from that end of the polyline; P1 and P2 in the drawing below.
    3. Create a line from these two points, P2 to P1
    4. Calculate the direction of that line using OBJDirection() from the OBJLib module.
    5. Now you need to offset a point, P3, in the direction of the line so that it ends "on the other side" of the street polyline. You can offset it by 10 meters or a known tolerance, and if that's not enough offset it again. You can use intersect to determine if the line intersects the streetpolyline. You can use the MapBasic function CartesianOffset(), and create a line from P1 and P3. Also, remember to use the direction calculated in 3.
    6. Now calculate the intersection between Footpath line and Street Polyline, P4. Use the MapBasic function IntersectNodes() to get the intersection point. You get this as a polyline so you have to extract the node from the polyline.
    7. Finally, you can create the new Footpath line using P1 and P4
    Over the weekend my mind kept thinking and in the following week, I decided to implement a tool in DrawTools that extends to intersections of other objects in the editable layer.
    I created the tool using a procedure and two supporting functions to make it more flexible; the supporting functions were added to the MapBasic module OBJLib.
    The function OBJCreateLineExtension() creates an extension line of a given length from the beginning or end of a given polyline. It's the red dotted line in the drawing above, P1 to P3. The function looks like this:
    Function OBJCreateLineExtension(	  ByVal oFromPline As Object			'One-segment polyline
    							, ByVal nFromStartOrEnd As Integer		'OBJ_FIRST_SEGMENT (1), OBJ_LAST_SEGMENT (-1)
    							, ByVal fLength As Float				'Length of Line Extension
    							, 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 the process fails
    
    Dim	oNewLine, oP2, oP3 As Object,
    	nNumPnts As Integer,
    	fX, fY, fAngle As Float
    
    OnError GoTo ErrorOccured
    
    OBJCreateLineExtension = CreatePoint(0,0)
    
    	If nFromStartOrEnd = OBJ_FIRST_SEGMENT Then	'Start Segment
    		oNewLine	= OBJReverse(ExtractNodes(oFromPline, 1, 1, 2, FALSE))
    	Else	'Assuming OBJ_LAST_SEGMENT
    		nNumPnts	= ObjectInfo(oFromPline, OBJ_INFO_NPOLYGONS + 1)
    		oNewLine	= ExtractNodes(oFromPline, 1, nNumPnts - 1, nNumPnts, FALSE)
    	End If
    
    	fAngle	= OBJDirection(oNewLine)
    
    	fX		= ObjectNodeX(oNewLine, 1, 2)
    	fY		= ObjectNodeY(oNewLine, 1, 2)
    	oP2		= CreatePoint(fX, fY)
    
    	If nCalculationMethod  = CALC_METHOD_CARTESIAN Then
    		oP3		= CartesianOffset(oP2, fAngle, fLength, sDistanceUnits)
    	Else
    		oP3		= SphericalOffset(oP2, fAngle, fLength, sDistanceUnits)
    	End If
    
    	oNewLine	= OBJCreatePlineFromPoints(oP2, oP3)
    
    	OBJCreateLineExtension = oNewLine
    
    	Exit Function
    '-------------------------
    ErrorOccured:
    	Call ERRCreate(Err(), Error$(), "OBJCreateLineExtension")
    	Call ERRShow()
    
    End Function
    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:
    • OBJLib which is part of the Common MapBasic Library
    • DrawTools, the source code for the tool can be found in the module DrawTools.mb

    DrawTools hasn't yet been updated with this new feature in MapInfo Marketplace. It may take a couple of weeks as I'm adding additional capabilities to it before publishing a new version.

    Let me know if you find such a tool useful and if it's enough to extend to the same layer that you add the new polyline to.



    ------------------------------
    Peter Horsbøll Møller
    Principal Presales Consultant | Distinguished Engineer
    Precisely | Trust in Data
    ------------------------------