MapInfo Pro

 View Only

MapBasic Monday: Follow those Polylines

  • 1.  MapBasic Monday: Follow those Polylines

    Employee
    Posted 12-11-2023 04:00
      |   view attached

    Happy #MapInfoMonday and #MapBasicMonday

    Last week I attended the Basarsoft Technology Day hosted by our partner Basarsoft in Ankara, Turkey. I did a live session with tips & tricks from MapInfo Pro v2023 and some #MapInfoMonday tips.

    During the meeting, I got a question: How can I follow a table of (poly)lines to see where they go? This question typically comes from people working in utilities or with streams or rivers. And of course from people doing navigation or route planning.

    Let me start by saying the best and fastest option would be to use a tool like MapInfo RouteFinder/RouteFinder for MapInfo. This would allow you to create a network from your dataset and then use this in various analyses with great performance.

    Having said this, you certainly can do a simple "follow the lines" analysis in MapInfo Pro using MapBasic.

    Prerequisites

    First, you need a linear dataset to make up your network. This could be a network of cables, pipes, roads, or as in my example streams. You can see my streams on the map below. They cover a larger area than what you see.

    Keep in mind that typically, a network also has a direction. For streams, the water flows downstream. I have checked the direction on parts of my network and the line direction indicates the direction in which the water flows. You can turn on Show Line Direction for a layer via the Style tab. This will make small arrows appear on the lines to indicate their direction.
    The idea is now that I can select a line segment from my stream dataset and get all the lines that are downstream from the selected line segment highlighted. I selected the line where my mouse cursor is positioned, and the red lines show the line segments downstream.
    Similarly, I can select another line segment and highlight all line segments upstream. I selected the line segment in the upper left corner where the mouse cursor is located. The red lines are the line segments upstream from that segment.
    As this search takes longer, you also get to see the progressbar so that you can see there is something going on in the back. The progressbar also gives users the ability to stop the query.
    I have built this functionality into a new tool: AnalysisHelper. It hasn't been published yet but I have attached the current version to this post and you can find the source code on GitHub.
    Now let me show you the logic behind this.
    First, we handle the input by reading the selected polyline. Based on the Node Type, we create a buffer around either the entire polyline or just the first or the last node. If the mnNode value is 0, it means that the direction isn't considered and in that case, we use the entire line for the buffer. In other cases, we expect the mnNode value to be either 1 or -1. This would be the first node or the last node which indicates the direction to use.
    OBJGetNode() is a function from the OBJLin.mb module that will return a single node from a line, polyline, or polygon as a point object.
    	msTab 	= SelectionInfo(SEL_INFO_TABLENAME)
    	sQuery	= SelectionInfo(SEL_INFO_SELNAME)
    
    	Fetch First From Selection
    	oLine 	= Selection.OBJ
    	If mnNode = 0 Then
    		oBuffer	= Buffer(oLine, mnBufferResolution, mfBufferWidth, msdistanceUnit)
    	Else
    		oNode 	= OBJGetNode(oLine, 1, mnNode)
    		oBuffer	= Buffer(oNode, mnBufferResolution, mfBufferWidth, msdistanceUnit)
    	End If
    Next, we use the buffer created to select the line segments that touch the initially selected line.
    We also create a table called __FOLLOW__LINES to hold the RowIDs of the line segments connected to the line selected by the user. We will use this table at the end to select all the lines found.
    And finally, we use the Progressbar statement to initiate the process.
    	If mnNode = 0 Then
    		Select *
    			From msTab
    			Where OBJ Intersects oBuffer
    			Into __Intersecting_lines NoSelect Hide
    	Else
    		Select *
    			From msTab
    			Where OBJGetNode(OBJ, 1, -1 * mnNode) Within oBuffer
    			Into __Intersecting_lines NoSelect Hide
    	End If
    
    	'**New method using Progressbar
    	If TableInfo(__Intersecting_lines, TAB_INFO_NROWS) = 0 Then
    		Call TABClose("__Intersecting_lines")
    		Exit Function
    	End If
    
    	Call TABClose("__FOLLOW__LINES")
    	Create Table __FOLLOW__LINES
    		( LINE_ROWID	Integer
    		, FOLLOW_ORDER	Integer
    		)
    		File PathToDirectory$(TempFileName$("")) & "__FOLLOW__LINES.tab"
    	Create Map For __FOLLOW__LINES
    		CoordSys Table msTab
    
    	Set Progressbars Off
    
    	Progressbar "Following the network..."
    		Calling FTLSelectUsingSelection_Single
    		Range TableInfo(msTab, TAB_INFO_NROWS)
    
    	Set Progressbars On
    It is the logic in the procedures called by the Progressbar that does the actual work of finding the connected lines.
    Initially, it checks to see if the table __Intersecting_lines is empty. If that's the case, there are no more lines to follow. This means that the Progressbar will be set to -1 which will halt the processing.
    For each line segment found, a buffer is created around either the entire line, the first node, or the last node. Just like we did before initiating the Progressbar. If multiple line segments were found, we combine these buffers into a single buffer object.
    We also insert the RowID of the individual lines into the table __FOLLOW_LINES along with the order and the node used.
    Before exiting, we run a query that will find lines intersecting the combined buffer we created using the lines intersecting the previous buffer.
    	If TableInfo(__Intersecting_lines, TAB_INFO_NROWS) = 0 Then
    		Call TABClose("__Intersecting_lines")
    		Progressbar = -1
    		Exit Sub
    	End If
    
    	mnLoop	= mnLoop + 1
    
    	Fetch First From __Intersecting_lines
    	Do Until EOT(__Intersecting_lines)
    		nRowID		= __Intersecting_lines.ROWID
    		nBaseRowID	= BaseTableRowID(__Intersecting_lines)
    		oLine 		= __Intersecting_lines.OBJ
    		oNode 		= OBJGetNode(oLine, 1, mnNode)
    		If nRowID = 1 Then
    			If mnNode = 0 Then
    				oBuffer	= Buffer(oLine, mnBufferResolution, mfBufferWidth, msdistanceUnit)
    			Else
    				oNode 	= OBJGetNode(oLine, 1, mnNode)
    				oBuffer	= Buffer(oNode, mnBufferResolution, mfBufferWidth, msdistanceUnit)
    			End If
    		Else
    			If mnNode = 0 Then
    				oBuffer	= Combine(oBuffer, Buffer(oLine, mnBufferResolution, mfBufferWidth, msdistanceUnit))
    			Else
    				oNode 	= OBJGetNode(oLine, 1, mnNode)
    				oBuffer	= Combine(oBuffer, Buffer(oNode, mnBufferResolution, mfBufferWidth, msdistanceUnit))
    			End If
    		End If
    
    		Insert Into __FOLLOW__LINES
    			(LINE_ROWID, FOLLOW_ORDER, OBJ) Values (nBaseRowID, mnLoop, oNode)
    
    		Fetch Next From __Intersecting_lines
    	Loop
    	Commit Table __FOLLOW__LINES
    	Call TABClose("__Intersecting_lines")
    
    	If mnNode = 0 Then
    		Select *
    			From msTab
    			Where Not ROWID In (Select LINE_ROWID From __FOLLOW__LINES)
    			And OBJ Intersects oBuffer
    			Into __Intersecting_lines NoSelect Hide
    	Else
    		Select *
    			From msTab
    			Where Not ROWID In (Select LINE_ROWID From __FOLLOW__LINES)
    			And OBJGetNode(OBJ, 1, -1 * mnNode) Within oBuffer
    			Into __Intersecting_lines NoSelect Hide
    	End If
    
    	Progressbar = TableInfo(__FOLLOW__LINES, TAB_INFO_NROWS)
    After the process has finished, we check to see that we found at least some lines, and then we use the RowIDs stored in the __FOLLOW_LINES table to select the lines from the base table.
    	If TableInfo(__FOLLOW__LINES, TAB_INFO_NROWS) = 0 Then
    		Note "No lines seem to connect downstream to the selected object!"
    		Call TABClose("__FOLLOW__LINES")
    		Exit Function
    	End If
    
    	Select * From msTab
    		Where ROWID In (Select LINE_ROWID From __FOLLOW__LINES)
    And that's it. This process works fine for smaller datasets. In my example using the upstream method, the tool found just under 500 line segments from a dataset of 6700 line segments in just around 30 seconds.
    This type of analysis can be done in various ways and you can make it more advanced to also include attributes to determine the direction of the flow and maybe even handle one-directional or bi-directional flow. But then it quickly starts to grow into a more advanced tool. In these situations, you would probably be better of with RouteFinder. 


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

    Attachment(s)

    zip
    AnalysisHelper.zip   91 KB 1 version