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

[WIP] matrix calculator, matrix import/updt, faster project creation #354

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions qaequilibrae/modules/processing_provider/Add_connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ def displayName(self):
return self.tr("Add centroid connectors")

def group(self):
return self.tr("Model Building")
return self.tr("01-Model Building")

r-akemii marked this conversation as resolved.
Show resolved Hide resolved
def groupId(self):
return self.tr("Model Building")
return self.tr("01-Model Building")

def shortHelpString(self):
return self.tr("Go through all the centroids and add connectors only if none exists for the chosen mode")
Expand Down
165 changes: 165 additions & 0 deletions qaequilibrae/modules/processing_provider/add_matrix_from_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import importlib.util as iutil
import sys
import numpy as np
import pandas as pd
from os.path import join
from scipy.sparse import coo_matrix

from qgis.core import QgsProcessingMultiStepFeedback, QgsProcessingParameterString, QgsProcessingParameterDefinition
from qgis.core import QgsProcessingParameterField, QgsProcessingParameterMapLayer, QgsProcessingParameterFile
from qgis.core import QgsProcessingAlgorithm

from qaequilibrae.modules.common_tools import standard_path
from qaequilibrae.i18n.translate import trlt

class AddMatrixFromLayer(QgsProcessingAlgorithm):

def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterMapLayer("matrix_layer", self.tr("Matrix Layer")))
self.addParameter(
QgsProcessingParameterField(
"origin",
self.tr("Origin"),
type=QgsProcessingParameterField.Numeric,
parentLayerParameterName="matrix_layer",
allowMultiple=False,
defaultValue="origin",
)
)
self.addParameter(
QgsProcessingParameterField(
"destination",
self.tr("Destination"),
type=QgsProcessingParameterField.Numeric,
parentLayerParameterName="matrix_layer",
allowMultiple=False,
defaultValue="destination",
)
)
self.addParameter(
QgsProcessingParameterField(
"value",
self.tr("Value"),
type=QgsProcessingParameterField.Numeric,
parentLayerParameterName="matrix_layer",
allowMultiple=False,
defaultValue="value",
)
)
self.addParameter(
QgsProcessingParameterFile(
"matrix_file",
self.tr("Matrix file"),
r-akemii marked this conversation as resolved.
Show resolved Hide resolved
behavior=QgsProcessingParameterFile.File,
fileFilter="*.aem",
defaultValue=None,
)

self.addParameter(
QgsProcessingParameterString(
"matrix_core",
self.tr("Matrix core"),
multiLine=False,
defaultValue="Value"),
]

def processAlgorithm(self, parameters, context, model_feedback):
feedback = QgsProcessingMultiStepFeedback(3, model_feedback)

# Checks if we have access to aequilibrae library
if iutil.find_spec("aequilibrae") is None:
sys.exit(self.tr("AequilibraE module not found"))
r-akemii marked this conversation as resolved.
Show resolved Hide resolved

from aequilibrae.matrix import AequilibraeMatrix

origin = parameters["origin"]
destination = parameters["destination"]
value = parameters["value"]

core_name = parameters["matrix_core"]

matrix_file = parameters["matrix_file"]

# Import layer as a pandas df
feedback.pushInfo(self.tr("Importing layer"))
layer = self.parameterAsVectorLayer(parameters, "matrix_layer", context)
cols = [origin, destination, value]
datagen = ([f[col] for col in cols] for f in layer.getFeatures())
matrix = pd.DataFrame.from_records(data=datagen, columns=cols)
feedback.pushInfo("")
feedback.setCurrentStep(1)

# Getting all zones
all_zones = np.array(sorted(list(set(list(matrix[origin].unique()) + list(matrix[destination].unique())))))
r-akemii marked this conversation as resolved.
Show resolved Hide resolved
num_zones = all_zones.shape[0]
idx = np.arange(num_zones)

# Creates the indexing dataframes
origs = pd.DataFrame({"from_index": all_zones, "from": idx})
dests = pd.DataFrame({"to_index": all_zones, "to": idx})

# adds the new index columns to the pandas dataframe
matrix = matrix.merge(origs, left_on=origin, right_on="from_index", how="left")
matrix = matrix.merge(dests, left_on=destination, right_on="to_index", how="left")

agg_matrix = matrix.groupby(["from", "to"]).sum()

# returns the indices
agg_matrix.reset_index(inplace=True)

# Creating the aequilibrae matrix file
mat = AequilibraeMatrix()
mat.load(matrix_file)

m = (
coo_matrix((agg_matrix[value], (agg_matrix["from"], agg_matrix["to"])), shape=(num_zones, num_zones))
.toarray()
.astype(np.float64)
)
mat.matrix[core_name][:, :] = m[:, :]

feedback.pushInfo(self.tr("{}x{} matrix imported ").format(num_zones, num_zones))
feedback.pushInfo(" ")
feedback.setCurrentStep(2)

mat.save()
mat.close()
del agg_matrix, matrix, m

feedback.pushInfo(" ")
feedback.setCurrentStep(3)

return {"Output": f"{mat.name}, {mat.description} ({file_name})"}

def name(self):
return self.tr("Add a matrix from a layer to an existing aem file")
r-akemii marked this conversation as resolved.
Show resolved Hide resolved

def displayName(self):
return self.tr("Add a matrix from a layer to an existing aem file")

def group(self):
return self.tr("02-Data")

def groupId(self):
return self.tr("02-Data")

def shortHelpString(self):
return "\n".join([self.string_order(1), self.string_order(2), self.string_order(3), self.string_order(4), self.string_order(5)])

def createInstance(self):
return AddMatrixFromLayer()

def string_order(self, order):
if order == 1:
return self.tr("Save a layer to an existing *.aem file. Notice that:")
elif order == 2:
return self.tr("- the original matrix stored in the layer needs to be in list format")
elif order == 3:
return self.tr("- origin and destination fields need to be integers")
elif order == 4:
return self.tr("- value field can be either integer or real")
elif order == 5:
return self.tr("- if matrix_core is already existing, then it will be updated and previous data will be lost")
r-akemii marked this conversation as resolved.
Show resolved Hide resolved

def tr(self, message):
return trlt("AddMatrixFromLayer", message)
8 changes: 5 additions & 3 deletions qaequilibrae/modules/processing_provider/assign_from_yaml.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import importlib.util as iutil
import sys

from datetime import datetime as dt

from qgis.core import QgsProcessingAlgorithm, QgsProcessingMultiStepFeedback, QgsProcessingParameterFile

from qaequilibrae.i18n.translate import trlt
Expand Down Expand Up @@ -112,7 +114,7 @@ def processAlgorithm(self, parameters, context, model_feedback):
# Saving outputs
feedback.pushInfo(self.tr("Saving outputs"))
feedback.pushInfo(str(assig.report()))
assig.save_results(params["result_name"])
assig.save_results(params["result_name"]+dt.now().strftime('_%Y-%m-%d_%Hh%M'))
r-akemii marked this conversation as resolved.
Show resolved Hide resolved
assig.save_skims(params["result_name"], which_ones="all", format="omx")
feedback.pushInfo(" ")
feedback.setCurrentStep(5)
Expand All @@ -128,10 +130,10 @@ def displayName(self):
return self.tr("Traffic assignment from file")

def group(self):
return self.tr("Paths and assignment")
return self.tr("03-Paths and assignment")

def groupId(self):
return self.tr("Paths and assignment")
return self.tr("03-Paths and assignment")

def shortHelpString(self):
return "\n".join([self.string_order(1), self.string_order(2), self.string_order(3)])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from qaequilibrae.i18n.translate import trlt


class MatrixFromLayer(QgsProcessingAlgorithm):
class CreateMatrixFromLayer(QgsProcessingAlgorithm):

def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterMapLayer("matrix_layer", self.tr("Matrix Layer")))
Expand Down Expand Up @@ -144,30 +144,30 @@ def processAlgorithm(self, parameters, context, model_feedback):

