#########################################################################
#
# Copyright (C) 2021 OSGeo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################
import re
import uuid
from unittest.mock import patch, PropertyMock, MagicMock
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.gis.geos import Polygon
from geonode.documents.models import Document
from geonode.geoapps.models import GeoApp
from geonode.resource.manager import resource_manager
from geonode.thumbs import utils
from geonode.thumbs import thumbnails
from geonode.layers.models import Dataset
from geonode.utils import DisableDjangoSignals
from geonode.maps.models import Map, MapLayer
from geonode.tests.base import GeoNodeBaseTestSupport, GeoNodeBaseSimpleTestSupport
[docs]
FIXTURES_DIR = "geonode/thumbs/tests/fixtures/"
[docs]
class ThumbnailsUtilsUnitTest(GeoNodeBaseSimpleTestSupport):
[docs]
def test_create_getmap_request(self):
request = utils._build_getmap_request(
layers=["geonode:Foo"], size=[512, 512], srs="epsg:4326", bbox=[-180, -90, 180, 90], bgcolor="fff"
)
self.assertDictEqual(
request,
{
"service": "WMS",
"version": "1.3.0",
"request": "GetMap",
"layers": "geonode:Foo",
"styles": "",
"width": "512",
"height": "512",
"crs": "epsg:4326",
"bbox": "-90,-180,90,180",
"format": "None",
"transparent": "FALSE",
"bgcolor": "0xff",
"exceptions": "None",
},
)
[docs]
def test_make_bbox_to_pixels_transf_same(self):
src_bbox = [0, 0, 1, 1]
dest_bbox = [0, 0, 1, 1]
transf = utils.make_bbox_to_pixels_transf(src_bbox, dest_bbox)
point = [0.5, 0.5]
transformed = transf(*point)
self.assertEqual(tuple(point), transformed, "Expected linear transformation to return the same coords.")
[docs]
def test_make_bbox_to_pixels_transf_diff(self):
multiplication_factor = 10
src_bbox = [0, 0, 1, 1]
dest_bbox = [0, 0, multiplication_factor * src_bbox[2], multiplication_factor * src_bbox[3]]
transf = utils.make_bbox_to_pixels_transf(src_bbox, dest_bbox)
point = [0.5, 0.5]
transformed = transf(*point)
self.assertEqual(
tuple(multiplication_factor * coord for coord in point),
transformed,
"Expected linear transformation to return multiplied coords.",
)
[docs]
def test_expand_bbox_to_ratio(self):
bbox = [623869.6556559108, 2458358.334500141, 4291621.974352865, 5270015.93640312, "EPSG:3857"]
center = [(bbox[1] + bbox[0]) / 2, (bbox[3] + bbox[2]) / 2]
width = 250
height = 200
new_bbox = utils.expand_bbox_to_ratio(bbox, target_height=height, target_width=width)
# round to 4 decimal places in order to get rid of float rounding errors
ratio = round(abs(new_bbox[3] - new_bbox[2]) / abs(new_bbox[1] - new_bbox[0]), 4)
new_center = [(new_bbox[1] + new_bbox[0]) / 2, (new_bbox[3] + new_bbox[2]) / 2]
self.assertEqual(height / width, ratio, "Expected ratio to be equal target ratio after transformation")
self.assertEqual(center, new_center, "Expected center to be preserved after transformation")
[docs]
class ThumbnailsUnitTest(GeoNodeBaseTestSupport):
[docs]
fixtures = GeoNodeBaseTestSupport.fixtures.copy() + [
FIXTURES_DIR + filename
for filename in [
"resource_base.json",
"service.json",
"style.json",
"dataset.json",
"map.json",
"map_dataset.json",
]
]
[docs]
re_uuid = "[0-F]{8}-([0-F]{4}-){3}[0-F]{12}"
@classmethod
[docs]
def setUpClass(cls):
# temporarily disconnect signals to load Service fixture
with DisableDjangoSignals():
super().setUpClass()
[docs]
def test_generate_thumbnail_name_dataset(self):
dataset_name = thumbnails._generate_thumbnail_name(Dataset.objects.first())
self.assertIsNotNone(
re.match(f"dataset-{self.re_uuid}-thumb.png", dataset_name, re.I),
"Dataset name should meet a provided pattern",
)
[docs]
def test_get_unique_upload_path(self):
dataset = Dataset.objects.first()
thumbnail_name = thumbnails._generate_thumbnail_name(dataset)
upload_path = utils.thumb_path(thumbnail_name)
new_upload_path = utils.get_unique_upload_path(thumbnail_name)
self.assertNotEqual(upload_path, new_upload_path)
@patch("geonode.maps.models.Map.maplayers", new_callable=PropertyMock)
[docs]
def test_generate_thumbnail_name_map_empty(self, layers_mock):
layers_mock.return_value = []
map_name = thumbnails._generate_thumbnail_name(Map.objects.first())
self.assertIsNone(map_name, "Map name for maps without layers should return None.")
@patch("geonode.maps.models.Map.maplayers", new_callable=PropertyMock)
@patch("geonode.maps.models.Map.uuid", new_callable=PropertyMock)
[docs]
def test_generate_thumbnail_name_map(self, uuid_mock, layers_mock):
layers_mock.return_value = [MapLayer()]
uuid_mock.return_value = str(uuid.uuid4())
map_name = thumbnails._generate_thumbnail_name(Map.objects.first())
self.assertIsNotNone(
re.match(f"map-{self.re_uuid}-thumb.png", map_name, re.I), "Map name should meet a provided pattern"
)
[docs]
def test_generate_thumbnail_name_document(self):
doc = resource_manager.create(
None,
resource_type=Document,
defaults=dict(
doc_url="http://geonode.org/map.pdf",
owner=get_user_model().objects.get(username="admin"),
title="Test doc",
),
)
name = thumbnails._generate_thumbnail_name(doc)
self.assertIsNotNone(
re.match(f"document-{self.re_uuid}-thumb.png", name, re.I), "Document name should meet a provided pattern"
)
[docs]
def test_generate_thumbnail_name_geoapp(self):
geo_app = resource_manager.create(
None,
resource_type=GeoApp,
defaults=dict(
title="Test GeoApp",
owner=get_user_model().objects.get(username="admin"),
blob='{"test_data": {"test": ["test_1","test_2","test_3"]}}',
),
)
name = thumbnails._generate_thumbnail_name(geo_app)
self.assertIsNotNone(
re.match(f"geoapp-{self.re_uuid}-thumb.png", name, re.I), "GeoApp name should meet a provided pattern"
)
[docs]
def test_datasets_locations_dataset(self):
dataset = Dataset.objects.get(title_en="theaters_nyc")
locations, bbox = thumbnails._datasets_locations(dataset)
self.assertFalse(bbox, "Expected BBOX not to be calculated")
self.assertEqual(locations, [[settings.OGC_SERVER["default"]["LOCATION"], [dataset.alternate], []]])
[docs]
def test_datasets_locations_dataset_default_bbox(self):
expected_bbox = [-8238681.374829309, -8220320.783295829, 4969844.0930337105, 4984363.884452854, "EPSG:3857"]
dataset = Dataset.objects.get(title_en="theaters_nyc")
locations, bbox = thumbnails._datasets_locations(dataset, compute_bbox=True)
self.assertEqual(bbox[-1].upper(), "EPSG:3857", "Expected calculated BBOX CRS to be EPSG:3857")
self.assertEqual(bbox, expected_bbox, "Expected calculated BBOX to match pre-converted one.")
self.assertEqual(locations, [[settings.OGC_SERVER["default"]["LOCATION"], [dataset.alternate], []]])
[docs]
def test_datasets_locations_dataset_bbox(self):
dataset = Dataset.objects.get(title_en="theaters_nyc")
locations, bbox = thumbnails._datasets_locations(dataset, compute_bbox=True, target_crs="EPSG:4326")
self.assertEqual(bbox[0:4], dataset.bbox[0:4], "Expected calculated BBOX to match dataset's")
self.assertEqual(
bbox[-1].lower(), dataset.bbox[-1].lower(), "Expected calculated BBOX's CRS to match dataset's"
)
self.assertEqual(locations, [[settings.OGC_SERVER["default"]["LOCATION"], [dataset.alternate], []]])
[docs]
def test_datasets_locations_simple_map(self):
dataset = Dataset.objects.get(title_en="theaters_nyc")
map = Map.objects.get(title_en="theaters_nyc_map")
MapLayer(
map=map,
name="Meteorite_Landings_from_NASA_Open_Data_Portal1",
current_style="test_style",
ows_url="https://maps.geosolutionsgroup.com/geoserver/wms",
).save()
locations, bbox = thumbnails._datasets_locations(map)
self.assertFalse(bbox, "Expected BBOX not to be calculated")
self.assertEqual(
locations,
[
[
settings.OGC_SERVER["default"]["LOCATION"],
[dataset.alternate, "geonode:Meteorite_Landings_from_NASA_Open_Data_Portal1"],
["theaters_nyc", "test_style"],
]
],
)
[docs]
def test_datasets_locations_simple_map_default_bbox(self):
expected_bbox = [-8238681.374829309, -8220320.783295829, 4969844.0930337105, 4984363.884452854, "EPSG:3857"]
dataset = Dataset.objects.get(title_en="theaters_nyc")
map = Map.objects.get(title_en="theaters_nyc_map")
locations, bbox = thumbnails._datasets_locations(map, compute_bbox=True)
self.assertEqual(bbox[-1].upper(), "EPSG:3857", "Expected calculated BBOX CRS to be EPSG:3857")
self.assertEqual(bbox, expected_bbox, "Expected calculated BBOX to match pre-converted one.")
self.assertEqual(
locations, [[settings.OGC_SERVER["default"]["LOCATION"], [dataset.alternate], ["theaters_nyc"]]]
)
[docs]
def test_datasets_locations_composition_map_default_bbox(self):
expected_locations = [
[
settings.GEOSERVER_LOCATION,
[
"geonode:theaters_nyc",
"geonode:Meteorite_Landings_from_NASA_Open_Data_Portal1",
"rt_geologia.dbg_risorse_minerarie",
],
[],
]
]
map = Map.objects.get(title_en="composition_map")
locations, bbox = thumbnails._datasets_locations(map, compute_bbox=True)
self.assertEqual(bbox[-1].upper(), "EPSG:3857", "Expected calculated BBOX CRS to be EPSG:3857")
self.assertEqual(locations, expected_locations, "Expected calculated locations to match pre-computed.")
[docs]
def test_create_map_thumbnail_using_ll_bbox_polygon(self):
map = Map.objects.get(title_en="theaters_nyc_map")
with patch("geonode.thumbs.thumbnails._datasets_locations") as _mck:
_mck.return_value = [MagicMock(), MagicMock()]
if not map.ll_bbox_polygon:
thumbnails.create_thumbnail(map, overwrite=True)
_mck.assert_called_with(map, compute_bbox=True, target_crs="EPSG:3857")
ll_bbox_polygon = Polygon.from_bbox((0, 22, 0, 22))
map.ll_bbox_polygon = ll_bbox_polygon
map.save()
thumbnails.create_thumbnail(map, overwrite=True)
_mck.assert_called_with(map, compute_bbox=False, target_crs="EPSG:3857")