Fix Crossing Links (Pipes) Interactively using WaterObjects.NET

Fix (Split) Crossing Links interactively using WaterObjects.NET

Fix Crossing Links main window


Introduction
Building a model from external source like CAD or shapefile is piece of cake in H&H Products and there are quite a lot queries to review as well as good number of tools to fix the issues. There is one particular feature that I wanted for so many years which hasn't shown up in the software, yet. Let’s say you run a query called “Find Crossing Pipes” and the software shows the links that are crossing and that basically it. The software doesn’t provide any way to split those links and combine the crossing links together automatically.  

So, I just wrote my own tool to fix the Crossing Links and sharing here so that you can use it and fix the crossing links interactively with ease.

The programming details covered herein are applicable to several of Bentley’s hydraulics and hydrology (H&H) applications including WaterGEMS, WaterCAD, HAMMER, SewerGEMS, SewerCAD, SewerGEMS Sanitary, CivilStorm, StormCAD, PondPack etc. In fact, most of the lines of code can be used to create the tool that can work with most H&H applications. This tool in particular, however, will work with WaterGEMS and WaterCAD only.
How to Run the Tool
If you simply obtain the executable (.exe) file then make sure to place/copy this file in the right directory. For example, for the .exe needs to be placed in the \Program Files (x86)\Bentley\WaterGEMS\ directory or in corresponding WaterCAD installed directory. Once the exe file is in the right location, run it and a window as shown in the figure above will show up.

Now, click on File > Open or simply click on Open button to open up the project. When the project is up, go to Queries > Find Crossing Links. This will show the list of crossing links. When double clicked on an item in the list, it will highlight the crossing pipes and zooms to that crossing links and also selects those links.

There are two ways to split the pipes, manual or automated. To perform pipe split one by one, select (highlight) the item in the list and click on manual button in the toolbar. To automatically split all the pipes, put a check mark on the ones that needs a split and then click on green arrow button, it sequentially go into every crossing links then will split them.

Once the split is performed, either click on File > Save or Save As and close the window.
Sample 1
In this sample we can see how two pipes crossing each other are being split by the tool. To split the pipes, "Manual" method has been used, in which one has to select the item in the list and then click the Manual button. When an item is double clicked, corresponding links are highlighted and zoomed to that location.
Sample 2
This sample adds little bit of complexity as the pipe that's previously been flagged as crossing pipes gets deleted during the split process. In this sample, one pipe crosses two pipes so one pipe needs to be split at two locations.
Sample 3
In this case, one pipe actually crosses two pipes at multiple locations. So this process adds more complexity to the tool. Notice, Find Crossing Links query has been run multiple times in this case because each pipe split creates another crossing pipe.
Programming Details
Bentleys WaterObjects.NET API will be primarily discussed in the sections below using C# as a programming language. It is assumed that the reader has some basic knowledge about the .NET programming.
References Used
Haestad.Domain
Haestad.Domain.ModelingObjects
Haestad.Drawing
Haestad.Drawing.Control
Haestad.Drawing.Domain
Haestad.Framework
Haestad.Framwork.Windows.Forms
Haestad.LicensingFacade
Haestad.Mapping
Haestad.Support
Haestad.Support.Mixed
Haestad.Water.Forms
Haestad.WaterGEMS
Namespaces Used
Haestad.Domain
Haestad.Drawing.Control.Application
Haestad.Drawing.Domain
Haestad.Drawing.Windows.Forms.Components
Haestad.Framework.Application
Haestad.Framework.Support
Haestad.Framework.Windows.Forms.Forms
Haestad.Framework.Windows.Forms.Resources
Haestad.Idaho.Application
Haestad.LicensingFacade
Haestad.Mapping.Support
Haestad.Support.Library
Haestad.Support.Mixed.Geometry
Haestad.Support.Support
Under the hood of the tool
The process of fixing the crossing links can be grouped in following categories.
•     Finding the crossing link and the link that’s being crossed over.
•     Finding the intersecting point of the crossing links.
•     Split-ing a Link at the intersecting point.
•     Keeping track of newly added or deleted elements as it will be needed.

