GRASS GIS / Poznámky pro družicová data Landsat
Tato stránka obsahuje poznámky pro stažení dat LandSat Thematic Mapper a následné zpracování těchto dat v prostřední GRASS GIS. Více v rámci předmětu Zpracování obrazových dat.
Postup pro manuální stažení dat
- Data jsou dostupná na adrese: http://eros.usgs.gov (bližší informace zde)
- Data mohou být stažena také z adres: http://glovis.usgs.gov nebo http://edcsns17.cr.usgs.gov/EarthExplorer
Návod pro http://glovis.usgs.gov:
Import dat
Data je nejprve nutno dekomprimovat, např.
tar xvzf LT51910252009213MOR00.tar.gz
Příklad skriptu pro Bash, který rozbalí všechny dostupné archivy v aktuálním adresáři (pro každý rozbalený archiv vytvoří samostatný adresář).
#!/bin/sh
for file in *.tar.gz ; do
echo "Zpracovavam $file..."
dir=${file%.tar.gz}
if [ -d $dir ] ; then
rm -rf $dir
fi
mkdir $dir
tar -xzf $file -C $dir
done
Archiv obsahuje několik souborů ve formátu geotiff (jeden soubor na kanál), seznam identických bodů (GCP, ground control points), metadata v textové podobě (MTL) a soubor README.GTF.
ls -w1 L5191025_02520090801_B10.TIF L5191025_02520090801_B20.TIF L5191025_02520090801_B30.TIF L5191025_02520090801_B40.TIF L5191025_02520090801_B50.TIF L5191025_02520090801_B60.TIF L5191025_02520090801_B70.TIF L5191025_02520090801_GCP.txt L5191025_02520090801_MTL.txt README.GTF
Nástroj knihovny GDAL gdalinfo poslouží pro kontrolu metadat, např.
gdalinfo L5191025_02520090801_B10.TIF
Driver: GTiff/GeoTIFF Files: L5191025_02520090801_B10.TIF Size is 8441, 7441 Coordinate System is: PROJCS["unnamed", GEOGCS["WGS 84", DATUM["WGS_1984", SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]], AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0], UNIT["degree",0.0174532925199433], AUTHORITY["EPSG","4326"]], PROJECTION["Transverse_Mercator"], PARAMETER["latitude_of_origin",0], PARAMETER["central_meridian",15], PARAMETER["scale_factor",0.9996], PARAMETER["false_easting",500000], PARAMETER["false_northing",0], UNIT["unknown",1], AUTHORITY["EPSG","32633"]] Origin = (398699.999999999941792,5679000.000000000000000) Pixel Size = (30.000000000000000,-30.000000000000000) Metadata: AREA_OR_POINT=Point Image Structure Metadata: INTERLEAVE=BAND Corner Coordinates: Upper Left ( 398700.000, 5679000.000) ( 13d32'54.25"E, 51d15'12.06"N) Lower Left ( 398700.000, 5455770.000) ( 13d36'29.29"E, 49d14'46.73"N) Upper Right ( 651930.000, 5679000.000) ( 17d10'35.91"E, 51d14'31.60"N) Lower Right ( 651930.000, 5455770.000) ( 17d 5'13.62"E, 49d14'9.03"N) Center ( 525315.000, 5567385.000) ( 15d21'18.49"E, 50d15'29.05"N) Band 1 Block=8441x1 Type=Byte, ColorInterp=Gray
Vytvoření lokace
Hromadný import dat
Data lze naimportovat pomocí modulu r.in.gdal (File -> Import raster data -> Import raster data), ukázka z příkazové řadky (přepínač -e nastaví po importu region podle vstupní dat).
r.in.gdal -e input=L5191025_02520090801_B10.TIF output=b10 title="kanal 1"
Základní metadata vypíše r.info.
r.info map=b10
+----------------------------------------------------------------------------+ | Layer: b10 Date: Thu Sep 16 17:48:23 2010 | | Mapset: PERMANENT Login of Creator: martin | | Location: tm | | DataBase: /home/martin/grassdata | | Title: kanal 1 ( b10 ) | | Timestamp: none | |----------------------------------------------------------------------------| | | | Type of Map: raster Number of Categories: 0 | | Data Type: CELL | | Rows: 7441 | | Columns: 8441 | | Total Cells: 62809481 | | Projection: UTM (zone 33) | | N: 5679000 S: 5455770 Res: 30 | | E: 651930 W: 398700 Res: 30 | | Range of data: min = 0 max = 255 | | | | Data Description: | | generated by r.in.gdal | | | | Comments: | | r.in.gdal -e input="L5191025_02520090801_B10.TIF" output="b10" | | | +----------------------------------------------------------------------------+
Data lze hromadně naimportovat pomocí jednoduchého skriptu. Následuje ukázka pro Bash a Python.
Bash
Ukázka skriptu pro Bash (pro každou družicovou scénu vytvoří vlastní mapset).
#!/bin/sh
function import()
{
idx=1
for file in `ls *.TIF 2>/dev/null`; do
oname=`echo $file | cut -d'_' -f3 | cut -d'.' -f1`
oname=${oname:0:2}
g.message "Importuji $file -> $oname@$mapset..."
g.mapset -c mapset=$mapset --quiet 2>/dev/null
r.in.gdal -e input=$file output=$oname title="kanal $idx" --overwrite --quiet
idx=$(($idx+1))
done
}
eval `g.gisenv`
if test -n "$1" ; then
mapset=$1
cd $1
import
else
for dir in `find . -maxdepth 1 -mindepth 1 -type d`; do
mapset=`echo $dir | cut -d'/' -f2`
cd $dir
import
cd ..
done
fi
g.mapset mapset=$MAPSET --quiet
Příklad spuštění:
- ./import.sh projde všechny adresáře
- ./import.sh LM41890261983200FFF03 naimportuje data pouze z vybraného adresáře
Python
Ukázka skriptu pro Python (pro každou družicovou scénu vytvoří vlastní mapset). Tento skript navíc nastaví timestamp rastrových dat.
#!/usr/bin/python
import os
import sys
import glob
import grass.script as grass
def get_timestamp(mapset):
try:
metafile = glob.glob(mapset + '/*MTL.txt')[0]
except IndexError:
return
result = dict()
try:
fd = open(metafile)
for line in fd.readlines():
line = line.rstrip('\n')
if len(line) == 0:
continue
if 'ACQUISITION_DATE' in line:
result['date'] = line.strip().split('=')[1].strip()
finally:
fd.close()
return result
def import_tifs(mapset):
meta = get_timestamp(mapset)
for file in os.listdir(mapset):
if os.path.splitext(file)[-1] != '.TIF':
continue
ffile = os.path.join(mapset, file)
name = os.path.splitext(file)[0].split('_')[-1] #B10, B61
if name[-1] == '0':
name = name[:2]
kanal = int(name[1]) # B
grass.message('Importuji %s -> %s@%s...' % (file, name, mapset))
grass.run_command('g.mapset',
flags = 'c',
mapset = mapset,
quiet = True,
stderr = open(os.devnull, 'w'))
grass.run_command('r.in.gdal',
input = ffile,
output = name,
quiet = True,
overwrite = True,
title = 'kanal %d' % kanal)
if meta:
year, month, day = meta['date'].split('-')
if month == '01':
month = 'jan'
elif month == '02':
month = 'feb'
elif month == '03':
month = 'mar'
elif month == '04':
month = 'apr'
elif month == '05':
month = 'may'
elif month == '06':
month = 'jun'
elif month == '07':
month = 'jul'
elif month == '08':
month = 'aug'
elif month == '09':
month = 'sep'
elif month == '10':
month = 'oct'
elif month == '11':
month = 'nov'
elif month == '12':
month = 'dec'
grass.run_command('r.timestamp',
map = name,
date = ' '.join((day, month, year)))
def main():
if len(sys.argv) == 1:
for directory in filter(os.path.isdir, os.listdir(os.getcwd())):
import_tifs(directory)
else:
import_tifs(sys.argv[1])
if __name__ == "__main__":
main()
wxGUI
Dále lze data naimportovat pomocí wxGUI.
File -> Import raster data -> Bulk import of raster data
Předzpracování dat
Po importu dat je vhodné nahradit hodnotu '0' za 'null' (žádná data, no-data) - r.null.
r.null map=B10 setnull=0
hromadně pro všechny mapsety (vyžaduje, vlastnictví adresáře mapsetu).
#!/usr/bin/python
import os
import grass.script as grass
cur_mapset = grass.gisenv()['MAPSET']
for mapset in grass.mapsets(accessible = False):
if mapset != cur_mapset:
grass.run_command('g.mapset',
mapset = mapset,
quiet = True)
for rast in grass.read_command('g.mlist', type = 'rast', mapset = mapset).splitlines():
grass.message("Zpracovavam %s@%s..." % (rast, mapset))
grass.run_command('r.null', map = rast, setnull = 0)
grass.run_command('g.mapset',
mapset = cur_mapset,
quiet = True)
Nastavení vyhledávací cesty
Mapset s naimportovanými daty přidáme do vyhledávací cesty pomocí g.mapsets
g.mapsets addmapset=LM41890261983200FFF03
nebo pomocí wxGUI
Settings | GRASS working environment | Mapset access
Vizualizace dat
Zájmové území
Nejprve vytvoříme vektorovou vrstvu s polygony zájmových měst (v.extract).
v.extract input=obce output=mesta where="nazev_eng = 'Ceske Budejovice' or \ nazev_eng = 'Vsetin' or nazev_eng = 'Teplice' or nazev_eng = 'Jablonec nad Nisou' or \ nazev_eng = 'Znojmo'"
Anebo pro každé město vytvoříme vlastní vektorovou vrstvu.
#!/usr/bin/python
import grass.script as grass
for mesto in ('Ceske Budejovice', 'Vsetin', 'Teplice', 'Jablonec nad Nisou', 'Znojmo'):
grass.run_command('v.extract',
input = 'obce',
output = mesto.replace(' ', '_'),
where="nazev_eng = '%s'" % mesto)
Výpočetní region nastavíme pomocí g.region, např. pro město "Vsetín" (rastrovou vrstvu zadáváme pro nastavení prostorového rozlišení).
g.region rast=B10 vect=Vsetin
alternativně lze nastavit výpočetní region širší, např. o 1 kilometr
g.region rast=B10 vect=Vsetin n=n+1e3 s=s-1e3 w=w-1e3 e=e+1e3
Dále vytvoříme masku zájmového území vybraného města.
v.to.rast input=Vsetin output=MASK use=val value=1
Interpolace chybějících dat
Pro interpolaci chybějících hodnot lze použít modul r.fillnulls.
g.region rast=B10 r.fillnulls input=B10 output=B10_fill
Atmosferické korekce
Další poznámky najdete na GRASS Wiki.
Nejprve nastavíme cestu k mapsetu a výpočetní region (s odstupem 10km).
g.mapsets addmapset=jablonec-nad-nisou-1980
g.region vect=Jablonec_nad_Nisou rast=B1 n=n+1e4 s=s-1e4 w=w-1e4 e=e+1e4 save=region
Poznámka: Následující moduly ignorují výpočetní region a operuje na celém gridu! Pokud si přejete pracovat pouze v aktuálním výpočetním regionu, musíte vytvořit kopie rastrových dat, např.
#!/usr/bin/python
import grass.script as grass
for band in grass.read_command('g.mlist',
type = 'rast',
pattern = 'B*').splitlines():
oname = 'Brg' + band[1:]
grass.message("Kopiruji %s -> %s..." % (band, oname))
grass.mapcalc(oname + ' = ' + band)
grass.run_command('r.null',
map = oname,
setnull = 0)
Provedeme konverzi digitálních hodnot (DH) na hodnoty záření na vrcholu atmosféry (TOA) pomocí modulu i.landsat.toar.
i.landsat.toar -tr input_prefix=Brg output_prefix=B_rad \
metfile=/work/geodata/zodh2010/data/jablonec-nad-nisou-1980/L4191025_02519840414_MTL.txt
Připravíme konfigurační soubor pro i.atcorr. Tento modul aplikuje atmosferické korekce, přepočte hodnoty záření na vrcholu atmosféry na hodnoty absolutní odrazivosti objektů.
Některé údaje získáme z metadatového souboru (MTL), jiné spočteme.
- Dat a čas pořízení dat
cd /work/geodata/zodh2010/data/jablonec-nad-nisou-1980/
grep -a 'ACQUISITION_DATE' L4191025_02519840414_MTL.txt
ACQUISITION_DATE = 1984-04-14
grep -a 'SCENE_CENTER_SCAN_TIME' L4191025_02519840414_MTL.txt
SCENE_CENTER_SCAN_TIME = 09:17:32.4240000Z
- Střed scény v zeměpisných souřadnicích
g.region -lg
... center_long=15.16068438 center_lat=50.72648033 ...
nebo
g.region -c
center easting: 511341.280354 center northing: 5619440.568602
echo '511341|5619440' | m.proj -ogd --q
15.16068045|50.72664675
Poznámka: V prvním případě se používá elipsoid lokace, v druhém WGS84.
- Průměrná výška
r.univar map=dem --q | grep 'mean:'
mean: 552.711
Příklad konfiguračního souboru:
7 # geometricke podminky Landsat TM 04 14 9.28 15.16068045 50.72664675 # mesic den h.mmm (GMT) zem. sirka delka 2 # atmosfericky model midlatitude summer 3 # aerosol model urban 50 # viditelnost [km] -0.552 # prumerna vyska [km] -1000 # sensor na urovni nosice 31 # 1. kanal Landsat 5 TM
Atmosferické korekce nakonec aplikujeme příkazem
i.atcorr iimg=B_rad1 icnd=atcorr.txt oimg=B_cor1
Detekce oblačnosti
Detekovat mraky (a jejich teplotu) umožňuje modul i.landsat.acca.
Nejprve konvertuje DH na hodnoty odrazivosti
i.landsat.toar -t input_prefix=Brg output_prefix=B_ref metfile=L4191025_02519840414_MTL.txt
Provedeme výpočet (pouze pro TM/ETM+)
i.landsat.acca -5 input_prefix=B_ref output=B_acca
r.report map=B_acca units=p
+-----------------------------------------------------------------------------+ | RASTER MAP CATEGORY REPORT | |LOCATION: zod2010 Mon Nov 8 23:05:41 2010| |-----------------------------------------------------------------------------| | north: 5633223.50868776 east: 525403.19584718 | |REGION south: 5605657.62851621 west: 497279.36486102 | | res: 29.99551705 res: 30.01476092 | |-----------------------------------------------------------------------------| |MASK:none | |-----------------------------------------------------------------------------| |MAP: LANDSAT-5 TM Automatic Cloud Cover Assessment (B_acca@landa in landa) | |-----------------------------------------------------------------------------| | Category Information | % | |#|description | cover| |-----------------------------------------------------------------------------| |6|Cold cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . .| 0.01| |9|Warm cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . .| 0.10| |*|no data. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .| 99.89| |-----------------------------------------------------------------------------| |TOTAL |100.00| +-----------------------------------------------------------------------------+
Nastavení masky
Příklad nastavení masky oblačnosti pomocí r.mask
r.mask -i input=B_acca maskcats=6,9
Výpočet pozice slunce
Pozici slunce můžeme určit pomocí r.sunmask. Nejprve z metadat určíme vstupní parametry.
cd /work/geodata/zodh2010/data/jablonec-nad-nisou-1980/
grep -a 'ACQUISITION_DATE' L4191025_02519840414_MTL.txt
ACQUISITION_DATE = 1984-04-14
grep -a 'SCENE_CENTER_SCAN_TIME' L4191025_02519840414_MTL.txt
SCENE_CENTER_SCAN_TIME = 09:17:32.4240000Z
r.sunmask -sg elevation=dem year=1984 month=4 day=14 hour=9 minute=17 second=32 timezone=0
sunazimuth=143.930862 sunangleabovehorizon=44.188877
Vypočtena data můžeme porovnat s hodnotami uvedenými v metadatovém souboru.
grep -a 'SUN_' L4191025_02519840414_MTL.txt
SUN_AZIMUTH = 143.8553375 SUN_ELEVATION = 44.1660847
Klasifikace dat
Více na cvičení předmětu Zpracování obrazových dat.
Pokud máme k dispozici výsledky analýzy oblačnosti, muže je použít pro vytvoření masky
r.mask -i input=B_acca maskcats=6 9
Vytvoříme obrazovou skupinu
i.group group=Brg subgroup=Brg input=Brg1,Brg2,Brg3,Brg4,Brg5,Brg7
Barevnou kompozici lze vytvořit pomocí r.composite
g.region rast=Brg4
r.composite red=Brg4 green=Brg5 blue=Brg3 output=Brg453
Provedeme digitalizaci vybraných trénovacích ploch, viz cvičení předmětu Zpracování obrazových dat.
Skript na závěr
Tento skript pro vybraný mapset (scénu) provede výše uvedené analýzy.
#!/usr/bin/python
#%module
#% description: Davkovy vypocet pro ZODH.
#%end
#%option
#% key: mapset
#% description: Nazev mapsetu
#% gisprompt: old,mapset,mapset
#% key_desc: name
#% required: yes
##% answer: ceske-budejovice-2000
#%end
#%option
#% key: city
#% description: Nazev mesta
#% required: yes
#% options: Ceske Budejovice,Jablonec nad Nisou,Teplice,Vsetin,Znojmo
##% answer: Ceske Budejovice
#%end
#%option
#% key: mtl
#% description: Cesta k MTL souboru
#% gisprompt: old,file,file
#% key_desc: path
#% required: yes
##% answer: /work/geodata/zodh2010/data/ceske-budejovice-2000/L5191026_02620020627_MTL.txt
#%end
#%option
#% key: output_prefix
#% description: Prefix pro vystupni datove vrstvy
#% answer: B
#%end
#%flag
#% key: r
#% description: Pred vypoctem vycistit mapset
#%end
import os
import sys
import tempfile
import grass.script as grass
def create_atcorr_file(mtlfile):
atcorrfile = list()
text = ''
bandid = None
try:
fd = open(mtlfile)
for line in fd.readlines():
if 'SENSOR_ID' in line:
sensor = line.split('=')[1].strip().replace('"', '')
if sensor != 'ETM+':
atcorrfile.append('7')
if sensor == 'MSS':
bandid = 31
else:
bandid = 25
else:
atcorrfile.append('8')
bandid = 61
elif 'ACQUISITION_DATE' in line:
year, month, day = line.split('=')[1].strip().split('-')
text = '%s %s ' % (month, day)
elif 'SCENE_CENTER_SCAN_TIME' in line:
time = line.split('=')[1].strip().replace('"', '')
h, m, s = time.split(':')
m = int(m) * 100 / 60
text += '%d.%.2d ' % (int(h), m)
region = grass.parse_command('g.region',
flags = 'lg')
text += '%f %f' % (float(region['center_long']),
float(region['center_lat']))
atcorrfile.append(text)
atcorrfile.append('2') # midlatitude summer
atcorrfile.append('3') # urban model
atcorrfile.append('15') # visibility
stats = grass.parse_command('r.univar',
flags = 'g',
map = 'dem')
atcorrfile.append('-%f' % (float(stats['mean']) / 1000))
atcorrfile.append('-1000')
finally:
fd.close()
return atcorrfile, bandid
def main():
mapset = options['mapset']
mesto = options['city']
mtlfile = options['mtl']
prefix = options['output_prefix']
if flags['r']:
grass.info("Odstranuji vsechny rastrove a vektorove mapy...")
grass.run_command('g.mremove',
flags = 'f',
rast = '*',
vect = '*')
path = grass.mapsets()
current_mapset = grass.gisenv()['MAPSET']
# globalni promenne
os.environ['GRASS_OVERWRITE'] = "1"
os.environ['GRASS_VERBOSE'] = "1"
# pridej mapset do cesty
grass.run_command('g.mapsets',
mapset = '%s,PERMANENT,%s' % \
(current_mapset, mapset))
# vytvor polygon mesta
grass.info("Vytvarim polygon mesta...")
grass.run_command('v.extract',
input = 'obce',
output = mesto.replace(' ', '_'),
where="nazev_eng = '%s'" % mesto)
mesto = mesto.replace(' ', '_')
# nastaveni regionu
grass.info("Nastavuji vypocetni region...")
grass.run_command('g.region',
vect = mesto,
rast = 'B1',
n = 'n+1e4',
s = 's-1e4',
w = 'w-1e4',
e = 'e+1e4')
# vytvor kopii rastrovych dat v ramci vypocetniho regionu
grass.info("Vytvarim kopie rastrovych dat...")
for band in grass.read_command('g.mlist',
type = 'rast',
pattern = 'B*',
mapset = mapset).splitlines():
oname = '%srg%s' % (prefix, band[1:])
grass.info(" %s -> %s" % (band, oname))
grass.mapcalc(oname + ' = ' + band)
grass.run_command('r.null',
map = oname,
setnull = 0)
grass.run_command('r.colors',
map = oname,
color = 'grey.eq')
# vypocet zareni
grass.info("Konvertuji rastrova data DH -> hodnoty zareni...")
grass.run_command('i.landsat.toar',
flags = 'tr',
input_prefix = '%srg' % prefix,
output_prefix = '%s_rad' % prefix,
metfile = mtlfile)
# atmosfericke korekce
grass.info("Aplikuji atmosfericke korekce...")
atcorr, bandid = create_atcorr_file(mtlfile)
isMss = bandid == 31
i = 1
for band in grass.read_command('g.mlist',
type = 'rast',
pattern = '%s_rad*' % prefix).splitlines():
oband = band.split('_', 1)[0] + '_cor' + str(i)
grass.info("Zpracovavam <%s> -> <%s>..." % (band, oband))
atcorr.append('%d' % bandid)
atcorrfile = tempfile.NamedTemporaryFile()
atcorrfile.write('\n'.join(atcorr))
atcorrfile.seek(0)
grass.run_command('i.atcorr',
iimg = band,
icnd = atcorrfile.name,
oimg = oband)
bandid += 1
i += 1
atcorr.pop()
# hodnoty odrazivost
grass.info("Konvertuji rastrova data DH -> hodnoty odrazivosti...")
grass.run_command('i.landsat.toar',
flags = 't',
input_prefix = '%srg' % prefix,
output_prefix = '%s_ref' % prefix,
metfile = mtlfile)
# detekce mraku
grass.info("Analyzuji oblacnost...")
if isMss:
grass.info("...nelze pro MSS")
else:
grass.run_command('i.landsat.acca',
flags = '5',
input_prefix = '%s_ref' % prefix,
output = '%s_acca' % prefix)
# obnov vyhledavaci cestu
grass.run_command('g.mapsets',
mapset = ','.join(path))
return 0
if __name__ == "__main__":
options, flags = grass.parser()
sys.exit(main())