Geographic Information Systems Asked by Kristjan Sander on March 18, 2021
Is it possible to interpolate geotiff raster no value cells that are not connected to the geotiff border by other no value cells with QGIS or other freely available tools?
The grey areas are gaps. I would like to have the small grey areas surrounded by RGB dots interpolated but leave the grey area in the upper part of the image (the sea) as it is. The image was created with the QGIS Raster -> Analysis -> Fill nodata with max distance=10. If I make the max distance larger, the dots at the coast start to change the sea, too.
QGIS Raster -> Analysis -> Fill nodata does not have such an option.
As I did not find a good solution, I set out to write it by myself.
I started with a routine to detect the outer cells to exclude them from the interpolation mask. Then I discovered that I could not get rasterio to fill the no data cells for reasons beyond my comprehension. I therefore included a custom interpolation as well.
It will be slow for large datasets but it works.
The Python code:
import rasterio
from rasterio.fill import fillnodata
from os.path import join
from sys import argv
from time import time
from numpy import ndarray
from scipy.spatial.kdtree import KDTree
try:
fpath = argv[1]
fname = argv[2]
fname_out = argv[3]
except IndexError:
print("Usage: path, input_filename, output_filename, nodata if not in tif")
exit(1)
dataset = rasterio.open(join(fpath, fname))
metadata = dataset.meta
if dataset.nodata is None:
try:
nodata = int(argv[4])
#dataset.nodata = int(nodata)
except IndexError:
print("Nodata value missing!")
exit(1)
else:
nodata = dataset.nodata
print("Nodata value:", nodata)
red, green, blue, alpha = dataset.read(1), dataset.read(2), dataset.read(3), dataset.read(4)
image = dataset.read()
# input for kdtree during interpolation
point_array = []
# remaining no value cells updated every iteration
no_values = []
# size of the image
y_max = len(red)
x_max = len(red[0])
# temporary mask to store outer no value cells
mask = ndarray(shape=(y_max,x_max), dtype=int)
for i in range(0, y_max):
for j in range(0, x_max):
mask[i][j] = 1
if (red[i][j] == nodata) and (green[i][j] == nodata) and (blue[i][j] == nodata):
no_values.append((i, j))
else:
point_array.append((i,j))
# for interpolation
kdt = KDTree(point_array)
# find no value cells adjacent to the borders of the image or already found no value cells.
# return updated temporary mask & list of remaining no value cells
def search(novalues, m, x_max, y_max):
result_mask = m.copy()
result_novalues = []
for y, x in novalues:
if (x == 0) or (y == 0) or (x == x_max) or (y == y_max):
result_mask[y][x] = 0
elif (m[y-1][x] == 0) or (m[y+1][x] == 0) or (m[y][x-1] == 0) or (m[y][x+1] == 0):
result_mask[y][x] = 0
else:
result_novalues.append((y,x))
return result_mask, result_novalues
print("Searching for outside cells...")
# repeat the search until the temporary mask remains the same
count = 0
while True:
start_time = time()
mask1, no_values = search(no_values, mask, x_max-1, y_max-1)
equal = True
for i in range(0, y_max):
if (mask[i] == mask1[i]).all():
pass
else:
equal = False
break
if equal is True:
break
mask = mask1
count += 1
print(time()-start_time)
print(count, "iterations")
print("Remaining no value cells:", len(no_values))
# construct the mask actually used for interpolation
mask = ndarray(shape=(y_max,x_max), dtype=bool)
for i in range(0, y_max):
for j in range(0, x_max):
mask[i][j] = 1
# fill it with remaining (inland) no value cells
for y,x in no_values:
mask[y][x] = 0
#image = fillnodata(image=image, mask=mask) # Does not work for no apparent reason :(
start_time = time()
print("Interpolating...")
# straightforward inverse nearest neighbour interpolation
for i in range(0, y_max):
for j in range(0, x_max):
if mask[i][j] == 0:
image[3][i][j] = 255 # Alpha
dist, ind = kdt.query((i, j), k=4)
r, g, b = [], [], []
print('n', i, j)
for p_i in ind:
print(point_array[p_i][0], point_array[p_i][1])
y = point_array[p_i][0]
x = point_array[p_i][1]
r.append(red[y, x])
g.append(green[y, x])
b.append(blue[y, x])
d_sum = sum(dist)
new_r, new_g, new_b = 0, 0, 0
for l, d in enumerate(dist):
w = d / d_sum
new_r, new_g, new_b = new_r + r[l]*w, new_g + g[l]*w, new_b + b[l]*w
new_r, new_g, new_b = int(new_r), int(new_g), int(new_b)
image[0][i][j], image[1][i][j], image[2][i][j] = new_r, new_g, new_b
print(new_r, new_g, new_b)
print(time()-start_time)
# save the output
metadata.update({'driver': 'GTiff'})
rgb_dataset = rasterio.open(join(fpath, fname_out), 'w', **metadata)
rgb_dataset.write(image)
rgb_dataset.close()
print("Saved", join(fpath, fname_out))
Correct answer by Kristjan Sander on March 18, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP