diff --git a/src/coordinate_transformation_api/crs_transform.py b/src/coordinate_transformation_api/crs_transform.py index f78af26..7c036ba 100644 --- a/src/coordinate_transformation_api/crs_transform.py +++ b/src/coordinate_transformation_api/crs_transform.py @@ -341,6 +341,32 @@ def get_individual_epsg_code(compound_crs: CRS) -> tuple[str, str]: return (f"{horizontal[0]}:{horizontal[1]}", f"{vertical[0]}:{vertical[1]}") +def build_input_coord(coord: CoordinatesType, epoch: float | None) -> CoordinatesType: + + # When 2D input is given with an epoch we need to add a height. So pyproj knows to + # that the epoch is an epoch and not the height, without this intervention the epoch + # would be place in the firth position of the tuple. + if len(coord) == TWO_DIMENSIONAL and epoch is not None: + return tuple([*coord, 0.0, epoch]) + + # Default behaviour + # The input_coord == coord that are given. When an epoch is provided with a 3D coord + # this is added or the value None is given for any other. Note: with 2D the additional None + # is the height. But this doesn't influence the result, because it's None. + input_coord = tuple( + [ + *coord, + ( + float(epoch) + if len(coord) == THREE_DIMENSIONAL and epoch is not None + else None + ), + ] + ) + + return input_coord + + def get_transform_crs_fun( # source_crs: str, target_crs: str, @@ -414,16 +440,7 @@ def transform_crs(val: CoordinatesType) -> tuple[float, ...]: # when one of the src or tgt crs has a dynamic time component # or the transformation used has a datetime component # for now simple check on coords length (which is not correct) - input = tuple( - [ - *val, - ( - float(epoch) - if len(val) == THREE_DIMENSIONAL and epoch is not None - else None - ), - ] - ) + input = build_input_coord(val, epoch) # GeoJSON and CityJSON by definition has coordinates always in lon-lat-height (or x-y-z) order. Transformer has been created with `always_xy=True`, # to ensure input and output coordinates are in in lon-lat-height (or x-y-z) order. diff --git a/tests/data/test_2d_with_epoch.json b/tests/data/test_2d_with_epoch.json new file mode 100644 index 0000000..4b75d45 --- /dev/null +++ b/tests/data/test_2d_with_epoch.json @@ -0,0 +1,8 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [663000.0, 5781000.0] + } +} diff --git a/tests/test_geojson_transformation.py b/tests/test_geojson_transformation.py index a279a04..ec474fe 100644 --- a/tests/test_geojson_transformation.py +++ b/tests/test_geojson_transformation.py @@ -162,6 +162,60 @@ def test_validate_crs_transformed_geojson(feature): validate_crs_transformed_geojson(feature_no_exc) +def test_2d_with_epoch(): + with open("tests/data/test_2d_with_epoch.json") as f: + data = json.load(f) + feature_2d_2000 = Feature(**data) + feature_2d_2020 = Feature(**data) + feature_2d_org = Feature(**data) + + crs_transform(feature_2d_2000, "EPSG:3043", "EPSG:32631", 2000) + crs_transform(feature_2d_2020, "EPSG:3043", "EPSG:32631", 2020) + + assert feature_2d_2000 != feature_2d_org + assert feature_2d_2020 != feature_2d_org + + coords_2000 = feature_2d_2000.geometry.coordinates + coords_2020 = feature_2d_2020.geometry.coordinates + coords_org = feature_2d_org.geometry.coordinates + + dif_2000_org = 0.29 + dif_2020_org = 0.76 + + assert ( + round( + math.sqrt( + ( + (coords_2000[0] - coords_org[0]) + * (coords_2000[0] - coords_org[0]) + ) + + ( + (coords_2000[1] - coords_org[1]) + * (coords_2000[1] - coords_org[1]) + ) + ), + 2, + ) + == dif_2000_org + ) + assert ( + round( + math.sqrt( + ( + (coords_2020[0] - coords_org[0]) + * (coords_2020[0] - coords_org[0]) + ) + + ( + (coords_2020[1] - coords_org[1]) + * (coords_2020[1] - coords_org[1]) + ) + ), + 2, + ) + == dif_2020_org + ) + + def test_wgs_epoch(): with open("tests/data/test_wgs_epoch.json") as f: data = json.load(f) diff --git a/tests/test_transformer_selection.py b/tests/test_transformer_selection.py index a1b807a..b7795e5 100644 --- a/tests/test_transformer_selection.py +++ b/tests/test_transformer_selection.py @@ -1,5 +1,9 @@ import pytest -from coordinate_transformation_api.crs_transform import get_transformer, needs_epoch +from coordinate_transformation_api.crs_transform import ( + build_input_coord, + get_transformer, + needs_epoch, +) # This test needs the modified proj.time.dependent.transformations.db from @@ -17,3 +21,16 @@ ) def test_time_dependant_operation_method(source, target, epoch, expectation): assert needs_epoch(get_transformer(source, target, epoch)) == expectation + + +@pytest.mark.parametrize( + ("coord", "epoch", "expectation"), + [ + (tuple([1000.0, 1000.0]), 2000.0, tuple([1000.0, 1000.0, 0.0, 2000.0])), + (tuple([1000.0, 1000.0]), None, tuple([1000.0, 1000.0, None])), + (tuple([1000.0, 1000.0, 10.0]), 2000.0, tuple([1000.0, 1000.0, 10.0, 2000.0])), + (tuple([1000.0, 1000.0, 10.0]), None, tuple([1000.0, 1000.0, 10.0, None])), + ], +) +def test_build_input_coord(coord, epoch, expectation): + assert build_input_coord(coord, epoch) == expectation