kopia lustrzana https://github.com/OpenDroneMap/WebODM
Updated rio-tiler to v2. Added custom NDVI indexes. Started develop of cropping plugin.
rodzic
af32e3aa29
commit
e84f1e194b
531
app/api/tiler.py
531
app/api/tiler.py
|
@ -1,15 +1,19 @@
|
||||||
import rasterio
|
import json
|
||||||
|
|
||||||
|
import numpy
|
||||||
from rasterio.enums import ColorInterp
|
from rasterio.enums import ColorInterp
|
||||||
import urllib
|
import urllib
|
||||||
import os
|
import os
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from rio_tiler.errors import TileOutsideBounds
|
from rio_tiler.errors import TileOutsideBounds
|
||||||
from rio_tiler.mercator import get_zooms
|
from rio_tiler.utils import has_alpha_band, \
|
||||||
from rio_tiler import main
|
|
||||||
from rio_tiler.utils import array_to_image, get_colormap, expression, linear_rescale, _chunks, _apply_discrete_colormap, has_alpha_band, \
|
|
||||||
non_alpha_indexes
|
non_alpha_indexes
|
||||||
|
from rio_tiler.utils import _stats as raster_stats
|
||||||
|
from rio_tiler.models import ImageStatistics
|
||||||
|
from rio_tiler.models import Metadata as RioMetadata
|
||||||
from rio_tiler.profiles import img_profiles
|
from rio_tiler.profiles import img_profiles
|
||||||
|
from rio_tiler.colormap import cmap as colormap
|
||||||
|
from rio_tiler.io import COGReader
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from app.raster_utils import export_raster_index
|
from app.raster_utils import export_raster_index
|
||||||
|
@ -23,13 +27,297 @@ from worker.tasks import export_raster_index
|
||||||
|
|
||||||
ZOOM_EXTRA_LEVELS = 2
|
ZOOM_EXTRA_LEVELS = 2
|
||||||
|
|
||||||
|
colormap = colormap.register(
|
||||||
|
{
|
||||||
|
"discrete_ndvi": {
|
||||||
|
0: [174, 0, 40, 255],
|
||||||
|
1: [174, 0, 40, 255],
|
||||||
|
2: [174, 0, 40, 255],
|
||||||
|
3: [174, 0, 40, 255],
|
||||||
|
4: [174, 0, 40, 255],
|
||||||
|
5: [174, 0, 40, 255],
|
||||||
|
6: [174, 0, 40, 255],
|
||||||
|
7: [174, 0, 40, 255],
|
||||||
|
8: [174, 0, 40, 255],
|
||||||
|
9: [174, 0, 40, 255],
|
||||||
|
10: [174, 0, 40, 255],
|
||||||
|
11: [174, 0, 40, 255],
|
||||||
|
12: [174, 0, 40, 255],
|
||||||
|
13: [174, 0, 40, 255],
|
||||||
|
14: [174, 0, 40, 255],
|
||||||
|
15: [174, 0, 40, 255],
|
||||||
|
16: [174, 0, 40, 255],
|
||||||
|
17: [174, 0, 40, 255],
|
||||||
|
18: [174, 0, 40, 255],
|
||||||
|
19: [174, 0, 40, 255],
|
||||||
|
20: [174, 0, 40, 255],
|
||||||
|
21: [174, 0, 40, 255],
|
||||||
|
22: [174, 0, 40, 255],
|
||||||
|
23: [174, 0, 40, 255],
|
||||||
|
24: [174, 0, 40, 255],
|
||||||
|
25: [174, 0, 40, 255],
|
||||||
|
26: [174, 0, 40, 255],
|
||||||
|
27: [174, 0, 40, 255],
|
||||||
|
28: [174, 0, 40, 255],
|
||||||
|
29: [174, 0, 40, 255],
|
||||||
|
30: [174, 0, 40, 255],
|
||||||
|
31: [174, 0, 40, 255],
|
||||||
|
32: [174, 0, 40, 255],
|
||||||
|
33: [174, 0, 40, 255],
|
||||||
|
34: [174, 0, 40, 255],
|
||||||
|
35: [174, 0, 40, 255],
|
||||||
|
36: [174, 0, 40, 255],
|
||||||
|
37: [174, 0, 40, 255],
|
||||||
|
38: [174, 0, 40, 255],
|
||||||
|
39: [174, 0, 40, 255],
|
||||||
|
40: [174, 0, 40, 255],
|
||||||
|
41: [174, 0, 40, 255],
|
||||||
|
42: [174, 0, 40, 255],
|
||||||
|
43: [174, 0, 40, 255],
|
||||||
|
44: [174, 0, 40, 255],
|
||||||
|
45: [174, 0, 40, 255],
|
||||||
|
46: [174, 0, 40, 255],
|
||||||
|
47: [174, 0, 40, 255],
|
||||||
|
48: [174, 0, 40, 255],
|
||||||
|
49: [174, 0, 40, 255],
|
||||||
|
50: [174, 0, 40, 255],
|
||||||
|
51: [254, 142, 86, 255],
|
||||||
|
52: [254, 142, 86, 255],
|
||||||
|
53: [254, 142, 86, 255],
|
||||||
|
54: [254, 142, 86, 255],
|
||||||
|
55: [254, 142, 86, 255],
|
||||||
|
56: [254, 142, 86, 255],
|
||||||
|
57: [254, 142, 86, 255],
|
||||||
|
58: [254, 142, 86, 255],
|
||||||
|
59: [254, 142, 86, 255],
|
||||||
|
60: [254, 142, 86, 255],
|
||||||
|
61: [254, 142, 86, 255],
|
||||||
|
62: [254, 142, 86, 255],
|
||||||
|
63: [254, 142, 86, 255],
|
||||||
|
64: [254, 142, 86, 255],
|
||||||
|
65: [254, 142, 86, 255],
|
||||||
|
66: [254, 142, 86, 255],
|
||||||
|
67: [254, 142, 86, 255],
|
||||||
|
68: [254, 142, 86, 255],
|
||||||
|
69: [254, 142, 86, 255],
|
||||||
|
70: [254, 142, 86, 255],
|
||||||
|
71: [254, 142, 86, 255],
|
||||||
|
72: [254, 142, 86, 255],
|
||||||
|
73: [254, 142, 86, 255],
|
||||||
|
74: [254, 142, 86, 255],
|
||||||
|
75: [254, 142, 86, 255],
|
||||||
|
76: [254, 142, 86, 255],
|
||||||
|
77: [254, 142, 86, 255],
|
||||||
|
78: [254, 142, 86, 255],
|
||||||
|
79: [254, 142, 86, 255],
|
||||||
|
80: [254, 142, 86, 255],
|
||||||
|
81: [254, 142, 86, 255],
|
||||||
|
82: [254, 142, 86, 255],
|
||||||
|
83: [254, 142, 86, 255],
|
||||||
|
84: [254, 142, 86, 255],
|
||||||
|
85: [254, 142, 86, 255],
|
||||||
|
86: [254, 142, 86, 255],
|
||||||
|
87: [254, 142, 86, 255],
|
||||||
|
88: [254, 142, 86, 255],
|
||||||
|
89: [254, 142, 86, 255],
|
||||||
|
90: [254, 142, 86, 255],
|
||||||
|
91: [254, 142, 86, 255],
|
||||||
|
92: [254, 142, 86, 255],
|
||||||
|
93: [254, 142, 86, 255],
|
||||||
|
94: [254, 142, 86, 255],
|
||||||
|
95: [254, 142, 86, 255],
|
||||||
|
96: [254, 142, 86, 255],
|
||||||
|
97: [254, 142, 86, 255],
|
||||||
|
98: [254, 142, 86, 255],
|
||||||
|
99: [254, 142, 86, 255],
|
||||||
|
100: [254, 142, 86, 255],
|
||||||
|
101: [254, 142, 86, 255],
|
||||||
|
102: [236, 246, 177, 255],
|
||||||
|
103: [236, 246, 177, 255],
|
||||||
|
104: [236, 246, 177, 255],
|
||||||
|
105: [236, 246, 177, 255],
|
||||||
|
106: [236, 246, 177, 255],
|
||||||
|
107: [236, 246, 177, 255],
|
||||||
|
108: [236, 246, 177, 255],
|
||||||
|
109: [236, 246, 177, 255],
|
||||||
|
110: [236, 246, 177, 255],
|
||||||
|
111: [236, 246, 177, 255],
|
||||||
|
112: [236, 246, 177, 255],
|
||||||
|
113: [236, 246, 177, 255],
|
||||||
|
114: [236, 246, 177, 255],
|
||||||
|
115: [236, 246, 177, 255],
|
||||||
|
116: [236, 246, 177, 255],
|
||||||
|
117: [236, 246, 177, 255],
|
||||||
|
118: [236, 246, 177, 255],
|
||||||
|
119: [236, 246, 177, 255],
|
||||||
|
120: [236, 246, 177, 255],
|
||||||
|
121: [236, 246, 177, 255],
|
||||||
|
122: [236, 246, 177, 255],
|
||||||
|
123: [236, 246, 177, 255],
|
||||||
|
124: [236, 246, 177, 255],
|
||||||
|
125: [236, 246, 177, 255],
|
||||||
|
126: [236, 246, 177, 255],
|
||||||
|
127: [236, 246, 177, 255],
|
||||||
|
128: [236, 246, 177, 255],
|
||||||
|
129: [236, 246, 177, 255],
|
||||||
|
130: [236, 246, 177, 255],
|
||||||
|
131: [236, 246, 177, 255],
|
||||||
|
132: [236, 246, 177, 255],
|
||||||
|
133: [236, 246, 177, 255],
|
||||||
|
134: [236, 246, 177, 255],
|
||||||
|
135: [236, 246, 177, 255],
|
||||||
|
136: [236, 246, 177, 255],
|
||||||
|
137: [236, 246, 177, 255],
|
||||||
|
138: [236, 246, 177, 255],
|
||||||
|
139: [236, 246, 177, 255],
|
||||||
|
140: [236, 246, 177, 255],
|
||||||
|
141: [236, 246, 177, 255],
|
||||||
|
142: [236, 246, 177, 255],
|
||||||
|
143: [236, 246, 177, 255],
|
||||||
|
144: [236, 246, 177, 255],
|
||||||
|
145: [236, 246, 177, 255],
|
||||||
|
146: [236, 246, 177, 255],
|
||||||
|
147: [236, 246, 177, 255],
|
||||||
|
148: [236, 246, 177, 255],
|
||||||
|
149: [236, 246, 177, 255],
|
||||||
|
150: [236, 246, 177, 255],
|
||||||
|
151: [236, 246, 177, 255],
|
||||||
|
152: [236, 246, 177, 255],
|
||||||
|
153: [84, 188, 108, 255],
|
||||||
|
154: [1, 126, 71, 255],
|
||||||
|
155: [1, 126, 71, 255],
|
||||||
|
156: [1, 126, 71, 255],
|
||||||
|
157: [1, 126, 71, 255],
|
||||||
|
158: [1, 126, 71, 255],
|
||||||
|
159: [1, 126, 71, 255],
|
||||||
|
160: [1, 126, 71, 255],
|
||||||
|
161: [1, 126, 71, 255],
|
||||||
|
162: [1, 126, 71, 255],
|
||||||
|
163: [1, 126, 71, 255],
|
||||||
|
164: [1, 126, 71, 255],
|
||||||
|
165: [1, 126, 71, 255],
|
||||||
|
166: [1, 126, 71, 255],
|
||||||
|
167: [1, 126, 71, 255],
|
||||||
|
168: [1, 126, 71, 255],
|
||||||
|
169: [1, 126, 71, 255],
|
||||||
|
170: [1, 126, 71, 255],
|
||||||
|
171: [1, 126, 71, 255],
|
||||||
|
172: [1, 126, 71, 255],
|
||||||
|
173: [1, 126, 71, 255],
|
||||||
|
174: [1, 126, 71, 255],
|
||||||
|
175: [1, 126, 71, 255],
|
||||||
|
176: [1, 126, 71, 255],
|
||||||
|
177: [1, 126, 71, 255],
|
||||||
|
178: [1, 126, 71, 255],
|
||||||
|
179: [1, 126, 71, 255],
|
||||||
|
180: [1, 126, 71, 255],
|
||||||
|
181: [1, 126, 71, 255],
|
||||||
|
182: [1, 126, 71, 255],
|
||||||
|
183: [1, 126, 71, 255],
|
||||||
|
184: [1, 126, 71, 255],
|
||||||
|
185: [1, 126, 71, 255],
|
||||||
|
186: [1, 126, 71, 255],
|
||||||
|
187: [1, 126, 71, 255],
|
||||||
|
188: [1, 126, 71, 255],
|
||||||
|
189: [1, 126, 71, 255],
|
||||||
|
190: [1, 126, 71, 255],
|
||||||
|
191: [1, 126, 71, 255],
|
||||||
|
192: [1, 126, 71, 255],
|
||||||
|
193: [1, 126, 71, 255],
|
||||||
|
194: [1, 126, 71, 255],
|
||||||
|
195: [1, 126, 71, 255],
|
||||||
|
196: [1, 126, 71, 255],
|
||||||
|
197: [1, 126, 71, 255],
|
||||||
|
198: [1, 126, 71, 255],
|
||||||
|
199: [1, 126, 71, 255],
|
||||||
|
200: [1, 126, 71, 255],
|
||||||
|
201: [1, 126, 71, 255],
|
||||||
|
202: [1, 126, 71, 255],
|
||||||
|
203: [1, 126, 71, 255],
|
||||||
|
204: [1, 126, 71, 255],
|
||||||
|
205: [1, 126, 71, 255],
|
||||||
|
206: [1, 126, 71, 255],
|
||||||
|
207: [1, 126, 71, 255],
|
||||||
|
208: [1, 126, 71, 255],
|
||||||
|
209: [1, 126, 71, 255],
|
||||||
|
210: [1, 126, 71, 255],
|
||||||
|
211: [1, 126, 71, 255],
|
||||||
|
212: [1, 126, 71, 255],
|
||||||
|
213: [1, 126, 71, 255],
|
||||||
|
214: [1, 126, 71, 255],
|
||||||
|
215: [1, 126, 71, 255],
|
||||||
|
216: [1, 126, 71, 255],
|
||||||
|
217: [1, 126, 71, 255],
|
||||||
|
218: [1, 126, 71, 255],
|
||||||
|
219: [1, 126, 71, 255],
|
||||||
|
220: [1, 126, 71, 255],
|
||||||
|
221: [1, 126, 71, 255],
|
||||||
|
222: [1, 126, 71, 255],
|
||||||
|
223: [1, 126, 71, 255],
|
||||||
|
224: [1, 126, 71, 255],
|
||||||
|
225: [1, 126, 71, 255],
|
||||||
|
226: [1, 126, 71, 255],
|
||||||
|
227: [1, 126, 71, 255],
|
||||||
|
228: [1, 126, 71, 255],
|
||||||
|
229: [1, 126, 71, 255],
|
||||||
|
230: [1, 126, 71, 255],
|
||||||
|
231: [1, 126, 71, 255],
|
||||||
|
232: [1, 126, 71, 255],
|
||||||
|
233: [1, 126, 71, 255],
|
||||||
|
234: [1, 126, 71, 255],
|
||||||
|
235: [1, 126, 71, 255],
|
||||||
|
236: [1, 126, 71, 255],
|
||||||
|
237: [1, 126, 71, 255],
|
||||||
|
238: [1, 126, 71, 255],
|
||||||
|
239: [1, 126, 71, 255],
|
||||||
|
240: [1, 126, 71, 255],
|
||||||
|
241: [1, 126, 71, 255],
|
||||||
|
242: [1, 126, 71, 255],
|
||||||
|
243: [1, 126, 71, 255],
|
||||||
|
244: [1, 126, 71, 255],
|
||||||
|
245: [1, 126, 71, 255],
|
||||||
|
246: [1, 126, 71, 255],
|
||||||
|
247: [1, 126, 71, 255],
|
||||||
|
248: [1, 126, 71, 255],
|
||||||
|
249: [1, 126, 71, 255],
|
||||||
|
250: [1, 126, 71, 255],
|
||||||
|
251: [1, 126, 71, 255],
|
||||||
|
252: [1, 126, 71, 255],
|
||||||
|
253: [1, 126, 71, 255],
|
||||||
|
254: [1, 126, 71, 255],
|
||||||
|
255: [1, 126, 71, 255]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
colormap = colormap.register({
|
||||||
|
"better_discrete_ndvi": {
|
||||||
|
0: [174, 0, 40, 255],
|
||||||
|
17: [223, 45, 44, 255],
|
||||||
|
34: [254, 109, 72, 255],
|
||||||
|
51: [254, 199, 125, 255],
|
||||||
|
68: [255, 223, 146, 255],
|
||||||
|
85: [255, 239, 173, 255],
|
||||||
|
102: [234, 248, 171, 255],
|
||||||
|
119: [212, 240, 148, 255],
|
||||||
|
136: [182, 227, 136, 255],
|
||||||
|
153: [155, 216, 114, 255],
|
||||||
|
170: [120, 202, 111, 255],
|
||||||
|
187: [121, 200, 115, 255],
|
||||||
|
204: [83, 189, 108, 255],
|
||||||
|
221: [22, 170, 94, 255],
|
||||||
|
238: [0, 151, 84, 255],
|
||||||
|
255: [1, 126, 71, 255],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def get_zoom_safe(src_dst):
|
def get_zoom_safe(src_dst):
|
||||||
minzoom, maxzoom = get_zooms(src_dst)
|
minzoom, maxzoom = src_dst.spatial_info["minzoom"], src_dst.spatial_info["maxzoom"]
|
||||||
if maxzoom < minzoom:
|
if maxzoom < minzoom:
|
||||||
maxzoom = minzoom
|
maxzoom = minzoom
|
||||||
|
|
||||||
return minzoom, maxzoom
|
return minzoom, maxzoom
|
||||||
|
|
||||||
|
|
||||||
def get_tile_url(task, tile_type, query_params):
|
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}}.png'.format(task.project.id, task.id, tile_type)
|
||||||
params = {}
|
params = {}
|
||||||
|
@ -43,13 +331,13 @@ def get_tile_url(task, tile_type, query_params):
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
def get_extent(task, tile_type):
|
def get_extent(task, tile_type):
|
||||||
extent_map = {
|
extent_map = {
|
||||||
'orthophoto': task.orthophoto_extent,
|
'orthophoto': task.orthophoto_extent,
|
||||||
'dsm': task.dsm_extent,
|
'dsm': task.dsm_extent,
|
||||||
'dtm': task.dtm_extent,
|
'dtm': task.dtm_extent,
|
||||||
}
|
}
|
||||||
|
|
||||||
if not tile_type in extent_map:
|
if not tile_type in extent_map:
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
|
|
||||||
|
@ -60,47 +348,11 @@ def get_extent(task, tile_type):
|
||||||
|
|
||||||
return extent
|
return extent
|
||||||
|
|
||||||
|
|
||||||
def get_raster_path(task, tile_type):
|
def get_raster_path(task, tile_type):
|
||||||
return task.get_asset_download_path(tile_type + ".tif")
|
return task.get_asset_download_path(tile_type + ".tif")
|
||||||
|
|
||||||
|
|
||||||
def rescale_tile(tile, mask, rescale = None):
|
|
||||||
if rescale:
|
|
||||||
try:
|
|
||||||
rescale_arr = list(map(float, rescale.split(",")))
|
|
||||||
except ValueError:
|
|
||||||
raise exceptions.ValidationError("Invalid rescale value")
|
|
||||||
|
|
||||||
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]):
|
|
||||||
if mask is not None:
|
|
||||||
tile[bdx] = np.where(
|
|
||||||
mask,
|
|
||||||
linear_rescale(
|
|
||||||
tile[bdx], in_range=rescale_arr[bdx], out_range=[0, 255]
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
tile[bdx] = linear_rescale(
|
|
||||||
tile[bdx], in_range=rescale_arr[bdx], out_range=[0, 255]
|
|
||||||
)
|
|
||||||
tile = tile.astype(np.uint8)
|
|
||||||
|
|
||||||
return tile, mask
|
|
||||||
|
|
||||||
|
|
||||||
def apply_colormap(tile, color_map = None):
|
|
||||||
if color_map is not None and isinstance(color_map, dict):
|
|
||||||
tile = _apply_discrete_colormap(tile, color_map)
|
|
||||||
elif color_map is not None:
|
|
||||||
tile = np.transpose(color_map[tile][0], [2, 0, 1]).astype(np.uint8)
|
|
||||||
|
|
||||||
return tile
|
|
||||||
|
|
||||||
class TileJson(TaskNestedView):
|
class TileJson(TaskNestedView):
|
||||||
def get(self, request, pk=None, project_pk=None, tile_type=""):
|
def get(self, request, pk=None, project_pk=None, tile_type=""):
|
||||||
"""
|
"""
|
||||||
|
@ -112,8 +364,8 @@ class TileJson(TaskNestedView):
|
||||||
if not os.path.isfile(raster_path):
|
if not os.path.isfile(raster_path):
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
|
|
||||||
with rasterio.open(raster_path) as src_dst:
|
with COGReader(raster_path) as src:
|
||||||
minzoom, maxzoom = get_zoom_safe(src_dst)
|
minzoom, maxzoom = get_zoom_safe(src)
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
'tilejson': '2.1.0',
|
'tilejson': '2.1.0',
|
||||||
|
@ -126,6 +378,7 @@ class TileJson(TaskNestedView):
|
||||||
'bounds': get_extent(task, tile_type).extent
|
'bounds': get_extent(task, tile_type).extent
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class Bounds(TaskNestedView):
|
class Bounds(TaskNestedView):
|
||||||
def get(self, request, pk=None, project_pk=None, tile_type=""):
|
def get(self, request, pk=None, project_pk=None, tile_type=""):
|
||||||
"""
|
"""
|
||||||
|
@ -138,13 +391,13 @@ class Bounds(TaskNestedView):
|
||||||
'bounds': get_extent(task, tile_type).extent
|
'bounds': get_extent(task, tile_type).extent
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class Metadata(TaskNestedView):
|
class Metadata(TaskNestedView):
|
||||||
def get(self, request, pk=None, project_pk=None, tile_type=""):
|
def get(self, request, pk=None, project_pk=None, tile_type=""):
|
||||||
"""
|
"""
|
||||||
Get the metadata for this tasks's asset type
|
Get the metadata for this tasks's asset type
|
||||||
"""
|
"""
|
||||||
task = self.get_and_check_task(request, pk)
|
task = self.get_and_check_task(request, pk)
|
||||||
|
|
||||||
formula = self.request.query_params.get('formula')
|
formula = self.request.query_params.get('formula')
|
||||||
bands = self.request.query_params.get('bands')
|
bands = self.request.query_params.get('bands')
|
||||||
|
|
||||||
|
@ -161,19 +414,34 @@ class Metadata(TaskNestedView):
|
||||||
|
|
||||||
if not os.path.isfile(raster_path):
|
if not os.path.isfile(raster_path):
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with rasterio.open(raster_path, "r") as src:
|
with COGReader(raster_path) as src:
|
||||||
band_count = src.meta['count']
|
band_count = src.metadata()['count']
|
||||||
if has_alpha_band(src):
|
if has_alpha_band(src.dataset):
|
||||||
band_count -= 1
|
band_count -= 1
|
||||||
|
|
||||||
nodata = None
|
nodata = None
|
||||||
# Workaround for https://github.com/OpenDroneMap/WebODM/issues/894
|
# Workaround for https://github.com/OpenDroneMap/WebODM/issues/894
|
||||||
if tile_type == 'orthophoto':
|
if tile_type == 'orthophoto':
|
||||||
nodata = 0
|
nodata = 0
|
||||||
|
|
||||||
info = main.metadata(src, pmin=pmin, pmax=pmax, histogram_bins=255, histogram_range=hrange, expr=expr, nodata=nodata)
|
# info = src.metadata(pmin=pmin, pmax=pmax, histogram_bins=255, histogram_range=hrange, expr=expr,
|
||||||
|
# nodata=nodata)
|
||||||
|
histogram_options = {"bins": 255, "range": hrange}
|
||||||
|
metadata = src.metadata(pmin=pmin, pmax=pmax, hist_options=histogram_options, nodata=nodata)
|
||||||
|
if expr is not None:
|
||||||
|
data, mask = src.preview(expression=expr)
|
||||||
|
data = numpy.ma.array(data)
|
||||||
|
data.mask = mask == 0
|
||||||
|
expression_bloc = expr.lower().split(",")
|
||||||
|
stats = {
|
||||||
|
f"{expression_bloc[b]}": raster_stats(data[b], percentiles=(2, 98))
|
||||||
|
for b in range(data.shape[0])
|
||||||
|
}
|
||||||
|
stats = {b: ImageStatistics(**s) for b, s in stats.items()}
|
||||||
|
metadata = RioMetadata(statistics=stats, **src.info().dict())
|
||||||
|
print(metadata)
|
||||||
|
print(metadata.json())
|
||||||
|
info = json.loads(metadata.json())
|
||||||
except IndexError as e:
|
except IndexError as e:
|
||||||
# Caught when trying to get an invalid raster metadata
|
# Caught when trying to get an invalid raster metadata
|
||||||
raise exceptions.ValidationError("Cannot retrieve raster metadata: %s" % str(e))
|
raise exceptions.ValidationError("Cannot retrieve raster metadata: %s" % str(e))
|
||||||
|
@ -191,6 +459,9 @@ class Metadata(TaskNestedView):
|
||||||
"rdylgn": "RdYlGn",
|
"rdylgn": "RdYlGn",
|
||||||
"rdylgn_r": "RdYlGn (Reverse)",
|
"rdylgn_r": "RdYlGn (Reverse)",
|
||||||
"spectral": "Spectral",
|
"spectral": "Spectral",
|
||||||
|
"discrete_ndvi": "Contrast NDVI",
|
||||||
|
"better_discrete_ndvi": "Custom NDVI Index",
|
||||||
|
"rplumbo": "Rplumbo (Better NDVI)",
|
||||||
"spectral_r": "Spectral (Reverse)",
|
"spectral_r": "Spectral (Reverse)",
|
||||||
"pastel1": "Pastel",
|
"pastel1": "Pastel",
|
||||||
}
|
}
|
||||||
|
@ -200,7 +471,8 @@ class Metadata(TaskNestedView):
|
||||||
if tile_type in ['dsm', 'dtm']:
|
if tile_type in ['dsm', 'dtm']:
|
||||||
colormaps = ['jet', 'terrain', 'gist_earth', 'pastel1']
|
colormaps = ['jet', 'terrain', 'gist_earth', 'pastel1']
|
||||||
elif formula and bands:
|
elif formula and bands:
|
||||||
colormaps = ['rdylgn', 'spectral', 'rdylgn_r', 'spectral_r']
|
colormaps = ['rdylgn', 'spectral', 'rdylgn_r', 'spectral_r', 'rplumbo', 'discrete_ndvi',
|
||||||
|
'better_discrete_ndvi']
|
||||||
algorithms = *get_algorithm_list(band_count),
|
algorithms = *get_algorithm_list(band_count),
|
||||||
|
|
||||||
info['color_maps'] = []
|
info['color_maps'] = []
|
||||||
|
@ -211,13 +483,12 @@ class Metadata(TaskNestedView):
|
||||||
try:
|
try:
|
||||||
info['color_maps'].append({
|
info['color_maps'].append({
|
||||||
'key': cmap,
|
'key': cmap,
|
||||||
'color_map': get_colormap(cmap, format="gdal"),
|
'color_map': colormap.get(cmap).values(),
|
||||||
'label': cmap_labels.get(cmap, cmap)
|
'label': cmap_labels.get(cmap, cmap)
|
||||||
})
|
})
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise exceptions.ValidationError("Not a valid color_map value: %s" % cmap)
|
raise exceptions.ValidationError("Not a valid color_map value: %s" % cmap)
|
||||||
|
|
||||||
del info['address']
|
|
||||||
info['name'] = task.name
|
info['name'] = task.name
|
||||||
info['scheme'] = 'xyz'
|
info['scheme'] = 'xyz'
|
||||||
info['tiles'] = [get_tile_url(task, tile_type, self.request.query_params)]
|
info['tiles'] = [get_tile_url(task, tile_type, self.request.query_params)]
|
||||||
|
@ -226,41 +497,40 @@ class Metadata(TaskNestedView):
|
||||||
info['maxzoom'] = info['minzoom']
|
info['maxzoom'] = info['minzoom']
|
||||||
info['maxzoom'] += ZOOM_EXTRA_LEVELS
|
info['maxzoom'] += ZOOM_EXTRA_LEVELS
|
||||||
info['minzoom'] -= ZOOM_EXTRA_LEVELS
|
info['minzoom'] -= ZOOM_EXTRA_LEVELS
|
||||||
|
info['bounds'] = {'value': src.bounds, 'crs': src.dataset.crs}
|
||||||
return Response(info)
|
return Response(info)
|
||||||
|
|
||||||
|
|
||||||
def get_elevation_tiles(elevation, url, x, y, z, tilesize, nodata, resampling, padding):
|
def get_elevation_tiles(elevation, url, x, y, z, tilesize, nodata, resampling, padding):
|
||||||
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:
|
||||||
|
try:
|
||||||
|
left, _ = 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:
|
try:
|
||||||
left, _ = main.tile(url, x - 1, y, z, indexes=1, tilesize=tilesize, nodata=nodata,
|
right, _ = src.tile(x + 1, y, z, indexes=1, tilesize=tilesize, nodata=nodata,
|
||||||
resampling_method=resampling, tile_edge_padding=padding)
|
resampling_method=resampling, padding=padding)
|
||||||
tile[tilesize:tilesize*2,0:tilesize] = left
|
tile[tilesize:tilesize * 2, tilesize * 2:tilesize * 3] = right
|
||||||
except TileOutsideBounds:
|
except TileOutsideBounds:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
bottom, _ = 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,
|
||||||
|
resampling_method=resampling, padding=padding)
|
||||||
|
tile[0:tilesize, tilesize:tilesize * 2] = top
|
||||||
|
except TileOutsideBounds:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
tile[tilesize:tilesize * 2, tilesize:tilesize * 2] = elevation
|
||||||
right, _ = main.tile(url, x + 1, y, z, indexes=1, tilesize=tilesize, nodata=nodata,
|
|
||||||
resampling_method=resampling, tile_edge_padding=padding)
|
|
||||||
tile[tilesize:tilesize*2,tilesize*2:tilesize*3] = right
|
|
||||||
except TileOutsideBounds:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
bottom, _ = main.tile(url, x, y + 1, z, indexes=1, tilesize=tilesize, nodata=nodata,
|
|
||||||
resampling_method=resampling, tile_edge_padding=padding)
|
|
||||||
tile[tilesize*2:tilesize*3,tilesize:tilesize*2] = bottom
|
|
||||||
except TileOutsideBounds:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
top, _ = main.tile(url, x, y - 1, z, indexes=1, tilesize=tilesize, nodata=nodata,
|
|
||||||
resampling_method=resampling, tile_edge_padding=padding)
|
|
||||||
tile[0:tilesize,tilesize:tilesize*2] = top
|
|
||||||
except TileOutsideBounds:
|
|
||||||
pass
|
|
||||||
|
|
||||||
tile[tilesize:tilesize*2,tilesize:tilesize*2] = elevation
|
|
||||||
|
|
||||||
return tile
|
return tile
|
||||||
|
|
||||||
|
@ -315,67 +585,63 @@ class Tiles(TaskNestedView):
|
||||||
if nodata is not None:
|
if nodata is not None:
|
||||||
nodata = np.nan if nodata == "nan" else float(nodata)
|
nodata = np.nan if nodata == "nan" else float(nodata)
|
||||||
tilesize = scale * 256
|
tilesize = scale * 256
|
||||||
|
|
||||||
url = get_raster_path(task, tile_type)
|
url = get_raster_path(task, tile_type)
|
||||||
|
with COGReader(url) as src:
|
||||||
|
if not src.tile_exists(z, x, y):
|
||||||
|
raise exceptions.NotFound("Outside of bounds")
|
||||||
|
|
||||||
if not os.path.isfile(url):
|
if not os.path.isfile(url):
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
|
|
||||||
with rasterio.open(url) as src:
|
with COGReader(url) as src:
|
||||||
minzoom, maxzoom = get_zoom_safe(src)
|
minzoom, maxzoom = get_zoom_safe(src)
|
||||||
has_alpha = has_alpha_band(src)
|
has_alpha = has_alpha_band(src.dataset)
|
||||||
if z < minzoom - ZOOM_EXTRA_LEVELS or z > maxzoom + ZOOM_EXTRA_LEVELS:
|
if z < minzoom - ZOOM_EXTRA_LEVELS or z > maxzoom + ZOOM_EXTRA_LEVELS:
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
|
|
||||||
# Handle N-bands datasets for orthophotos (not plant health)
|
# Handle N-bands datasets for orthophotos (not plant health)
|
||||||
if tile_type == 'orthophoto' and expr is None:
|
if tile_type == 'orthophoto' and expr is None:
|
||||||
ci = src.colorinterp
|
ci = src.dataset.colorinterp
|
||||||
|
|
||||||
# More than 4 bands?
|
# More than 4 bands?
|
||||||
if len(ci) > 4:
|
if len(ci) > 4:
|
||||||
# Try to find RGBA band order
|
# Try to find RGBA band order
|
||||||
if ColorInterp.red in ci and \
|
if ColorInterp.red in ci and \
|
||||||
ColorInterp.green in ci and \
|
ColorInterp.green in ci and \
|
||||||
ColorInterp.blue in ci:
|
ColorInterp.blue in ci:
|
||||||
indexes = (ci.index(ColorInterp.red) + 1,
|
indexes = (ci.index(ColorInterp.red) + 1,
|
||||||
ci.index(ColorInterp.green) + 1,
|
ci.index(ColorInterp.green) + 1,
|
||||||
ci.index(ColorInterp.blue) + 1,)
|
ci.index(ColorInterp.blue) + 1,)
|
||||||
else:
|
else:
|
||||||
# Fallback to first three
|
# Fallback to first three
|
||||||
indexes = (1, 2, 3, )
|
indexes = (1, 2, 3,)
|
||||||
elif has_alpha:
|
elif has_alpha:
|
||||||
indexes = non_alpha_indexes(src)
|
indexes = non_alpha_indexes(src.dataset)
|
||||||
|
|
||||||
# Workaround for https://github.com/OpenDroneMap/WebODM/issues/894
|
# Workaround for https://github.com/OpenDroneMap/WebODM/issues/894
|
||||||
if nodata is None and tile_type =='orthophoto':
|
if nodata is None and tile_type == 'orthophoto':
|
||||||
nodata = 0
|
nodata = 0
|
||||||
|
|
||||||
resampling="nearest"
|
resampling = "nearest"
|
||||||
padding=0
|
padding = 0
|
||||||
if tile_type in ["dsm", "dtm"]:
|
if tile_type in ["dsm", "dtm"]:
|
||||||
resampling="bilinear"
|
resampling = "bilinear"
|
||||||
padding=16
|
padding = 16
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if expr is not None:
|
with COGReader(url) as src:
|
||||||
tile, mask = expression(
|
if expr is not None:
|
||||||
url, x, y, z, expr=expr, tilesize=tilesize, nodata=nodata, tile_edge_padding=padding, resampling_method=resampling
|
tile = src.tile(x, y, z, expression=expr, tilesize=tilesize, nodata=nodata,
|
||||||
)
|
padding=padding,
|
||||||
else:
|
resampling_method=resampling)
|
||||||
tile, mask = main.tile(
|
else:
|
||||||
url, x, y, z, indexes=indexes, tilesize=tilesize, nodata=nodata, tile_edge_padding=padding, resampling_method=resampling
|
tile = src.tile(x, y, z, indexes=indexes, tilesize=tilesize, nodata=nodata,
|
||||||
)
|
padding=padding, resampling_method=resampling)
|
||||||
except TileOutsideBounds:
|
except TileOutsideBounds:
|
||||||
raise exceptions.NotFound("Outside of bounds")
|
raise exceptions.NotFound("Outside of bounds")
|
||||||
|
|
||||||
if color_map:
|
if color_map:
|
||||||
try:
|
try:
|
||||||
color_map = get_colormap(color_map, format="gdal")
|
colormap.get(color_map)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise exceptions.ValidationError("Not a valid color_map value")
|
raise exceptions.ValidationError("Not a valid color_map value")
|
||||||
|
|
||||||
intensity = None
|
intensity = None
|
||||||
|
|
||||||
if hillshade is not None:
|
if hillshade is not None:
|
||||||
try:
|
try:
|
||||||
hillshade = float(hillshade)
|
hillshade = float(hillshade)
|
||||||
|
@ -385,7 +651,8 @@ class Tiles(TaskNestedView):
|
||||||
raise exceptions.ValidationError("Invalid hillshade value")
|
raise exceptions.ValidationError("Invalid hillshade value")
|
||||||
|
|
||||||
if tile.shape[0] != 1:
|
if tile.shape[0] != 1:
|
||||||
raise exceptions.ValidationError("Cannot compute hillshade of non-elevation raster (multiple bands found)")
|
raise exceptions.ValidationError(
|
||||||
|
"Cannot compute hillshade of non-elevation raster (multiple bands found)")
|
||||||
|
|
||||||
delta_scale = (maxzoom + ZOOM_EXTRA_LEVELS + 1 - z) * 4
|
delta_scale = (maxzoom + ZOOM_EXTRA_LEVELS + 1 - z) * 4
|
||||||
dx = src.meta["transform"][0] * delta_scale
|
dx = src.meta["transform"][0] * delta_scale
|
||||||
|
@ -397,26 +664,34 @@ class Tiles(TaskNestedView):
|
||||||
# requires neighbor tiles to be rendered seamlessly
|
# requires neighbor tiles to be rendered seamlessly
|
||||||
elevation = get_elevation_tiles(tile[0], url, x, y, z, tilesize, nodata, resampling, padding)
|
elevation = get_elevation_tiles(tile[0], url, x, y, z, tilesize, nodata, resampling, padding)
|
||||||
intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade)
|
intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade)
|
||||||
intensity = intensity[tilesize:tilesize*2,tilesize:tilesize*2]
|
intensity = intensity[tilesize:tilesize * 2, tilesize:tilesize * 2]
|
||||||
|
|
||||||
|
|
||||||
rgb, rmask = rescale_tile(tile, mask, rescale=rescale)
|
|
||||||
rgb = apply_colormap(rgb, color_map)
|
|
||||||
|
|
||||||
if intensity is not None:
|
if intensity is not None:
|
||||||
# Quick check
|
# Quick check
|
||||||
if rgb.shape[0] != 3:
|
if tile.data.shape[0] != 3:
|
||||||
raise exceptions.ValidationError("Cannot process tile: intensity image provided, but no RGB data was computed.")
|
raise exceptions.ValidationError(
|
||||||
|
"Cannot process tile: intensity image provided, but no RGB data was computed.")
|
||||||
|
|
||||||
intensity = intensity * 255.0
|
intensity = intensity * 255.0
|
||||||
rgb = hsv_blend(rgb, intensity)
|
rgb = hsv_blend(tile.data, intensity)
|
||||||
|
|
||||||
options = img_profiles.get(driver, {})
|
options = img_profiles.get(driver, {})
|
||||||
|
rescale_arr = tuple(map(float, rescale.split(",")))
|
||||||
|
if color_map is not None and isinstance(color_map, dict):
|
||||||
|
return HttpResponse(
|
||||||
|
tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, colormap=color_map, **options),
|
||||||
|
content_type="image/{}".format(ext)
|
||||||
|
)
|
||||||
|
elif color_map is not None:
|
||||||
|
return HttpResponse(
|
||||||
|
tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, colormap=colormap.get(color_map),
|
||||||
|
**options),
|
||||||
|
content_type="image/{}".format(ext)
|
||||||
|
)
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
array_to_image(rgb, rmask, img_format=driver, **options),
|
tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, **options),
|
||||||
content_type="image/{}".format(ext)
|
content_type="image/{}".format(ext)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Export(TaskNestedView):
|
class Export(TaskNestedView):
|
||||||
def post(self, request, pk=None, project_pk=None):
|
def post(self, request, pk=None, project_pk=None):
|
||||||
"""
|
"""
|
||||||
|
@ -452,4 +727,4 @@ class Export(TaskNestedView):
|
||||||
raise exceptions.NotFound()
|
raise exceptions.NotFound()
|
||||||
|
|
||||||
celery_task_id = export_raster_index.delay(url, expr).task_id
|
celery_task_id = export_raster_index.delay(url, expr).task_id
|
||||||
return Response({'celery_task_id': celery_task_id})
|
return Response({'celery_task_id': celery_task_id})
|
||||||
|
|
|
@ -20,7 +20,7 @@ djangorestframework==3.9.1
|
||||||
djangorestframework-jwt==1.9.0
|
djangorestframework-jwt==1.9.0
|
||||||
drf-nested-routers==0.11.1
|
drf-nested-routers==0.11.1
|
||||||
funcsigs==1.0.2
|
funcsigs==1.0.2
|
||||||
futures==3.0.5
|
futures==3.1.1
|
||||||
geojson==2.3.0
|
geojson==2.3.0
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
itypes==1.1.0
|
itypes==1.1.0
|
||||||
|
@ -32,10 +32,10 @@ openapi-codec==1.1.7
|
||||||
packaging==16.8
|
packaging==16.8
|
||||||
piexif==1.0.13
|
piexif==1.0.13
|
||||||
pilkit==2.0
|
pilkit==2.0
|
||||||
Pillow==6.2.2
|
Pillow==8.3.2
|
||||||
pip-autoremove==0.9.0
|
pip-autoremove==0.9.0
|
||||||
psycopg2==2.8.4
|
psycopg2==2.8.6
|
||||||
psycopg2-binary==2.8.4
|
psycopg2-binary==2.8.6
|
||||||
PyJWT==1.5.3
|
PyJWT==1.5.3
|
||||||
pyodm==1.5.6
|
pyodm==1.5.6
|
||||||
pyparsing==2.1.10
|
pyparsing==2.1.10
|
||||||
|
@ -53,13 +53,13 @@ tzlocal==1.3
|
||||||
uritemplate==3.0.0
|
uritemplate==3.0.0
|
||||||
vine==1.3.0
|
vine==1.3.0
|
||||||
webcolors==1.5
|
webcolors==1.5
|
||||||
-e git://github.com/OpenDroneMap/rio-tiler.git#egg=rio-tiler
|
rio-tiler==2.1.2
|
||||||
rio-color==1.0.0
|
rio-color==1.0.4
|
||||||
rio-cogeo==1.1.8
|
rio-cogeo==2.3.1
|
||||||
rasterio==1.1.5 ; sys_platform == 'linux' or sys_platform == 'darwin'
|
rasterio==1.2.8
|
||||||
https://download.lfd.uci.edu/pythonlibs/s2jqpv5t/rasterio-1.1.3-cp37-cp37m-win_amd64.whl ; sys_platform == "win32"
|
|
||||||
https://download.lfd.uci.edu/pythonlibs/s2jqpv5t/GDAL-3.0.4-cp37-cp37m-win_amd64.whl ; sys_platform == "win32"
|
|
||||||
Shapely==1.7.0 ; sys_platform == "win32"
|
Shapely==1.7.0 ; sys_platform == "win32"
|
||||||
eventlet==0.25.1 ; sys_platform == "win32"
|
eventlet==0.25.1 ; sys_platform == "win32"
|
||||||
pyopenssl==19.1.0 ; sys_platform == "win32"
|
pyopenssl==19.1.0 ; sys_platform == "win32"
|
||||||
numpy==1.21.1
|
numpy==1.21.1
|
||||||
|
urllib3~=1.24.1
|
||||||
|
numexpr~=2.7.3
|
Ładowanie…
Reference in New Issue