This document provides a few basic Bash scripts to get you started with earth imagery processing in the terminal.
Dependencies: FFmpeg, GDAL (👈cheat sheet), Gifsicle, ImageMagick
If wrangling imagery in the terminal is not your cup of tea 🍵 and you're lucky enough to have a Photoshop license, have no fear. The concepts outlined by Tom Patterson for Landsat 8 here can be easily applied to other sensors.
If you'd like to compose your imagery in the browser, Pierre Markuse and others share custom scripts for visualizing things like fire and urban areas using Sentinel Hub. Go… wild.
Band combinations here are examples, not scientific law. I encourage you to experiment with your own combinations. If you need a starting point, Harris Geospatial and Esri have both published fun little rundowns of Landsat 8 false color band combinations. Here's a quick rundown of how Landsat 8 and Sentinel-2 bands compare.
Basically, there's many ways to approach this. Pick your favorite. If your favorite is Bash, here we go:
Takes no input. File extension in this command may need to be altered (TIF v TIFF) depending on source. 👈 This is true of all of the Landsat 8 scripts here.
function l8rgb() {
prefix=${PWD##*/}
gdal_merge.py -separate -co "PHOTOMETRIC=RGB" -of GTiff *\B4.TIF *\B3.TIF *\B2.TIF -o $prefix"_l8rgb.tif"
open $prefix"_l8rgb.tif"
}
Create a pansharpened RGB image from raw Landsat 8 bands in a folder
function l8ps(){
prefix=${PWD##*/}
gdal_pansharpen.py *\B8.TIF *\B4.TIF *\B3.TIF *\B2.TIF $prefix"_l8_ps_rgb.tif" -r cubic -co PHOTOMETRIC=RGB
open $prefix"_l8_ps_rgb.tif"
}
Takes no input. Requires proper GDAL driver install (good luck!)
function s2rgb() {
prefix=${PWD##*/}
gdal_merge.py -separate -co "PHOTOMETRIC=RGB" -of GTiff *\B04.jp2 *\B03.jp2 *\B02.jp2 -o $prefix"_s2rgb.tif"
open $prefix"_s2rgb.tif"
}
function image_min_max() {
file_name="${1%%.*}"
gdal_translate $1 -scale -ot byte -co COMPRESS=LZW $file_name"_stretched_to_min_max.tif"
open $file_name"_stretched_to_min_max.tif"
}
function l8fire() {
prefix=${PWD##*/}
gdal_merge.py -separate -co "PHOTOMETRIC=RGB" -of GTiff *\B7.TIF *\B3.TIF *\B2.TIF -o $prefix"_l8_fire_false_color.tif"
open $prefix"_l8_fire_false_color.tif"
}
function s2fire() {
prefix=${PWD##*/}
gdal_merge.py -separate -co "PHOTOMETRIC=RGB" -of GTiff *\B12.jp2 *\B11.jp2 *\B05.jp2 -o $prefix"_s2_fire_false_color.tif"
open $prefix"_s2_fire_false_color.tif"
}
Landsat 8 NDVI Vegetation Mapping
function l8ndvi() {
prefix=${PWD##*/}
gdal_calc.py -A *\B5.TIF -B *\B4.TIF --outfile=$prefix"_l8ndvi.tif" --calc="(A.astype(float)-B.astype(float))/(A.astype(float)+B.astype(float))" --NoDataValue=0 --type=Float32
open $prefix"_l8ndvi.tif"
}
function s2ndvi() {
prefix=${PWD##*/}
echo naming based on $prefix folder
gdal_calc.py -A *\B08.jp2.tif -B *\B04.jp2.tif --outfile=$prefix"_s2ndvi.tif" --calc="(A.astype(float)-B.astype(float))/(A.astype(float)+B.astype(float))" --NoDataValue=0 --type=Float32
open $prefix"_s2ndvi.tif"
}
Landsat 8 NDWI Water Mapping
function l8ndwi() {
prefix=${PWD##*/}
gdal_calc.py -A *\B3.TIF -B *\B5.TIF --outfile=$prefix"_l8ndwi.tif" --calc="(A.astype(float)-B.astype(float))/(A.astype(float)+B.astype(float))" --NoDataValue=0 --type=Float32
open $prefix"_l8ndwi.tif"
}
function s2ndwi() {
prefix=${PWD##*/}
gdal_calc.py -A *\B03.jp2 -B *\B08.jp2 --outfile=$prefix"_s2ndwi.tif" --calc="(A.astype(float)-B.astype(float))/(A.astype(float)+B.astype(float))" --NoDataValue=0 --type=Float32
open $prefix"_s2ndwi.tif"
}
Take all images (in alphabetical order) with the same file extension and make a video at a specified frame rate.
function video_from_images(){
frame_rate=$1
file_extension=$2
output=$3
ffmpeg -r $frame_rate -f image2 -pattern_type glob -i '*.'"$file_extension" -acodec libmp3lame $output
}
Make any video (relatively) websafe. Helpful when a video seems broken (but isn't), or to maximize compatibility.
function websafe(){
suffix=_websafe.mp4
file_name="${1%%.*}"
ffmpeg -an -i $1 -vcodec libx264 -pix_fmt yuv420p -profile:v baseline -level 3 -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" $file_name$suffix
echo saved $file_name$suffix
}
function gif_fade(){
file_extension=$1
fade_length=$2
output=$3
convert *.$file_extension -morph $fade_length $output
}
function optimize_gif(){
input=$1
output=$2
gifsicle -b -O3 $input -o $output
}
function l8aws() {
scene=$1
pathrow=$(echo expr $1|awk -F '_' '{print $3}'|sed 's/^\(.\{3\}\)/\1\//')
open "https://landsatonaws.com/L8/"$pathrow"/"$scene
}