Finding the crossing link and the link that’s being crossed over
In the API there is way to call basically most of the queries. From the LayoutController which we can obtain from ParentFormUIModel, we can get FeatureManager property. One of the method in this property is called ExecuteDrawingQuery(). This method will return the crossing links as HmIDCollecion.
string errorMessage;
HmIDCollection crossingLinkIDs = new HmIDCollection();
crossingLinkIDs = ParentFormUIModel.LayoutController.FeatureManager.ExecuteDrawingQuery(
       DrawingQueryType.CrossingPipes,
       Project as IDomainProject,
       Project.NumericPresentationManager,
       new IQueryParameter[0],
       null,
       new NullProgressIndicator(),
       out errorMessage);

return crossingLinkIDs;
The drawback of the above API is, it does not return the links that are being crossed over. So, one has to reinvent the wheel.

Finding the intersecting point of the crossing links
In the API, there is a class called MathLibrary which has a static DoLineSegmentsIntersect() method. This method will check if given line segments, not the entire line intersects. This is a little bit of problem with the API for our purpose. If a given segment shares common start or stop geometry then the method will return true which we need to avoid so we need ignore if the end points that have save geometry.
private bool GetIntersectingPoint(GeometryPoint[] firstLinkCoords, GeometryPoint[] secondLinkCoords, out GeometryPoint intersectingPoint)
{
    intersectingPoint = firstLinkCoords[0];
    bool breakLoop = false;
    bool foundIntersectingPoint = false;
   
    for (int i = 0; i < (firstLinkCoords.Length - 1); i++)
    {
      if (breakLoop)
         break;

      for (int j = 0; j < (secondLinkCoords.Length - 1); j++)
      {
         if (MathLibrary.DoLineSegmentsIntersect(secondLinkCoords[j], secondLinkCoords[j + 1], firstLinkCoords[i], firstLinkCoords[i + 1]))
         {
            if (secondLinkCoords[j] != firstLinkCoords[i] &&
               secondLinkCoords[j] != firstLinkCoords[i + 1] &&
               secondLinkCoords[j + 1] != firstLinkCoords[i] &&
               secondLinkCoords[j + 1] != firstLinkCoords[i + 1])
            {

               intersectingPoint = GeometryLibrary.GetLineSegmentIntersection(secondLinkCoords[j], secondLinkCoords[j + 1], firstLinkCoords[i], firstLinkCoords[i + 1]);
               foundIntersectingPoint = true;
               breakLoop = true;
               break;
            }
         }
      }
   }
   return foundIntersectingPoint;
}
Split-ing a Link at the intersecting point
Here again we use the API to split the link. During the process we register to DomainElementsChangeHandler to store the newly added as well as deleted link IDs. This is very important as it helps to map the deleted element IDs with the new IDs.
private int SplitLink(IDomainManager domainManager, int newNodeID, int linkToBeSplitID, int startNodeID, int stopNodeID)
{
   DomainElementEventChannel domainElementEventChannel = ApplicationModel.DomainElementEventChannel;
   int nodeID = -1;

   try
   {
      domainElementEventChannel.ElementsChanged += new DomainElementsChangeHandler(domainElementEventChannel_ElementsChanged);

      nodeID = ParentFormUIModel.LayoutController.DomainManager.SplitLink(
            new ElementIdentifier(DomainElementTypeID(newNodeID), newNodeID),
            new ElementIdentifier(DomainElementTypeID(linkToBeSplitID), linkToBeSplitID),
            new ElementIdentifier(DomainElementTypeID(startNodeID), startNodeID),
            new ElementIdentifier(DomainElementTypeID(stopNodeID), stopNodeID),
            null,
            (ILabeledElementDomainProject)Project,
            new GraphicalUndoManager(100)); // can be any number

   }
   finally
   {
      domainElementEventChannel.ElementsChanged -= domainElementEventChannel_ElementsChanged;
   }
   return nodeID;
}
Keeping track of newly added or deleted elements
It is very important to keep track of which element got deleted and what's the new ID replacing the old one. When a crossing link identifies a link ID as a crossing link ID, that particular ID may have been removed by previous split link process so managing the ID map is another key part of this tool.
Notes
In the actual solution (Visual Studio file), there are a lot more lines of code than the code discussed above, and may not have an exact match. There, the solution file may exhibit slightly different code lines than above. The above code is to highlight the key codes for the task.
What if the Tool does Not Work or Help is Needed?
-If you just received the exe file, make sure the file is placed in the right location
-If you are working with Visual Studio/Express, you can contact me (by email at Akshaya.Niraula@bentley.com).
History
•     2013/10/06 - Initial Post.