diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..07c497e74357af082d0142c43a2d37e2c039c8ea --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +All changes that impact users of this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +<!--- +This document is intended for users of the applications and API. Changes to things +like tests should not be noted in this document. + +When updating this file for a PR, add an entry for your change under Unreleased +and one of the following headings: + - Added - for new features. + - Changed - for changes in existing functionality. + - Deprecated - for soon-to-be removed features. + - Removed - for now removed features. + - Fixed - for any bug fixes. + - Security - in case of vulnerabilities. + +If the heading does not yet exist under Unreleased, then add it as a 3rd heading, +with three #. + + +When preparing for a public release candidate add a new 2nd heading, with two #, under +Unreleased with the version number and the release date, in year-month-day +format. Then, add a link for the new version at the bottom of this document and +update the Unreleased link so that it compares against the latest release tag. + + +When preparing for a bug fix release create a new 2nd heading above the Fixed +heading to indicate that only the bug fixes and security fixes are in the bug fix +release. +--> + +## Unreleased + +### Changed +- Removed all `pyproj` calls from csm.py, abstracting them into the reprojection and pyproj.Transformer code inside utils.py. Updated the transformations to use the new pipeline style syntax to avoid deprecation warnings about old syntax.p + diff --git a/knoten/csm.py b/knoten/csm.py index 0c627905ab691b8c170ce5c3e2452d613a8aac09..ee703142c56d4cb9eb3260429c0c932f7a8b83e6 100644 --- a/knoten/csm.py +++ b/knoten/csm.py @@ -6,13 +6,13 @@ from csmapi import csmapi import jinja2 from osgeo import ogr import numpy as np -import pyproj import requests import scipy.stats from functools import singledispatch from plio.io.io_gdal import GeoDataset +from knoten import utils class NumpyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, np.ndarray): @@ -157,10 +157,16 @@ def _(dem, image_pt, camera, max_its = 20, tolerance = 0.001): intersection = generate_ground_point(0.0, image_pt, camera) iterations = 0 semi_major, semi_minor = get_radii(camera) - ecef = pyproj.Proj(proj='geocent', a=semi_major, b=semi_minor) - lla = pyproj.Proj(proj='latlon', a=semi_major, b=semi_minor) + + source_proj = f'+proj=cart +a={semi_major} +b={semi_minor}' + dest_proj = f'+proj=lonlat +a={semi_major} +b={semi_minor}' + transformer = utils.create_transformer(source_proj, dest_proj) + while iterations != max_its: - lon, lat, alt = pyproj.transform(ecef, lla, intersection.x, intersection.y, intersection.z) + lon, lat, alt = transformer.transform(intersection.x, + intersection.y, + intersection.z, + errcheck=True) px, py = dem.latlon_to_pixel(lat, lon) height = dem.read_array(1, [px, py, 1, 1])[0][0] @@ -231,8 +237,9 @@ def generate_latlon_boundary(camera, boundary, dem=0.0, radii=None, **kwargs): else: semi_major, semi_minor = radii - ecef = pyproj.Proj(proj='geocent', a=semi_major, b=semi_minor) - lla = pyproj.Proj(proj='latlon', a=semi_major, b=semi_minor) + source_proj = f'+proj=cart +a={semi_major} +b={semi_minor}' + dest_proj = f'+proj=lonlat +a={semi_major} +b={semi_minor}' + transformer = utils.create_transformer(source_proj, dest_proj) gnds = np.empty((len(boundary), 3)) @@ -245,7 +252,7 @@ def generate_latlon_boundary(camera, boundary, dem=0.0, radii=None, **kwargs): gnds[i] = [gnd.x, gnd.y, gnd.z] - lons, lats, alts = pyproj.transform(ecef, lla, gnds[:,0], gnds[:,1], gnds[:,2]) + lons, lats, alts = transformer.transform(gnds[:,0], gnds[:,1], gnds[:,2]) return lons, lats, alts def generate_gcps(camera, boundary, radii=None): @@ -392,16 +399,16 @@ def generate_bodyfixed_footprint(camera, boundary, radii=None): latlon_fp = generate_latlon_footprint(camera, boundary, radii=radii) - ecef = pyproj.Proj(proj='geocent', a=semi_major, b=semi_minor) - lla = pyproj.Proj(proj='latlon', a=semi_major, b=semi_minor) - + source_proj = f'+proj=lonlat +a={semi_major} +b={semi_minor}' + dest_proj = f'+proj=cart +a={semi_major} +b={semi_minor}' + transformer = utils.create_transformer(source_proj, dest_proj) # Step over all geometry objects in the latlon footprint for i in range(latlon_fp.GetGeometryCount()): latlon_coords = np.array(latlon_fp.GetGeometryRef(i).GetGeometryRef(0).GetPoints()) # Check if the geometry object is populated with points if len(latlon_coords) > 0: - x, y, z = pyproj.transform(lla, ecef, latlon_coords[:,0], latlon_coords[:,1], latlon_coords[:,2]) + x, y, z = transformer.transform(latlon_coords[:,0], latlon_coords[:,1], latlon_coords[:,2]) # Step over all coordinate points in a geometry object and update said point for j, _ in enumerate(latlon_coords): diff --git a/knoten/utils.py b/knoten/utils.py index 47bc704747219209f19bb38144f5e670b5547329..121721ad7890b0fc334ad1aac4faa044b91f8789 100644 --- a/knoten/utils.py +++ b/knoten/utils.py @@ -35,9 +35,17 @@ def reproject(record, semi_major, semi_minor, source_proj, dest_proj, **kwargs): Transformed coordinates as y, x, z """ - source_pyproj = pyproj.Proj(proj = source_proj, a = semi_major, b = semi_minor) - dest_pyproj = pyproj.Proj(proj = dest_proj, a = semi_major, b = semi_minor) - - y, x, z = pyproj.transform(source_pyproj, dest_pyproj, record[0], record[1], record[2], **kwargs) + transformer = pyproj.Transformer.from_crs(f'+proj={source_proj} +a={semi_major} +b={semi_minor}', + f'+proj={dest_proj} +a={semi_major} +b={semi_minor}', + always_xy=True) + source_proj = f'+proj={source_proj} +a={semi_major} +b={semi_minor}' + dest_proj = f'+proj={dest_proj} +a={semi_major} +b={semi_minor}' + transformer = create_transformer(source_proj, dest_proj) + x, y, z = transformer.transform(record[0], record[1], record[2], errcheck=True) return y, x, z + +def create_transformer(source_proj, dest_proj): + return pyproj.Transformer.from_crs(source_proj, + dest_proj, + always_xy=True) \ No newline at end of file diff --git a/tests/test_reproject.py b/tests/test_reproject.py index 0b479af862e41825ac822fbdaf1e307f8c40c47f..52fe2068f11a2c16d45463cb417648ac2695601d 100644 --- a/tests/test_reproject.py +++ b/tests/test_reproject.py @@ -4,7 +4,8 @@ import pytest from knoten import utils def test_reproject(): - with mock.patch('pyproj.transform', return_value=[1,1,1]) as mock_pyproj: - res = utils.reproject([1,1,1], 10, 10, 'geocent', 'latlon') - mock_pyproj.assert_called_once() - assert res == (1,1,1) + res = utils.reproject([0,1,0], 10, 10, 'geocent', 'latlon') + print(res) + assert res[0] == 0 + assert res[1] == 90 + assert res[2] == -9