Edge Networks

An edge network is a single object that contains multiple discontinuous lines. A common example is a single object representing a series of contour lines. Unlike a polygon or a polyline, you need to consider both point and edge arrays (as opposed to points only) to describe how disparate line-work within an edge network joins together.

Some examples in this page use non-standard external libraries.

Libraries referenced:

Edge network examples

Creating an edge network

This example demonstrates creation of an EdgeNetwork with five metre separation between line joins.

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import numpy as np
proj = Project() # Connect to default project

with proj.new("/cad/edge network", EdgeNetwork, overwrite=True) as edgenetwork:
    # Create a green flat rectangle with 5m spacing between joins
    edgenetwork.points = np.array([[0, 0, 0], [0, 50, 0],
                                   [0, 55, 0], [100, 55, 0],
                                   [100, 50, 0], [100, 0, 0],
                                   [95, -5, 0], [0, -5, 0]])
    edgenetwork.edges = np.array([[0, 1], [2, 3], [4, 5], [6, 7]])
    edgenetwork.point_colours[0:2] = [0,255,0,255] # Green
    edgenetwork.point_colours[2:4] = [255,0,0,255] # Red
    edgenetwork.point_colours[4:6] = [0,0,255,255] # Blue
    edgenetwork.point_colours[6:8] = [255,255,255,255] # White

Creating a joined edge network

This example creates the same points as above but with all edges joined (which could more easily be represented as a Polygon object).

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import numpy as np
proj = Project() # Connect to default project

with proj.new("/cad/edge network (joined)", EdgeNetwork, overwrite=True) as edgenetwork:
    # Create a green flat rectangle with 5m spacing between joins
    edgenetwork.points = np.array([[0, 0, 0], [0, 50, 0],
                                   [0, 55, 0], [100, 55, 0],
                                   [100, 50, 0], [100, 0, 0],
                                   [95, -5, 0], [0, -5, 0]])
    edgenetwork.edges = np.array([[0, 1], [1, 2], [2, 3], [3, 4],
                                  [4, 5], [5, 6], [6, 7], [7, 0]])
    edgenetwork.point_colours[0:2] = [0,255,0,255] # Green
    edgenetwork.point_colours[2:4] = [255,0,0,255] # Red
    edgenetwork.point_colours[4:6] = [0,0,255,255] # Blue
    edgenetwork.point_colours[6:8] = [255,255,255,255] # White

Creating an edge network from a DXF file

This example will import a DXF file containing several polylines (open) and polygons (closed) and create a single EdgeNetwork object with the data.

Note:  The following example refers to a DXF file that can be downloaded here: contours.dxf

Important:  This makes use of the ezdxf library, which is not included by default with Python. You may need to install it before trying the script.

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import ezdxf

# Use the path you have saved the example shp file:
file_path =  "F:\\Python SDK Help\\data\\contours.dxf"

dxf = ezdxf.readfile(file_path)
# Temporary storage for data to go into our EdgeNetwork
points, edges, colours = [], [], []
# Read the dxf modelspace data
for item in dxf.modelspace():
    # We're only interested in polyline objects within the file
    if item.dxftype() == 'POLYLINE':
        # Note the index this line started at for polygon closure
        this_line_start = len(points)
        # Iterate each of the points within the polyline
        for i in range(len(item.vertices)):
            vert = item.vertices[i]
            points.append([vert.dxf.location.x, vert.dxf.location.y, vert.dxf.location.z])
            # Convert RGB provided tuple to RGBA list with 255 alpha value
            rgb = list(vert.rgb)
            rgb.append(255)
            colours.append(rgb)
            # For an EdgeNetwork we don't want to join every single point to the next.
            # Once we're up to the last point, we don't need to append an edge join for it,
            # or we join it back to the start of the line to make it a polygon.
            if i < len(item.vertices)-1:
                edges.append([len(points)-1, len(points)])
            else:
                # If the polyline is closed, then join it back to the start,
                # otherwise leave a break for the next line
                if item.is_closed:
                    edges.append([len(points)-1, this_line_start])

proj = Project() # Connect to default project
with proj.new("/cad/dxf file", EdgeNetwork, overwrite=True) as edge_network:
    edge_network.points = points
    edge_network.edges = edges
    edge_network.point_colours = colours

Converting an EdgeNetwork into a PointSet

This example demonstrates extracting the points out of the object created above and putting them into a PointSet. The methodology is identical for other objects, such as extracting points from a surface.

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork, PointSet
import numpy as np
proj = Project() # Connect to default project

input_path = "/cad/dxf file"
output_path = "/cad/dxf file - points"
if proj.find_object(input_path):
    with proj.read(input_path) as edge_network:
        with proj.new(output_path, PointSet, overwrite=True) as points:
            points.points = edge_network.points
            points.point_colours = edge_network.point_colours
