[GIS] Python rasterio save GeoTIFF files

geotiff-tiffpythonrapideyerasteriosave

After following the steps in the tutorial of the Planet website (https://developers.planet.com/tutorials/convert-planetscope-imagery-from-radiance-to-reflectance/), I want to save each individual band of a RapidEye image Level 3B as a separate GeoTIFF layer file to be able to open it in any other GIS-software such as QGIS, ArcGIS other. I could not succeed in saving each band separately as GeoTIFF layer file. Here is the code:

#!/usr/bin/env python
# coding: utf-8

import rasterio
import numpy as np

filename = "20180308_133037_1024_3B_AnalyticMS.tif"

# Load red and NIR bands - note all PlanetScope 4-band images have band order BGRN
with rasterio.open(filename) as src:
    band_blue_radiance = src.read(1)

with rasterio.open(filename) as src:
    band_green_radiance = src.read(2)

with rasterio.open(filename) as src:
    band_red_radiance = src.read(3)

with rasterio.open(filename) as src:
    band_nir_radiance = src.read(4)

from xml.dom import minidom

xmldoc = minidom.parse("20180308_133037_1024_3B_AnalyticMS_metadata.xml")
nodes = xmldoc.getElementsByTagName("ps:bandSpecificMetadata")

# XML parser refers to bands by numbers 1-4
coeffs = {}
for node in nodes:
    bn = node.getElementsByTagName("ps:bandNumber")[0].firstChild.data
    if bn in ['1', '2', '3', '4']:
        i = int(bn)
        value = node.getElementsByTagName("ps:reflectanceCoefficient")[0].firstChild.data
        coeffs[i] = float(value)

print "Conversion coefficients:", coeffs

# Multiply the Digital Number (DN) values in each band by the TOA reflectance coefficients
band_blue_reflectance = band_blue_radiance * coeffs[1]
band_green_reflectance = band_green_radiance * coeffs[2]
band_red_reflectance = band_red_radiance * coeffs[3]
band_nir_reflectance = band_nir_radiance * coeffs[4]

import numpy as np
print "Red band radiance is from {} to {}".format(np.amin(band_red_radiance), np.amax(band_red_radiance))
print "Red band reflectance is from {} to {}".format(np.amin(band_red_reflectance), np.amax(band_red_reflectance))

print "Before Scaling, red band reflectance is from {} to {}".format(np.amin(band_red_reflectance), np.amax(band_red_reflectance))

# Here we include a fixed scaling factor. This is common practice.
scale = 10000
blue_ref_scaled = scale * band_blue_reflectance
green_ref_scaled = scale * band_green_reflectance
red_ref_scaled = scale * band_red_reflectance
nir_ref_scaled = scale * band_nir_reflectance

print "After Scaling, red band reflectance is from {} to {}".format(np.amin(red_ref_scaled), np.amax(red_ref_scaled))

Here I tried unsuccessfully to save each individual band as GeotiFF file.

I have tried, any idea how to do the save of each band correctly to further open in QGIS orArGIS for calculating NDVI there?

dst_crs='EPSG:32720'

##  saving bands individualy  
new_dataset = rasterio.open(
       'ReflectanceB1.tif',
       'w',
       driver='GTiff',
       height=band_blue_reflectance.shape[0],
       width=band_blue_reflectance.shape[1],
       count=1,
       dtype=band_blue_reflectance.dtype,
       crs=dst_crs,
)    

new_dataset.write(band_blue_reflectance, 1)
new_dataset.close()


new_dataset = rasterio.open(
       'ReflectanceB2.tif',
       'w',
       driver='GTiff',
       height=band_red_reflectance.shape[0],
       width=band_red_reflectance.shape[1],
       count=1,
       dtype=band_red_reflectance.dtype,
       crs=dst_crs,
)    

new_dataset.write(band_red_reflectance, 1)
new_dataset.close()

new_dataset = rasterio.open(
       'ReflectanceB3.tif',
       'w',
       driver='GTiff',
       height=band_red_reflectance.shape[0],
       width=band_red_reflectance.shape[1],
       count=1,
       dtype=band_red_reflectance.dtype,
       crs=dst_crs,
)    

new_dataset.write(band_red_reflectance, 1)
new_dataset.close()    


new_dataset = rasterio.open(
       'ReflectanceB4.tif',
       'w',
       driver='GTiff',
       height=band_red_reflectance.shape[0],
       width=band_red_reflectance.shape[1],
       count=1,
       dtype=band_red_reflectance.dtype,
       crs=dst_crs,
)    

new_dataset.write(band_red_reflectance, 1)
new_dataset.close()

I'm using as input image "20180308_133037_1024_3B_AnalyticMS.tif" instead of "20180308_133037_1024_3B_AnalyticMS_SR.tif" don't know if is the problem

I'm getting resulting Geotiff file but when I open it in ArcMap I get this error

enter image description here

The Geotiff band are there and can be seen at ArcMap screen but the error appears anyway.

Is it related to coordinate systems to be defined while saving in rasterio and needing to have it correctly projected for clipping with shapefiles?

Best Answer

I would recommend using a wrapper around rasterio for this task called rioxarray.

Here is how you would do the calculations:

#!/usr/bin/env python
# coding: utf-8

import numpy as np
import rioxarray
import xarray

filename = "20180308_133037_1024_3B_AnalyticMS.tif"

# Load red and NIR bands - note all PlanetScope 4-band images have band order BGRN
xds = xarray.open_rasterio(filename)
band_blue_radiance = xds.sel(band=1)
band_green_radiance = xds.sel(band=2)
band_red_radiance = xds.sel(band=3)
band_nir_radiance = xds.sel(band=4)

from xml.dom import minidom

xmldoc = minidom.parse("20180308_133037_1024_3B_AnalyticMS_metadata.xml")
nodes = xmldoc.getElementsByTagName("ps:bandSpecificMetadata")

# XML parser refers to bands by numbers 1-4
coeffs = {}
for node in nodes:
    bn = node.getElementsByTagName("ps:bandNumber")[0].firstChild.data
    if bn in ['1', '2', '3', '4']:
        i = int(bn)
        value = node.getElementsByTagName("ps:reflectanceCoefficient")[0].firstChild.data
        coeffs[i] = float(value)

# print "Conversion coefficients:", coeffs

# Multiply the Digital Number (DN) values in each band by the TOA reflectance coefficients
band_blue_reflectance = band_blue_radiance * coeffs[1]
band_green_reflectance = band_green_radiance * coeffs[2]
band_red_reflectance = band_red_radiance * coeffs[3]
band_nir_reflectance = band_nir_radiance * coeffs[4]

print("Red band radiance is from {} to {}".format(np.amin(band_red_radiance), np.amax(band_red_radiance)))
print("Red band reflectance is from {} to {}".format(np.amin(band_red_reflectance), np.amax(band_red_reflectance)))

print("Before Scaling, red band reflectance is from {} to {}".format(np.amin(band_red_reflectance), np.amax(band_red_reflectance)))

# Here we include a fixed scaling factor. This is common practice.
scale = 10000
blue_ref_scaled = scale * band_blue_reflectance
green_ref_scaled = scale * band_green_reflectance
red_ref_scaled = scale * band_red_reflectance
nir_ref_scaled = scale * band_nir_reflectance

print("After Scaling, red band reflectance is from {} to {}".format(np.amin(red_ref_scaled), np.amax(red_ref_scaled)))

And here is how you would write it out to a raster:

def to_raster(in_xds, template_xds, out_file):
    in_xds = in_xds.rio.write_crs(template_xds.rio.crs)
    if template_xds.rio.nodata is not None:
        in_xds.attrs["_FillValue"] = template_xds.rio.nodata
    in_xds.rio.to_raster(out_file)

to_raster(blue_ref_scaled, xds, "ReflectanceB1.tif")
to_raster(green_ref_scaled, xds, "ReflectanceB2.tif")
to_raster(red_ref_scaled, xds, "ReflectanceB3.tif")
to_raster(nir_ref_scaled, xds, "ReflectanceB4.tif")