Skip to content

Commit

Permalink
Merge pull request #68 from GispoCoding/65-handle-class-of-traveler
Browse files Browse the repository at this point in the history
65 handle class of traveler
  • Loading branch information
JuhoErvasti authored Nov 5, 2024
2 parents 8f45c51 + b2d6fe7 commit 2612b75
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 32 deletions.
14 changes: 13 additions & 1 deletion fvh3t/core/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,23 @@ class Gate:
must be a line.
"""

def __init__(self, geom: QgsGeometry, *, counts_negative: bool = False, counts_positive: bool = False) -> None:
def __init__(
self,
geom: QgsGeometry,
name: str,
*,
counts_negative: bool = False,
counts_positive: bool = False,
) -> None:
if geom.type() != QgsWkbTypes.GeometryType.LineGeometry:
msg = "Gate must be created from a line geometry!"
raise InvalidGeometryTypeException(msg)

self.__geom: QgsGeometry = geom
self.__trajectory_count: int = 0

self.__name: str = name

self.__counts_negative: bool = counts_negative
self.__counts_positive: bool = counts_positive

Expand All @@ -41,6 +50,9 @@ def __init__(self, geom: QgsGeometry, *, counts_negative: bool = False, counts_p
self.__segments: tuple[GateSegment, ...] = ()
self.create_segments()

def name(self) -> str:
return self.__name

def set_counts_negative(self, *, state: bool) -> None:
self.__counts_negative = state

Expand Down
27 changes: 24 additions & 3 deletions fvh3t/core/gate_layer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from qgis.core import QgsFeature, QgsFeatureSource, QgsField, QgsVectorLayer, QgsWkbTypes
from qgis.PyQt.QtCore import QMetaType, QVariant
from qgis.PyQt.QtCore import QDateTime, QMetaType, QVariant

from fvh3t.core.exceptions import InvalidFeatureException, InvalidLayerException
from fvh3t.core.gate import Gate
Expand All @@ -20,10 +20,12 @@ class GateLayer:
def __init__(
self,
layer: QgsVectorLayer,
name_field: str,
counts_negative_field: str,
counts_positive_field: str,
) -> None:
self.__layer: QgsVectorLayer = layer
self.__name_field = name_field
self.__counts_negative_field = counts_negative_field
self.__counts_positive_field = counts_positive_field

Expand All @@ -32,16 +34,23 @@ def __init__(
self.create_gates()

def create_gates(self) -> None:
name_field_idx: int = self.__layer.fields().indexOf(self.__name_field)
counts_negative_field_idx: int = self.__layer.fields().indexOf(self.__counts_negative_field)
counts_positive_field_idx: int = self.__layer.fields().indexOf(self.__counts_positive_field)

gates: list[Gate] = []

for feature in self.__layer.getFeatures():
name: str = feature[name_field_idx]
counts_negative: bool = feature[counts_negative_field_idx]
counts_positive: bool = feature[counts_positive_field_idx]

gate = Gate(feature.geometry(), counts_negative=counts_negative, counts_positive=counts_positive)
gate = Gate(
feature.geometry(),
name=name,
counts_negative=counts_negative,
counts_positive=counts_positive,
)

gates.append(gate)

Expand All @@ -50,12 +59,16 @@ def create_gates(self) -> None:
def gates(self) -> tuple[Gate, ...]:
return self.__gates

def as_line_layer(self) -> QgsVectorLayer | None:
def as_line_layer(self, traveler_class: str, start_time: QDateTime, end_time: QDateTime) -> QgsVectorLayer | None:
line_layer = QgsVectorLayer("LineString?crs=3067", "Line Layer", "memory")

line_layer.startEditing()

line_layer.addAttribute(QgsField("fid", QVariant.Int))
line_layer.addAttribute(QgsField("name", QVariant.String))
line_layer.addAttribute(QgsField("traveler_class", QVariant.String))
line_layer.addAttribute(QgsField("start_time", QVariant.Time))
line_layer.addAttribute(QgsField("end_time", QVariant.Time))
line_layer.addAttribute(QgsField("counts_negative", QVariant.Bool))
line_layer.addAttribute(QgsField("counts_positive", QVariant.Bool))
line_layer.addAttribute(QgsField("trajectory_count", QVariant.Int))
Expand All @@ -68,6 +81,10 @@ def as_line_layer(self) -> QgsVectorLayer | None:
feature.setAttributes(
[
i,
gate.name(),
traveler_class,
start_time,
end_time,
gate.counts_negative(),
gate.counts_positive(),
gate.trajectory_count(),
Expand Down Expand Up @@ -119,6 +136,10 @@ def is_valid(self) -> bool:
msg = "Layer has no features."
raise InvalidLayerException(msg)

if not self.is_field_valid(self.__name_field, accepted_types=[QMetaType.Type.QString]):
msg = "Name field either not found or of incorrect type."
raise InvalidLayerException(msg)

if not self.is_field_valid(self.__counts_negative_field, accepted_types=[QMetaType.Type.Bool]):
msg = "Counts negative field either not found or of incorrect type."
raise InvalidLayerException(msg)
Expand Down
1 change: 1 addition & 0 deletions fvh3t/core/line_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def create_line_layer() -> QgsVectorLayer:

layer.startEditing()

layer.addAttribute(QgsField("name", QVariant.String))
layer.addAttribute(QgsField("counts_negative", QVariant.Bool))
layer.addAttribute(QgsField("counts_positive", QVariant.Bool))

Expand Down
26 changes: 24 additions & 2 deletions fvh3t/fvh3t_processing/count_trajectories.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
QgsProcessingFeedback,
QgsProcessingParameterDateTime,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterString,
QgsProcessingParameterVectorLayer,
QgsUnitTypes,
)
Expand All @@ -22,6 +23,7 @@
class CountTrajectories(QgsProcessingAlgorithm):
INPUT_POINTS = "INPUT_POINTS"
INPUT_LINES = "INPUT_LINES"
TRAVELER_CLASS = "TRAVELER_CLASS"
START_TIME = "START_TIME"
END_TIME = "END_TIME"
OUTPUT_GATES = "OUTPUT_GATES"
Expand Down Expand Up @@ -62,6 +64,14 @@ def initAlgorithm(self, config=None): # noqa N802
)
)

self.addParameter(
QgsProcessingParameterString(
name=self.TRAVELER_CLASS,
description="Class of traveler",
optional=True,
)
)

self.addParameter(
QgsProcessingParameterDateTime(
name=self.START_TIME,
Expand Down Expand Up @@ -109,6 +119,7 @@ def processAlgorithm( # noqa N802
feedback = QgsProcessingFeedback()

point_layer = self.parameterAsVectorLayer(parameters, self.INPUT_POINTS, context)
traveler_class = self.parameterAsString(parameters, self.TRAVELER_CLASS, context)
start_time: QDateTime = self.parameterAsDateTime(parameters, self.START_TIME, context)
end_time: QDateTime = self.parameterAsDateTime(parameters, self.END_TIME, context)

Expand Down Expand Up @@ -151,6 +162,11 @@ def processAlgorithm( # noqa N802
filter_expression: str | None = None
if start_time_unix != min_timestamp or end_time_unix != max_timestamp:
filter_expression = f'"timestamp" BETWEEN {start_time_unix} AND {end_time_unix}'
if not filter_expression:
if traveler_class:
filter_expression = f"\"label\" = '{traveler_class}'"
elif traveler_class:
filter_expression += f" AND \"label\" = '{traveler_class}'"

trajectory_layer = TrajectoryLayer(
point_layer,
Expand Down Expand Up @@ -186,14 +202,20 @@ def processAlgorithm( # noqa N802

feedback.pushInfo(f"Line layer has {line_layer.featureCount()} features.")

gate_layer = GateLayer(line_layer, "counts_negative", "counts_positive")
gate_layer = GateLayer(line_layer, "name", "counts_negative", "counts_positive")

gates = gate_layer.gates()

for gate in gates:
gate.count_trajectories_from_layer(trajectory_layer)

exported_gate_layer = gate_layer.as_line_layer()
if not start_time:
start_time = QDateTime.fromMSecsSinceEpoch(int(min_timestamp))
if not end_time:
end_time = QDateTime.fromMSecsSinceEpoch(int(max_timestamp))
exported_gate_layer = gate_layer.as_line_layer(
traveler_class=traveler_class, start_time=start_time, end_time=end_time
)

if exported_gate_layer is None:
msg = "Gate layer is None"
Expand Down
12 changes: 8 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def size_changing_trajectory():
def two_point_gate():
return Gate(
QgsGeometry.fromPolylineXY([QgsPointXY(-0.5, 0.5), QgsPointXY(0.5, 0.5)]),
name="two_point_gate",
counts_negative=True,
counts_positive=True,
)
Expand All @@ -75,6 +76,7 @@ def two_point_gate():
def three_point_gate():
return Gate(
QgsGeometry.fromPolylineXY([QgsPointXY(1, 1), QgsPointXY(2, 1), QgsPointXY(2, 2)]),
name="three_point_gate",
counts_negative=True,
counts_positive=True,
)
Expand Down Expand Up @@ -328,11 +330,12 @@ def qgis_gate_line_layer():

layer.startEditing()

layer.addAttribute(QgsField("name", QVariant.String))
layer.addAttribute(QgsField("counts_negative", QVariant.Bool))
layer.addAttribute(QgsField("counts_positive", QVariant.Bool))

gate1 = QgsFeature(layer.fields())
gate1.setAttributes([True, True])
gate1.setAttributes(["name", True, True])
gate1.setGeometry(
QgsGeometry.fromPolylineXY(
[
Expand All @@ -344,7 +347,7 @@ def qgis_gate_line_layer():
)

gate2 = QgsFeature(layer.fields())
gate2.setAttributes([False, True])
gate2.setAttributes(["name", False, True])
gate2.setGeometry(
QgsGeometry.fromPolylineXY(
[
Expand All @@ -355,7 +358,7 @@ def qgis_gate_line_layer():
)

gate3 = QgsFeature(layer.fields())
gate3.setAttributes([True, False])
gate3.setAttributes(["name", True, False])
gate3.setGeometry(
QgsGeometry.fromPolylineXY(
[
Expand Down Expand Up @@ -383,11 +386,12 @@ def qgis_gate_line_layer_wrong_field_type():

layer.startEditing()

layer.addAttribute(QgsField("name", QVariant.String))
layer.addAttribute(QgsField("counts_negative", QVariant.Bool))
layer.addAttribute(QgsField("counts_positive", QVariant.Int))

gate = QgsFeature(layer.fields())
gate.setAttributes([True, True])
gate.setAttributes(["name", True, True])
gate.setGeometry(
QgsGeometry.fromPolylineXY(
[
Expand Down
8 changes: 4 additions & 4 deletions tests/core/test_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ def test_trajectory_count(qgis_point_layer_for_gate_count):
)

geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(-0.5, 0.5), QgsPointXY(0.5, 0.5)])
gate1 = Gate(geom1, counts_positive=True)
gate1 = Gate(geom1, name="gate1", counts_positive=True)

geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(-1, 0.75), QgsPointXY(1, 1)])
gate2 = Gate(geom2, counts_negative=True)
gate2 = Gate(geom2, name="gate2", counts_negative=True)

geom3 = QgsGeometry.fromPolylineXY([QgsPointXY(0.5, 0), QgsPointXY(-0.75, 2)])
gate3 = Gate(geom3, counts_negative=True, counts_positive=True)
gate3 = Gate(geom3, name="gate3", counts_negative=True, counts_positive=True)

geom4 = QgsGeometry.fromPolylineXY([QgsPointXY(-1, -0.5), QgsPointXY(1, -0.5)])
gate4 = Gate(geom4, counts_negative=True, counts_positive=True)
gate4 = Gate(geom4, name="gate4", counts_negative=True, counts_positive=True)

gate1.count_trajectories_from_layer(traj_layer)
assert gate1.trajectory_count() == 1
Expand Down
6 changes: 5 additions & 1 deletion tests/core/test_gate_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
def test_gate_layer_create_gates(qgis_gate_line_layer):
gate_layer = GateLayer(
qgis_gate_line_layer,
"name",
"counts_negative",
"counts_positive",
)
Expand Down Expand Up @@ -43,13 +44,15 @@ def test_is_field_valid(qgis_gate_line_layer, qgis_gate_line_layer_wrong_field_t
with pytest.raises(InvalidLayerException, match="Counts negative field either not found or of incorrect type."):
GateLayer(
qgis_gate_line_layer,
"name",
"count_positive", # use wrong field names on purpose
"count_negative",
)

with pytest.raises(InvalidLayerException, match="Counts positive field either not found or of incorrect type."):
GateLayer(
qgis_gate_line_layer_wrong_field_type,
"name",
"counts_negative",
"counts_positive",
)
Expand All @@ -61,7 +64,7 @@ def test_create_valid_gate_from_empty_line_layer():
layer.startEditing()

gate = QgsFeature(layer.fields())
gate.setAttributes([True, True])
gate.setAttributes(["name", True, True])
gate.setGeometry(
QgsGeometry.fromPolylineXY(
[
Expand All @@ -77,6 +80,7 @@ def test_create_valid_gate_from_empty_line_layer():

gate_layer = GateLayer(
layer,
"name",
"counts_negative",
"counts_positive",
)
Expand Down
Loading

0 comments on commit 2612b75

Please sign in to comment.