Edge Networks

An edge network is a single object representing multiple discontinuous lines, such as contour lines. An edge network is represented in the SDK with the EdgeNetwork class. Unlike polygons or polylines, edge networks use both point and edge arrays to describe how disparate lines are connected.

Edge network examples

The following examples demonstrate various ways to create, manipulate, and analyse edge networks using the SDK.

Note

Some examples on this page use non-standard external libraries. You may need to install these separately. External libraries include the following:

Creating an edge network

This example demonstrates how to create an EdgeNetwork with a specific arrangement of points and edges. The edges in this example are disjoint, meaning that no two edges share the same endpoint.

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

with project.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 an edge network using the same points as in the previous example, but with all edges joined to form a continuous loop. This approach effectively creates a shape that could be more easily represented using a Polygon object.

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

with project.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 demonstrates how to import a DXF file containing several polylines (open) and polygons (closed) and create a single EdgeNetwork object with the data.

Note:  To run this example script, first download the example DXF file contours.dxfto a folder on your system. The script references the folder location F:\Python SDK Help\data\. You will need to update this location in the script to match the folder where you extracted the file to.

Note:  This example uses the ezdxf library, which is not included by default with Python. You may need to install it using pip install ezdxf before running 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])

project = Project() # Connect to default project
with project.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 how to extract points from an EdgeNetwork object and create a PointSet object with those points. The same approach can be used to extract points from other objects, such as surfaces.

from mapteksdk.project import Project
from mapteksdk.data import PointSet
project = Project() # Connect to default project

input_path = "/cad/dxf file"
output_path = "/cad/dxf file - points"
if project.find_object(input_path):
    with project.read(input_path) as edge_network:
        with project.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 demonstrates how to assign attributes to edge and point primitives in order to facilitate colouring based on these attributes. Specifically, it assigns the X, Y, and Z coordinates of each point as attributes and calculates the distance of each edge, which is also assigned as an attribute.

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

path = "/cad/dxf file"
edge_distances = []
for obj in project.get_selected():
    with project.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)

The animation below illustrates how to access and use these attributes in the PointStudio. In the SDK, you can use colour maps (called legends in the application) to colour the object by these attributes (see Colour Maps).

Removing points below a given height

This example demonstrates how to remove points from an EdgeNetwork object that are below a given height.

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

path = "/cad/dxf file"

if project.find_object(path):
    with project.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(path))

Removing edges by length

This example demonstrates how to remove edges (rather than points) from an EdgeNetwork object that are longer than a specified length.

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

path = "/cad/dxf file"
edge_distances = []
if project.find_object(path):
    with project.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(path))

Deleting selected edges

This example demonstrates how to delete edges selected by the user from an edge network.

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

for item in project.get_selected():
    if item.is_a(EdgeNetwork):
        with project.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 colours edges in selected edge network, polyline, and polygon objects based on the percentage grade (or slope) calculated between the two endpoints of each edge. It uses predefined minimum and maximum grade thresholds to determine the colour.

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__":
    project = 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 = project.get_selected()
    for item in selection:
        with project.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 demonstrates how to convert an EdgeNetwork into a collection of Polyline objects by tracing connected lines throughout the network. The process involves recursively tracing from a starting edge, identifying connected edges, and storing them to avoid revisits. Once all edges have been processed, the results are returned, and the polylines are constructed.

This approach is effective for linework created manually, such as haul road networks, but may not be suitable for all edge networks, particularly those generated by certain software tools (such as like contour linework).

Note:  You can download the data used in this example 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__":
    project = Project()
    input("Select EdgeNetwork and press any key to continue..")
    selection = project.get_selected()
    for item in selection:
        if item.is_a(EdgeNetwork):
            with project.read(item) as edges:
                explode_edgenetwork(edges, project)