From 8387ed7bf2715b94881c7b76068c72f6e678900f Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 14:36:49 -0400 Subject: [PATCH 1/9] Smart tiles, faster rendering --- app/api/tiler.py | 210 +++++++++--------- app/api/urls.py | 4 +- .../app/js/components/LayersControlLayer.jsx | 3 +- app/static/app/js/components/Map.jsx | 5 + 4 files changed, 117 insertions(+), 105 deletions(-) diff --git a/app/api/tiler.py b/app/api/tiler.py index 13cc0136..03c69232 100644 --- a/app/api/tiler.py +++ b/app/api/tiler.py @@ -1,5 +1,4 @@ import json -import numpy import rio_tiler.utils from rasterio.enums import ColorInterp from rasterio.crs import CRS @@ -43,13 +42,13 @@ def get_zoom_safe(src_dst): def get_tile_url(task, tile_type, query_params): - url = '/api/projects/{}/tasks/{}/{}/tiles/{{z}}/{{x}}/{{y}}.png'.format(task.project.id, task.id, tile_type) + url = '/api/projects/{}/tasks/{}/{}/tiles/{{z}}/{{x}}/{{y}}'.format(task.project.id, task.id, tile_type) params = {} for k in ['formula', 'bands', 'rescale', 'color_map', 'hillshade']: if query_params.get(k): params[k] = query_params.get(k) - + if len(params) > 0: url = url + '?' + urllib.parse.urlencode(params) @@ -172,7 +171,7 @@ class Metadata(TaskNestedView): data, mask = src.preview(expression=expr, vrt_options={'cutline': boundaries_cutline}) else: data, mask = src.preview(expression=expr) - data = numpy.ma.array(data) + data = np.ma.array(data) data.mask = mask == 0 stats = { str(b + 1): raster_stats(data[b], percentiles=(pmin, pmax), bins=255, range=hrange) @@ -250,52 +249,18 @@ class Metadata(TaskNestedView): return Response(info) -def get_elevation_tiles(elevation, url, x, y, z, tilesize, nodata, resampling, padding): - tile = np.full((tilesize * 3, tilesize * 3), nodata, dtype=elevation.dtype) - with COGReader(url) as src: - try: - left, _discard_ = src.tile(x - 1, y, z, indexes=1, tilesize=tilesize, nodata=nodata, - resampling_method=resampling, padding=padding) - tile[tilesize:tilesize * 2, 0:tilesize] = left - except TileOutsideBounds: - pass - - try: - right, _discard_ = src.tile(x + 1, y, z, indexes=1, tilesize=tilesize, nodata=nodata, - resampling_method=resampling, padding=padding) - tile[tilesize:tilesize * 2, tilesize * 2:tilesize * 3] = right - except TileOutsideBounds: - pass - try: - bottom, _discard_ = src.tile(x, y + 1, z, indexes=1, tilesize=tilesize, nodata=nodata, - resampling_method=resampling, padding=padding) - tile[tilesize * 2:tilesize * 3, tilesize:tilesize * 2] = bottom - except TileOutsideBounds: - pass - try: - top, _discard_ = src.tile(x, y - 1, z, indexes=1, tilesize=tilesize, nodata=nodata, - resampling_method=resampling, padding=padding) - tile[0:tilesize, tilesize:tilesize * 2] = top - except TileOutsideBounds: - pass - tile[tilesize:tilesize * 2, tilesize:tilesize * 2] = elevation - return tile - - class Tiles(TaskNestedView): - def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y="", scale=1): + def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y="", scale=1, ext=None): """ Get a tile image """ task = self.get_and_check_task(request, pk) - + z = int(z) x = int(x) y = int(y) scale = int(scale) - ext = "png" - driver = "jpeg" if ext == "jpg" else ext indexes = None nodata = None @@ -306,6 +271,8 @@ class Tiles(TaskNestedView): rescale = self.request.query_params.get('rescale') color_map = self.request.query_params.get('color_map') hillshade = self.request.query_params.get('hillshade') + tilesize = self.request.query_params.get('size') + boundaries_feature = self.request.query_params.get('boundaries') if boundaries_feature == '': boundaries_feature = None @@ -320,6 +287,17 @@ class Tiles(TaskNestedView): if rescale == '': rescale = None if color_map == '': color_map = None if hillshade == '' or hillshade == '0': hillshade = None + if tilesize == '' or tilesize is None: tilesize = 256 + + try: + tilesize = int(tilesize) + if tilesize != 256 and tilesize != 512: + raise ValueError("Invalid size") + + if tilesize == 512: + z -= 1 + except ValueError: + raise exceptions.ValidationError(_("Invalid tile size parameter")) try: expr, _discard_ = lookup_formula(formula, bands) @@ -342,7 +320,7 @@ class Tiles(TaskNestedView): if nodata is not None: nodata = np.nan if nodata == "nan" else float(nodata) - tilesize = scale * 256 + tilesize = scale * tilesize url = get_raster_path(task, tile_type) if not os.path.isfile(url): raise exceptions.NotFound() @@ -351,7 +329,6 @@ class Tiles(TaskNestedView): if not src.tile_exists(z, x, y): raise exceptions.NotFound(_("Outside of bounds")) - with COGReader(url) as src: minzoom, maxzoom = get_zoom_safe(src) has_alpha = has_alpha_band(src.dataset) if z < minzoom - ZOOM_EXTRA_LEVELS or z > maxzoom + ZOOM_EXTRA_LEVELS: @@ -385,93 +362,122 @@ class Tiles(TaskNestedView): if nodata is None and tile_type == 'orthophoto': nodata = 0 - resampling = "nearest" - padding = 0 - if tile_type in ["dsm", "dtm"]: - resampling = "bilinear" - padding = 16 + resampling = "nearest" + padding = 0 + tile_buffer = None - try: - with COGReader(url) as src: + if tile_type in ["dsm", "dtm"]: + resampling = "bilinear" + padding = 16 + + # Hillshading is not a local tile operation and + # requires neighbor tiles to be rendered seamlessly + if hillshade is not None: + tile_buffer = tilesize + + try: if expr is not None: if boundaries_cutline is not None: tile = src.tile(x, y, z, expression=expr, tilesize=tilesize, nodata=nodata, padding=padding, + tile_buffer=tile_buffer, resampling_method=resampling, vrt_options={'cutline': boundaries_cutline}) else: tile = src.tile(x, y, z, expression=expr, tilesize=tilesize, nodata=nodata, padding=padding, + tile_buffer=tile_buffer, resampling_method=resampling) else: if boundaries_cutline is not None: tile = src.tile(x, y, z, tilesize=tilesize, nodata=nodata, padding=padding, + tile_buffer=tile_buffer, resampling_method=resampling, vrt_options={'cutline': boundaries_cutline}) else: tile = src.tile(x, y, z, indexes=indexes, tilesize=tilesize, nodata=nodata, - padding=padding, resampling_method=resampling) - - except TileOutsideBounds: - raise exceptions.NotFound(_("Outside of bounds")) - - if color_map: + padding=padding, + tile_buffer=tile_buffer, + resampling_method=resampling) + except TileOutsideBounds: + raise exceptions.NotFound(_("Outside of bounds")) + + if color_map: + try: + colormap.get(color_map) + except InvalidColorMapName: + raise exceptions.ValidationError(_("Not a valid color_map value")) + + intensity = None try: - colormap.get(color_map) - except InvalidColorMapName: - raise exceptions.ValidationError(_("Not a valid color_map value")) - - intensity = None - try: - rescale_arr = list(map(float, rescale.split(","))) - except ValueError: - raise exceptions.ValidationError(_("Invalid rescale value")) - - options = img_profiles.get(driver, {}) - if hillshade is not None: - try: - hillshade = float(hillshade) - if hillshade <= 0: - hillshade = 1.0 + rescale_arr = list(map(float, rescale.split(","))) except ValueError: - raise exceptions.ValidationError(_("Invalid hillshade value")) - if tile.data.shape[0] != 1: - raise exceptions.ValidationError( - _("Cannot compute hillshade of non-elevation raster (multiple bands found)")) - delta_scale = (maxzoom + ZOOM_EXTRA_LEVELS + 1 - z) * 4 - dx = src.dataset.meta["transform"][0] * delta_scale - dy = -src.dataset.meta["transform"][4] * delta_scale - ls = LightSource(azdeg=315, altdeg=45) - # Hillshading is not a local tile operation and - # requires neighbor tiles to be rendered seamlessly - elevation = get_elevation_tiles(tile.data[0], url, x, y, z, tilesize, nodata, resampling, padding) - intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade) - intensity = intensity[tilesize:tilesize * 2, tilesize:tilesize * 2] + raise exceptions.ValidationError(_("Invalid rescale value")) - if intensity is not None: - rgb = tile.post_process(in_range=(rescale_arr,)) - if colormap: - rgb, _discard_ = apply_cmap(rgb.data, colormap.get(color_map)) - if rgb.data.shape[0] != 3: - raise exceptions.ValidationError( - _("Cannot process tile: intensity image provided, but no RGB data was computed.")) - intensity = intensity * 255.0 - rgb = hsv_blend(rgb, intensity) - if rgb is not None: + # Auto? + if ext is None: + # Check for transparency + if np.equal(tile.mask, 255).all(): + ext = "jpg" + else: + ext = "png" + + driver = "jpeg" if ext == "jpg" else ext + + options = img_profiles.get(driver, {}) + if hillshade is not None: + try: + hillshade = float(hillshade) + if hillshade <= 0: + hillshade = 1.0 + except ValueError: + raise exceptions.ValidationError(_("Invalid hillshade value")) + if tile.data.shape[0] != 1: + raise exceptions.ValidationError( + _("Cannot compute hillshade of non-elevation raster (multiple bands found)")) + delta_scale = (maxzoom + ZOOM_EXTRA_LEVELS + 1 - z) * 4 + dx = src.dataset.meta["transform"][0] * delta_scale + dy = -src.dataset.meta["transform"][4] * delta_scale + ls = LightSource(azdeg=315, altdeg=45) + + # Remove elevation data from edge buffer tiles + # (to keep intensity uniform across tiles) + elevation = tile.data[0] + elevation[0:tilesize, 0:tilesize] = nodata + elevation[tilesize*2:tilesize*3, 0:tilesize] = nodata + elevation[0:tilesize, tilesize*2:tilesize*3] = nodata + elevation[tilesize*2:tilesize*3, tilesize*2:tilesize*3] = nodata + + intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade) + intensity = intensity[tilesize:tilesize * 2, tilesize:tilesize * 2] + + if intensity is not None: + rgb = tile.post_process(in_range=(rescale_arr,)) + rgb_data = rgb.data[:,tilesize:tilesize * 2, tilesize:tilesize * 2] + if colormap: + rgb, _discard_ = apply_cmap(rgb_data, colormap.get(color_map)) + if rgb.data.shape[0] != 3: + raise exceptions.ValidationError( + _("Cannot process tile: intensity image provided, but no RGB data was computed.")) + intensity = intensity * 255.0 + rgb = hsv_blend(rgb, intensity) + if rgb is not None: + mask = tile.mask[tilesize:tilesize * 2, tilesize:tilesize * 2] + return HttpResponse( + render(rgb, mask, img_format=driver, **options), + content_type="image/{}".format(ext) + ) + + if color_map is not None: return HttpResponse( - render(rgb, tile.mask, img_format=driver, **options), + tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, colormap=colormap.get(color_map), + **options), content_type="image/{}".format(ext) ) - if color_map is not None: return HttpResponse( - tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, colormap=colormap.get(color_map), - **options), + tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, **options), content_type="image/{}".format(ext) ) - return HttpResponse( - tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, **options), - content_type="image/{}".format(ext) - ) class Export(TaskNestedView): diff --git a/app/api/urls.py b/app/api/urls.py index f8c34f3f..e4c0adab 100644 --- a/app/api/urls.py +++ b/app/api/urls.py @@ -37,8 +37,8 @@ urlpatterns = [ url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles\.json$', TileJson.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/bounds$', Bounds.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/metadata$', Metadata.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)\.png$', Tiles.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)@(?P[\d]+)x\.png$', Tiles.as_view()), + url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)\.?(?Ppng|jpg)?$', Tiles.as_view()), + url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)@(?P[\d]+)x\.?(?Ppng|jpg)?$', Tiles.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm|georeferenced_model)/export$', Export.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/download/(?P.+)$', TaskDownloads.as_view()), diff --git a/app/static/app/js/components/LayersControlLayer.jsx b/app/static/app/js/components/LayersControlLayer.jsx index bd6bf231..36509ad5 100644 --- a/app/static/app/js/components/LayersControlLayer.jsx +++ b/app/static/app/js/components/LayersControlLayer.jsx @@ -192,7 +192,8 @@ export default class LayersControlLayer extends React.Component { formula, bands, hillshade, - rescale: this.rescale + rescale: this.rescale, + size: 512 }; } diff --git a/app/static/app/js/components/Map.jsx b/app/static/app/js/components/Map.jsx index 223e78ea..c15e42d9 100644 --- a/app/static/app/js/components/Map.jsx +++ b/app/static/app/js/components/Map.jsx @@ -138,6 +138,7 @@ class Map extends React.Component { // Build URL let tileUrl = mres.tiles[0]; + const TILESIZE = 512; // Set rescale if (statistics){ @@ -150,7 +151,10 @@ class Map extends React.Component { params["rescale"] = encodeURIComponent("-1,1"); } + params["size"] = TILESIZE; tileUrl = Utils.buildUrlWithQuery(tileUrl, params); + }else{ + tileUrl = Utils.buildUrlWithQuery(tileUrl, { size: TILESIZE }); } const layer = Leaflet.tileLayer(tileUrl, { @@ -158,6 +162,7 @@ class Map extends React.Component { minZoom: 0, maxZoom: maxzoom + 99, maxNativeZoom: maxzoom - 1, + tileSize: TILESIZE, tms: scheme === 'tms', opacity: this.state.opacity / 100, detectRetina: true From c2d99ec24e92ed28b971eca284914b83bdccc450 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 14:37:17 -0400 Subject: [PATCH 2/9] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index abaec53a..64f44fd5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "WebODM", - "version": "1.9.14", + "version": "1.9.15", "description": "User-friendly, extendable application and API for processing aerial imagery.", "main": "index.js", "scripts": { From ea4c491228fcdbcd7e78cef7cd928955a53555d5 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 14:49:20 -0400 Subject: [PATCH 3/9] webp support --- app/api/tiler.py | 5 ++++- app/api/urls.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/api/tiler.py b/app/api/tiler.py index 03c69232..b6f029a2 100644 --- a/app/api/tiler.py +++ b/app/api/tiler.py @@ -419,7 +419,10 @@ class Tiles(TaskNestedView): if np.equal(tile.mask, 255).all(): ext = "jpg" else: - ext = "png" + if 'image/webp' in request.headers.get('Accept', ''): + ext = "webp" + else: + ext = "png" driver = "jpeg" if ext == "jpg" else ext diff --git a/app/api/urls.py b/app/api/urls.py index e4c0adab..bebfccd4 100644 --- a/app/api/urls.py +++ b/app/api/urls.py @@ -37,8 +37,8 @@ urlpatterns = [ url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles\.json$', TileJson.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/bounds$', Bounds.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/metadata$', Metadata.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)\.?(?Ppng|jpg)?$', Tiles.as_view()), - url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)@(?P[\d]+)x\.?(?Ppng|jpg)?$', Tiles.as_view()), + url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)\.?(?Ppng|jpg|webp)?$', Tiles.as_view()), + url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm)/tiles/(?P[\d]+)/(?P[\d]+)/(?P[\d]+)@(?P[\d]+)x\.?(?Ppng|jpg|webp)?$', Tiles.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/(?Porthophoto|dsm|dtm|georeferenced_model)/export$', Export.as_view()), url(r'projects/(?P[^/.]+)/tasks/(?P[^/.]+)/download/(?P.+)$', TaskDownloads.as_view()), From e0eb80e256cb2b68034d207c754174194cb49c79 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 15:06:13 -0400 Subject: [PATCH 4/9] Fix test --- app/tests/test_api_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/test_api_task.py b/app/tests/test_api_task.py index d9da2118..01f42538 100644 --- a/app/tests/test_api_task.py +++ b/app/tests/test_api_task.py @@ -496,7 +496,7 @@ class TestApiTask(BootTransactionTestCase): self.assertEqual(metadata['scheme'], 'xyz') # Tiles URL has no extra params - self.assertTrue(metadata['tiles'][0].endswith('.png')) + self.assertTrue(metadata['tiles'][0].endswith('{z}/{x}/{y}')) # Histogram stats are available (3 bands for orthophoto) self.assertTrue(len(metadata['statistics']) == 3) From 2bcef41ce593501676f792b6319b9b9a004cce13 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 15:10:46 -0400 Subject: [PATCH 5/9] Update locales --- locale | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale b/locale index 3a5fd449..4e01fc1c 160000 --- a/locale +++ b/locale @@ -1 +1 @@ -Subproject commit 3a5fd44968553977618328d06a9b9bd7c0cfd2ba +Subproject commit 4e01fc1ccea78e41494191e082997000ee032c06 From e811803271fc4eb9c83114c1fd8a04ef5f026fd8 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 15:49:25 -0400 Subject: [PATCH 6/9] Some tests --- app/tests/test_api_task.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/app/tests/test_api_task.py b/app/tests/test_api_task.py index ff2ea0a2..fe76b250 100644 --- a/app/tests/test_api_task.py +++ b/app/tests/test_api_task.py @@ -647,7 +647,8 @@ class TestApiTask(BootTransactionTestCase): ("dtm", "hillshade=0", status.HTTP_200_OK), ("orthophoto", "hillshade=3", status.HTTP_400_BAD_REQUEST), - + + ("orthophoto", "", status.HTTP_200_OK), ("orthophoto", "formula=NDVI&bands=RGN", status.HTTP_200_OK), ("orthophoto", "formula=VARI&bands=RGN", status.HTTP_400_BAD_REQUEST), ("orthophoto", "formula=VARI&bands=RGB", status.HTTP_200_OK), @@ -673,9 +674,32 @@ class TestApiTask(BootTransactionTestCase): params.append(("orthophoto", "formula={}&bands={}&color_map=rdylgn".format(k, f), status.HTTP_200_OK)) for tile_type, url, sc in params: - res = client.get("/api/projects/{}/tasks/{}/{}/tiles/{}.png?{}".format(project.id, task.id, tile_type, tile_path[tile_type], url)) + res = client.get("/api/projects/{}/tasks/{}/{}/tiles/{}?{}".format(project.id, task.id, tile_type, tile_path[tile_type], url)) self.assertEqual(res.status_code, sc) - + + # Can request PNG/JPG/WEBP tiles explicitely + for ext in ["png", "jpg", "webp"]: + res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.{}".format(project.id, task.id, tile_path['orthophoto'], ext)) + self.assertEqual(res.status_code, status.HTTP_200_OK) + self.assertEqual(res.get('content-type') == 'image/%s' % ext) + + # Size is 256 by default + res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.png".format(project.id, task.id, tile_path['orthophoto'])) + with Image.open(io.BytesIO(res.content)) as i: + self.assertEqual(i.width, 256) + self.assertEqual(i.height, 256) + + # Can request 512 tiles + res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.png?size=512".format(project.id, task.id, tile_path['orthophoto'])) + with Image.open(io.BytesIO(res.content)) as i: + self.assertEqual(i.width, 512) + self.assertEqual(i.height, 512) + + # Cannot request invalid tiles sizes + for s in ["1024", "abc", "-1"]: + res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.png?size={}".format(project.id, task.id, tile_path['orthophoto'], s)) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + # Another user does not have access to the resources other_client = APIClient() other_client.login(username="testuser2", password="test1234") From 3fac4d412c5576151c04256f64c182454e2fcd12 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 15:51:14 -0400 Subject: [PATCH 7/9] Fix test --- app/tests/test_api_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/test_api_task.py b/app/tests/test_api_task.py index fe76b250..07d4f3b6 100644 --- a/app/tests/test_api_task.py +++ b/app/tests/test_api_task.py @@ -681,7 +681,7 @@ class TestApiTask(BootTransactionTestCase): for ext in ["png", "jpg", "webp"]: res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.{}".format(project.id, task.id, tile_path['orthophoto'], ext)) self.assertEqual(res.status_code, status.HTTP_200_OK) - self.assertEqual(res.get('content-type') == 'image/%s' % ext) + self.assertEqual(res.get('content-type'), "image/" + ext) # Size is 256 by default res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.png".format(project.id, task.id, tile_path['orthophoto'])) From 6d3c92ad9bfbbb714b513913904c2ce1d84fde5d Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 15:57:13 -0400 Subject: [PATCH 8/9] Fix tile path test --- app/tests/test_api_task.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/tests/test_api_task.py b/app/tests/test_api_task.py index 07d4f3b6..256b766a 100644 --- a/app/tests/test_api_task.py +++ b/app/tests/test_api_task.py @@ -554,6 +554,9 @@ class TestApiTask(BootTransactionTestCase): 'dsm': '18/64083/92370', 'dtm': '18/64083/92370' } + tile_path_512 = { + 'orthophoto': '18/32042/46185' + } # Metadata for DSM/DTM for tile_type in ['dsm', 'dtm']: @@ -690,7 +693,7 @@ class TestApiTask(BootTransactionTestCase): self.assertEqual(i.height, 256) # Can request 512 tiles - res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.png?size=512".format(project.id, task.id, tile_path['orthophoto'])) + res = client.get("/api/projects/{}/tasks/{}/orthophoto/tiles/{}.png?size=512".format(project.id, task.id, tile_path_512['orthophoto'])) with Image.open(io.BytesIO(res.content)) as i: self.assertEqual(i.width, 512) self.assertEqual(i.height, 512) From d4938f1d0dedaf6894af50fcab5dce2f374cdec0 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 8 Jul 2022 16:02:24 -0400 Subject: [PATCH 9/9] Fix #1196 --- app/static/app/js/components/TaskListItem.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/static/app/js/components/TaskListItem.jsx b/app/static/app/js/components/TaskListItem.jsx index 069cda7d..b8296ef7 100644 --- a/app/static/app/js/components/TaskListItem.jsx +++ b/app/static/app/js/components/TaskListItem.jsx @@ -438,12 +438,12 @@ class TaskListItem extends React.Component { }; if (task.status === statusCodes.COMPLETED){ - if (task.available_assets.indexOf("orthophoto.tif") !== -1){ + if (task.available_assets.indexOf("orthophoto.tif") !== -1 || task.available_assets.indexOf("dsm.tif") !== -1){ addActionButton(" " + _("View Map"), "btn-primary", "fa fa-globe", () => { location.href = `/map/project/${task.project}/task/${task.id}/`; }); }else{ - showOrthophotoMissingWarning = true; + showOrthophotoMissingWarning = task.available_assets.indexOf("orthophoto.tif") === -1; } addActionButton(" " + _("View 3D Model"), "btn-primary", "fa fa-cube", () => {