mat.save()
mat.close()
del matrix
del agg_matrix, matrix, m

feedback.pushInfo(" ")
feedback.setCurrentStep(3)

return {"Output": f"{mat.name}, {mat.description} ({file_name})"}

def name(self):
return self.tr("Import matrices")
return self.tr("Create an aequilibrae matrix file (.aem) from a layer")
r-akemii marked this conversation as resolved.
Show resolved Hide resolved

def displayName(self):
return self.tr("Import matrices")
return self.tr("Create an aequilibrae matrix file (.aem) from a layer")

def group(self):
return self.tr("Data")
return self.tr("02-Data")

def groupId(self):
return self.tr("Data")
return self.tr("02-Data")

def shortHelpString(self):
return "\n".join([self.string_order(1), self.string_order(2), self.string_order(3), self.string_order(4)])

def createInstance(self):
return MatrixFromLayer()
return CreateMatrixFromLayer()

def string_order(self, order):
if order == 1:
Expand All @@ -180,4 +180,4 @@ def string_order(self, order):
return self.tr("- value field can be either integer or real")

def tr(self, message):
return trlt("MatrixFromLayer", message)
return trlt("CreateMatrixFromLayer", message)
4 changes: 2 additions & 2 deletions qaequilibrae/modules/processing_provider/export_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ def displayName(self):
return self.tr("Export matrices")

def group(self):
return self.tr("Data")
return self.tr("02-Data")

def groupId(self):
return self.tr("Data")
return self.tr("02-Data")

def shortHelpString(self):
return self.tr("Export an existing *.omx or *.aem matrix file into *.csv, *.aem or *.omx")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ def displayName(self):
return self.tr("Create project from link layer")

def group(self):
return self.tr("Model Building")
return self.tr("01-Model Building")

def groupId(self):
return self.tr("Model Building")
return self.tr("01-Model Building")

def shortHelpString(self):
return self.tr("Create an AequilibraE project from a given link layer")
Expand Down
6 changes: 4 additions & 2 deletions qaequilibrae/modules/processing_provider/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ def loadAlgorithms(self):
from .Add_connectors import AddConnectors
from .assign_from_yaml import TrafficAssignYAML
from .export_matrix import ExportMatrix
from .matrix_from_layer import MatrixFromLayer
from .create_matrix_from_layer import CreateMatrixFromLayer
from .add_matrix_from_layer import AddMatrixFromLayer
from .project_from_layer import ProjectFromLayer
from .renumber_nodes_from_layer import RenumberNodesFromLayer

self.addAlgorithm(MatrixFromLayer())
self.addAlgorithm(CreateMatrixFromLayer())
self.addAlgorithm(AddMatrixFromLayer())
self.addAlgorithm(ExportMatrix())
self.addAlgorithm(ProjectFromLayer())
self.addAlgorithm(RenumberNodesFromLayer())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ def displayName(self):
return self.tr("Add/Renumber nodes from layer")

def group(self):
return self.tr("Model Building")
return self.tr("01-Model Building")

def groupId(self):
return self.tr("Model Building")
return self.tr("01-Model Building")

def shortHelpString(self):
return f"{self.string_order(1)}\n{self.string_order(2)} {self.string_order(3)}"
Expand Down
Loading