kopia lustrzana https://github.com/OpenDroneMap/WebODM
KMZ export support
rodzic
dd3b413401
commit
8054b650f9
|
@ -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)
|
tile = np.full((tilesize * 3, tilesize * 3), nodata, dtype=elevation.dtype)
|
||||||
with COGReader(url) as src:
|
with COGReader(url) as src:
|
||||||
try:
|
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)
|
resampling_method=resampling, padding=padding)
|
||||||
tile[tilesize:tilesize * 2, 0:tilesize] = left
|
tile[tilesize:tilesize * 2, 0:tilesize] = left
|
||||||
except TileOutsideBounds:
|
except TileOutsideBounds:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
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)
|
resampling_method=resampling, padding=padding)
|
||||||
tile[tilesize:tilesize * 2, tilesize * 2:tilesize * 3] = right
|
tile[tilesize:tilesize * 2, tilesize * 2:tilesize * 3] = right
|
||||||
except TileOutsideBounds:
|
except TileOutsideBounds:
|
||||||
pass
|
pass
|
||||||
try:
|
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)
|
resampling_method=resampling, padding=padding)
|
||||||
tile[tilesize * 2:tilesize * 3, tilesize:tilesize * 2] = bottom
|
tile[tilesize * 2:tilesize * 3, tilesize:tilesize * 2] = bottom
|
||||||
except TileOutsideBounds:
|
except TileOutsideBounds:
|
||||||
pass
|
pass
|
||||||
try:
|
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)
|
resampling_method=resampling, padding=padding)
|
||||||
tile[0:tilesize, tilesize:tilesize * 2] = top
|
tile[0:tilesize, tilesize:tilesize * 2] = top
|
||||||
except TileOutsideBounds:
|
except TileOutsideBounds:
|
||||||
|
@ -314,7 +314,7 @@ class Tiles(TaskNestedView):
|
||||||
if hillshade == '' or hillshade == '0': hillshade = None
|
if hillshade == '' or hillshade == '0': hillshade = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
expr, _ = lookup_formula(formula, bands)
|
expr, _discard_ = lookup_formula(formula, bands)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exceptions.ValidationError(str(e))
|
raise exceptions.ValidationError(str(e))
|
||||||
|
|
||||||
|
@ -442,7 +442,7 @@ class Tiles(TaskNestedView):
|
||||||
if intensity is not None:
|
if intensity is not None:
|
||||||
rgb = tile.post_process(in_range=(rescale_arr,))
|
rgb = tile.post_process(in_range=(rescale_arr,))
|
||||||
if colormap:
|
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:
|
if rgb.data.shape[0] != 3:
|
||||||
raise exceptions.ValidationError(
|
raise exceptions.ValidationError(
|
||||||
_("Cannot process tile: intensity image provided, but no RGB data was computed."))
|
_("Cannot process tile: intensity image provided, but no RGB data was computed."))
|
||||||
|
@ -486,21 +486,23 @@ class Export(TaskNestedView):
|
||||||
if bands == '': bands = None
|
if bands == '': bands = None
|
||||||
if rescale == '': rescale = None
|
if rescale == '': rescale = None
|
||||||
if epsg == '': epsg = None
|
if epsg == '': epsg = None
|
||||||
|
if color_map == '': color_map = None
|
||||||
|
if hillshade == '': hillshade = None
|
||||||
|
|
||||||
expr = None
|
expr = None
|
||||||
|
|
||||||
if not export_format in ['gtiff', 'gtiff-rgb', 'jpg', 'png']:
|
if not export_format in ['gtiff', 'gtiff-rgb', 'jpg', 'png', 'kmz']:
|
||||||
raise exceptions.ValidationError(_("Unsupported format: %(format)") % {'format': export_format})
|
raise exceptions.ValidationError(_("Unsupported format: %(value)s") % {'value': export_format})
|
||||||
|
|
||||||
if epsg is not None:
|
if epsg is not None:
|
||||||
try:
|
try:
|
||||||
epsg = int(epsg)
|
epsg = int(epsg)
|
||||||
except ValueError:
|
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:
|
if formula and bands:
|
||||||
try:
|
try:
|
||||||
expr, _ = lookup_formula(formula, bands)
|
expr, _discard_ = lookup_formula(formula, bands)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exceptions.ValidationError(str(e))
|
raise exceptions.ValidationError(str(e))
|
||||||
|
|
||||||
|
@ -516,7 +518,7 @@ class Export(TaskNestedView):
|
||||||
try:
|
try:
|
||||||
rescale = list(map(float, rescale.split(",")))
|
rescale = list(map(float, rescale.split(",")))
|
||||||
except ValueError:
|
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:
|
if hillshade is not None:
|
||||||
try:
|
try:
|
||||||
|
@ -524,7 +526,7 @@ class Export(TaskNestedView):
|
||||||
if hillshade < 0:
|
if hillshade < 0:
|
||||||
raise Exception("Hillshade must be > 0")
|
raise Exception("Hillshade must be > 0")
|
||||||
except:
|
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)
|
url = get_raster_path(task, asset_type)
|
||||||
|
|
||||||
|
@ -549,5 +551,7 @@ class Export(TaskNestedView):
|
||||||
rescale=rescale,
|
rescale=rescale,
|
||||||
color_map=color_map,
|
color_map=color_map,
|
||||||
hillshade=hillshade,
|
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})
|
return Response({'celery_task_id': celery_task_id, 'filename': filename})
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Export a raster index after applying a band expression
|
# Export a raster index after applying a band expression
|
||||||
import rasterio
|
import rasterio
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import numexpr as ne
|
import numexpr as ne
|
||||||
from rasterio.enums import ColorInterp
|
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.hsvblend import hsv_blend
|
||||||
from app.api.hillshade import LightSource
|
from app.api.hillshade import LightSource
|
||||||
from rasterio.warp import calculate_default_transform, reproject, Resampling
|
from rasterio.warp import calculate_default_transform, reproject, Resampling
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('app.logger')
|
logger = logging.getLogger('app.logger')
|
||||||
|
|
||||||
|
@ -21,7 +23,8 @@ def extension_for_export_format(export_format):
|
||||||
'gtiff': 'tif',
|
'gtiff': 'tif',
|
||||||
'gtiff-rgb': 'tif',
|
'gtiff-rgb': 'tif',
|
||||||
'jpg': 'jpg',
|
'jpg': 'jpg',
|
||||||
'png': 'png'
|
'png': 'png',
|
||||||
|
'kmz': 'kmz'
|
||||||
}
|
}
|
||||||
return extensions.get(export_format, 'tif')
|
return extensions.get(export_format, 'tif')
|
||||||
|
|
||||||
|
@ -33,6 +36,8 @@ def export_raster(input, output, **opts):
|
||||||
color_map = opts.get('color_map')
|
color_map = opts.get('color_map')
|
||||||
hillshade = opts.get('hillshade')
|
hillshade = opts.get('hillshade')
|
||||||
dem = opts.get('dem')
|
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:
|
with rasterio.open(input) as src:
|
||||||
profile = src.meta.copy()
|
profile = src.meta.copy()
|
||||||
|
@ -43,6 +48,18 @@ def export_raster(input, output, **opts):
|
||||||
with_alpha = True
|
with_alpha = True
|
||||||
rgb = False
|
rgb = False
|
||||||
indexes = src.indexes
|
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":
|
if export_format == "jpg":
|
||||||
driver = "JPEG"
|
driver = "JPEG"
|
||||||
|
@ -95,7 +112,7 @@ def export_raster(input, output, **opts):
|
||||||
if not skip_rescale and rescale is not None:
|
if not skip_rescale and rescale is not None:
|
||||||
arr = linear_rescale(arr, in_range=rescale)
|
arr = linear_rescale(arr, in_range=rescale)
|
||||||
if not skip_alpha and not with_alpha:
|
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:
|
if not skip_type and rgb and arr.dtype != np.uint8:
|
||||||
arr = arr.astype(np.uint8)
|
arr = arr.astype(np.uint8)
|
||||||
|
|
||||||
|
@ -173,7 +190,7 @@ def export_raster(input, output, **opts):
|
||||||
# Make sure this is float32
|
# Make sure this is float32
|
||||||
arr = arr.astype(np.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?
|
# Apply colormap?
|
||||||
if rgb and cmap is not None:
|
if rgb and cmap is not None:
|
||||||
rgb_data, _ = apply_cmap(process(arr, skip_alpha=True), cmap)
|
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)
|
write_band(process(arr)[0], dst, 1)
|
||||||
elif dem:
|
elif dem:
|
||||||
# Apply hillshading, colormaps to elevation
|
# 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()
|
arr = src.read()
|
||||||
|
|
||||||
intensity = None
|
intensity = None
|
||||||
|
@ -221,7 +238,7 @@ def export_raster(input, output, **opts):
|
||||||
write_band(process(arr)[0], dst, 1)
|
write_band(process(arr)[0], dst, 1)
|
||||||
else:
|
else:
|
||||||
# Copy bands as-is
|
# Copy bands as-is
|
||||||
with rasterio.open(output, 'w', **profile) as dst:
|
with rasterio.open(output_raster, 'w', **profile) as dst:
|
||||||
band_num = 1
|
band_num = 1
|
||||||
for idx in indexes:
|
for idx in indexes:
|
||||||
ci = src.colorinterp[idx - 1]
|
ci = src.colorinterp[idx - 1]
|
||||||
|
@ -234,3 +251,8 @@ def export_raster(input, output, **opts):
|
||||||
else:
|
else:
|
||||||
write_band(process(arr), dst, band_num)
|
write_band(process(arr), dst, band_num)
|
||||||
band_num += 1
|
band_num += 1
|
||||||
|
|
||||||
|
if kmz:
|
||||||
|
subprocess.check_output(["gdal_translate", "-of", "KMLSUPEROVERLAY",
|
||||||
|
"-co", "Name={}".format(name),
|
||||||
|
"-co", "FORMAT=JPEG", output_raster, output])
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.core.exceptions import SuspiciousFileOperation
|
from django.core.exceptions import SuspiciousFileOperation
|
||||||
|
from shlex import _find_unsafe
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def path_traversal_check(unsafe_path, known_safe_path):
|
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))
|
raise SuspiciousFileOperation("{} is not safe".format(unsafe_path))
|
||||||
|
|
||||||
# Passes the check
|
# 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('"', '\\\"') + '"'
|
|
@ -9,7 +9,7 @@ import Workers from '../classes/Workers';
|
||||||
|
|
||||||
export default class ExportAssetPanel extends React.Component {
|
export default class ExportAssetPanel extends React.Component {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
exportFormats: ["gtiff-rgb", "gtiff", "jpg", "png"],
|
exportFormats: ["gtiff-rgb", "gtiff", "jpg", "png", "kmz"],
|
||||||
asset: "",
|
asset: "",
|
||||||
exportParams: {},
|
exportParams: {},
|
||||||
task: null,
|
task: null,
|
||||||
|
@ -45,6 +45,10 @@ export default class ExportAssetPanel extends React.Component {
|
||||||
'png': {
|
'png': {
|
||||||
label: _("PNG (RGB)"),
|
label: _("PNG (RGB)"),
|
||||||
icon: "fas fa-palette"
|
icon: "fas fa-palette"
|
||||||
|
},
|
||||||
|
'kmz': {
|
||||||
|
label: _("KMZ (RGB)"),
|
||||||
|
icon: "fa fa-globe"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue