Skip to content

Commit

Permalink
Working on setup panel
Browse files Browse the repository at this point in the history
  • Loading branch information
timlinux committed Sep 15, 2024
1 parent d02ecb1 commit 2e82220
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 17 deletions.
84 changes: 67 additions & 17 deletions geest/gui/geest_dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
QHeaderView,
QTreeView,
QMenu,
QCheckBox, # Add QCheckBox for Edit Toggle
)
from qgis.PyQt.QtCore import QPoint, Qt, QTimer
from qgis.PyQt.QtGui import QMovie
import json
import os
from .geest_treeview import CustomTreeView, JsonTreeModel
from .setup_panel import GeospatialWidget
from .layer_details_dialog import LayerDetailDialog
from ..utilities import resources_path

Expand All @@ -28,6 +30,7 @@ def __init__(self, parent=None, json_file=None):
super().__init__(parent)

self.json_file = json_file
self.tree_view_visible = True
widget = QWidget()
layout = QVBoxLayout(widget)

Expand All @@ -37,6 +40,11 @@ def __init__(self, parent=None, json_file=None):
else:
self.json_data = {"dimensions": []}

# setup instance (hidden by default)
self.setup_widget = GeospatialWidget()
self.setup_widget.setVisible(False) # Initially hide the GeospatialWidget
layout.addWidget(self.setup_widget)

# Create a CustomTreeView widget to handle editing and reverts
self.treeView = CustomTreeView()
self.treeView.setDragDropMode(QTreeView.InternalMove)
Expand All @@ -46,9 +54,8 @@ def __init__(self, parent=None, json_file=None):
self.model = JsonTreeModel(self.json_data)
self.treeView.setModel(self.model)

self.treeView.setEditTriggers(
QTreeView.DoubleClicked
) # Only allow editing on double-click
# Only allow editing on double-click (initially enabled)
self.treeView.setEditTriggers(QTreeView.DoubleClicked)

# Enable custom context menu
self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
Expand All @@ -70,37 +77,64 @@ def __init__(self, parent=None, json_file=None):

button_bar = QHBoxLayout()

add_dimension_button = QPushButton("⭐️ Add Dimension")
add_dimension_button.clicked.connect(self.add_dimension)
# "Add Dimension" button (initially enabled)
self.add_dimension_button = QPushButton("⭐️ Add Dimension")
self.add_dimension_button.clicked.connect(self.add_dimension)

load_json_button = QPushButton("📂 Load Template")
# Load and Save buttons
load_json_button = QPushButton("📂 Load")
load_json_button.clicked.connect(self.load_json_from_file)

export_json_button = QPushButton("📦️ Save Template")
export_json_button = QPushButton("💾 Save")
export_json_button.clicked.connect(self.export_json_to_file)

button_bar.addWidget(add_dimension_button)
button_bar.addStretch()
# Prepare the throbber for the button (hidden initially)
self.prepare_throbber = QLabel(self)
movie = QMovie(resources_path("resources", "throbber-small.gif"))
self.prepare_throbber.setMovie(movie)
self.prepare_throbber.setVisible(False) # Hide initially
button_bar.addWidget(self.prepare_throbber)

self.prepare_button = QPushButton("🛸 Prepare")
self.prepare_button = QPushButton("▶️ Prepare")
self.prepare_button.clicked.connect(self.process_leaves)
movie.start()

# Button to toggle between Tree View and Setup Panel
self.toggle_view_button = QPushButton("Setup")
self.toggle_view_button.clicked.connect(self.toggle_view)

button_bar.addWidget(self.toggle_view_button)
# Add Edit Toggle checkbox
self.edit_toggle = QCheckBox("Edit")
self.edit_toggle.setChecked(True) # Initially enabled
self.edit_toggle.stateChanged.connect(self.toggle_edit_mode)

button_bar.addWidget(self.add_dimension_button)
button_bar.addStretch()

button_bar.addWidget(self.prepare_button)
button_bar.addStretch()

button_bar.addWidget(load_json_button)
button_bar.addWidget(export_json_button)
button_bar.addWidget(self.edit_toggle) # Add the edit toggle
layout.addLayout(button_bar)

widget.setLayout(layout)
self.setWidget(widget)

