Skip to content

Commit 166ef85

Browse files
committed
OP#10404: Refuse geo-only track files without a declared CRS
A missing CRS used to fall back to EPSG:25833 with a warning, which shifts every road user silently if the guess is wrong. The parser now raises MissingGeoCoordinatesCrs for a missing or blank CRS and the fallback constant is gone.
1 parent 9a3ae0c commit 166ef85

4 files changed

Lines changed: 43 additions & 23 deletions

File tree

‎OTAnalytics/application/orthophoto.py‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
from OTAnalytics.domain.track_repository import TrackRepository
1919
from OTAnalytics.domain.video import VideoRepository
2020

21-
# The CRS OTFusion's default geo offset shifts local world coordinates into.
22-
# Files written before OTFusion declared its CRS (OTCloud OP#10404) are assumed
23-
# to be in it.
24-
FALLBACK_GEO_CRS = "EPSG:25833"
2521
ORTHOPHOTO_REQUIRED = (
2622
"These track files place road users only by geo coordinates, so they need an"
2723
" orthophoto to be shown and counted. Nothing was loaded."

‎OTAnalytics/domain/orthophoto.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class MixedTrackFiles(OrthophotoError):
3535
"""Camera Track Files were loaded into a project that shows an Orthophoto."""
3636

3737

38+
class MissingGeoCoordinatesCrs(OrthophotoError):
39+
"""A Geo-only Track File does not say which CRS its geo coordinates are in."""
40+
41+
3842
class OrthophotoNotFound(OrthophotoError):
3943
"""The Orthophoto the project declares cannot be obtained."""
4044

‎OTAnalytics/plugin_parser/feathers_parser.py‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
import polars as pl
1313

1414
import OTAnalytics.plugin_parser.ottrk_dataformat as ottrk_format
15-
from OTAnalytics.application.logger import logger
1615
from OTAnalytics.application.orthophoto import (
17-
FALLBACK_GEO_CRS,
1816
NoOrthophotoGeoreference,
1917
ProvideOrthophotoGeoreference,
2018
)
@@ -25,7 +23,7 @@
2523
)
2624
from OTAnalytics.domain import track
2725
from OTAnalytics.domain.georeference import GeoreferenceMetadata, geo_to_pixel_transform
28-
from OTAnalytics.domain.orthophoto import MixedTrackFiles
26+
from OTAnalytics.domain.orthophoto import MissingGeoCoordinatesCrs, MixedTrackFiles
2927
from OTAnalytics.domain.track_dataset.track_dataset import TrackDataset
3028
from OTAnalytics.domain.video import VideoMetadata
3129
from OTAnalytics.plugin_datastore.polars_track_store import (
@@ -55,6 +53,11 @@
5553
" geo coordinates only (OTFusion output) and {camera} come with their own"
5654
" video. Load them in separate projects. Nothing was loaded."
5755
)
56+
MISSING_GEO_COORDINATES_CRS = (
57+
"'{file}' does not say which CRS its geo coordinates are in, so it cannot be"
58+
" placed on the orthophoto. Export it again with a current OTFusion."
59+
" Nothing was loaded."
60+
)
5861

5962

6063
def place_on_orthophoto(
@@ -177,13 +180,12 @@ def parse(self, file: Path) -> TrackParseResult:
177180
)
178181

179182
def _geo_coordinates_crs(self, metadata: dict, file: Path) -> str:
180-
if (crs := self.parse_geo_coordinates_crs(metadata)) is not None:
181-
return crs
182-
logger().warning(
183-
f"'{file.name}' does not say which CRS its geo coordinates are in."
184-
f" Assuming {FALLBACK_GEO_CRS}."
185-
)
186-
return FALLBACK_GEO_CRS
183+
crs = self.parse_geo_coordinates_crs(metadata)
184+
if crs is None or not crs.strip():
185+
raise MissingGeoCoordinatesCrs(
186+
MISSING_GEO_COORDINATES_CRS.format(file=file.name)
187+
)
188+
return crs
187189

188190
def _parse_video_metadata(self, metadata: dict) -> VideoMetadata:
189191
"""

‎tests/unit/OTAnalytics/plugin_parser/test_feathers_parser.py‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@
1616
from pytest import approx
1717

1818
import OTAnalytics.plugin_parser.ottrk_dataformat as ottrk_format
19-
from OTAnalytics.application.orthophoto import (
20-
FALLBACK_GEO_CRS,
21-
ProvideOrthophotoGeoreference,
22-
)
19+
from OTAnalytics.application.orthophoto import ProvideOrthophotoGeoreference
2320
from OTAnalytics.application.parser.track_parser import (
2421
DetectionMetadata,
2522
TrackParseResult,
2623
)
2724
from OTAnalytics.domain import track
2825
from OTAnalytics.domain.georeference import GeoreferenceMetadata
29-
from OTAnalytics.domain.orthophoto import MixedTrackFiles, OrthophotoRequired
26+
from OTAnalytics.domain.orthophoto import (
27+
MissingGeoCoordinatesCrs,
28+
MixedTrackFiles,
29+
OrthophotoRequired,
30+
)
3031
from OTAnalytics.domain.track_dataset.track_dataset import (
3132
IncompatibleGeoreferenceMetadataError,
3233
)
@@ -672,17 +673,34 @@ def test_asks_for_the_mapping_in_the_declared_crs(
672673

673674
given.orthophoto_georeference.for_crs.assert_called_once_with(GEO_ONLY_CRS)
674675

675-
def test_falls_back_to_default_crs_when_none_is_declared(
676-
self, test_data_tmp_dir: Path
676+
def test_refuses_when_no_crs_is_declared(self, test_data_tmp_dir: Path) -> None:
677+
given = setup_default_geo_only(
678+
create_given_geo_only(test_data_tmp_dir), declares_crs=False
679+
)
680+
target = create_target_geo_only(given)
681+
682+
with pytest.raises(MissingGeoCoordinatesCrs, match=given.feather_file.name):
683+
target.parse(given.feather_file)
684+
685+
given.orthophoto_georeference.for_crs.assert_not_called()
686+
687+
@pytest.mark.parametrize("blank_crs", ["", " ", None])
688+
def test_refuses_when_the_declared_crs_is_blank(
689+
self, test_data_tmp_dir: Path, blank_crs: str | None
677690
) -> None:
678691
given = setup_default_geo_only(
679692
create_given_geo_only(test_data_tmp_dir), declares_crs=False
680693
)
694+
sidecar_file = given.tmp_dir / "fusion_metadata.json"
695+
sidecar = json.loads(sidecar_file.read_text())
696+
sidecar[ottrk_format.GEO_COORDINATES] = {ottrk_format.CRS: blank_crs}
697+
sidecar_file.write_text(json.dumps(sidecar))
681698
target = create_target_geo_only(given)
682699

683-
target.parse(given.feather_file)
700+
with pytest.raises(MissingGeoCoordinatesCrs):
701+
target.parse(given.feather_file)
684702

685-
given.orthophoto_georeference.for_crs.assert_called_once_with(FALLBACK_GEO_CRS)
703+
given.orthophoto_georeference.for_crs.assert_not_called()
686704

687705
def test_places_detections_outside_the_orthophoto_without_clipping(
688706
self, test_data_tmp_dir: Path

0 commit comments

Comments
 (0)