diff --git a/Pipfile b/Pipfile index 232f597..a643e09 100644 --- a/Pipfile +++ b/Pipfile @@ -32,6 +32,7 @@ gunicorn = "*" social-auth-app-django = "*" libtiff = "*" lxml = "*" +pyproj = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index eedd9d6..9c65a92 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "701e1afb9dea4d94e60d48cc4f70a2a08a8d886850d1c878a90518cc242f93b4" + "sha256": "275d768578ddd8abb33af24e4ebd9db88d1c290a545ced46973189cb5f1703ba" }, "pipfile-spec": 6, "requires": { @@ -336,6 +336,32 @@ ], "version": "==2.4.2" }, + "pyproj": { + "hashes": [ + "sha256:050e6a7414178b54ff9a43728606402bb6d27d2b0b91f777cd21520ae249a248", + "sha256:0802f617d908cba8d05ced8ecd068e41424fae790fc1fc1340cc1f07a135e584", + "sha256:0d4c13f192d4e3a354338fd7702811a8c75b93f6b583d6cfbebf86824e22b689", + "sha256:12f833f0de417a11972e5e016a4a457e9e193981dc2f8cfc88a9a38d9d109788", + "sha256:273f8e0c82dbdb8c0916b50a7036e01fe24d38c8b71b4eab6a7963f94fa69876", + "sha256:39d5716e3f1a3bc07674cebe8cb8b9885ac6e95874e56a180b0c98cdbaa7f1a9", + "sha256:454c615e91294e69f40d8717655b45ff834aaf34cc95348062a0f33da491b23b", + "sha256:455a6a7c7aa826101469b8880df8ade0975c07299fbf37a328f80c0730b7b09d", + "sha256:4eda076a3eae8b984c8576eed0a4cbe26094fc95224cfe6333b202ffc5610728", + "sha256:6f129a00afdd817dbb331af5709221f35012bcc11a23b8c83fa09197c1190786", + "sha256:7d3989d15ad1d300f0d09d5ff8e44302af767c096256958db8132368a03c9905", + "sha256:7fda8df34ff7c8640c847ffede42e14fea6562bed5557409446ef2ce7a661a66", + "sha256:8b19387d369b3b81729474f71840030ec72123b83d4d7b84a95855ed94a64131", + "sha256:96b5e9f7eaebc659b330f352482e9fd2cdbbab4af604f8a8024cbbce640ebb3f", + "sha256:9dd9ce7df1fa5ad50eea6ee613b5afd2cf8f6360f50e6d44771753034ef71bd8", + "sha256:a9888807c098a21cfc53e2138209b182682d504278875ff6f2fe82281e9f97c1", + "sha256:db6719f8570ce4e5d9a245346623a92dd00b53e4ad066237e8cdbf2bbb1d60d6", + "sha256:eb83cb00ad71fb84f9ee6cbb3042e7e2518042f85122d2393d0dbf75cf515427", + "sha256:f80d5b50fe2a4a27fa49258120246f0494346e9241e1624830fbeec5609053d6", + "sha256:fc71286a43583a75b8c372410c84c31b79ca9d92ca2fb9c7f63d387e938e8929" + ], + "index": "pypi", + "version": "==2.2.2" + }, "pytest": { "hashes": [ "sha256:3805d095f1ea279b9870c3eeae5dddf8a81b10952c8835cd628cf1875b0ef031", @@ -368,11 +394,11 @@ }, "redis": { "hashes": [ - "sha256:0607faf60d44768e17f65e506fe390679b54be6fd6d5f0c2d28f3ebf4f0535e7", - "sha256:9c96c5bf11a8c47eb33cefdefd41c47cf1ff68db41c51b56b3ec7938b7c627f7" + "sha256:98a22fb750c9b9bb46e75e945dc3f61d0ab30d06117cbb21ff9cd1d315fedd3b", + "sha256:c504251769031b0dd7dd5cf786050a6050197c6de0d37778c80c08cb04ae8275" ], "index": "pypi", - "version": "==3.3.7" + "version": "==3.3.8" }, "redo": { "hashes": [ diff --git a/tests/operations/gis_operations/sample.csv b/tests/operations/gis_operations/sample.csv new file mode 100644 index 0000000..d3c4e5a --- /dev/null +++ b/tests/operations/gis_operations/sample.csv @@ -0,0 +1,50 @@ +x,y,utm_left,utm_top,utm_right,utm_bottom,utm_zone +0,0,213300.0,2034900.0,252292.85471248726,1995900.0,15 +1,0,213377.98570942497,2034900.0,252370.8404219122,1995900.0,15 +2,0,213455.97141884995,2034900.0,252448.8261313372,1995900.0,15 +3,0,213533.95712827492,2034900.0,252526.81184076215,1995900.0,15 +4,0,213611.9428376999,2034900.0,252604.79755018716,1995900.0,15 +5,0,213689.92854712487,2034900.0,252682.7832596121,1995900.0,15 +6,0,213767.91425654985,2034900.0,252760.7689690371,1995900.0,15 +0,1,213300.0,2034822.0,252292.85471248726,1995822.0,15 +1,1,213377.98570942497,2034822.0,252370.8404219122,1995822.0,15 +2,1,213455.97141884995,2034822.0,252448.8261313372,1995822.0,15 +3,1,213533.95712827492,2034822.0,252526.81184076215,1995822.0,15 +4,1,213611.9428376999,2034822.0,252604.79755018716,1995822.0,15 +5,1,213689.92854712487,2034822.0,252682.7832596121,1995822.0,15 +6,1,213767.91425654985,2034822.0,252760.7689690371,1995822.0,15 +0,2,213300.0,2034744.0,252292.85471248726,1995744.0,15 +1,2,213377.98570942497,2034744.0,252370.8404219122,1995744.0,15 +2,2,213455.97141884995,2034744.0,252448.8261313372,1995744.0,15 +3,2,213533.95712827492,2034744.0,252526.81184076215,1995744.0,15 +4,2,213611.9428376999,2034744.0,252604.79755018716,1995744.0,15 +5,2,213689.92854712487,2034744.0,252682.7832596121,1995744.0,15 +6,2,213767.91425654985,2034744.0,252760.7689690371,1995744.0,15 +0,3,213300.0,2034666.0,252292.85471248726,1995666.0,15 +1,3,213377.98570942497,2034666.0,252370.8404219122,1995666.0,15 +2,3,213455.97141884995,2034666.0,252448.8261313372,1995666.0,15 +3,3,213533.95712827492,2034666.0,252526.81184076215,1995666.0,15 +4,3,213611.9428376999,2034666.0,252604.79755018716,1995666.0,15 +5,3,213689.92854712487,2034666.0,252682.7832596121,1995666.0,15 +6,3,213767.91425654985,2034666.0,252760.7689690371,1995666.0,15 +0,4,213300.0,2034588.0,252292.85471248726,1995588.0,15 +1,4,213377.98570942497,2034588.0,252370.8404219122,1995588.0,15 +2,4,213455.97141884995,2034588.0,252448.8261313372,1995588.0,15 +3,4,213533.95712827492,2034588.0,252526.81184076215,1995588.0,15 +4,4,213611.9428376999,2034588.0,252604.79755018716,1995588.0,15 +5,4,213689.92854712487,2034588.0,252682.7832596121,1995588.0,15 +6,4,213767.91425654985,2034588.0,252760.7689690371,1995588.0,15 +0,5,213300.0,2034510.0,252292.85471248726,1995510.0,15 +1,5,213377.98570942497,2034510.0,252370.8404219122,1995510.0,15 +2,5,213455.97141884995,2034510.0,252448.8261313372,1995510.0,15 +3,5,213533.95712827492,2034510.0,252526.81184076215,1995510.0,15 +4,5,213611.9428376999,2034510.0,252604.79755018716,1995510.0,15 +5,5,213689.92854712487,2034510.0,252682.7832596121,1995510.0,15 +6,5,213767.91425654985,2034510.0,252760.7689690371,1995510.0,15 +0,6,213300.0,2034432.0,252292.85471248726,1995432.0,15 +1,6,213377.98570942497,2034432.0,252370.8404219122,1995432.0,15 +2,6,213455.97141884995,2034432.0,252448.8261313372,1995432.0,15 +3,6,213533.95712827492,2034432.0,252526.81184076215,1995432.0,15 +4,6,213611.9428376999,2034432.0,252604.79755018716,1995432.0,15 +5,6,213689.92854712487,2034432.0,252682.7832596121,1995432.0,15 +6,6,213767.91425654985,2034432.0,252760.7689690371,1995432.0,15 diff --git a/tests/operations/gis_operations/test_compute_lat_long.py b/tests/operations/gis_operations/test_compute_lat_long.py new file mode 100644 index 0000000..e17bef2 --- /dev/null +++ b/tests/operations/gis_operations/test_compute_lat_long.py @@ -0,0 +1,48 @@ +from unittest import TestCase +from unittest.mock import call, patch, MagicMock, Mock, PropertyMock, ANY + +from theia.operations.gis_operations import ComputeLatLong +from theia.api.models import ImageryRequest, JobBundle, PipelineStage + +class TestComputeLatLong(TestCase): + def setUp(self): + self.request = ImageryRequest(adapter_name='dummy') + self.stage = PipelineStage( + select_images=['composite'], + sort_order=3, + config={ + 'output_filename': 'foo.csv', + }, + ) + self.bundle = JobBundle(current_stage=self.stage, imagery_request=self.request) + self.operation = ComputeLatLong(self.bundle) + + @patch('theia.operations.gis_operations.ComputeLatLong.convert_to_lat_long', side_effect=[(5.0, 6.0), (7.0, 8.0)]) + def test_commit_lat_long_values_for_row(self, mock_object): + row_with_lat_long = self.operation.add_lat_long_values_to_row( + { + 'x' : 1.0, + 'y' : 2.0, + 'utm_left' : 3.0, + 'utm_top' : 3.0, + 'utm_right' : 3.0, + 'utm_bottom' : 3.0, + 'utm_zone': 15, + } + ) + assert all(expected in row_with_lat_long.keys() for expected in ["x","y","utm_left","utm_top","utm_right","utm_bottom"]) + assert row_with_lat_long.get('top_left_latitude')==5.0 + assert row_with_lat_long.get('top_left_longitude')==6.0 + assert row_with_lat_long.get('bottom_right_latitude')==7.0 + assert row_with_lat_long.get('bottom_right_longitude')==8.0 + + @patch('pyproj.proj.Proj.__call__', return_value=('latitude_number', 'longitude_number')) + @patch('pyproj.proj.Proj.__init__', return_value=None) + def test_convert_to_lat_long(self, mock_init, mock_call): + latitude, longitude = self.operation.convert_to_lat_long(1.0, 2.0, 15) + + mock_init.assert_called_once_with(proj='utm', zone=15, ellps='WGS84') + mock_call.assert_called_once_with(1.0, 2.0, inverse=True) + + assert latitude == 'latitude_number' + assert longitude == 'longitude_number' \ No newline at end of file diff --git a/theia/operations/__init__.py b/theia/operations/__init__.py index 5225afd..282f8a8 100644 --- a/theia/operations/__init__.py +++ b/theia/operations/__init__.py @@ -4,6 +4,7 @@ operations = { 'gis_operations.compute_corners': gis_operations.ComputeCorners, + 'gis_operations.compute_lat_long': gis_operations.ComputeLatLong, 'image_operations.resize_image': image_operations.ResizeImage, 'image_operations.remap_image': image_operations.RemapImage, diff --git a/theia/operations/gis_operations/__init__.py b/theia/operations/gis_operations/__init__.py index d0426b4..c9c6995 100644 --- a/theia/operations/gis_operations/__init__.py +++ b/theia/operations/gis_operations/__init__.py @@ -1 +1,2 @@ from .compute_corners import ComputeCorners +from .compute_lat_long import ComputeLatLong \ No newline at end of file diff --git a/theia/operations/gis_operations/compute_lat_long.py b/theia/operations/gis_operations/compute_lat_long.py new file mode 100644 index 0000000..53c2075 --- /dev/null +++ b/theia/operations/gis_operations/compute_lat_long.py @@ -0,0 +1,35 @@ +import csv +from pyproj import Proj + +from ..abstract_operation import AbstractOperation + +class ComputeLatLong(AbstractOperation): + def apply(self, filenames): + pass + + # TODO: parse csv from string to integer + + def convert_to_lat_long(self, utm_x: float, utm_y: float, utm_zone: int): + conversion_method = Proj(proj='utm', zone=utm_zone, ellps='WGS84') + return conversion_method(utm_x, utm_y, inverse=True) + + def add_lat_long_values_to_row(self, utm_row_as_dictionary): + top_left_lat, top_left_long = self.convert_to_lat_long( + utm_row_as_dictionary['utm_left'], + utm_row_as_dictionary['utm_top'], + utm_row_as_dictionary['utm_zone'], + ) + utm_row_as_dictionary.update({ + 'top_left_latitude': top_left_lat, + 'top_left_longitude': top_left_long, + }) + bottom_right_lat, bottom_right_long = self.convert_to_lat_long( + utm_row_as_dictionary['utm_right'], + utm_row_as_dictionary['utm_bottom'], + utm_row_as_dictionary['utm_zone'], + ) + utm_row_as_dictionary.update({ + 'bottom_right_latitude': bottom_right_lat, + 'bottom_right_longitude': bottom_right_long, + }) + return utm_row_as_dictionary