# Prepare the throbber for the button (hidden initially)
self.prepare_throbber = QLabel(self)
movie = QMovie(resources_path("resources", "throbber.gif"))
self.prepare_throbber.setMovie(movie)
self.prepare_throbber.setScaledContents(True)
self.prepare_throbber.setVisible(False) # Hide initially
movie.start()
def toggle_view(self):
"""Toggle between the tree view and the GeospatialWidget."""
if self.tree_view_visible:
self.treeView.setVisible(False)
self.setup_widget.setVisible(True)
self.toggle_view_button.setText("Tree")
else:
self.treeView.setVisible(True)
self.setup_widget.setVisible(False)
self.toggle_view_button.setText("Setup")

self.tree_view_visible = not self.tree_view_visible

def load_json(self):
"""Load the JSON data from the file."""
Expand Down Expand Up @@ -131,6 +165,9 @@ def add_dimension(self):

def open_context_menu(self, position: QPoint):
"""Handle right-click context menu."""
if not self.edit_toggle.isChecked(): # Disable context menu if not in edit mode
return

index = self.treeView.indexAt(position)
if not index.isValid():
return
Expand Down Expand Up @@ -221,6 +258,19 @@ def update_layer_data(updated_data):
# Show the dialog (exec_ will block until the dialog is closed)
dialog.exec_()

def toggle_edit_mode(self):
"""Enable or disable edit mode based on the 'Edit' toggle state."""
edit_mode = self.edit_toggle.isChecked()

# Enable or disable the "Add Dimension" button
self.add_dimension_button.setVisible(edit_mode)

# Enable or disable double-click editing in the tree view
if edit_mode:
self.treeView.setEditTriggers(QTreeView.DoubleClicked)
else:
self.treeView.setEditTriggers(QTreeView.NoEditTriggers)

