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 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 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 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 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]) 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 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 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 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 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)
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 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
Similar to the above example, this will remove edges (rather than points) 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
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 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__": 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 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__": 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)