Skip to content

Commit

Permalink
Tracking setup for mobile application (#489)
Browse files Browse the repository at this point in the history
* add project setting to enable tracking in the mobile app

User can enable or disable tracking as well as choose desired tracking
precision. On the first activation plugin will automatically create a
new GPKG with the tracking layer and add it to the project.

* fix tests

* address review

* formatting

* adjust tests to upstream changes

* make sure that tracking layer has correct flags

* Revert "adjust tests to upstream changes"

This reverts commit c627ed6.

* fix conflicts

* code style
  • Loading branch information
alexbruy authored Aug 21, 2023
1 parent 7eb8db6 commit 7de96e6
Show file tree
Hide file tree
Showing 4 changed files with 513 additions and 217 deletions.
67 changes: 65 additions & 2 deletions Mergin/project_settings_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@
import os
from qgis.PyQt import uic
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QFileDialog
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import QFileDialog, QMessageBox
from qgis.core import (
QgsProject,
QgsExpressionContext,
QgsExpressionContextUtils,
QgsFeature,
QgsFeatureRequest,
QgsExpression,
QgsVectorLayer,
)
from qgis.gui import QgsOptionsWidgetFactory, QgsOptionsPageWidget
from .attachment_fields_model import AttachmentFieldsModel
from .utils import icon_path, mergin_project_local_path, prefix_for_relative_path, resolve_target_dir
from .utils import (
icon_path,
mergin_project_local_path,
prefix_for_relative_path,
resolve_target_dir,
create_tracking_layer,
set_tracking_layer_flags,
)

ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ui", "ui_project_config.ui")
ProjectConfigUiWidget, _ = uic.loadUiType(ui_file)
Expand Down Expand Up @@ -55,6 +64,21 @@ def __init__(self, parent=None):
idx = self.cmb_snapping_mode.findData(mode) if ok else 0
self.cmb_snapping_mode.setCurrentIndex(idx if idx > 0 else 0)

enabled, ok = QgsProject.instance().readBoolEntry("Mergin", "PositionTracking/Enabled")
if ok:
self.chk_tracking_enabled.setChecked(enabled)
else:
self.chk_tracking_enabled.setChecked(False)
self.chk_tracking_enabled.stateChanged.connect(self.check_project)

self.cmb_tracking_precision.addItem("Best", 0)
self.cmb_tracking_precision.addItem("Normal", 1)
self.cmb_tracking_precision.addItem("Low", 2)

mode, ok = QgsProject.instance().readNumEntry("Mergin", "PositionTracking/UpdateFrequency")
idx = self.cmb_tracking_precision.findData(mode) if ok else 1
self.cmb_tracking_precision.setCurrentIndex(idx)

self.local_project_dir = mergin_project_local_path()

if self.local_project_dir:
Expand Down Expand Up @@ -168,9 +192,47 @@ def update_preview(self, expression, layer, field_name):
else:
self.label_preview.setText(f"<i>{val}.jpg</i>")

def check_project(self, state):
"""
Check whether project is saved and we can create tracking
layer for it.
"""
if QgsProject.instance().absolutePath() == "":
QMessageBox.warning(
self,
"Project is not saved",
"It seems that your project is not saved yet, please save "
"project before enabling tracking as we need to know where "
"to place required files.",
)
self.chk_tracking_enabled.blockSignals(True)
self.chk_tracking_enabled.setCheckState(Qt.Unchecked)
self.chk_tracking_enabled.blockSignals(False)

def setup_tracking(self):
if self.chk_tracking_enabled.checkState() == Qt.Unchecked:
return

# check if tracking layer already exists
tracking_layer_id, ok = QgsProject.instance().readEntry("Mergin", "PositionTracking/TrackingLayer")
if tracking_layer_id != "" and tracking_layer_id in QgsProject.instance().mapLayers():
# tracking layer already exists in the project, make sure it has correct flags
layer = QgsProject.instance().mapLayers()[tracking_layer_id]
if layer is not None and layer.isValid():
set_tracking_layer_flags(layer)
return

# tracking layer does not exists or was removed from the project
# create a new layer and add it as a tracking layer
create_tracking_layer(QgsProject.instance().absolutePath())

def apply(self):
QgsProject.instance().writeEntry("Mergin", "PhotoQuality", self.cmb_photo_quality.currentData())
QgsProject.instance().writeEntry("Mergin", "Snapping", self.cmb_snapping_mode.currentData())
QgsProject.instance().writeEntry("Mergin", "PositionTracking/Enabled", self.chk_tracking_enabled.isChecked())
QgsProject.instance().writeEntry(
"Mergin", "PositionTracking/UpdateFrequency", self.cmb_tracking_precision.currentData()
)
for i in range(self.attachments_model.rowCount()):
index = self.attachments_model.index(i, 1)
if index.isValid():
Expand All @@ -181,3 +243,4 @@ def apply(self):
QgsProject.instance().writeEntry("Mergin", f"PhotoNaming/{layer_id}/{field_name}", expression)

self.save_config_file()
self.setup_tracking()
29 changes: 28 additions & 1 deletion Mergin/test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
import os
import json
import copy
import tempfile

from qgis.PyQt.QtCore import QVariant
from qgis.core import (
QgsProject,
QgsDatumTransform,
QgsCoordinateReferenceSystem,
QgsCoordinateTransformContext,
QgsVectorLayer,
QgsWkbTypes,
)

from qgis.testing import start_app, unittest
from Mergin.utils import same_schema, get_datum_shift_grids, is_valid_name
from Mergin.utils import same_schema, get_datum_shift_grids, is_valid_name, create_tracking_layer

test_data_path = os.path.join(os.path.dirname(__file__), "data")

Expand Down Expand Up @@ -170,6 +173,30 @@ def test_name_validation(self):
for t in test_cases:
self.assertEqual(is_valid_name(t[0]), t[1])

def test_create_tracking_layer(self):
with tempfile.TemporaryDirectory() as temp_dir:
file_path = create_tracking_layer(temp_dir)

self.assertTrue(os.path.exists(file_path))
dir_name, file_name = os.path.split(file_path)
self.assertEqual(file_name, "tracking_layer.gpkg")

layer = QgsVectorLayer(file_path, "", "ogr")
self.assertTrue(layer.isValid())
self.assertEqual(layer.wkbType(), QgsWkbTypes.LineStringZM)

fields = layer.fields()
self.assertEqual(len(fields), 5)
self.assertEqual(fields[0].name(), "fid")
self.assertEqual(fields[1].name(), "tracking_start_time")
self.assertEqual(fields[1].type(), QVariant.DateTime)
self.assertEqual(fields[2].name(), "tracking_end_time")
self.assertEqual(fields[2].type(), QVariant.DateTime)
self.assertEqual(fields[3].name(), "total_distance")
self.assertEqual(fields[3].type(), QVariant.Double)
self.assertEqual(fields[4].name(), "tracked_by")
self.assertEqual(fields[4].type(), QVariant.String)


if __name__ == "__main__":
nose2.main()
Loading

0 comments on commit 7de96e6

Please sign in to comment.