Initial commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
from qgis.core import (QgsFeature, QgsField, QgsVectorLayer, QgsProject, QgsSpatialIndex)
|
||||
|
||||
print("Associating home points to drop points...")
|
||||
|
||||
# Load the NODES and HOME_POINT layers
|
||||
nodes_layer = QgsProject.instance().mapLayersByName('NODES')[0]
|
||||
home_points_layer = QgsProject.instance().mapLayersByName('HOME_POINTS')[0]
|
||||
|
||||
# Prepare the drop_point_id field for the HOME_POINT layer
|
||||
home_points_layer.startEditing()
|
||||
home_points_prov = home_points_layer.dataProvider()
|
||||
home_points_prov.addAttributes([QgsField("drop_point_id", QVariant.Int)])
|
||||
home_points_layer.updateFields()
|
||||
|
||||
# Build a spatial index for the NODES layer
|
||||
index = QgsSpatialIndex()
|
||||
node_features = {f.id(): f for f in nodes_layer.getFeatures()}
|
||||
index.addFeatures(node_features.values())
|
||||
|
||||
# Find the nearest node for each home point and update the drop_point_id attribute
|
||||
for hp in home_points_layer.getFeatures():
|
||||
nearest_nodes = index.nearestNeighbor(hp.geometry().asPoint(), 1)
|
||||
if nearest_nodes:
|
||||
nearest_node_id = nearest_nodes[0]
|
||||
nearest_node = node_features[nearest_node_id]
|
||||
node_id = nearest_node["id"]
|
||||
home_points_layer.changeAttributeValue(hp.id(),
|
||||
home_points_layer.fields().indexOf("drop_point_id"),
|
||||
node_id)
|
||||
|
||||
# Stop editing the HOME_POINT layer and save changes
|
||||
home_points_layer.commitChanges()
|
||||
|
||||
print("Done.")
|
||||
@@ -0,0 +1,77 @@
|
||||
import osmnx as ox
|
||||
import geopandas as gpd
|
||||
from shapely import wkt
|
||||
from shapely.geometry import Point
|
||||
from qgis.core import QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry, QgsWkbTypes, QgsField
|
||||
|
||||
print("Getting road centerlines...")
|
||||
|
||||
# Get the HOME_POINTS layer
|
||||
home_points_layer = QgsProject.instance().mapLayersByName('HOME_POINTS')[0]
|
||||
|
||||
# Check if there's at least one feature in the layer
|
||||
if home_points_layer.featureCount() > 0:
|
||||
# Generate a buffer around each point
|
||||
buffer_distance = 0.01 # change to your desired buffer distance
|
||||
buffers = []
|
||||
for feature in home_points_layer.getFeatures():
|
||||
point = wkt.loads(feature.geometry().asWkt())
|
||||
buffer_polygon = point.buffer(buffer_distance)
|
||||
buffers.append(buffer_polygon)
|
||||
|
||||
# Combine all buffers into a single polygon
|
||||
union_polygon = gpd.GeoSeries(buffers).unary_union
|
||||
|
||||
# Create PROJECTAREA layer in memory
|
||||
project_area_layer = QgsVectorLayer("Polygon?crs=epsg:4326", "PROJECTAREA", "memory")
|
||||
pr = project_area_layer.dataProvider()
|
||||
|
||||
# Start editing the layer
|
||||
project_area_layer.startEditing()
|
||||
|
||||
# Create new feature for the layer
|
||||
f = QgsFeature()
|
||||
f.setGeometry(QgsGeometry.fromWkt(union_polygon.wkt))
|
||||
pr.addFeature(f)
|
||||
|
||||
# Commit changes
|
||||
project_area_layer.commitChanges()
|
||||
|
||||
# Add the layer to the Layers panel in QGIS
|
||||
project_area_layer.setOpacity(0.3)
|
||||
QgsProject.instance().addMapLayer(project_area_layer)
|
||||
|
||||
# Use OSMnx to download the street network
|
||||
G = ox.graph_from_polygon(union_polygon, network_type='drive')
|
||||
|
||||
# Convert the graph into GeoDataFrames
|
||||
gdf_nodes, gdf_edges = ox.graph_to_gdfs(G)
|
||||
|
||||
# Convert gdf_edges (GeoDataFrame) to WKT format for QGIS
|
||||
wkt_list = gdf_edges['geometry'].to_wkt().tolist()
|
||||
|
||||
# Create a new layer for the street centerlines
|
||||
vl = QgsVectorLayer("LineString?crs=epsg:4326", "CENTERLINES", "memory")
|
||||
|
||||
# Start editing the layer
|
||||
vl.startEditing()
|
||||
|
||||
# Get the data provider
|
||||
pr = vl.dataProvider()
|
||||
|
||||
# Create new features for the layer
|
||||
for wkt in wkt_list:
|
||||
f = QgsFeature()
|
||||
f.setGeometry(QgsGeometry.fromWkt(wkt))
|
||||
pr.addFeature(f)
|
||||
|
||||
# Commit changes
|
||||
vl.commitChanges()
|
||||
|
||||
# Add the layer to the Layers panel in QGIS
|
||||
QgsProject.instance().addMapLayer(vl)
|
||||
|
||||
else:
|
||||
print("No points found in the HOME_POINTS layer.")
|
||||
|
||||
print("Done.")
|
||||
@@ -0,0 +1,50 @@
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.core import (QgsFeature, QgsField, QgsGeometry, QgsPointXY, QgsVectorLayer, QgsProject)
|
||||
from geopy.distance import great_circle
|
||||
|
||||
# Constants
|
||||
SEARCH_RADIUS = 300 / 3.28084 # converting feet to meters
|
||||
COST_PER_METER = 1.5
|
||||
|
||||
# Locate the existing layers
|
||||
home_points_layer = QgsProject.instance().mapLayersByName('HOME_POINTS')[0]
|
||||
poles_layer = QgsProject.instance().mapLayersByName('POLES')[0]
|
||||
edges_layer = QgsProject.instance().mapLayersByName('EDGES')[0]
|
||||
|
||||
# Define the data provider
|
||||
pr = edges_layer.dataProvider()
|
||||
|
||||
# Iterate over every home point
|
||||
for home_feature in home_points_layer.getFeatures():
|
||||
home_point = home_feature.geometry().asPoint()
|
||||
|
||||
# Initialize nearest neighbor search
|
||||
nearest_neighbor = None
|
||||
nearest_distance = None
|
||||
|
||||
# Search for the nearest pole
|
||||
for pole_feature in poles_layer.getFeatures():
|
||||
pole_point = pole_feature.geometry().asPoint()
|
||||
distance = great_circle((home_point.y(), home_point.x()), (pole_point.y(), pole_point.x())).meters
|
||||
|
||||
# If the pole is within 300 feet and it's closer than the previous nearest neighbor
|
||||
if distance < SEARCH_RADIUS and (nearest_neighbor is None or distance < nearest_distance):
|
||||
nearest_neighbor = pole_feature
|
||||
nearest_distance = distance
|
||||
|
||||
# If a nearest neighbor was found within 300 feet
|
||||
if nearest_neighbor is not None:
|
||||
# Create a new edge feature
|
||||
edge = QgsFeature()
|
||||
edge.setGeometry(QgsGeometry.fromPolylineXY([home_point, nearest_neighbor.geometry().asPoint()]))
|
||||
|
||||
# Calculate cost
|
||||
cost = nearest_distance * COST_PER_METER
|
||||
|
||||
# Set attributes
|
||||
edge.setAttributes(['Aerial Drop', nearest_distance, cost])
|
||||
pr.addFeature(edge)
|
||||
|
||||
# Update the layer's extent when new features have been added
|
||||
edges_layer.updateExtents()
|
||||
print("Done.")
|
||||
@@ -0,0 +1,70 @@
|
||||
from geopy.distance import great_circle
|
||||
from qgis.core import (QgsFeature, QgsField, QgsGeometry, QgsPoint, QgsPointXY, QgsVectorLayer, QgsProject, QgsSpatialIndex)
|
||||
|
||||
# Locate the aerial path layer
|
||||
aerial_path_layer = QgsProject.instance().mapLayersByName('AERIAL_PATH')[0]
|
||||
|
||||
# Locate the edges layer
|
||||
edges_layer = QgsProject.instance().mapLayersByName('EDGES')[0]
|
||||
|
||||
# Define the data provider
|
||||
pr = edges_layer.dataProvider()
|
||||
|
||||
# Initialize spatial index
|
||||
index = QgsSpatialIndex()
|
||||
|
||||
# List to hold new features and their IDs
|
||||
new_edges = []
|
||||
features_by_id = {}
|
||||
|
||||
# Iterate over every feature of the AERIAL_PATH layer
|
||||
for feature in aerial_path_layer.getFeatures():
|
||||
# Get the geometry of the feature
|
||||
geom = feature.geometry()
|
||||
|
||||
# Handle both 2D and 3D geometries
|
||||
if geom.isMultipart():
|
||||
vertices = [v for part in geom.asMultiPolyline() for v in part]
|
||||
else:
|
||||
vertices = geom.asPolyline()
|
||||
|
||||
for i in range(1, len(vertices)):
|
||||
# Calculate the distance between the two points in feet
|
||||
pt1 = vertices[i - 1]
|
||||
pt2 = vertices[i]
|
||||
length = great_circle((pt1.y(), pt1.x()), (pt2.y(), pt2.x())).feet
|
||||
|
||||
# Create a new feature in the EDGES layer for each vertex pair
|
||||
edge = QgsFeature()
|
||||
new_geom = QgsGeometry.fromPolyline([QgsPoint(pt1), QgsPoint(pt2)])
|
||||
edge.setGeometry(new_geom)
|
||||
edge.setAttributes(['Aerial', length, length * 2.5])
|
||||
|
||||
new_edges.append(edge)
|
||||
pr.addFeature(edge)
|
||||
index.insertFeature(edge)
|
||||
features_by_id[edge.id()] = edge
|
||||
|
||||
# Start editing the edges layer
|
||||
edges_layer.startEditing()
|
||||
|
||||
# Adjust intersecting line endpoints to the average intersection point
|
||||
for edge_id, edge in features_by_id.items():
|
||||
endpoints = [QgsPoint(point) for point in edge.geometry().asPolyline()]
|
||||
for i in [0, -1]: # Check both endpoints
|
||||
# Find nearby endpoints (within a small threshold)
|
||||
search_radius = 0.00005
|
||||
nearby_ids = index.intersects(QgsGeometry.fromPointXY(QgsPointXY(endpoints[i])).buffer(search_radius, 8).boundingBox())
|
||||
nearby_endpoints = [QgsPoint(point) for id in nearby_ids for point in features_by_id[id].geometry().asPolyline() if QgsPoint(point).distance(endpoints[i]) < search_radius]
|
||||
if len(nearby_endpoints) > 2:
|
||||
# Update endpoint to the average nearby endpoint
|
||||
avg_x = sum(point.x() for point in nearby_endpoints) / len(nearby_endpoints)
|
||||
avg_y = sum(point.y() for point in nearby_endpoints) / len(nearby_endpoints)
|
||||
endpoints[i] = QgsPoint(avg_x, avg_y) # Create new QgsPoint object
|
||||
# Update geometry in the layer
|
||||
edges_layer.changeGeometry(edge.id(), QgsGeometry.fromPolyline(endpoints))
|
||||
|
||||
# Commit the changes and update the layer's extent when new features have been added
|
||||
edges_layer.commitChanges()
|
||||
edges_layer.updateExtents()
|
||||
print("Done.")
|
||||
@@ -0,0 +1,95 @@
|
||||
# Import required libraries
|
||||
import networkx as nx
|
||||
import time
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.core import QgsProject, QgsVectorLayer, QgsFeature, QgsField, QgsGeometry, QgsPoint
|
||||
import heapq
|
||||
|
||||
# Get layers
|
||||
edges_layer = QgsProject.instance().mapLayersByName('EDGES')[0]
|
||||
nodes_layer = QgsProject.instance().mapLayersByName('NODES')[0]
|
||||
home_points_layer = QgsProject.instance().mapLayersByName('HOME_POINTS')[0]
|
||||
|
||||
# Build the graph
|
||||
G = nx.Graph()
|
||||
nodes_data = {feature['id']: feature.geometry() for feature in nodes_layer.getFeatures()} # store node geometries by id
|
||||
edges_data = {(feature['start_node'], feature['end_node']): (feature.geometry(), feature['length'], feature['cost']) for feature in edges_layer.getFeatures()} # store edge geometries by node pairs
|
||||
|
||||
for feature in nodes_layer.getFeatures():
|
||||
G.add_node(feature['id'])
|
||||
|
||||
for feature in edges_layer.getFeatures():
|
||||
start_node = feature['start_node'] # assuming edges layer has start_node & end_node fields
|
||||
end_node = feature['end_node']
|
||||
G.add_edge(start_node, end_node, weight=feature['cost'], type=feature['type'], length=feature['length'], cost=feature['cost'])
|
||||
|
||||
# Identify home nodes
|
||||
home_nodes = {feature['drop_point_id'] for feature in home_points_layer.getFeatures() if feature['drop_point_id'] in G}
|
||||
|
||||
# Create a subgraph of G that only includes the home nodes and any nodes that connect them
|
||||
subgraph = nx.Graph()
|
||||
visited = set()
|
||||
|
||||
# Start at any 'home' node
|
||||
start = next(iter(home_nodes))
|
||||
visited.add(start)
|
||||
|
||||
counter = 0
|
||||
total = len(home_nodes)
|
||||
process_times = []
|
||||
|
||||
while home_nodes.difference(visited):
|
||||
start_time = time.time()
|
||||
|
||||
# Find the nearest 'home' node that is not in our tree yet
|
||||
next_node = min(
|
||||
(node for node in home_nodes if node not in visited),
|
||||
key=lambda node: nx.shortest_path_length(G, start, node, weight='cost')
|
||||
)
|
||||
|
||||
# Create a subgraph excluding other home nodes except start and next_node
|
||||
G_sub = G.copy()
|
||||
remove_nodes = home_nodes - {start} - {next_node}
|
||||
G_sub.remove_nodes_from(remove_nodes)
|
||||
|
||||
# Add the shortest path to this node to our tree
|
||||
shortest_path = nx.shortest_path(G_sub, start, next_node, weight='cost')
|
||||
subgraph.add_edges_from(zip(shortest_path, shortest_path[1:]))
|
||||
visited.add(next_node)
|
||||
start = next_node
|
||||
|
||||
end_time = time.time()
|
||||
process_times.append(end_time - start_time)
|
||||
counter += 1
|
||||
avg_time = sum(process_times) / len(process_times)
|
||||
remaining = (total - counter) * avg_time
|
||||
hours, remainder = divmod(remaining, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
print(f'Processing home node {counter} of {total}.\nEstimated time remaining: {int(hours)} hours, {int(minutes)} minutes, and {int(seconds)} seconds')
|
||||
|
||||
# Create a minimum spanning tree from the subgraph
|
||||
mst = nx.minimum_spanning_tree(subgraph, weight='cost')
|
||||
|
||||
# Prepare the NETWORK layer
|
||||
network_layer = QgsVectorLayer("LineString", "NETWORK", "memory")
|
||||
network_prov = network_layer.dataProvider()
|
||||
|
||||
# Add 'id', 'type', 'length', and 'cost' fields to the NETWORK layer
|
||||
network_prov.addAttributes([QgsField("id", QVariant.Int), QgsField("type", QVariant.String), QgsField("length", QVariant.Double), QgsField("cost", QVariant.Double)])
|
||||
network_layer.updateFields()
|
||||
|
||||
# Add edges from the minimum spanning tree to the NETWORK layer
|
||||
for edge in mst.edges():
|
||||
edge_geometry, edge_length, edge_cost = edges_data[(edge[0], edge[1])] if (edge[0], edge[1]) in edges_data else edges_data[(edge[1], edge[0])] # fetch geometry, length, cost by node pair
|
||||
edge_type = G.get_edge_data(edge[0], edge[1])['type'] # get edge 'type' from the original graph G
|
||||
network_feature = QgsFeature()
|
||||
network_feature.setGeometry(edge_geometry)
|
||||
network_feature.setAttributes([edge[0], edge_type, edge_length, edge_cost]) # set the type, length, cost attribute from edge data
|
||||
network_prov.addFeature(network_feature)
|
||||
|
||||
# Update NETWORK layer
|
||||
network_layer.updateExtents()
|
||||
|
||||
# Add NETWORK layer to Layers panel
|
||||
QgsProject.instance().addMapLayer(network_layer)
|
||||
print("Done.")
|
||||
@@ -0,0 +1,78 @@
|
||||
import itertools
|
||||
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.core import (QgsFeature, QgsField, QgsGeometry, QgsPointXY,
|
||||
QgsVectorLayer, QgsProject, QgsVectorDataProvider)
|
||||
|
||||
print("Creating nodes...")
|
||||
|
||||
# Load the EDGES layer
|
||||
edges_layer = QgsProject.instance().mapLayersByName('EDGES')[0]
|
||||
|
||||
# Prepare the NODES layer
|
||||
nodes_layer = QgsVectorLayer("Point", "NODES", "memory")
|
||||
prov = nodes_layer.dataProvider()
|
||||
|
||||
# Add 'id' field to the NODES layer
|
||||
prov.addAttributes([QgsField("id", QVariant.Int)])
|
||||
nodes_layer.updateFields()
|
||||
|
||||
# Prepare the start_node and end_node fields for the EDGES layer
|
||||
edges_layer.startEditing()
|
||||
edges_prov = edges_layer.dataProvider()
|
||||
edges_prov.addAttributes([QgsField("start_node", QVariant.Int),
|
||||
QgsField("end_node", QVariant.Int)])
|
||||
edges_layer.updateFields()
|
||||
|
||||
# To store the unique nodes
|
||||
unique_nodes = []
|
||||
|
||||
id_counter = itertools.count(start=1)
|
||||
|
||||
node_id_map = {}
|
||||
|
||||
total_features = edges_layer.featureCount()
|
||||
count = 0
|
||||
|
||||
for feature in edges_layer.getFeatures():
|
||||
count += 1
|
||||
print(f'Processing feature {count} of {total_features} ({(count/total_features)*100:.2f}%)')
|
||||
|
||||
# Get the start and end points
|
||||
start_point = feature.geometry().asPolyline()[0]
|
||||
end_point = feature.geometry().asPolyline()[-1]
|
||||
|
||||
# Check if nodes are unique and add them to NODES layer
|
||||
if start_point not in unique_nodes:
|
||||
unique_nodes.append(start_point)
|
||||
node_id = next(id_counter)
|
||||
node_feature = QgsFeature()
|
||||
node_feature.setGeometry(QgsGeometry.fromPointXY(start_point))
|
||||
node_feature.setAttributes([node_id])
|
||||
prov.addFeature(node_feature)
|
||||
node_id_map[start_point] = node_id
|
||||
|
||||
if end_point not in unique_nodes:
|
||||
unique_nodes.append(end_point)
|
||||
node_id = next(id_counter)
|
||||
node_feature = QgsFeature()
|
||||
node_feature.setGeometry(QgsGeometry.fromPointXY(end_point))
|
||||
node_feature.setAttributes([node_id])
|
||||
prov.addFeature(node_feature)
|
||||
node_id_map[end_point] = node_id
|
||||
|
||||
# Populate the start_node and end_node fields for the EDGES layer
|
||||
edges_layer.changeAttributeValue(feature.id(),
|
||||
edges_layer.fields().indexOf("start_node"),
|
||||
node_id_map[start_point])
|
||||
edges_layer.changeAttributeValue(feature.id(),
|
||||
edges_layer.fields().indexOf("end_node"),
|
||||
node_id_map[end_point])
|
||||
|
||||
# Stop editing the EDGES layer and save changes
|
||||
edges_layer.commitChanges()
|
||||
|
||||
# Add the layer to the Layers panel
|
||||
QgsProject.instance().addMapLayer(nodes_layer)
|
||||
|
||||
print("Done.")
|
||||
@@ -0,0 +1,52 @@
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.core import (QgsFeature, QgsField, QgsGeometry, QgsPointXY, QgsVectorLayer, QgsProject)
|
||||
|
||||
# Locate the edges layer
|
||||
edges_layer = QgsProject.instance().mapLayersByName('EDGES')[0]
|
||||
|
||||
# Create a new temporary layer for the poles
|
||||
poles_layer = QgsVectorLayer('Point?crs=epsg:4326', 'POLES', 'memory')
|
||||
|
||||
# Define the data provider
|
||||
pr = poles_layer.dataProvider()
|
||||
|
||||
# Add a new field to the new layer
|
||||
pr.addAttributes([QgsField('id', QVariant.Int)])
|
||||
poles_layer.updateFields()
|
||||
|
||||
# Store coordinates of existing poles to avoid duplicates
|
||||
existing_poles = set()
|
||||
|
||||
# Iterate over every feature of the EDGES layer
|
||||
for feature in edges_layer.getFeatures():
|
||||
# Only consider edges of type 'Aerial'
|
||||
if feature['type'] == 'Aerial':
|
||||
# Get the geometry of the feature
|
||||
geom = feature.geometry()
|
||||
|
||||
# Handle both 2D and 3D geometries
|
||||
if geom.isMultipart():
|
||||
vertices = [v for part in geom.asMultiPolyline() for v in part]
|
||||
else:
|
||||
vertices = geom.asPolyline()
|
||||
|
||||
for i, vertex in enumerate(vertices):
|
||||
# Do not create a new pole if one already exists at this location
|
||||
vertex_coordinates = (vertex[0], vertex[1])
|
||||
if vertex_coordinates in existing_poles:
|
||||
continue
|
||||
|
||||
# Create a new feature in the POLES layer for each vertex
|
||||
pole = QgsFeature()
|
||||
pole.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(*vertex_coordinates)))
|
||||
pole.setAttributes([i])
|
||||
pr.addFeature(pole)
|
||||
|
||||
# Remember this location so we don't create a duplicate pole
|
||||
existing_poles.add(vertex_coordinates)
|
||||
|
||||
# Update the layer's extent when new features have been added
|
||||
poles_layer.updateExtents()
|
||||
|
||||
# Add the new layer to the project
|
||||
QgsProject.instance().addMapLayer(poles_layer)
|
||||
@@ -0,0 +1,55 @@
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.core import (QgsFeature, QgsField, QgsGeometry, QgsPointXY, QgsVectorLayer, QgsProject)
|
||||
from geopy.distance import great_circle
|
||||
|
||||
# Constants
|
||||
SEARCH_RADIUS = 50
|
||||
COST_PER_METER = 19.10
|
||||
|
||||
# Locate the existing layers
|
||||
poles_layer = QgsProject.instance().mapLayersByName('POLES')[0]
|
||||
edges_layer = QgsProject.instance().mapLayersByName('EDGES')[0]
|
||||
|
||||
# Define the data provider
|
||||
pr = edges_layer.dataProvider()
|
||||
|
||||
# Iterate over every pole
|
||||
for pole_feature in poles_layer.getFeatures():
|
||||
pole_point = pole_feature.geometry().asPoint()
|
||||
|
||||
# Initialize nearest neighbor search
|
||||
nearest_vertex = None
|
||||
nearest_distance = None
|
||||
|
||||
# Search for the nearest vertex in underground edges
|
||||
for edge_feature in edges_layer.getFeatures():
|
||||
if edge_feature['type'] == 'Underground':
|
||||
# Extract vertices
|
||||
geom = edge_feature.geometry()
|
||||
vertices = geom.asPolyline() if not geom.isMultipart() else [v for part in geom.asMultiPolyline() for v in part]
|
||||
|
||||
for vertex in vertices:
|
||||
vertex_point = QgsPointXY(vertex[0], vertex[1])
|
||||
distance = great_circle((pole_point.y(), pole_point.x()), (vertex_point.y(), vertex_point.x())).meters
|
||||
|
||||
# If the vertex is within 50 feet and it's closer than the previous nearest neighbor
|
||||
if distance < SEARCH_RADIUS and (nearest_vertex is None or distance < nearest_distance):
|
||||
nearest_vertex = vertex_point
|
||||
nearest_distance = distance
|
||||
|
||||
# If a nearest vertex was found within 50 feet
|
||||
if nearest_vertex is not None:
|
||||
# Create a new edge feature
|
||||
edge = QgsFeature()
|
||||
edge.setGeometry(QgsGeometry.fromPolylineXY([pole_point, nearest_vertex]))
|
||||
|
||||
# Calculate cost
|
||||
cost = nearest_distance * COST_PER_METER
|
||||
|
||||
# Set attributes
|
||||
edge.setAttributes(['Transition', nearest_distance, cost])
|
||||
pr.addFeature(edge)
|
||||
|
||||
# Update the layer's extent when new features have been added
|
||||
edges_layer.updateExtents()
|
||||
print("Done.")
|
||||
@@ -0,0 +1,203 @@
|
||||
import geopandas as gpd
|
||||
from shapely.geometry import LineString, Point
|
||||
from shapely.affinity import translate
|
||||
from geopy.distance import great_circle
|
||||
from qgis.core import (
|
||||
QgsProject, QgsVectorLayer, QgsFeature, QgsGeometry,
|
||||
QgsWkbTypes, QgsField, QgsFields
|
||||
)
|
||||
|
||||
underground_cpf = 19.00
|
||||
buried_drop_cpf = 3.00
|
||||
|
||||
def extend_line(line, extension_length):
|
||||
"""Extend a line by a given length."""
|
||||
start_point = line.coords[0]
|
||||
end_point = line.coords[-1]
|
||||
|
||||
# Calculate the line's direction
|
||||
dx = end_point[0] - start_point[0]
|
||||
dy = end_point[1] - start_point[1]
|
||||
|
||||
# Calculate the extension length along each axis
|
||||
length = math.sqrt(dx**2 + dy**2)
|
||||
extension_dx = dx / length * extension_length
|
||||
extension_dy = dy / length * extension_length
|
||||
|
||||
# Create the extended line by translating the end point
|
||||
extended_line = LineString([start_point, translate(line, extension_dx, extension_dy).coords[-1]])
|
||||
|
||||
return extended_line
|
||||
|
||||
def closest_point_on_line(point, lines):
|
||||
"""Find closest point on the closest line to the given point."""
|
||||
closest_line = min(lines, key=point.distance)
|
||||
return closest_line.interpolate(closest_line.project(point))
|
||||
|
||||
# Load your layers
|
||||
homes_layer = QgsProject.instance().mapLayersByName('HOME_POINTS')[0]
|
||||
centerlines_layer = QgsProject.instance().mapLayersByName('CENTERLINES')[0]
|
||||
|
||||
# Convert layers to GeoDataFrames
|
||||
gdf_homes = gpd.GeoDataFrame.from_features([f for f in homes_layer.getFeatures()], crs=homes_layer.crs().toWkt())
|
||||
gdf_centerlines = gpd.GeoDataFrame.from_features([f for f in centerlines_layer.getFeatures()], crs=centerlines_layer.crs().toWkt())
|
||||
|
||||
num_homes = len(gdf_homes)
|
||||
print("Number of home points:", num_homes)
|
||||
|
||||
# Create an in-memory layer to store the connecting lines
|
||||
vl = QgsVectorLayer("LineString?crs=epsg:4326", "DROPS", "memory")
|
||||
vl_ext = QgsVectorLayer("LineString?crs=epsg:4326", "DROPS_EXT", "memory")
|
||||
pr = vl.dataProvider()
|
||||
pr_ext = vl_ext.dataProvider()
|
||||
|
||||
print("Creating drops...")
|
||||
|
||||
# For each home, find the nearest point on the centerlines and create a line
|
||||
for idx, home in gdf_homes.iterrows():
|
||||
nearest_point = closest_point_on_line(home.geometry, gdf_centerlines.geometry)
|
||||
line = LineString([(home.geometry.x, home.geometry.y), (nearest_point.x, nearest_point.y)])
|
||||
|
||||
# Add the line to the "DROPS" layer
|
||||
feat = QgsFeature()
|
||||
feat.setGeometry(QgsGeometry.fromWkt(line.wkt))
|
||||
pr.addFeature(feat)
|
||||
|
||||
# Extend the line by 5% of its length beyond the nearest point and add it to the "DROPS_EXT" layer
|
||||
line_ext = extend_line(line, line.length*0.05)
|
||||
feat_ext = QgsFeature()
|
||||
feat_ext.setGeometry(QgsGeometry.fromWkt(line_ext.wkt))
|
||||
pr_ext.addFeature(feat_ext)
|
||||
|
||||
# After processing each home, print the progress
|
||||
progress = (idx + 1) / num_homes * 100 # Progress as percentage
|
||||
print(f'\rProgress: {progress:.2f}%', end='') # Print the progress
|
||||
|
||||
# Update the layer's extent and add it to the project
|
||||
vl.updateExtents()
|
||||
QgsProject.instance().addMapLayer(vl)
|
||||
|
||||
print("\nSplitting centerlines at drops...")
|
||||
|
||||
# Define the splitting function
|
||||
def split_with_lines(input_layer, split_layer, output):
|
||||
"""
|
||||
This function will use native:splitwithlines processing algorithm
|
||||
from QGIS's processing toolbox to split features in one layer using lines
|
||||
from another layer.
|
||||
"""
|
||||
params = {
|
||||
'INPUT': input_layer,
|
||||
'LINES': split_layer,
|
||||
'OUTPUT': output
|
||||
}
|
||||
|
||||
res = processing.run("native:splitwithlines", params)
|
||||
return res
|
||||
|
||||
# Use the 'DROPS_EXT' layer to split features in the 'CENTERLINES' layer
|
||||
centerlines_split = split_with_lines(centerlines_layer, vl_ext, 'memory:')
|
||||
# Add the split layer to the project instance
|
||||
split_layer = centerlines_split['OUTPUT']
|
||||
#split_layer.setName('EDGES')
|
||||
#QgsProject.instance().addMapLayer(split_layer)
|
||||
|
||||
print("Deleting duplicate edges...")
|
||||
|
||||
# Define the delete duplicates function
|
||||
def delete_duplicates(input_layer, output):
|
||||
"""
|
||||
This function will use qgis:deleteduplicategeometries processing algorithm
|
||||
from QGIS's processing toolbox to remove duplicate geometries in a layer.
|
||||
"""
|
||||
params = {
|
||||
'INPUT': input_layer,
|
||||
'OUTPUT': output
|
||||
}
|
||||
|
||||
res = processing.run("qgis:deleteduplicategeometries", params)
|
||||
return res
|
||||
|
||||
# Use the delete_duplicates function on the split layer
|
||||
split_layer_no_duplicates = delete_duplicates(split_layer, 'memory:')
|
||||
|
||||
# Add the split layer without duplicates to the project instance
|
||||
split_layer_nd = split_layer_no_duplicates['OUTPUT']
|
||||
|
||||
# Get the data provider
|
||||
dp = split_layer_nd.dataProvider()
|
||||
|
||||
# Add new fields
|
||||
dp.addAttributes([
|
||||
QgsField("type", QVariant.String),
|
||||
QgsField("length", QVariant.Double),
|
||||
QgsField("cost", QVariant.Double)
|
||||
])
|
||||
|
||||
# Update to apply the changes
|
||||
split_layer_nd.updateFields()
|
||||
|
||||
# Now you have to update all features to have 'type' as 'Underground'
|
||||
# Start editing
|
||||
split_layer_nd.startEditing()
|
||||
|
||||
for feature in split_layer_nd.getFeatures():
|
||||
# set type to 'Underground'
|
||||
feature.setAttribute('type', 'Underground')
|
||||
|
||||
# set length to the great circle distance of the geometry
|
||||
# convert coordinates to tuples (latitude, longitude)
|
||||
start_point = feature.geometry().asPolyline()[0]
|
||||
end_point = feature.geometry().asPolyline()[-1]
|
||||
start_coordinates = (start_point.y(), start_point.x())
|
||||
end_coordinates = (end_point.y(), end_point.x())
|
||||
|
||||
# calculate great circle distance in feet
|
||||
length_in_feet = great_circle(start_coordinates, end_coordinates).feet
|
||||
feature.setAttribute('length', length_in_feet)
|
||||
|
||||
# set cost to a computed value, for example length * underground_cpf
|
||||
feature.setAttribute('cost', length_in_feet * underground_cpf)
|
||||
|
||||
# update the feature in the layer
|
||||
split_layer_nd.updateFeature(feature)
|
||||
|
||||
# Convert 'DROPS' layer to GeoDataFrame
|
||||
gdf_drops = gpd.GeoDataFrame.from_features([f for f in vl.getFeatures()], crs=vl.crs().toWkt())
|
||||
|
||||
# Add the 'DROPS' to the 'EDGES'
|
||||
for idx, drop in gdf_drops.iterrows():
|
||||
# Create a new feature for the drop
|
||||
drop_feature = QgsFeature(dp.fields())
|
||||
|
||||
# Convert drop's Shapely geometry to QgsGeometry
|
||||
qgis_geom = QgsGeometry.fromWkt(drop.geometry.wkt)
|
||||
drop_feature.setGeometry(qgis_geom)
|
||||
|
||||
# Add the drop feature to the 'EDGES' layer
|
||||
dp.addFeature(drop_feature)
|
||||
|
||||
# Set 'type' to 'Buried Drop'
|
||||
drop_feature.setAttribute('type', 'Buried Drop')
|
||||
|
||||
# Calculate the great circle length of the drop in feet
|
||||
start_point = drop.geometry.coords[0]
|
||||
end_point = drop.geometry.coords[-1]
|
||||
start_coordinates = (start_point[1], start_point[0]) # Note the reverse order (y, x) for geopy
|
||||
end_coordinates = (end_point[1], end_point[0]) # Note the reverse order (y, x) for geopy
|
||||
length_in_feet = great_circle(start_coordinates, end_coordinates).feet
|
||||
drop_feature.setAttribute('length', length_in_feet)
|
||||
|
||||
# Calculate cost as length * buried_drop_cpf
|
||||
drop_feature.setAttribute('cost', length_in_feet * buried_drop_cpf)
|
||||
|
||||
# Update the feature in the layer
|
||||
split_layer_nd.updateFeature(drop_feature)
|
||||
|
||||
# Commit changes
|
||||
split_layer_nd.commitChanges()
|
||||
|
||||
split_layer_nd.setName('EDGES')
|
||||
QgsProject.instance().addMapLayer(split_layer_nd)
|
||||
|
||||
print("Done.")
|
||||
Reference in New Issue
Block a user