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

Updates on Data Visualization menu #321

Merged
merged 8 commits into from
Apr 12, 2024
Merged
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
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM qgis/qgis:release-3_32
FROM qgis/qgis:release-3_34

# [Optional] Uncomment this section to install additional OS packages.
RUN apt update -y && apt install -y pandoc zip
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/DevWorkflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ jobs:
strategy:
max-parallel: 4
matrix:
container: [ 'qgis/qgis:latest', 'qgis/qgis:release-3_32']
container: [ 'qgis/qgis:latest', 'qgis/qgis:release-3_34']
container:
image: ${{ matrix.container }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install dependencies
run: |
Expand Down
1 change: 1 addition & 0 deletions ci/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pytest-qgis
pytest-qt
pytest-cov
pytest-mock
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import importlib.util as iutil
import logging
import os

import openmatrix as omx
import numpy as np
from aequilibrae.matrix import AequilibraeMatrix, AequilibraeData

Expand All @@ -14,12 +13,6 @@

FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), "forms/ui_data_viewer.ui"))

# Checks if we can display OMX
spec = iutil.find_spec("openmatrix")
has_omx = spec is not None
if has_omx:
import openmatrix as omx


class DisplayAequilibraEFormatsDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, qgis_project, file_path="", proj=False):
Expand All @@ -39,27 +32,18 @@ def __init__(self, qgis_project, file_path="", proj=False):
self.continue_with_data()
return

formats = ["Aequilibrae matrix(*.aem)", "Aequilibrae dataset(*.aed)"]

dflt = ".aem"
if has_omx:
formats.insert(0, "Open Matrix(*.omx)")
dflt = ".omx"

self.data_path, self.data_type = GetOutputFileName(
self,
self.tr("AequilibraE custom formats"),
formats,
dflt,
standard_path(),
)
self.data_path, self.data_type = self.get_file_name()

if self.data_type is None:
self.error = self.tr("Path provided is not a valid dataset")
self.exit_with_error()
else:
self.data_type = self.data_type.upper()
self.continue_with_data()

if self.error:
self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, True)
self.but_load.clicked.connect(self.get_file_name)

def continue_with_data(self):
self.setWindowTitle(self.tr("File path:").format(self.data_path))
Expand All @@ -68,13 +52,16 @@ def continue_with_data(self):
if self.data_type == "AED":
self.data_to_show = AequilibraeData()
elif self.data_type == "AEM":
self.data_to_show = AequilibraeMatrix()
if not self.from_proj:
self.qgis_project.matrices[self.data_path] = self.data_to_show
self.data_to_show = AequilibraeMatrix()
if not self.from_proj:
self.qgis_project.matrices[self.data_path] = self.data_to_show
try:
self.data_to_show.load(self.data_path)
self.list_cores = self.data_to_show.names
self.list_indices = self.data_to_show.index_names
if self.data_type == "AED":
self.list_cores = self.data_to_show.fields
elif self.data_type == "AEM":
self.list_cores = self.data_to_show.names
self.list_indices = self.data_to_show.index_names
except Exception as e:
self.error = self.tr("Could not load dataset")
self.logger.error(e.args)
Expand Down Expand Up @@ -187,15 +174,20 @@ def change_matrix_cores(self):
self.add_matrix_parameters(idx, core)
self.format_showing()

def export(self):
new_name, file_type = GetOutputFileName(
def csv_file_path(self):
new_name, _ = GetOutputFileName(
self, self.data_type, ["Comma-separated file(*.csv)"], ".csv", self.data_path
)
return new_name

def export(self):
new_name = self.csv_file_path()

if new_name is not None:
self.data_to_show.export(new_name)

def exit_with_error(self):
qgis.utils.iface.messageBar().pushMessage("Error:", self.error, level=1)
qgis.utils.iface.messageBar().pushMessage("Error:", self.error, level=1, duration=10)
self.close()

def exit_procedure(self):
Expand All @@ -214,3 +206,17 @@ def add_matrix_parameters(self, idx, field):
self.data_to_show.matrix_view = np.array(self.omx[field])
self.data_to_show.index = np.array(list(self.omx.mapping(idx).keys()))
self.data_to_show.matrix[field] = self.data_to_show.matrix_view[:, :]

def get_file_name(self):
formats = ["Aequilibrae matrix(*.aem)", "Aequilibrae dataset(*.aed)", "OpenMatrix(*.omx)"]
dflt = ".aem"

data_path, data_type = GetOutputFileName(
self,
self.tr("AequilibraE custom formats"),
formats,
dflt,
standard_path(),
)

return data_path, data_type
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</font>
</property>
<property name="currentIndex">
<number>2</number>
<number>0</number>
</property>
<widget class="QWidget" name="tab_mat">
<attribute name="title">
Expand Down
51 changes: 26 additions & 25 deletions qaequilibrae/modules/matrix_procedures/load_project_data.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import importlib.util as iutil
import os
from os.path import join
import pandas as pd
Expand All @@ -7,7 +6,7 @@

import qgis
from qgis.PyQt import QtWidgets, uic
from qgis.PyQt.QtWidgets import QAbstractItemView
from qgis.PyQt.QtWidgets import QAbstractItemView, QTabWidget
from qaequilibrae.modules.matrix_procedures.display_aequilibrae_formats_dialog import DisplayAequilibraEFormatsDialog
from qaequilibrae.modules.matrix_procedures.load_result_table import load_result_table
from qaequilibrae.modules.matrix_procedures.matrix_lister import list_matrices
Expand All @@ -16,37 +15,39 @@

FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), "forms/ui_project_data.ui"))

