COG streaming API (WIP)

pull/746/head
Piero Toffanin 2019-11-05 15:47:29 -05:00
rodzic a4a6f273be
commit 6479d564ba
9 zmienionych plików z 189 dodań i 47 usunięć

Wyświetl plik

@ -286,18 +286,18 @@ class TaskNestedView(APIView):
return task
class TaskTiles(TaskNestedView):
def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y=""):
"""
Get a tile image
"""
task = self.get_and_check_task(request, pk)
tile_path = task.get_tile_path(tile_type, z, x, y)
if os.path.isfile(tile_path):
tile = open(tile_path, "rb")
return HttpResponse(FileWrapper(tile), content_type="image/png")
else:
raise exceptions.NotFound()
# class TaskTiles(TaskNestedView):
# def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y=""):
# """
# Get a tile image
# """
# task = self.get_and_check_task(request, pk)
# tile_path = task.get_tile_path(tile_type, z, x, y)
# if os.path.isfile(tile_path):
# tile = open(tile_path, "rb")
# return HttpResponse(FileWrapper(tile), content_type="image/png")
# else:
# raise exceptions.NotFound()
def download_file_response(request, filePath, content_disposition):

Wyświetl plik

@ -1,10 +1,71 @@
import rasterio
from django.http import HttpResponse
from rio_tiler.errors import TileOutsideBounds
from rio_tiler.mercator import get_zooms
from rio_tiler import main
from rio_tiler.utils import array_to_image, get_colormap, expression, linear_rescale, _chunks
from rio_color.operations import parse_operations
from rio_color.utils import scale_dtype, to_math_type
from rio_tiler.profiles import img_profiles
import numpy
from .tasks import TaskNestedView
from rest_framework import exceptions
from rest_framework.response import Response
def get_tile_url(task, tile_type):
return '/api/projects/{}/tasks/{}/{}/tiles/{{z}}/{{x}}/{{y}}.png'.format(task.project.id, task.id, tile_type)
def get_extent(task, tile_type):
extent_map = {
'orthophoto': task.orthophoto_extent,
'dsm': task.dsm_extent,
'dtm': task.dtm_extent,
}
if not tile_type in extent_map:
raise exceptions.ValidationError("Type {} is not a valid tile type".format(tile_type))
extent = extent_map[tile_type]
if extent is None:
raise exceptions.ValidationError(
"A {} has not been processed for this task. Tiles are not available.".format(tile_type))
return extent.extent
def get_raster_path(task, tile_type):
return task.get_asset_download_path(tile_type + ".tif")
def postprocess(tile, mask, rescale = None, color_formula = None):
if rescale:
rescale_arr = list(map(float, rescale.split(",")))
rescale_arr = list(_chunks(rescale_arr, 2))
if len(rescale_arr) != tile.shape[0]:
rescale_arr = ((rescale_arr[0]),) * tile.shape[0]
for bdx in range(tile.shape[0]):
tile[bdx] = numpy.where(
mask,
linear_rescale(
tile[bdx], in_range=rescale_arr[bdx], out_range=[0, 255]
),
0,
)
tile = tile.astype(numpy.uint8)
if color_formula:
# make sure one last time we don't have
# negative value before applying color formula
tile[tile < 0] = 0
for ops in parse_operations(color_formula):
tile = scale_dtype(ops(to_math_type(tile)), numpy.uint8)
return tile, mask
class TileJson(TaskNestedView):
def get(self, request, pk=None, project_pk=None, tile_type=""):
"""
@ -12,21 +73,7 @@ class TileJson(TaskNestedView):
"""
task = self.get_and_check_task(request, pk)
extent_map = {
'orthophoto': task.orthophoto_extent,
'dsm': task.dsm_extent,
'dtm': task.dtm_extent,
}
if not tile_type in extent_map:
raise exceptions.ValidationError("Type {} is not a valid tile type".format(tile_type))
extent = extent_map[tile_type]
if extent is None:
raise exceptions.ValidationError("A {} has not been processed for this task. Tiles are not available.".format(tile_type))
raster_path = task.get_asset_download_path(tile_type + ".tif")
raster_path = get_raster_path(task, tile_type)
with rasterio.open(raster_path) as src_dst:
minzoom, maxzoom = get_zooms(src_dst)
@ -34,13 +81,91 @@ class TileJson(TaskNestedView):
'tilejson': '2.1.0',
'name': task.name,
'version': '1.0.0',
'scheme': 'tms',
'tiles': ['/api/projects/{}/tasks/{}/{}/tiles/{{z}}/{{x}}/{{y}}.png'.format(task.project.id, task.id, tile_type)],
'scheme': 'xyz',
'tiles': [get_tile_url(task, tile_type)],
'minzoom': minzoom,
'maxzoom': maxzoom,
'bounds': extent.extent
'bounds': get_extent(task, tile_type)
})
class Bounds(TaskNestedView):
def get(self, request, pk=None, project_pk=None, tile_type=""):
"""
Get the bounds for this tasks's asset type
"""
task = self.get_and_check_task(request, pk)
return Response({
'url': get_tile_url(task, tile_type),
'bounds': get_extent(task, tile_type)
})
class Metadata(TaskNestedView):
def get(self, request, pk=None, project_pk=None, tile_type=""):
"""
Get the metadata for this tasks's asset type
"""
task = self.get_and_check_task(request, pk)
raster_path = get_raster_path(task, tile_type)
info = main.metadata(raster_path, pmin=2.0, pmax=98.0)
info['address'] = get_tile_url(task, tile_type)
return Response(info)
class Tiles(TaskNestedView):
def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y="", scale=1):
"""
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 = self.request.query_params.get('indexes')
expr = self.request.query_params.get('expr')
rescale = self.request.query_params.get('rescale')
color_formula = self.request.query_params.get('color_formula')
color_map = self.request.query_params.get('color_map')
nodata = self.request.query_params.get('nodata')
if tile_type in ['dsm', 'dtm'] and rescale is None:
#raise exceptions.ValidationError("Cannot get tiles without rescale parameter. Add ?rescale=min,max to the URL.")
if rescale is None:
rescale = '157.0500,164.850'
if nodata is not None:
nodata = numpy.nan if nodata == "nan" else float(nodata)
tilesize = scale * 256
url = get_raster_path(task, tile_type)
try:
if expr is not None:
tile, mask = expression(
url, x, y, z, expr=expr, tilesize=tilesize, nodata=nodata
)
else:
tile, mask = main.tile(
url, x, y, z, indexes=indexes, tilesize=tilesize, nodata=nodata
)
except TileOutsideBounds:
raise exceptions.NotFound("Outside of bounds")
rtile, rmask = postprocess(
tile, mask, rescale=rescale, color_formula=color_formula
)
if color_map:
color_map = get_colormap(color_map, format="gdal")
options = img_profiles.get(driver, {})
return HttpResponse(
array_to_image(rtile, rmask, img_format=driver, color_map=color_map, **options),
content_type="image/{}".format(ext)
)

Wyświetl plik

@ -3,12 +3,12 @@ from django.conf.urls import url, include
from app.api.presets import PresetViewSet
from app.plugins import get_api_url_patterns
from .projects import ProjectViewSet
from .tasks import TaskViewSet, TaskTiles, TaskDownloads, TaskAssets, TaskAssetsImport
from .tasks import TaskViewSet, TaskDownloads, TaskAssets, TaskAssetsImport
from .processingnodes import ProcessingNodeViewSet, ProcessingNodeOptionsView
from .admin import UserViewSet, GroupViewSet
from rest_framework_nested import routers
from rest_framework_jwt.views import obtain_jwt_token
from .tiler import TileJson
from .tiler import TileJson, Bounds, Metadata, Tiles
router = routers.DefaultRouter()
router.register(r'projects', ProjectViewSet)
@ -30,9 +30,12 @@ urlpatterns = [
url(r'^', include(tasks_router.urls)),
url(r'^', include(admin_router.urls)),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)\.png$', TaskTiles.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles\.json$', TileJson.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/bounds$', Bounds.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/metadata$', Metadata.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)\.png$', Tiles.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)@(?P<scale>[\d]+)x\.png$', Tiles.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/download/(?P<asset>.+)$', TaskDownloads.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/assets/(?P<unsafe_asset_path>.+)$', TaskAssets.as_view()),

Wyświetl plik

@ -100,7 +100,7 @@ def build_plugins():
# Check for webpack.config.js (if we need to build it)
if plugin.path_exists("public/webpack.config.js"):
if settings.DEV:
if settings.DEV and webpack_watch_process_count() <= 2:
logger.info("Running webpack with watcher for {}".format(plugin.get_name()))
subprocess.Popen(['webpack-cli', '--watch'], cwd=plugin.get_path("public"))
elif not plugin.path_exists("public/build"):
@ -108,6 +108,22 @@ def build_plugins():
subprocess.call(['webpack-cli'], cwd=plugin.get_path("public"))
def webpack_watch_process_count():
count = 0
try:
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
for pid in pids:
try:
if "/usr/bin/webpack-cli" in open(os.path.join('/proc', pid, 'cmdline'), 'r').read().split('\0'):
count += 1
except IOError: # proc has already terminated
continue
except:
logger.warning("webpack_watch_process_count is not supported on this platform.")
return count
def register_plugins():
for plugin in get_active_plugins():
try:

Wyświetl plik

@ -85,7 +85,7 @@ module.exports = {
},
watchOptions: {
ignored: /node_modules/,
ignored: ['node_modules', './**/*.py'],
aggregateTimeout: 300,
poll: 1000
}

Wyświetl plik

@ -22,8 +22,6 @@ import update from 'immutability-helper';
class Map extends React.Component {
static defaultProps = {
maxzoom: 18,
minzoom: 0,
showBackground: false,
opacity: 100,
mapType: "orthophoto",
@ -31,8 +29,6 @@ class Map extends React.Component {
};
static propTypes = {
maxzoom: PropTypes.number,
minzoom: PropTypes.number,
showBackground: PropTypes.bool,
tiles: PropTypes.array.isRequired,
opacity: PropTypes.number,
@ -97,9 +93,9 @@ class Map extends React.Component {
);
const layer = Leaflet.tileLayer(info.tiles[0], {
bounds,
minZoom: info.minzoom,
maxZoom: L.Browser.retina ? (info.maxzoom + 1) : info.maxzoom,
maxNativeZoom: L.Browser.retina ? (info.maxzoom - 1) : info.maxzoom,
minZoom: 0,
maxZoom: info.maxzoom + 4,
maxNativeZoom: info.maxzoom,
tms: info.scheme === 'tms',
opacity: this.props.opacity / 100,
detectRetina: true
@ -203,7 +199,9 @@ class Map extends React.Component {
this.map = Leaflet.map(this.container, {
scrollWheelZoom: true,
positionControl: true,
zoomControl: false
zoomControl: false,
minZoom: 0,
maxZoom: 24
});
PluginsAPI.Map.triggerWillAddControls({
@ -247,7 +245,7 @@ https://a.tile.openstreetmap.org/{z}/{x}/{y}.png
if (url){
customLayer.clearLayers();
const l = L.tileLayer(url, {
maxZoom: 21,
maxZoom: 24,
minZoom: 0
});
customLayer.addLayer(l);

Wyświetl plik

@ -1,3 +1,2 @@
geojson==2.4.1
opencv-python==4.1.0.25
rasterio==1.0.23

Wyświetl plik

@ -54,4 +54,5 @@ uritemplate==3.0.0
vine==1.1.4
webcolors==1.5
rasterio==1.1.0
rio-tiler==1.3.0
rio-tiler==1.3.0
rio-color==1.0.0

Wyświetl plik

@ -93,7 +93,7 @@ module.exports = {
},
watchOptions: {
ignored: /node_modules/,
ignored: ['node_modules', './**/*.py'],
aggregateTimeout: 300,
poll: 1000
}