Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Point multibuffer function #303

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions geest/core/multibuffer_point.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
from qgis.core import (
QgsVectorLayer,
QgsField,
QgsFeatureRequest,
QgsProcessingFeedback,
QgsCoordinateReferenceSystem,
QgsProcessingException,
edit,
)
from PyQt5.QtCore import QVariant
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Qt imports via the QGIS api

import processing


class MultiBufferCreator:
def __init__(self, distance_list, subset_size=5):
"""
Initialize the MultiBufferCreator class.

:param distance_list: List of buffer distances (in meters or seconds if using time-based buffers)
:param subset_size: Number of features to process in each subset
"""
self.distance_list = distance_list
self.subset_size = subset_size

# TODO: refactor function
def create_multibuffers(
self,
point_layer,
output_path,
mode="driving-car",
measurement="distance",
crs="EPSG:4326",
):
"""
Creates multibuffers for each point in the input point layer.

:param point_layer: QgsVectorLayer containing point features
:param output_path: Path to save the merged output layer
:param mode: Mode of travel for ORS API (e.g., 'driving-car')
:param measurement: 'distance' or 'time'
:param crs: Coordinate reference system (default is WGS84)
:return: QgsVectorLayer containing the buffers as polygons
"""
# Prepare to collect intermediate layers
temp_layers = []
features = list(point_layer.getFeatures())
total_features = len(features)

# Process features in subsets to handle large datasets
for i in range(0, total_features, self.subset_size):
subset_features = features[i : i + self.subset_size]
# Create a temporary layer for the subset
subset_layer = QgsVectorLayer(f"Point?crs={crs}", f"subset_{i}", "memory")
subset_layer_data = subset_layer.dataProvider()
subset_layer_data.addAttributes(point_layer.fields())
subset_layer.updateFields()
subset_layer_data.addFeatures(subset_features)

# Prepare parameters for ORS isochrones tool

params = {
"INPUT_PROVIDER": 0, # 0 for OpenRouteService
"INPUT_PROFILE": mode,
"INPUT_POINT_LAYER": subset_layer,
"INPUT_FIELD": "", # No specific field for ranges
"INPUT_METRIC": 0 if measurement == "distance" else 1,
"INPUT_UNITS": 0, # 0 for meters/seconds
"INPUT_RANGES": ",".join(map(str, self.distance_list)),
"INPUT_DATE_TIME": None,
"INPUT_INTERVAL": None,
"INPUT_SMOOTHING": None,
"INPUT_SRID": "",
"LOCATION_TYPE": 0,
"INPUT_AVOID_FEATURES": "",
"INPUT_AVOID_BORDERS": "",
"INPUT_AVOID_COUNTRIES": "",
"INPUT_AVOID_POLYGONS": "",
"OUTPUT": "memory:",
}

try:
# Run the ORS isochrones tool
result = processing.run(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this will go?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

"ORS Tools:isochrones_from_layer",
params,
feedback=QgsProcessingFeedback(),
)
isochrone_layer = result["OUTPUT"]
temp_layers.append(isochrone_layer)

# Provide progress feedback
print(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QgsMessageLog please

f"Processed subset {i + 1} to {min(i + self.subset_size, total_features)} of {total_features}"
)

except QgsProcessingException as e:
print(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

f"Error processing subset {i + 1} to {min(i + self.subset_size, total_features)}: {e}"
)
continue

# Merge all isochrone layers into one
if temp_layers:
merge_params = {
"LAYERS": temp_layers,
"CRS": QgsCoordinateReferenceSystem(crs),
"OUTPUT": "memory:",
}
merged_result = processing.run("native:mergevectorlayers", merge_params)
merged_layer = merged_result["OUTPUT"]

# Proceed to create bands by differencing the ranges
self._create_bands(merged_layer, output_path, crs)
else:
print("No isochrones were created.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Print

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return the results?


def _create_bands(self, merged_layer, output_path, crs):
"""
Creates bands by differencing isochrone ranges.

:param merged_layer: The merged isochrone layer
:param output_path: Path to save the final output layer
:param crs: Coordinate reference system
"""
# Extract unique ranges from the 'value' field added by ORS
ranges_field = "value"
unique_ranges = sorted(
{feat[ranges_field] for feat in merged_layer.getFeatures()}
)

# Create dissolved layers for each range
range_layers = {}
for r in unique_ranges:
# Select features matching the current range
expr = f'"{ranges_field}" = {r}'
request = QgsFeatureRequest().setFilterExpression(expr)
features = [feat for feat in merged_layer.getFeatures(request)]
if features:
# Create a memory layer for this range
range_layer = QgsVectorLayer(
f"Polygon?crs={crs}", f"range_{r}", "memory"
)
dp = range_layer.dataProvider()
dp.addAttributes(merged_layer.fields())
range_layer.updateFields()
dp.addFeatures(features)

# Dissolve the range layer to create a single feature
dissolve_params = {
"INPUT": range_layer,
"FIELD": [],
"OUTPUT": "memory:",
}
dissolve_result = processing.run("native:dissolve", dissolve_params)
dissolved_layer = dissolve_result["OUTPUT"]
range_layers[r] = dissolved_layer

# Create bands by computing differences between the ranges
band_layers = []
sorted_ranges = sorted(range_layers.keys(), reverse=True)
for i in range(len(sorted_ranges) - 1):
current_range = sorted_ranges[i]
next_range = sorted_ranges[i + 1]
current_layer = range_layers[current_range]
next_layer = range_layers[next_range]

# Difference between current and next range
difference_params = {
"INPUT": current_layer,
"OVERLAY": next_layer,
"OUTPUT": "memory:",
}
diff_result = processing.run("native:difference", difference_params)
diff_layer = diff_result["OUTPUT"]

# Add 'rasField' attribute to store the range value
diff_layer.dataProvider().addAttributes(
[QgsField("rasField", QVariant.Int)]
)
diff_layer.updateFields()
with edit(diff_layer):
for feat in diff_layer.getFeatures():
feat["rasField"] = current_range
diff_layer.updateFeature(feat)

band_layers.append(diff_layer)

# Handle the smallest range separately
smallest_range = sorted_ranges[-1]
smallest_layer = range_layers[smallest_range]
smallest_layer.dataProvider().addAttributes(
[QgsField("rasField", QVariant.Int)]
)
smallest_layer.updateFields()
with edit(smallest_layer):
for feat in smallest_layer.getFeatures():
feat["rasField"] = smallest_range
smallest_layer.updateFeature(feat)
band_layers.append(smallest_layer)

# Merge all band layers into the final output
merge_bands_params = {
"LAYERS": band_layers,
"CRS": QgsCoordinateReferenceSystem(crs),
"OUTPUT": output_path,
}
final_merge_result = processing.run(
"native:mergevectorlayers", merge_bands_params
)
final_layer = QgsVectorLayer(output_path, "MultiBuffer", "ogr")

print(f"Multi-buffer layer created at {output_path}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also this one

1 change: 0 additions & 1 deletion geest/gui/tree_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from geest.core.workflow_queue_manager import WorkflowQueueManager



class TreePanel(QWidget):
def __init__(self, parent=None, json_file=None):
super().__init__(parent)
Expand Down
Loading