# Checks if we can display OMX
spec = iutil.find_spec("openmatrix")
has_omx = spec is not None


class LoadProjectDataDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, qgs_proj):
def __init__(self, qgs_proj, proj=True):
QtWidgets.QDialog.__init__(self)
self.iface = qgs_proj.iface
self.setupUi(self)
self.data_to_show = None
self.error = None
self.qgs_proj = qgs_proj
self.project = qgs_proj.project

self.matrices: pd.DataFrame = None
self.matrices_model: PandasModel = None

self.results: pd.DataFrame = None
self.results_model: PandasModel = None

for table in [self.list_matrices, self.list_results]:
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.setSelectionMode(QAbstractItemView.SingleSelection)

self.load_matrices()
self.load_results()

self.but_update_matrices.clicked.connect(self.update_matrix_table)
self.but_load_Results.clicked.connect(self.load_result_table)
self.but_load_matrix.clicked.connect(self.display_matrix)
self.from_proj = proj
self.project = qgs_proj.project if self.from_proj else None

if self.from_proj:
self.matrices: pd.DataFrame = None
self.matrices_model: PandasModel = None

self.results: pd.DataFrame = None
self.results_model: PandasModel = None

for table in [self.list_matrices, self.list_results]:
table.setSelectionBehavior(QAbstractItemView.SelectRows)
table.setSelectionMode(QAbstractItemView.SingleSelection)

self.load_matrices()
self.load_results()

self.but_update_matrices.clicked.connect(self.update_matrix_table)
self.but_load_Results.clicked.connect(self.load_result_table)
self.but_load_matrix.clicked.connect(self.display_matrix)
else:
QTabWidget.setTabVisible(self.tabs, 0, False)
QTabWidget.setTabVisible(self.tabs, 1, False)

self.but_load_data.clicked.connect(self.display_external_data)

def display_matrix(self):
Expand Down
7 changes: 3 additions & 4 deletions qaequilibrae/modules/menu_actions/action_show_project_data.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
def run_show_project_data(qgis_project):
from qaequilibrae.modules.matrix_procedures import LoadProjectDataDialog

if qgis_project.project is None:
qgis_project.iface.messageBar().pushMessage("Error", "You need to load a project first", level=3, duration=10)
return
dlg2 = LoadProjectDataDialog(qgis_project)
has_project = False if qgis_project.project is None else True

dlg2 = LoadProjectDataDialog(qgis_project, has_project)
dlg2.show()
dlg2.exec_()
41 changes: 41 additions & 0 deletions test/test_display_aequilibrae_formats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os
import numpy as np
import pytest

from qaequilibrae.modules.matrix_procedures.display_aequilibrae_formats_dialog import DisplayAequilibraEFormatsDialog


def test_display_data_no_path(ae, mocker):
function = "qaequilibrae.modules.matrix_procedures.display_aequilibrae_formats_dialog.DisplayAequilibraEFormatsDialog.get_file_name"
mocker.patch(function, return_value=(None, None))

dialog = DisplayAequilibraEFormatsDialog(ae)
dialog.close()

messagebar = ae.iface.messageBar()
assert (
messagebar.messages[1][-1] == "Error::Path provided is not a valid dataset"
), "Level 1 error message is missing"


@pytest.mark.parametrize("has_project", [True, False])
@pytest.mark.parametrize("file_name", ("demand.aem", "SiouxFalls.omx"))
def test_display_data_with_path(tmpdir, ae_with_project, mocker, has_project, file_name):
file_path = f"test/data/SiouxFalls_project/matrices/{file_name}"
name, extension = file_name.split(".")
file_func = "qaequilibrae.modules.matrix_procedures.display_aequilibrae_formats_dialog.DisplayAequilibraEFormatsDialog.get_file_name"
mocker.patch(file_func, return_value=(file_path, extension.upper()))

out_func = "qaequilibrae.modules.matrix_procedures.display_aequilibrae_formats_dialog.DisplayAequilibraEFormatsDialog.csv_file_path"
mocker.patch(out_func, return_value=f"{tmpdir}/{name}.csv")

dialog = DisplayAequilibraEFormatsDialog(ae_with_project, file_path, has_project)
dialog.export()
dialog.exit_procedure()

assert dialog.error is None
assert np.sum(dialog.data_to_show.__dict__["matrix"]["matrix"]) == 360600
assert "matrix" in dialog.list_cores
assert "taz" in dialog.list_indices
assert dialog.data_type == extension.upper()
assert os.path.isfile(f"{tmpdir}/{name}.csv")
8 changes: 0 additions & 8 deletions test/test_qaequilibrae_menu_without_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,6 @@ def test_add_zoning_data_menu(ae, qtbot):
assert messagebar.messages[3][-1] == "Error:You need to load a project first", "Level 3 error message is missing"


def test_display_project_data_menu(ae, qtbot):
action = ae.menuActions["Data"][0]
assert action.text() == "Visualize data", "Wrong text content"
action.trigger()
messagebar = ae.iface.messageBar()
assert messagebar.messages[3][-1] == "Error:You need to load a project first", "Level 3 error message is missing"


def test_import_matrices_menu(ae, qtbot):
from qaequilibrae.modules.matrix_procedures import LoadMatrixDialog

Expand Down