else:
    print("Couldn't find existing object '{}'".format(input_path))

Assigning attributes to edges and points for colouring

This example shows how to apply attributes to edge and point primitives. The attributes applied are the X, Y, Z value of each point (to allow colouring by X, Y, or Z) as well as calculating the distance of every edge and applying that as an attribute to the edges.

The animation below illustrates how to access and use these attributes in the software. In the SDK to do similar you will need to use colour maps (represented as Legends in the software).

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import math
proj = Project() # Connect to default project

path = "/cad/dxf file"
edge_distances = []
for obj in proj.get_selected():
    with proj.edit(obj) as edge_network:
        # Store each of the coordinate components for every point as an attribute
        edge_network.point_attributes.save_attribute("z values", edge_network.points[:,2])
        edge_network.point_attributes.save_attribute("y values", edge_network.points[:,1])
        edge_network.point_attributes.save_attribute("x values", edge_network.points[:,0])
        
        # Calculate the distance for every edge
        for edge in edge_network.edges: # [i1, i2]
            p1 = edge_network.points[edge[0]] # [x, y, z]
            p2 = edge_network.points[edge[1]] # [x, y, z]
            dist = math.sqrt(((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) + ((p1[2]-p2[2])**2))
            edge_distances.append(dist)
        # Save each length as an attribute of the associated edge
        edge_network.edge_attributes.save_attribute("edge_len", edge_distances)       

Removing points below a height

This example will remove all points from the above object that are below a specified height.

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import numpy as np
proj = Project() # Connect to default project

path = "/cad/dxf file"

if proj.find_object(path):
    with proj.edit(path) as edge_network:
        # Get index of all points with a z value below 220
        height_filter = 220
        # np.where will provide the indices of all items matching the criteria
        points_below = np.where(edge_network.points[:,2] < height_filter)
        # Built in remove_points function will allow removal of one or more points
        # The parameter uptdate_immediately defaults to true and will refresh
        # related arrays to reflect the changes
        edge_network.remove_points(points_below)      
else:
    print("Couldn't find existing object '{}'".format(input_path))

Removing edges by length

Similar to the above example, this will remove edges (rather than points) that are longer than a specified length.

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import numpy as np
import math
proj = Project() # Connect to default project

path = "/cad/dxf file"
edge_distances = []
if proj.find_object(path):
    with proj.edit(path) as edge_network:
        # Calculate the distance for every edge
        for edge in edge_network.edges: # [i1, i2]
            p1 = edge_network.points[edge[0]] # [x, y, z]
            p2 = edge_network.points[edge[1]] # [x, y, z]
            dist = math.sqrt(((p1[0]-p2[0])**2) + ((p1[1]-p2[1])**2) + ((p1[2]-p2[2])**2))
            edge_distances.append(dist)
        
        # Get index of all edges with a length above 20
        length_filter = 20
        # np.where will provide the indices of all items matching the criteria
        edge_distances = np.array(edge_distances)
        long_edges = np.where(edge_distances > length_filter)
        # Built in remove_edges function will allow removal of one or more edges.
        # The parameter uptdate_immediately defaults to true and will refresh
        # related arrays to reflect the changes
        edge_network.remove_edges(long_edges)      
else:
    print("Couldn't find existing object '{}'".format(input_path))

Deleting selected edges

from mapteksdk.project import Project
from mapteksdk.data import EdgeNetwork
import numpy as np
proj = Project() # Connect to default project

for item in proj.get_selected():
    if item.is_a(EdgeNetwork):
        with proj.edit(item) as edge_network:
            # Get list of indices for selected edges
            selected_edges = np.where(edge_network.edge_selection)
            # Remove selected edge indices
            edge_network.remove_edges(selected_edges)      

Colouring edges by grade thresholds

This example shows how to colour each edge segment throughout an EdgeNetwork , a Polyline or a Polygon , based on the grade between the first and second point of the edge and minimum/maximum grade thresholds defined.

from mapteksdk.project import Project
import numpy as np

def calculate_percentage_grade(start:np.ndarray, end:np.ndarray):
    """Calculate the percentage grade of the line between points start and end.

    Parameters
    ----------
    start (ndarray): Numpy array with three elements representing the start point.
    end (ndarray): Numpy array with three elements representing the end point.
    Returns
    -------
    float: The percentage grade between the two points.
    """
    # Make sure end is always the higher point.
    #if start[2] > end[2]:
     ##   temp = start
      #  start = end
      #  end = temp

    # Rise is just difference in heights of the two points.
    rise = end[2] - start[2]
    # Run is distance between start and stop but ignoring the z component.
    run_vector = start - end
    run = np.linalg.norm(run_vector[:2])
    return 100 * (rise / run)

if __name__ == "__main__":
    proj = Project()
    input("Select EdgeNetwork and press any key to continue..")
    colour_above_max_grade = [255, 0, 0, 255] # Red
    colour_below_min_grade = [0, 0, 255, 255] # Blue
    colour_within_threshold = [0, 255, 0, 255] # Green
    min_grade, max_grade = 0., 20. # min % and max % for colour ranges
    
    selection = proj.get_selected()
    for item in selection:
        with proj.edit(item) as edges:
            # By checking if the object has an 'edges' attribute,
            # we can use this on EdgeNetwork, Polyline, Polygon and Surface
            if hasattr(edges, 'edges'):
                for i in range(edges.edge_count):
                    this_edge = edges.edges[i]
                    p1 = edges.points[this_edge[0]]
                    p2 = edges.points[this_edge[1]]
                    grade = calculate_percentage_grade(p1, p2)
                    # Note: If creating a new EdgeNetwork, Polyline or Polygon, you will
                    # need to call edges.save() to populate the edge array in the database
                    # before assigning edge colours.
                    if grade > max_grade:
                        # Colouring edge_colours instead of points will provide 
                        # a solid transition between colour changes.
                        edges.edge_colours[i] = colour_above_max_grade
                    elif grade < min_grade:
                        edges.edge_colours[i] = colour_below_min_grade
                    else:
                        edges.edge_colours[i] = colour_within_threshold

Exploding an edge network into a set of polylines

This example takes an EdgeNetwork and traces connected lines throughout it to generate a set of output Polyline objects. It works by recursively tracing from a start edge, finding edges with connected indices and storing them in a set so that they aren't revisited. Once all edges have been visited, results are returned and the polylines are built.

This works well for linework drawn by humans (such as haul road networks), but may not work in all cases or all edge networks generated by certain software tools (such as contour linework).

Note:  Data used for this example can be found here: EdgeNetworkExample.dxf

from mapteksdk.data import EdgeNetwork, Polyline
from mapteksdk.project import Project
import numpy as np

def explode_edgenetwork(edgenetwork, proj=None):
    """ Converts EdgeNetwork (single object with several disconnected lines) into
    a series of Polylines.
    
    The results are stored under /{EdgeNetwork path}_exploded/Line x
    
    For each start edge this will recursively trace it until it has no more neighbours.
    While tracing it, it will mark traced edges as seen so they aren't revisited later.
    
    Returns:
        dict[id]=ndarray(points)
    """
    def trace_from(this_edge, edges, points, seen_edges, result):
        """Recursively trace edges from this_edge that has not yet been seen

        Args:
            this_edge (int): index of current edge to trace from
            edges (ndarray): array of edges
            seen_edges (Set): list of visited edges
            result (ndarray): array of edges to join for this line

        Returns:
            tuple(ndarray(edges), set(seen_edges))
        """
        # Neighbour = start of next edge is end of this edge     
        neighbour = np.where(edges[:,0] == edges[this_edge][1])
        if len(neighbour) > 0:
            for val, j in np.ndenumerate(neighbour):
                if j in seen_edges: continue
                seen_edges.add(j)
                result = np.hstack((result, edges[j]))
                return trace_from(j, edges, points, seen_edges, result)
        return (result, seen_edges)
    # Store individual lines in results
    results = {}
    # Store set of seen edge indices to prevent re-tracing
    seen_edges = set()
    line_id = 0
    for i in range(edgenetwork.edge_count):
        if i in seen_edges: continue
        line_id += 1
        seen_edges.add(i)
        this_edges, seen_edges = trace_from(i,
                                           edgenetwork.edges,
                                           edgenetwork.points,
                                           seen_edges,
                                           edgenetwork.edges[i])
        # As edges will create duplicates on subsequent edges,
        # we will remove consecutive duplicates [0,1,1,2,2,3,1,1,0] = [0,1,2,3,1,0]
        selection = np.ones(this_edges.shape[0], dtype=bool)
        selection[1:] = this_edges[1:] != this_edges[:-1]
        this_edges = this_edges[selection]
        # Convert the edge list to a line of x,y,z coordinates
        this_line = edgenetwork.points[this_edges]
        results[f"Line_{line_id}"] = this_line
        if proj:
            with proj.new_or_edit(f"{edgenetwork.id.path}_exploded/Line {line_id}", Polyline) as test:
                test.points = this_line
            print(f"Saved Polyline {edgenetwork.id.path}_exploded/Line {line_id}")
    return results

if __name__ == "__main__":
    proj = Project()
    input("Select EdgeNetwork and press any key to continue..")
    selection = proj.get_selected()
    for item in selection:
        if item.is_a(EdgeNetwork):
            with proj.read(item) as edges:
                explode_edgenetwork(edges, proj)