GRASS GIS / Poznámky pro družicová data Landsat

Z GeoWikiCZ

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

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
Nastavení přístupu k mapsetům

Vizualizace dat

Příklad vizualizace kompozitního snímku (kanály 453)

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)


Nastavení výpočetního regionu podle vektorové vrstvy

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
Nastavení masky zájmové oblasti

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
Barevná syntéza 321
Výsledek detekce oblačnosti
Barevná syntéza 321 s maskou oblačnosti

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

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())

Externí odkazy