def process_leaves(self):
"""
This function processes all nodes in the QTreeView that have the 'layer' role.
Expand Down
210 changes: 210 additions & 0 deletions geest/gui/setup_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import os
import re
from PyQt5.QtWidgets import (
QWidget,
QLabel,
QVBoxLayout,
QPushButton,
QHBoxLayout,
QFileDialog,
QMessageBox,
)
from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox
from qgis.core import (
QgsMapLayerProxyModel,
QgsWkbTypes,
QgsProject,
QgsVectorLayer,
QgsCoordinateReferenceSystem,
QgsRectangle,
QgsFeature,
QgsGeometry,
QgsVectorFileWriter,
)
from qgis.PyQt.QtCore import QFileInfo


class GeospatialWidget(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("GEEST")
self.working_dir = ""
self.initUI()

def initUI(self):
layout = QVBoxLayout()

# Title
self.title_label = QLabel(
"Geospatial Assessment of Women Employment and Business Opportunities in the Renewable Energy Sector",
self,
)
self.title_label.setWordWrap(True)
layout.addWidget(self.title_label)

# Description
self.description_label = QLabel(
"With support from the [Canada Clean Energy and Forest Climate Facility (CCEFCFy)], "
"the [Geospatial Operational Support Team (GOST, DECSC)] launched the project "
'"Geospatial Assessment of Women Employment and Business Opportunities in the Renewable Energy Sector."',
self,
)
self.description_label.setWordWrap(True)
layout.addWidget(self.description_label)

# Study Area Combobox - Filtered to polygon/multipolygon layers
self.study_area_label = QLabel("Study Area Layer:")
layout.addWidget(self.study_area_label)

self.layer_combo = QgsMapLayerComboBox()
self.layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)
#self.layer_combo.setFilterExpression(
# f"geometry_type={QgsWkbTypes.PolygonGeometry} OR geometry_type={QgsWkbTypes.MultiPolygonGeometry}"
#)
layout.addWidget(self.layer_combo)

# Area Name Field ComboBox
self.area_name_label = QLabel("Area Name Field:")
layout.addWidget(self.area_name_label)

self.field_combo = QgsFieldComboBox() # QgsFieldComboBox for selecting fields
layout.addWidget(self.field_combo)

# Link the map layer combo box with the field combo box
self.layer_combo.layerChanged.connect(self.field_combo.setLayer)

# Directory Selector for Working Directory
self.dir_label = QLabel("Working Directory:")
layout.addWidget(self.dir_label)

dir_layout = QHBoxLayout()
self.dir_display = QLabel("/path/to/working/dir")
self.dir_button = QPushButton("Select Directory")
self.dir_button.clicked.connect(self.select_directory)
dir_layout.addWidget(self.dir_display)
dir_layout.addWidget(self.dir_button)
layout.addLayout(dir_layout)

# Text for analysis preparation
self.preparation_label = QLabel(
"After selecting your study area layer, we will prepare the analysis region."
)
layout.addWidget(self.preparation_label)

# Continue Button
self.continue_button = QPushButton("Continue")
self.continue_button.clicked.connect(self.on_continue)
layout.addWidget(self.continue_button)

self.setLayout(layout)

def select_directory(self):
"""Opens a file dialog to select the working directory."""
directory = QFileDialog.getExistingDirectory(self, "Select Working Directory")
if directory:
self.working_dir = directory
self.dir_display.setText(directory)

def on_continue(self):
"""Triggered when the Continue button is pressed. Handles analysis preparation."""
# Ensure a study layer and working directory are selected
layer = self.layer_combo.currentLayer()
if not layer:
QMessageBox.critical(self, "Error", "Please select a study area layer.")
return

if not self.working_dir:
QMessageBox.critical(self, "Error", "Please select a working directory.")
return

# Validate that the area name field exists
field_name = self.field_combo.currentField() # Get the selected field name
if not field_name or field_name not in layer.fields().names():
QMessageBox.critical(
self, "Error", f"Invalid area name field '{field_name}'."
)
return

# Create the subdirectory 'study_area'
study_area_dir = os.path.join(self.working_dir, "study_area")
if not os.path.exists(study_area_dir):
os.makedirs(study_area_dir)

# Deconstruct polygons into parts and process each
features = layer.getFeatures()
for feature in features:
geom = feature.geometry()

# Get area name and normalize for file names
area_name = feature[field_name]
normalized_name = re.sub(r"\s+", "_", area_name.lower())

# Calculate bounding box and ensure it's a multiple of 100m
bbox = geom.boundingBox()
bbox_100m = self.create_bbox_multiple_100m(bbox)

# Save the bounding box as a GeoJSON file
bbox_file = os.path.join(study_area_dir, f"{normalized_name}.geojson")
self.save_bbox_to_geojson(bbox_100m, bbox_file, area_name)

# Generate and save the 100m grid
grid_file = os.path.join(study_area_dir, f"{normalized_name}_grid.geojson")
self.create_and_save_grid(bbox_100m, grid_file)

# Add the created layers to QGIS in a new layer group
self.add_layers_to_qgis(study_area_dir)

def create_bbox_multiple_100m(self, bbox):
"""Adjusts bounding box dimensions to be a multiple of 100m."""
# Convert coordinates to EPSG:3857 (meters)
crs = QgsCoordinateReferenceSystem("EPSG:3857")
bbox = bbox.reproject(crs)

# Adjust bbox dimensions to be exact multiples of 100m
def make_multiple(val, mult):
return mult * round(val / mult)

x_min = make_multiple(bbox.xMinimum(), 100)
y_min = make_multiple(bbox.yMinimum(), 100)
x_max = make_multiple(bbox.xMaximum(), 100)
y_max = make_multiple(bbox.yMaximum(), 100)

return QgsRectangle(x_min, y_min, x_max, y_max)

def save_bbox_to_geojson(self, bbox, filepath, area_name):
"""Saves the bounding box to a GeoJSON file with the area name as an attribute."""
# Create vector layer for the bounding box
bbox_layer = QgsVectorLayer("Polygon?crs=EPSG:3857", "bbox", "memory")
provider = bbox_layer.dataProvider()
bbox_layer.startEditing()
feature = QgsFeature()
feature.setGeometry(QgsGeometry.fromRect(bbox))
feature.setAttributes([area_name])
provider.addFeatures([feature])
bbox_layer.commitChanges()

# Save to file
QgsVectorFileWriter.writeAsVectorFormat(
bbox_layer, filepath, "utf-8", driverName="GeoJSON"
)

def create_and_save_grid(self, bbox, filepath):
"""Creates a 100m grid over the bounding box and saves it to a GeoJSON file."""
grid_layer = QgsGridGenerator.createGridLayer(
bbox, 100, 100, QgsCoordinateReferenceSystem("EPSG:3857")
)
QgsVectorFileWriter.writeAsVectorFormat(
grid_layer, filepath, "utf-8", driverName="GeoJSON"
)

def add_layers_to_qgis(self, directory):
"""Adds the generated layers to QGIS in a new layer group."""
group = QgsProject.instance().layerTreeRoot().addGroup("study area")

for file in os.listdir(directory):
if file.endswith(".geojson"):
layer = QgsVectorLayer(
os.path.join(directory, file), QFileInfo(file).baseName(), "ogr"
)
QgsProject.instance().addMapLayer(layer, False)
group.addLayer(layer)
Binary file added geest/resources/throbber-small.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 2e82220

Please sign in to comment.