kopia lustrzana https://github.com/OpenDroneMap/WebODM
COG streaming API (WIP)
rodzic
a4a6f273be
commit
6479d564ba
|
@ -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):
|
||||
|
|
161
app/api/tiler.py
161
app/api/tiler.py
|
@ -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)
|
||||
)
|
|
@ -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()),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -85,7 +85,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/,
|
||||
ignored: ['node_modules', './**/*.py'],
|
||||
aggregateTimeout: 300,
|
||||
poll: 1000
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
geojson==2.4.1
|
||||
opencv-python==4.1.0.25
|
||||
rasterio==1.0.23
|
||||
|
|
|
@ -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
|
|
@ -93,7 +93,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules/,
|
||||
ignored: ['node_modules', './**/*.py'],
|
||||
aggregateTimeout: 300,
|
||||
poll: 1000
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue