KMZ export support

pull/1086/head
Piero Toffanin 2021-11-02 18:15:51 -04:00
rodzic dd3b413401
commit 8054b650f9
4 zmienionych plików z 64 dodań i 21 usunięć

Wyświetl plik

@ -246,26 +246,26 @@ def get_elevation_tiles(elevation, url, x, y, z, tilesize, nodata, resampling, p
tile = np.full((tilesize * 3, tilesize * 3), nodata, dtype=elevation.dtype)
with COGReader(url) as src:
try:
left, _ = src.tile(x - 1, y, z, indexes=1, tilesize=tilesize, nodata=nodata,
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, _ = src.tile(x + 1, y, z, indexes=1, tilesize=tilesize, nodata=nodata,
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, _ = src.tile(x, y + 1, z, indexes=1, tilesize=tilesize, nodata=nodata,
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, _ = src.tile(x, y - 1, z, indexes=1, tilesize=tilesize, nodata=nodata,
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:
@ -314,7 +314,7 @@ class Tiles(TaskNestedView):
if hillshade == '' or hillshade == '0': hillshade = None
try:
expr, _ = lookup_formula(formula, bands)
expr, _discard_ = lookup_formula(formula, bands)
except ValueError as e:
raise exceptions.ValidationError(str(e))
@ -442,7 +442,7 @@ class Tiles(TaskNestedView):
if intensity is not None:
rgb = tile.post_process(in_range=(rescale_arr,))
if colormap:
rgb, _ = apply_cmap(rgb.data, colormap.get(color_map))
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."))
@ -486,21 +486,23 @@ class Export(TaskNestedView):
if bands == '': bands = None
if rescale == '': rescale = None
if epsg == '': epsg = None
if color_map == '': color_map = None
if hillshade == '': hillshade = None
expr = None
if not export_format in ['gtiff', 'gtiff-rgb', 'jpg', 'png']:
raise exceptions.ValidationError(_("Unsupported format: %(format)") % {'format': export_format})
if not export_format in ['gtiff', 'gtiff-rgb', 'jpg', 'png', 'kmz']:
raise exceptions.ValidationError(_("Unsupported format: %(value)s") % {'value': export_format})
if epsg is not None:
try:
epsg = int(epsg)
except ValueError:
raise exception.ValidationError(_("Invalid EPSG code: %(epsg)") % {'epsg': epsg})
raise exceptions.ValidationError(_("Invalid EPSG code: %(value)s") % {'value': epsg})
if formula and bands:
try:
expr, _ = lookup_formula(formula, bands)
expr, _discard_ = lookup_formula(formula, bands)
except ValueError as e:
raise exceptions.ValidationError(str(e))
@ -516,7 +518,7 @@ class Export(TaskNestedView):
try:
rescale = list(map(float, rescale.split(",")))
except ValueError:
raise exception.ValidationError(_("Invalid rescale value: %(value)") % {'value': rescale})
raise exceptions.ValidationError(_("Invalid rescale value: %(value)s") % {'value': rescale})
if hillshade is not None:
try:
@ -524,7 +526,7 @@ class Export(TaskNestedView):
if hillshade < 0:
raise Exception("Hillshade must be > 0")
except:
raise exception.ValidationError(_("Invalid hillshade value: %(value)") % {'value': hillshade})
raise exceptions.ValidationError(_("Invalid hillshade value: %(value)s") % {'value': hillshade})
url = get_raster_path(task, asset_type)
@ -549,5 +551,7 @@ class Export(TaskNestedView):
rescale=rescale,
color_map=color_map,
hillshade=hillshade,
dem=asset_type in ['dsm', 'dtm']).task_id
dem=asset_type in ['dsm', 'dtm'],
name=task.name,
asset_type=asset_type).task_id
return Response({'celery_task_id': celery_task_id, 'filename': filename})

Wyświetl plik

@ -1,6 +1,9 @@
# Export a raster index after applying a band expression
import rasterio
import re
import logging
import os
import subprocess
import numpy as np
import numexpr as ne
from rasterio.enums import ColorInterp
@ -10,7 +13,6 @@ from rio_tiler.errors import InvalidColorMapName
from app.api.hsvblend import hsv_blend
from app.api.hillshade import LightSource
from rasterio.warp import calculate_default_transform, reproject, Resampling
import logging
logger = logging.getLogger('app.logger')
@ -21,7 +23,8 @@ def extension_for_export_format(export_format):
'gtiff': 'tif',
'gtiff-rgb': 'tif',
'jpg': 'jpg',
'png': 'png'
'png': 'png',
'kmz': 'kmz'
}
return extensions.get(export_format, 'tif')
@ -33,6 +36,8 @@ def export_raster(input, output, **opts):
color_map = opts.get('color_map')
hillshade = opts.get('hillshade')
dem = opts.get('dem')
name = opts.get('name', 'raster') # KMZ specific
asset_type = opts.get('asset_type', 'raster') # KMZ specific
with rasterio.open(input) as src:
profile = src.meta.copy()
@ -43,6 +48,18 @@ def export_raster(input, output, **opts):
with_alpha = True
rgb = False
indexes = src.indexes
output_raster = output
jpg_background = 255 # white
# KMZ is special, we just export it as jpg with EPSG:4326
# and then call GDAL to tile/package it
kmz = export_format == "kmz"
if kmz:
export_format = "jpg"
epsg = 4326
path_base, _ = os.path.splitext(output)
output_raster = path_base + ".jpg"
jpg_background = 0 # black
if export_format == "jpg":
driver = "JPEG"
@ -95,7 +112,7 @@ def export_raster(input, output, **opts):
if not skip_rescale and rescale is not None:
arr = linear_rescale(arr, in_range=rescale)
if not skip_alpha and not with_alpha:
arr[mask==0] = 255 # Set white background
arr[mask==0] = jpg_background
if not skip_type and rgb and arr.dtype != np.uint8:
arr = arr.astype(np.uint8)
@ -173,7 +190,7 @@ def export_raster(input, output, **opts):
# Make sure this is float32
arr = arr.astype(np.float32)
with rasterio.open(output, 'w', **profile) as dst:
with rasterio.open(output_raster, 'w', **profile) as dst:
# Apply colormap?
if rgb and cmap is not None:
rgb_data, _ = apply_cmap(process(arr, skip_alpha=True), cmap)
@ -190,7 +207,7 @@ def export_raster(input, output, **opts):
write_band(process(arr)[0], dst, 1)
elif dem:
# Apply hillshading, colormaps to elevation
with rasterio.open(output, 'w', **profile) as dst:
with rasterio.open(output_raster, 'w', **profile) as dst:
arr = src.read()
intensity = None
@ -221,7 +238,7 @@ def export_raster(input, output, **opts):
write_band(process(arr)[0], dst, 1)
else:
# Copy bands as-is
with rasterio.open(output, 'w', **profile) as dst:
with rasterio.open(output_raster, 'w', **profile) as dst:
band_num = 1
for idx in indexes:
ci = src.colorinterp[idx - 1]
@ -234,3 +251,8 @@ def export_raster(input, output, **opts):
else:
write_band(process(arr), dst, band_num)
band_num += 1
if kmz:
subprocess.check_output(["gdal_translate", "-of", "KMLSUPEROVERLAY",
"-co", "Name={}".format(name),
"-co", "FORMAT=JPEG", output_raster, output])

Wyświetl plik

@ -1,4 +1,5 @@
from django.core.exceptions import SuspiciousFileOperation
from shlex import _find_unsafe
import os
def path_traversal_check(unsafe_path, known_safe_path):
@ -9,4 +10,16 @@ def path_traversal_check(unsafe_path, known_safe_path):
raise SuspiciousFileOperation("{} is not safe".format(unsafe_path))
# Passes the check
return unsafe_path
return unsafe_path
def double_quote(s):
"""Return a shell-escaped version of the string *s*."""
if not s:
return '""'
if _find_unsafe(s) is None:
return s
# use double quotes, and prefix double quotes with a \
# the string $"b is then quoted as "$\"b"
return '"' + s.replace('"', '\\\"') + '"'

Wyświetl plik

@ -9,7 +9,7 @@ import Workers from '../classes/Workers';
export default class ExportAssetPanel extends React.Component {
static defaultProps = {
exportFormats: ["gtiff-rgb", "gtiff", "jpg", "png"],
exportFormats: ["gtiff-rgb", "gtiff", "jpg", "png", "kmz"],
asset: "",
exportParams: {},
task: null,
@ -45,6 +45,10 @@ export default class ExportAssetPanel extends React.Component {
'png': {
label: _("PNG (RGB)"),
icon: "fas fa-palette"
},
'kmz': {
label: _("KMZ (RGB)"),
icon: "fa fa-globe"
}
};