diff --git a/fvh3t/core/__init__.py b/fvh3t/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fvh3t/core/gate.py b/fvh3t/core/gate.py new file mode 100644 index 0000000..a2f709f --- /dev/null +++ b/fvh3t/core/gate.py @@ -0,0 +1,15 @@ +from qgis.core import QgsGeometry + +class Gate: + """ + A wrapper class around a QgsGeometry which represents a + gate through which trajectories can pass. The geometry + must be a line. + """ + + def __init__(self, geom: QgsGeometry) -> None: + self.__geom: QgsGeometry = geom + + def geometry(self) -> QgsGeometry: + return self.__geom + diff --git a/fvh3t/core/trajectory.py b/fvh3t/core/trajectory.py new file mode 100644 index 0000000..54e6825 --- /dev/null +++ b/fvh3t/core/trajectory.py @@ -0,0 +1,61 @@ +from typing import NamedTuple + +from qgis.core import QgsGeometry, QgsPointXY, QgsVectorLayer, QgsWkbTypes + +from fvh3t.core.gate import Gate + +class TrajectoryNode(NamedTuple): + """ + A simple data container representing one node in a + trajectory. + """ + + point: QgsPointXY + timestamp: int + + @classmethod + def from_coordinates(cls, x: float, y: float, timestamp: int): + return cls(QgsPointXY(x, y), timestamp) + + +class Trajectory: + """ + Class representing a trajectory which consists + of nodes which have a location and a timestamp + """ + + def __init__(self, nodes: tuple[TrajectoryNode, ...]) -> None: + self.__nodes: tuple[TrajectoryNode, ...] = nodes + + def as_geometry(self) -> QgsGeometry: + return QgsGeometry.fromPolylineXY([node.point for node in self.__nodes]) + + def intersects_gate(self, other: Gate) -> bool: + return self.as_geometry().intersects(other.geometry()) + + def average_speed(self) -> float: + # TODO: implement function + return 0.0 + + +class TrajectoryLayer: + """ + Wrapper around a QgsVectorLayer object from which trajectories + can be instantiated, i.e. + + 1. is a point layer + 2. has a valid identifier field + 3. has a valid timestamp field + """ + + def __init__(self, layer: QgsVectorLayer, id_field: str, timestamp_field: str) -> None: + self.__layer: QgsVectorLayer = layer + self.__id_field: str = id_field + self.__timestamp_field: str = timestamp_field + + def is_valid(self) -> bool: + is_point_layer: bool = self.__layer.geometryType() == QgsWkbTypes.GeometryType.PointGeometry + id_field_exists: bool = self.__layer.fields().indexFromName(self.__id_field) != -1 + timestamp_field_exists: bool = self.__layer.fields().indexFromName(self.__timestamp_field) != -1 + + return is_point_layer and id_field_exists and timestamp_field_exists diff --git a/tests/conftest.py b/tests/conftest.py index f420bbf..09a726b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,3 +13,17 @@ This should be used with tests that add stuff to QgsProject. """ + +import pytest +from fvh3t.core.gate import Gate + +from fvh3t.core.trajectory import Trajectory, TrajectoryNode +from qgis.core import QgsGeometry, QgsPointXY + +@pytest.fixture +def two_node_trajectory(): + return Trajectory((TrajectoryNode.from_coordinates(0, 0, 1000), TrajectoryNode.from_coordinates(0, 1, 2000))) + +@pytest.fixture +def two_point_gate(): + return Gate(QgsGeometry.fromPolylineXY([QgsPointXY(-0.5, 0.5), QgsPointXY(0.5, 0.5)])) diff --git a/tests/core/test_trajectory.py b/tests/core/test_trajectory.py new file mode 100644 index 0000000..e55b6e7 --- /dev/null +++ b/tests/core/test_trajectory.py @@ -0,0 +1,17 @@ +from fvh3t.core.trajectory import Trajectory +from fvh3t.core.gate import Gate +from qgis.core import QgsGeometry, QgsPointXY + +def test_trajectory_as_geometry(two_node_trajectory: Trajectory) -> None: + assert two_node_trajectory.as_geometry().asWkt() == "LineString (0 0, 0 1)" + +def test_trajectory_intersects_gate(two_node_trajectory): + geom1 = QgsGeometry.fromPolylineXY([QgsPointXY(-0.5, 0.5), QgsPointXY(0.5, 0.5)]) + geom2 = QgsGeometry.fromPolylineXY([QgsPointXY(-1, -1), QgsPointXY(-2, -2)]) + + gate1 = Gate(geom1) + gate2 = Gate(geom2) + + assert two_node_trajectory.intersects_gate(gate1) + assert not two_node_trajectory.intersects_gate(gate2) + diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 63bf0e2..3c5980d 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -2,4 +2,4 @@ def test_plugin_name(): - assert plugin_name() == "FVH-3T" + assert plugin_name() == "TrafficTrajectoryToolkit"