فهرست منبع

Added i.landsat.toar, changed satellite images wx menu, completed a reference in i.eb.h_SEBAL01

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@43982 15284696-431f-4ddb-bdfa-cd5b030d7da7
Yann Chemin 14 سال پیش
والد
کامیت
0041c3cf5c

+ 65 - 52
gui/wxpython/xml/menudata.xml

@@ -2632,24 +2632,56 @@
 	  </items>
 	</menu>
     <separator />
-	<menuitem>
-	  <label>Atmospheric correction</label>
-	  <help>Performs atmospheric correction using the 6S algorithm.</help>
-	  <keywords>imagery,atmospheric correction</keywords>
-	  <handler>OnMenuCmd</handler>
-	  <command>i.atcorr</command>
-	</menuitem>
+	<menu>
+	  <label>Satellite images tools</label>
+	  <items>
+	    <menuitem>
+	      <label>Landsat DN to radiance/reflectance</label>
+	      <help>Computes correction from DN to top of atmosphere radiance reflectance, optionally DOS amtospheric correction.</help>
+	      <keywords>imagery,Landsat,radiance,reflectance,Dark Object Subtraction, atmospheric correction</keywords>
+	      <handler>OnMenuCmd</handler>
+	      <command>i.landsat.toar</command>
+	    </menuitem>
+	    <menuitem>
+	      <label>Modis quality control</label>
+	      <help>Extract quality control parameters from Modis QC layers</help>
+	      <keywords>QC,Quality Control,surface reflectance,Modis</keywords>
+	      <handler>OnMenuCmd</handler>
+	      <command>i.modis.qc</command>
+	    </menuitem>
+	    <menuitem>
+	      <label>Atmospheric correction</label>
+	      <help>Performs atmospheric correction using the 6S algorithm.</help>
+	      <keywords>imagery,atmospheric correction</keywords>
+	      <handler>OnMenuCmd</handler>
+	      <command>i.atcorr</command>
+	    </menuitem>
+	    <menuitem>
+	      <label>LatLong map</label>
+	      <help>creates a latitude/longitude map</help>
+	      <keywords>imagery,latitude,longitude,projection</keywords>
+	      <handler>OnMenuCmd</handler>
+	      <command>i.latlong</command>
+	    </menuitem>
+	    <menuitem>
+	      <label>Sunshine hours</label>
+	      <help>creates a sunshine hours map</help>
+	      <keywords>sunshine,hours,daytime</keywords>
+	      <handler>OnMenuCmd</handler>
+	      <command>i.sunhours</command>
+	    </menuitem>
+	  </items>
+	</menu>
 	<menu>
 	  <label>Evapotranspiration calculation</label>
 	  <items>
 	    <menuitem>
-	      <label>Evapotranspiration</label>
-	      <help>actual evapotranspiration for diurnal period (Bastiaanssen, 1995)</help>
-	      <keywords>imagery,actual evapotranspiration,energy balance,SEBAL</keywords>
+	      <label>Vegetation indices</label>
+	      <help>Calculates different types of vegetation indices.</help>
+	      <keywords>imagery,vegetation index,biophysical parameters</keywords>
 	      <handler>OnMenuCmd</handler>
-	      <command>i.eb.eta</command>
+	      <command>i.vi</command>
 	    </menuitem>
-	    <separator />
 	    <menuitem>
 	      <label>Albedo</label>
 	      <help>Computes broad band albedo from surface reflectance.</help>
@@ -2658,11 +2690,19 @@
 	      <command>i.albedo</command>
 	    </menuitem>
 	    <menuitem>
-	      <label>Evaporative fraction</label>
-	      <help>Computes evaporative fraction (Bastiaanssen, 1995) and root zone soil moisture (Makin, Molden and Bastiaanssen, 2001)</help>
-	      <keywords>imagery,evaporative fraction,soil moisture,energy balance,SEBAL</keywords>
+	      <label>Emissivity</label>
+	      <help>Computes emissivity from NDVI, generic method for spares land.</help>
+	      <keywords>emissivity,land flux,energy balance</keywords>
 	      <handler>OnMenuCmd</handler>
-	      <command>i.eb.evapfr</command>
+	      <command>i.emissivity</command>
+	    </menuitem>
+	    <separator />
+	    <menuitem>
+	      <label>Soil heat flux</label>
+	      <help>Soil heat flux approximation (Bastiaanssen, 1995)</help>
+	      <keywords>imagery,soil heat flux,energy balance,SEBAL</keywords>
+	      <handler>OnMenuCmd</handler>
+	      <command>i.eb.soilheatflux</command>
 	    </menuitem>
 	    <menuitem>
 	      <label>Sensible heat flux</label>
@@ -2671,19 +2711,20 @@
 	      <handler>OnMenuCmd</handler>
 	      <command>i.eb.h_SEBAL01</command>
 	    </menuitem>
+	    <separator />
 	    <menuitem>
-	      <label>Soil heat flux</label>
-	      <help>Soil heat flux approximation (Bastiaanssen, 1995)</help>
-	      <keywords>imagery,soil heat flux,energy balance,SEBAL</keywords>
+	      <label>Evaporative fraction</label>
+	      <help>Computes evaporative fraction (Bastiaanssen, 1995) and root zone soil moisture (Makin, Molden and Bastiaanssen, 2001)</help>
+	      <keywords>imagery,evaporative fraction,soil moisture,energy balance,SEBAL</keywords>
 	      <handler>OnMenuCmd</handler>
-	      <command>i.eb.soilheatflux</command>
+	      <command>i.eb.evapfr</command>
 	    </menuitem>
 	    <menuitem>
-	      <label>Emissivity</label>
-	      <help>Computes emissivity from NDVI, generic method for spares land.</help>
-	      <keywords>emissivity,land flux,energy balance</keywords>
+	      <label>Evapotranspiration</label>
+	      <help>actual evapotranspiration for diurnal period (Bastiaanssen, 1995)</help>
+	      <keywords>imagery,actual evapotranspiration,energy balance,SEBAL</keywords>
 	      <handler>OnMenuCmd</handler>
-	      <command>i.emissivity</command>
+	      <command>i.eb.eta</command>
 	    </menuitem>
 	    <menuitem>
 	      <label>Temporal integration of ETa</label>
@@ -2692,20 +2733,6 @@
 	      <handler>OnMenuCmd</handler>
 	      <command>i.evapo.time_integration</command>
 	    </menuitem>
-	    <menuitem>
-	      <label>LatLong map</label>
-	      <help>creates a latitude/longitude map</help>
-	      <keywords>imagery,latitude,longitude,projection</keywords>
-	      <handler>OnMenuCmd</handler>
-	      <command>i.latlong</command>
-	    </menuitem>
-	    <menuitem>
-	      <label>Vegetation indices</label>
-	      <help>Calculates different types of vegetation indices.</help>
-	      <keywords>imagery,vegetation index,biophysical parameters</keywords>
-	      <handler>OnMenuCmd</handler>
-	      <command>i.vi</command>
-	    </menuitem>
 	  </items>
 	</menu>
 	<menuitem>
@@ -2715,20 +2742,6 @@
 	  <handler>OnMenuCmd</handler>
 	  <command>i.biomass</command>
 	</menuitem>
-	<menuitem>
-	  <label>Modis quality control</label>
-	  <help>Extract quality control parameters from Modis QC layers</help>
-	  <keywords>QC,Quality Control,surface reflectance,Modis</keywords>
-	  <handler>OnMenuCmd</handler>
-	  <command>i.modis.qc</command>
-	</menuitem>
-	<menuitem>
-	  <label>Sunshine hours</label>
-	  <help>creates a sunshine hours map</help>
-	  <keywords>sunshine,hours,daytime</keywords>
-	  <handler>OnMenuCmd</handler>
-	  <command>i.sunhours</command>
-	</menuitem>
 	<separator />
 	<menu>
 	  <label>Report and statistics</label>

+ 1 - 0
imagery/Makefile

@@ -17,6 +17,7 @@ SUBDIRS = \
 	i.gensigset \
 	i.group \
 	i.his.rgb \
+	i.landsat.toar \
 	i.latlong \
 	i.maxlik \
 	i.modis.qc \

+ 1 - 1
imagery/i.eb.h_SEBAL01/i.eb.h_SEBAL01.html

@@ -49,7 +49,7 @@ Full process will need those:
 
   <p>[2] Chemin Y., Alexandridis T.A., 2001. Improving spatial resolution of ET seasonal for irrigated rice in Zhanghe, China}''. Asian Journal of Geoinformatics. 5(1):3-11,2004. 
 
-  <p>[3] Alexandridis T.K., Cherif I., Chemin Y., Silleos N.G., Stavrinos E., Zalidis G.C. Integrated methodology for estimating water use in Mediterranean agricultural areas. Remote Sensing. -(-):,2009. (submitted))
+  <p>[3] Alexandridis T.K., Cherif I., Chemin Y., Silleos N.G., Stavrinos E., Zalidis G.C. Integrated methodology for estimating water use in Mediterranean agricultural areas. Remote Sensing, 1(3):445-465, 2009.
 
 
 <H2>AUTHORS</H2>

+ 10 - 0
imagery/i.landsat.toar/Makefile

@@ -0,0 +1,10 @@
+MODULE_TOPDIR = ../..
+
+PGM = i.landsat.toar
+
+LIBES = $(RASTERLIB) $(GISLIB)
+DEPENDENCIES = $(RASTERDEP) $(GISDEP)
+
+include $(MODULE_TOPDIR)/include/Make/Module.make
+
+default: cmd

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1101 - 0
imagery/i.landsat.toar/earth_sun.c


+ 25 - 0
imagery/i.landsat.toar/earth_sun.h

@@ -0,0 +1,25 @@
+/*
+ *  Modified from LIBNOVA-0.12: see earth-sun.c
+ *
+ *  Copyright (C) 2000 - 2005 Liam Girdwood
+ *  Modified to GRASS (C) 2006 E. Jorge Tizado
+ */
+
+#ifndef _EARTH_SUN_H
+#define _EARTH_SUN_H
+
+struct ln_vsop
+{
+    double A;
+    double B;
+    double C;
+};
+
+/* Local prototypes */
+
+double ln_calc_series(const struct ln_vsop *data, int terms, double t);
+double julian_int(int year, int month, int day);
+double julian_char(char date[]);
+double earth_sun(char date[]);
+
+#endif

+ 213 - 0
imagery/i.landsat.toar/i.landsat.toar.html

@@ -0,0 +1,213 @@
+<H2>DESCRIPTION</H2>
+
+<EM>i.landsat.toar</EM> is used to transform the calibrated digital number
+of Landsat imagery products to top-of-atmosphere radiance or
+top-of-atmosphere reflectance and temperature (band 6 of the sensors TM and
+ETM+). Optionally, it can be used to calculate the at-surface radiance or
+reflectance with atmospheric correction (DOS method).
+
+<p> Usually, to do so the production date, the acquisition date, and the
+solar elevation is needed. Moreover, for Landsat-7 ETM+ it is also needed
+the gain (high or low) of the nine respective bands.</p>
+
+<p> Optionally, the data can be read from header file (.met) for all
+Landsat MSS, TM and ETM+. However, if the solar elevation or the product
+creation date are given the values of the metfile are overwriten. This is
+necessary when the data in the metfile is incorrect or not accurate.</p>
+
+<p> <b>Attention</b>: Any null value or smaller than QCALmin in the input
+raster is set to null in the output raster and it is not included in the
+equations.</p>
+
+
+<H2>Uncorrected at-sensor values (method=uncorrected, default)</H2>
+
+<p> The standard geometric and radiometric corrections result in a
+calibrated digital number (QCAL = DN) images. To further standardize the
+impact of illumination geometry, the QCAL images are first converted first
+to at-sensor radiance and then to at-sensor reflectance. The thermal band
+is first converted from QCAL to at-sensor radiance, and then to effective
+at-sensor temperature in Kelvin degrees.</p>
+
+<p>
+Radiometric calibration converts QCAL to <b>at-sensor radiance</b>, a
+radiometric quantity measured in  W/(m&sup2; * sr * &micro;m) using the equations:
+  <ul>
+    <li> gain = (Lmax - Lmin) / (QCALmax - QCALmin)</li>
+    <li> bias = Lmin - gain * QCALmin </li>
+    <li> radiance = gain * QCAL + bias </li>
+  </ul>
+where,
+<em>Lmax</em> and <em>Lmin</em> are the calibration constants, and
+<em>QCALmax</em> and <em>QCALmin</em> are the highest and the lowest points
+of the range of rescaled radiance in QCAL.
+
+<p>
+Then, to calculate <b>at-sensor reflectance</b> the equations are:
+  <ul>
+    <li> sun_radiance = [Esun * sin(e)] / (PI * d^2)</li>
+    <li> reflectance = radiance / sun_radiance </li>
+  </ul>
+where,
+<em>d</em> is the earth-sun distance in astronomical units,
+<em>e</em> is the solar elevation angle, and
+<em>Esun</em> is the mean solar exoatmospheric irradiance in W/(m&sup2; * &micro;m).
+
+
+<H2>Corrected at-sensor values (method=corrected)</H2>
+
+<p>
+At-sensor reflectance values range from zero to one, whereas at-sensor
+radiance must be greater or equal to zero. However, since Lmin can be a
+negative number then the at-sensor values can also be negative. To avoid
+these possible negative values and set the minimum possible values
+at-sensor to zero, this method corrects the radiance to output a corrected
+at-sensor values using the equations (not for thermal bands):
+  <ul>
+    <li> radiance = (uncorrected_radiance - Lmin) </li>
+    <li> reflectance = radiance / sun_radiance </li>
+  </ul>
+
+
+<p>
+<b>Note</b>: Other possibility to avoid negative values is set to zero this
+values (radiance and/or reflectance), but this option is ease with
+uncorrected method and r.mapcalc.</p>
+
+
+<H2>Simplified at-surface values (method=dos[1-4])</H2>
+
+<p>
+Atmospheric correction and reflectance calibration remove the path
+radiance, i.e. the stray light from the atmosphere, and the spectral effect
+of solar illumination. To output these simple <b>at-surface radiance</b>
+and <b>at-surface reflectance</b>, the equations are (not for thermal
+bands):
+  <ul>
+    <li> sun_radiance = TAUv * [Esun * sin(e) * TAUz + Esky] / (PI * d^2) </li>
+    <li> radiance_path = radiance_dark - percent * sun_radiance </li>
+    <li> radiance = (at-sensor_radiance - radiance_path) </li>
+    <li> reflectance = radiance / sun_radiance </li>
+  </ul>
+where,
+<em>percent</em> is a value between 0.0 and 1.0 (usually 0.01),
+<em>Esky</em> is the diffuse sky irradiance,
+<em>TAUz</em> is the atmospheric transmittance along the path from the sun
+to the ground surface, and
+<em>TAUv</em> is the atmospheric transmittance along the path from the
+ground surface to the sensor.
+<em>radiance_dark</em> is the at-sensor radiance calculated from the
+darkest object, i.e. DN with a least 'dark_parameter' (usually 1000) pixels
+for the entire image.
+
+The values are,
+  <ul>
+	<li>DOS1: TAUv = 1.0, TAUz = 1.0 and Esky = 0.0</li>
+	<li>DOS2: TAUv = 1.0, Esky = 0.0, and TAUz = sin(e) for all bands
+	   with maximum wave length less than 1. (i.e. bands 4-6 MSS, 1-4 TM,
+	   and 1-4 ETM+) other bands TAUz = 1.0</li>
+	<li>DOS3: TAUv = exp[-t/cos(sat_zenith)],
+	   TAUz = exp[-t/sin(e)], Esky = rayleigh</li>
+	<li>DOS4: TAUv = exp[-t/cos(sat_zenith)],
+	   TAUz = exp[-t/sin(e)], Esky = PI * radiance_dark </li>
+  </ul>
+
+<p>
+<b>Attention</b>: Output radiance remain untouched (i.e. no set to 0. when
+it is negative) then they are possible negative values. However, output
+reflectance is set to 0. when is obtained a negative value.</p>
+
+
+<H2>NOTES</H2>
+
+<p>
+In verbose mode (flag -v), the program write basic satellite data and the
+parameters used in the transformations.</p>
+
+<p>
+In L5_MTL mode (flag -t), the Landsat 5TM imagery that has a _MTL.txt metadata
+file can be processed. Landsat 7 ETM+ does not need a flag since .met and _MTL.txt
+are sufficient compatible for this sensor.</p>
+
+<p>
+Production date is not an exact value but it is necessary to apply correct
+calibration constants, which were changed in the dates:
+  <ul>
+    <li>Landsat-1 MSS: never </li>
+    <li>Landsat-2 MSS: July 16, 1975</li>
+    <li>Landsat-3 MSS: June 1, 1978</li>
+    <li>Landsat-4 MSS: August 26, 1982 and April 1, 1983</li>
+    <li>Landsat-4 TM:  August 1, 1983 and January 15, 1984</li>
+    <li>Landsat-5 MSS: April 6, 1984 and November 9, 1984</li>
+    <li>Landsat-5 TM:  May 4, 2003 and April, 2 2007</li>
+    <li>Landsat-7 ETM+: July 1, 2000</li>
+  </ul>
+
+
+<H2>EXAMPLES</H2>
+
+<p>
+Transform digital numbers of Landsat-7 ETM+ in band rasters 203_30.1,
+203_30.2 [...] to uncorrected at-sensor reflectance in output files
+203_30.toar.1, 203_30.toar.2 [...] and at-sensor temperature in output
+files 293_39.toar.61 and 293_39.toar.62:</p>
+
+<div class="code"><pre>
+i.landsat.toar -7 band=203_30 met=p203r030_7x20010620.met
+</pre></div>
+
+<p>or</p>
+
+<div class="code"><pre>
+i.landsat.toar -7 band=203_30 product=2004-06-07 date=2001-06-20 \
+   solar=64.3242970 gain="HHHLHLHHL"
+</pre></div>
+
+<H2>REFERENCES</H2>
+<ol>
+
+    <li>Chander G., B.L. Markham and D.L. Helder: Remote Sensing of
+        Environment, vol. 113, 2009.</li>
+
+    <li>Chander G.H. and B. Markham: IEEE Transactions On Geoscience And
+        Remote Sensing, vol. 41, no. 11, November 2003.</li>
+
+    <li>Chavez P.S., jr. 1996. Image-based atmospheric corrections -
+        Revisited and Improved. Photogrammetric Engineering and Remote
+	Sensing 62(9): 1025-1036.</li>
+
+    <li>Huang et al: At-Satellite Reflectance: A First Order Normalization
+        Of Landsat 7 ETM+ Images. 2002.</li>
+
+    <li>R. Irish: <a href="http://ltpwww.gsfc.nasa.gov/IAS/handbook/handbook_toc.html">Landsat
+        7. Science Data Users Handbook. February 17, 2007.</a></li>
+
+    <li>Markham B.L. and J.L. Barker: Landsat MSS and TM Post-Calibration
+        Dynamic Ranges, Exoatmospheric Reflectances and At-Satellite
+	Temperatures. EOSAT Landsat Technical Notes, No. 1, 1986</li>
+
+    <li>Moran M.S., R.D. Jackson, P.N. Slater and P.M. Teillet: Remote
+        Sensing of Environment, vol. 41. 1992.</li>
+
+    <li>Song et al : Classification and Change Detection Using Landsat TM
+        Data: When and How to Correct Atmospheric Effects?. Remote Sensing
+	of Environment, vol. 75. 2001. </li>
+</ol>
+
+
+<H2>SEE ALSO</H2>
+
+<em>
+<A HREF="r.mapcalc.html">r.mapcalc</A><br>
+<A HREF="r.in.gdal.html">r.in.gdal</A><br>
+</em>
+
+
+<H2>AUTHOR</H2>
+
+E. Jorge Tizado  (ej.tizado unileon es)<br>
+Dept. Biodiversity and Environmental Management,
+University of León, Spain<BR>
+
+<p>
+<i>Last changed: $Date: 2010-10-16 09:57:25 +1100 (Sat, 16 Oct 2010) $</i>

+ 168 - 0
imagery/i.landsat.toar/landsat.c

@@ -0,0 +1,168 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <grass/gis.h>
+
+#include "landsat.h"
+
+#define PI   3.1415926535897932384626433832795
+#define R2D 57.295779513082320877
+#define D2R  0.017453292519943295769
+
+/****************************************************************************
+ * PURPOSE: Calibrated Digital Number to at-satellite Radiance
+ *****************************************************************************/
+double lsat_qcal2rad(double qcal, band_data * band)
+{
+    return (double)(qcal * band->gain + band->bias);
+}
+
+/****************************************************************************
+ * PURPOSE: Radiance of non-thermal band to at-satellite Reflectance
+ *****************************************************************************/
+double lsat_rad2ref(double rad, band_data * band)
+{
+    return (double)(rad / band->K2);
+}
+
+/****************************************************************************
+ * PURPOSE: Radiance of thermal band to at-satellite Temperature
+ *****************************************************************************/
+double lsat_rad2temp(double rad, band_data * band)
+{
+    return (double)(band->K2 / log((band->K1 / rad) + 1.0));
+}
+
+/****************************************************************************
+ * PURPOSE: Some band constants
+ *
+ *      zenith = 90 - sun_elevation
+ *      sin( sun_elevation ) = cos( sun_zenith )
+ *
+ *      lsat : satellite data
+ *         i : band number
+ *    method : level of atmospheric correction
+ *   percent : percent of solar irradiance in path radiance
+ *       dos : digital number of dark object for DOS
+  *****************************************************************************/
+
+#define abs(x)	(((x)>0)?(x):(-x))
+
+void lsat_bandctes(lsat_data * lsat, int i, char method,
+		   double percent, int dos, double sat_zenith,
+		   double rayleigh)
+{
+    double pi_d2, sin_e, cos_v, rad_sun;
+
+    /* TAUv  = at. transmittance surface-sensor */
+    /* TAUz  = at. transmittance sun-surface    */
+    /* Edown = diffuse sky spectral irradiance  */
+    double TAUv, TAUz, Edown;
+
+    pi_d2 = (double)(PI * lsat->dist_es * lsat->dist_es);
+    sin_e = (double)(sin(D2R * lsat->sun_elev));
+    cos_v = (double)(cos(D2R * sat_zenith));
+
+	/** Global irradiance on the sensor.
+		Radiance to reflectance coefficient, only NO thermal bands.
+		K1 and K2 variables are also utilized as thermal constants
+	*/
+    if (lsat->band[i].thermal == 0) {
+	switch (method) {
+	case DOS2:
+	    {
+		TAUv = 1.;
+		TAUz = (lsat->band[i].wavemax < 1.) ? sin_e : 1.;
+		Edown = 0.;
+		break;
+	    }
+	case DOS2b:
+	    {
+		TAUv = (lsat->band[i].wavemax < 1.) ? cos_v : 1.;
+		TAUz = (lsat->band[i].wavemax < 1.) ? sin_e : 1.;
+		Edown = 0.;
+		break;
+	    }
+	case DOS3:
+	    {
+		double t;
+
+		t = 2. / (lsat->band[i].wavemax + lsat->band[i].wavemin);
+		t = 0.008569 * t * t * t * t * (1 + 0.0113 * t * t +
+						0.000013 * t * t * t * t);
+		TAUv = exp(-t / cos_v);
+		TAUz = exp(-t / sin_e);
+		Edown = rayleigh;
+		break;
+	    }
+	case DOS4:
+	    {
+		double Ro =
+		    (lsat->band[i].lmax - lsat->band[i].lmin) * (dos -
+								 lsat->band
+								 [i].qcalmin)
+		    / (lsat->band[i].qcalmax - lsat->band[i].qcalmin) +
+		    lsat->band[i].lmin;
+		double Tv = 1.;
+		double Tz = 1.;
+		double Lp = 0.;
+
+		do {
+		    TAUz = Tz;
+		    TAUv = Tv;
+		    Lp = Ro -
+			percent * TAUv * (lsat->band[i].esun * sin_e * TAUz +
+					  PI * Lp) / pi_d2;
+		    Tz = 1 - (4 * pi_d2 * Lp) / (lsat->band[i].esun * sin_e);
+		    Tv = exp(sin_e * log(Tz) / cos_v);
+		    /* G_message("TAUv = %.5f (%.5f), TAUz = %.5f (%.5f) and Edown = %.5f\n", TAUv, Tv, TAUz, Tz, PI * Lp ); */
+		    /* } while( abs(TAUv - Tv) > 0.0000001 || abs(TAUz - Tz) > 0.0000001); */
+		} while (TAUv != Tv && TAUz != Tz);
+		TAUz = (Tz < 1. ? Tz : 1.);
+		TAUv = (Tv < 1. ? Tv : 1.);
+		Edown = (Lp < 0. ? 0. : PI * Lp);
+		break;
+	    }
+	default:		/* DOS1 and Without atmospheric-correction */
+	    TAUv = 1.;
+	    TAUz = 1.;
+	    Edown = 0.;
+	    break;
+	}
+	rad_sun = TAUv * (lsat->band[i].esun * sin_e * TAUz + Edown) / pi_d2;
+	G_message("... TAUv = %.5f, TAUz = %.5f, Edown = %.5f\n", TAUv, TAUz,
+		  Edown);
+
+	lsat->band[i].K1 = 0.;
+	lsat->band[i].K2 = rad_sun;
+    }
+
+	/** Digital number to radiance coefficients.
+		Whitout atmospheric calibration for thermal bands.
+	*/
+    lsat->band[i].gain = ((lsat->band[i].lmax - lsat->band[i].lmin) /
+			  (lsat->band[i].qcalmax - lsat->band[i].qcalmin));
+
+    if (method == UNCORRECTED || lsat->band[i].thermal) {
+	/* L = G * (DN - Qmin) + Lmin
+	   -> bias = Lmin - G * Qmin    */
+	lsat->band[i].bias =
+	    (lsat->band[i].lmin - lsat->band[i].gain * lsat->band[i].qcalmin);
+    }
+    else {
+	if (method == CORRECTED) {
+	    /* L = G * (DN - Qmin) + Lmin - Lmin
+	       -> bias = - G * Qmin */
+	    lsat->band[i].bias =
+		-(lsat->band[i].gain * lsat->band[i].qcalmin);
+	    /* Another possibility is cut when rad < 0 */
+	}
+	else if (method > DOS) {
+	    /* L = Lsat - Lpath =
+	       G * DNsat + B - (G * dark + B - p * rad_sun) =
+	       G * DNsat - G * dark + p * rad_sun
+	       -> bias = p * rad_sun - G * dark */
+	    lsat->band[i].bias = percent * rad_sun - lsat->band[i].gain * dos;
+	}
+    }
+}

+ 66 - 0
imagery/i.landsat.toar/landsat.h

@@ -0,0 +1,66 @@
+#ifndef _LANDSAT_H
+#define _LANDSAT_H
+
+#define UNCORRECTED     0
+#define CORRECTED       1
+#define DOS      		10
+#define DOS1			12
+#define DOS2			14
+#define DOS2b			15
+#define DOS3			16
+#define DOS4			18
+
+
+/*****************************************************
+ * Landsat Structures
+ *
+ * Lmax and Lmin in  W / (m^2 * sr * µm) -> Radiance
+ * Esun in  W / (m^2 * µm)               -> Irradiance
+ ****************************************************/
+
+#define MAX_BANDS   9
+
+typedef struct
+{
+    int number;			/* Band number                   */
+    int code;			/* Band code                     */
+
+    double wavemax, wavemin;	/* Wavelength in µm              */
+
+    double lmax, lmin;		/* Spectral radiance             */
+    double qcalmax, qcalmin;	/* Quantized calibrated pixel    */
+    double esun;		/* Mean solar irradiance         */
+
+    char thermal;		/* Flag to thermal band          */
+    double gain, bias;		/* Gain and Bias of sensor       */
+    double K1, K2;		/* Thermal calibration constants,
+				   or Rad2Ref constants          */
+
+} band_data;
+
+typedef struct
+{
+    unsigned char number;	/* Landsat number                */
+
+    char creation[11];		/* Image production date         */
+    char date[11];		/* Image acquisition date        */
+    double dist_es;		/* Distance Earth-Sun            */
+    double sun_elev;		/* Solar elevation               */
+
+    char sensor[5];		/* Type of sensor: MSS, TM, ETM+ */
+    int bands;			/* Total number of bands         */
+    band_data band[MAX_BANDS];	/* Data for each band            */
+} lsat_data;
+
+
+/*****************************************************************************
+ * Landsat Equations Prototypes
+ *****************************************************************************/
+
+double lsat_qcal2rad(double, band_data *);
+double lsat_rad2ref(double, band_data *);
+double lsat_rad2temp(double, band_data *);
+
+void lsat_bandctes(lsat_data *, int, char, double, int, double, double);
+
+#endif

+ 280 - 0
imagery/i.landsat.toar/landsat_met.c

@@ -0,0 +1,280 @@
+#include<stdio.h>
+#include<stdlib.h>
+#include <math.h>
+
+#include <grass/gis.h>
+#include <grass/glocale.h>
+
+#include "local_proto.h"
+#include "earth_sun.h"
+
+#define ETM_MET_SIZE    5600	/* .met file size  5516 bytes */
+#define TM5_MET_SIZE    28700	/* .met file size 28686 bytes */
+#define MAX_STR      127
+
+inline void chrncpy(char *dest, char *src, int n)
+{
+    if (src == NULL)
+	n = 1;
+    else
+	strncpy(dest, src, n);
+    dest[n - 1] = '\0';
+}
+
+/****************************************************************************
+ * PURPOSE:     Read values of Landsat-7 ETM+ from header (.met) file
+ *****************************************************************************/
+void get_value_met7(const char mettext[], char *text, char value[])
+{
+    char *ptr;
+
+    value[0] = 0;
+
+    ptr = strstr(mettext, text);
+    if (ptr == NULL)
+	return;
+
+    while (*ptr++ != '=') ;
+    sscanf(ptr, "%s", value);
+
+    return;
+}
+
+void met_ETM(char *metfile, lsat_data * lsat)
+{
+    FILE *f;
+    char mettext[ETM_MET_SIZE];
+    char name[MAX_STR], value[MAX_STR];
+    int i;
+
+    static int band[] = { 1, 2, 3, 4, 5, 6, 6, 7, 8 };
+    static int code[] = { 1, 2, 3, 4, 5, 61, 62, 7, 8 };
+
+    static double esun[] =
+	{ 1969., 1840., 1551., 1044., 225.7, 0., 82.07, 1368. };
+
+    if ((f = fopen(metfile, "r")) == NULL)
+	G_fatal_error(_("Metadata file <%s> not found"), metfile);
+
+    fread(mettext, 1, ETM_MET_SIZE, f);
+
+    /* --------------------------------------- */
+    lsat->number = 7;
+
+    get_value_met7(mettext, "SENSOR_ID", value);
+    chrncpy(lsat->sensor, value + 1, 5);
+
+    if (lsat->creation[0] == 0) {
+	get_value_met7(mettext, "CREATION_TIME", value);
+	chrncpy(lsat->creation, value, 11);
+    }
+
+    get_value_met7(mettext, "ACQUISITION_DATE", value);
+    chrncpy(lsat->date, value, 11);
+    lsat->dist_es = earth_sun(lsat->date);
+
+    get_value_met7(mettext, "SUN_ELEVATION", value);
+    lsat->sun_elev = atof(value);
+
+    lsat->bands = 9;
+    for (i = 0; i < lsat->bands; i++) {
+	lsat->band[i].number = *(band + i);
+	lsat->band[i].code = *(code + i);
+	lsat->band[i].esun = *(esun + lsat->band[i].number - 1);
+	snprintf(name, MAX_STR, "LMAX_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].lmax = atof(value);
+	snprintf(name, MAX_STR, "LMIN_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].lmin = atof(value);
+	snprintf(name, MAX_STR, "QCALMAX_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].qcalmax = atof(value);
+	snprintf(name, MAX_STR, "QCALMIN_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].qcalmin = atof(value);
+	if (lsat->band[i].number == 6) {
+	    lsat->band[i].thermal = 1;
+	    lsat->band[i].K1 = 666.09;
+	    lsat->band[i].K2 = 1282.71;
+	}
+	else {
+	    lsat->band[i].thermal = 0;
+	}
+    }
+
+    (void)fclose(f);
+    return;
+}
+
+/****************************************************************************
+ * PURPOSE:     Read values of Landsat MSS/TM from header (.met) file
+ *****************************************************************************/
+void get_value_met(const char mettext[], char *text, char value[])
+{
+    char *ptr;
+    int i;
+
+    value[0] = 0;
+
+    ptr = strstr(mettext, text);
+    if (ptr == NULL)
+	return;
+
+    ptr = strstr(ptr, " VALUE ");
+    if (ptr == NULL)
+	return;
+
+    i = 0;
+    while (*ptr++ != '\"') ;
+    while (*ptr != '\"' && i < MAX_STR)
+	value[i++] = *ptr++;
+    value[i] = '\0';
+
+    return;
+}
+
+void met_TM5(char *metfile, lsat_data * lsat)
+{
+    FILE *f;
+    char mettext[TM5_MET_SIZE];
+    char value[MAX_STR];
+
+    /* char metdate[MAX_STR]; */
+
+    if ((f = fopen(metfile, "r")) == NULL)
+	G_fatal_error(_("Metadata file <%s> not found"), metfile);
+
+    fread(mettext, 1, TM5_MET_SIZE, f);
+
+    /* --------------------------------------- */
+    get_value_met(mettext, "CALENDARDATE", value);
+    chrncpy(lsat->date, value, 11);
+
+    if (lsat->creation[0] == 0) {
+	get_value_met(mettext, "PRODUCTIONDATETIME", value);
+	chrncpy(lsat->creation, value, 11);
+    }
+
+    if (lsat->creation[0] == 0)
+	G_fatal_error(_("Product creation date not in metadata file <%s>"),
+		      metfile);
+    G_debug(1, "met_TM5: Product creation date = [%s]", lsat->creation);
+
+
+    get_value_met(mettext, "SolarElevation", value);
+    if (!value)
+	G_warning("Unable to read solar elevation from metadata file");
+    else
+	lsat->sun_elev = atof(value);
+    G_debug(1, "met_TM5: value=[%s], SolarElevation = %.2f", value,
+	    lsat->sun_elev);
+
+
+    get_value_met(mettext, "PLATFORMSHORTNAME", value);
+    G_debug(1, "met_TM5: PLATFORMSHORTNAME=[%s]", value);
+    switch (value[8]) {
+    case '1':
+	set_MSS1(lsat);
+	break;
+    case '2':
+	set_MSS2(lsat);
+	break;
+    case '3':
+	set_MSS3(lsat);
+	break;
+    case '4':
+	get_value_met(mettext, "SENSORSHORTNAME", value);
+	if (value[0] == 'M')
+	    set_MSS4(lsat);
+	else
+	    set_TM4(lsat);
+	break;
+    case '5':
+	get_value_met(mettext, "SENSORSHORTNAME", value);
+	if (value[0] == 'M')
+	    set_MSS5(lsat);
+	else
+	    set_TM5(lsat);
+	break;
+    default:
+	G_warning("Unable to recognize satellite platform [%s]", value);
+	break;
+    }
+
+    (void)fclose(f);
+    return;
+}
+
+
+
+/****************************************************************************
+ * PURPOSE:     Read values of Landsat TM5 from header (.mtl) file
+ *****************************************************************************/
+
+/****************************************************************************
+ * EXPLANATION: This module is a modification of the met_ETM() found before
+ *              to allow TM5 from GLOVIS to use .MTL extension that responds
+ *              near to perfectly to the .met parser. While L7 files using
+ *              .MTL from GLOVIS can be processed as if having .met files
+ *              seemlessly, TM5 using .MTL need to read basic info and 
+ *              additionally the LMIN, LMAX, QCALMIN, QCALMAX being explicitely
+ *              provided in the .MTL as if in a .met file.
+ *****************************************************************************/
+void mtl_TM5(char *metfile, lsat_data * lsat)
+{
+    FILE *f;
+    char mettext[ETM_MET_SIZE];
+    char name[MAX_STR], value[MAX_STR];
+    int i;
+
+    static int band[] = { 1, 2, 3, 4, 5, 6, 7 };
+    static int code[] = { 1, 2, 3, 4, 5, 6, 7 };
+
+    if ((f = fopen(metfile, "r")) == NULL)
+	G_fatal_error(_("Metadata file <%s> not found"), metfile);
+
+    fread(mettext, 1, ETM_MET_SIZE, f);
+
+    /* --------------------------------------- */
+    get_value_met7(mettext, "SENSOR_ID", value);
+    chrncpy(lsat->sensor, value + 1, 3);
+
+    if (lsat->creation[0] == 0) {
+	get_value_met7(mettext, "PRODUCT_CREATION_TIME", value);
+	chrncpy(lsat->creation, value, 11);
+    }
+
+    get_value_met7(mettext, "ACQUISITION_DATE", value);
+    chrncpy(lsat->date, value, 11);
+    lsat->dist_es = earth_sun(lsat->date);
+
+    get_value_met7(mettext, "SUN_ELEVATION", value);
+    lsat->sun_elev = atof(value);
+
+    /* We still have to initialize most of the info */
+    /* So instead of rewriting a new function, we use set_TM5()... */
+    set_TM5(lsat);
+    /* ... and we rewrite the necessary 'a la Landsat 7' */
+
+    lsat->bands = 7;
+    for (i = 0; i < lsat->bands; i++) {
+	lsat->band[i].code = *(code + i);
+	snprintf(name, MAX_STR, "LMAX_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].lmax = atof(value);
+	snprintf(name, MAX_STR, "LMIN_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].lmin = atof(value);
+	snprintf(name, MAX_STR, "QCALMAX_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].qcalmax = atof(value);
+	snprintf(name, MAX_STR, "QCALMIN_BAND%d", lsat->band[i].code);
+	get_value_met7(mettext, name, value);
+	lsat->band[i].qcalmin = atof(value);
+	if (lsat->band[i].number == 6)
+	    lsat->band[i].thermal = 1;
+    }
+    (void)fclose(f);
+    return;
+}

+ 520 - 0
imagery/i.landsat.toar/landsat_set.c

@@ -0,0 +1,520 @@
+#include<stdio.h>
+#include<stdlib.h>
+#include<string.h>
+#include <math.h>
+
+#include <grass/gis.h>
+#include <grass/glocale.h>
+
+#include "local_proto.h"
+#include "earth_sun.h"
+
+void sensor_MSS(lsat_data * lsat)
+{
+    int i;
+
+    /* green, red, near infrared, near infrared */
+    int band[] = { 1, 2, 3, 4 };
+    int code[] = { 4, 5, 6, 7 };
+    double wmax[] = { 0.6, 0.7, 0.8, 1.1 };
+    double wmin[] = { 0.5, 0.6, 0.7, 0.8 };
+    /* 79-82, 79-82, 79-82, 79-82 */
+
+    strcpy(lsat->sensor, "MSS");
+
+    lsat->bands = 4;
+    for (i = 0; i < lsat->bands; i++) {
+	lsat->band[i].number = *(band + i);
+	lsat->band[i].code = *(code + i);
+	lsat->band[i].wavemax = *(wmax + i);
+	lsat->band[i].wavemin = *(wmin + i);
+	lsat->band[i].qcalmax = 255.;
+	lsat->band[i].qcalmin = 0.;
+	lsat->band[i].thermal = 0;
+    }
+    return;
+}
+
+void sensor_TM(lsat_data * lsat)
+{
+    int i;
+
+    /* blue, green red, near infrared, shortwave IR, thermal IR, shortwave IR */
+    int band[] = { 1, 2, 3, 4, 5, 6, 7 };
+    double wmax[] = { 0.52, 0.60, 0.69, 0.90, 1.75, 12.50, 2.35 };
+    double wmin[] = { 0.45, 0.52, 0.63, 0.76, 1.55, 10.40, 2.08 };
+    /* 30, 30, 30, 30, 30, 120, 30 */
+
+    strcpy(lsat->sensor, "TM");
+
+    lsat->bands = 7;
+    for (i = 0; i < lsat->bands; i++) {
+	lsat->band[i].number = *(band + i);
+	lsat->band[i].code = *(band + i);
+	lsat->band[i].wavemax = *(wmax + i);
+	lsat->band[i].wavemin = *(wmin + i);
+	lsat->band[i].qcalmax = 255.;
+	lsat->band[i].qcalmin = 0.;	/* Modified in set_TM5 by date */
+	lsat->band[i].thermal = (lsat->band[i].number == 6 ? 1 : 0);
+    }
+    return;
+}
+
+void sensor_ETM(lsat_data * lsat)
+{
+    int i;
+
+    /* blue, green red, near infrared, shortwave IR, thermal IR, shortwave IR, panchromatic */
+    int band[] = { 1, 2, 3, 4, 5, 6, 6, 7, 8 };
+    int code[] = { 1, 2, 3, 4, 5, 61, 62, 7, 8 };
+    double wmax[] = { 0.515, 0.605, 0.690, 0.90, 1.75, 12.50, 2.35, 0.90 };
+    double wmin[] = { 0.450, 0.525, 0.630, 0.75, 1.55, 10.40, 2.09, 0.52 };
+    /* 30, 30, 30, 30, 30, 60, 30, 15 */
+
+    strcpy(lsat->sensor, "ETM+");
+
+    lsat->bands = 9;
+    for (i = 0; i < lsat->bands; i++) {
+	lsat->band[i].number = *(band + i);
+	lsat->band[i].code = *(code + i);
+	lsat->band[i].wavemax = *(wmax + i);
+	lsat->band[i].wavemin = *(wmin + i);
+	lsat->band[i].qcalmax = 255.;
+	lsat->band[i].qcalmin = 1.;
+	lsat->band[i].thermal = (lsat->band[i].number == 6 ? 1 : 0);
+    }
+    return;
+}
+
+
+/** **********************************************
+ ** Before access to this function ...
+ ** store previously
+ ** >>> adquisition date,
+ ** >>> creation date, and
+ ** >>> sun_elev
+ ** **********************************************/
+
+/****************************************************************************
+ * PURPOSE:     Store values of Landsat-1 MSS
+ *              July 23, 1972 to January 7, 1978
+ *****************************************************************************/
+void set_MSS1(lsat_data * lsat)
+{
+    int i, j;
+
+    /** Markham and Barker. EOSAT Landsat Technical Notes, No. 1, 1986;
+        Chander, Markham and Helder. Remote Sensing of Environment, 113 (2009)*/
+
+    /* Spectral radiances at detector */
+    double lmax[] = { 248., 200., 176., 153. };
+    double lmin[] = { 0., 0., 0., 0. };
+
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1823., 1559., 1276., 880.1 };
+
+    lsat->number = 1;
+    sensor_MSS(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	lsat->band[i].esun = *(esun + j);
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+    }
+    return;
+}
+
+/****************************************************************************
+ * PURPOSE:     Store values of Landsat-2 MSS
+ *              January 22, 1975 to February 25, 1982
+ *****************************************************************************/
+void set_MSS2(lsat_data * lsat)
+{
+    int i, j;
+    double julian, *lmax, *lmin;
+
+    /** Markham and Barker. EOSAT Landsat Technical Notes, No. 1, 1986;
+        Chander, Markham and Helder. Remote Sensing of Environment, 113 (2009)*/
+
+    /* Spectral radiances at detector */
+    double Lmax[][4] = {
+	{210., 156., 140., 138.},	/* before      July 16, 1975 */
+	{263., 176., 152., 130.333}	/* on or after July 16, 1975 */
+    };
+    double Lmin[][4] = {
+	{10., 7., 7., 5.},
+	{8., 6., 6., 3.667}
+    };
+
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1829., 1539., 1268., 886.6 };
+
+    julian = julian_char(lsat->creation);
+    if (julian < julian_char("1975-07-16"))
+	i = 0;
+    else
+	i = 1;
+    lmax = Lmax[i];
+    lmin = Lmin[i];
+
+    lsat->number = 2;
+    sensor_MSS(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	lsat->band[i].esun = *(esun + j);
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+    }
+    return;
+}
+
+/****************************************************************************
+ * PURPOSE:     Store values of Landsat-3 MSS
+ *              March 5, 1978 to March 31, 1983
+ *
+ *              tiene una banda 8 thermal
+ *****************************************************************************/
+void set_MSS3(lsat_data * lsat)
+{
+    int i, j;
+    double julian, *lmax, *lmin;
+
+    /** Markham and Barker. EOSAT Landsat Technical Notes, No. 1, 1986;
+        Chander, Markham and Helder. Remote Sensing of Environment, 113 (2009)*/
+    /* Spectral radiances at detector */
+    double Lmax[][4] = {
+	{220., 175., 145., 147.},	/* before      June 1, 1978 */
+	{259., 179., 149., 128.}	/* on or after June 1, 1978 */
+    };
+    double Lmin[][4] = {
+	{4., 3., 3., 1.},
+	{4., 3., 3., 1.}
+    };
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1839., 1555., 1291., 887.9 };
+
+    julian = julian_char(lsat->creation);
+    if (julian < julian_char("1978-06-01"))
+	i = 0;
+    else
+	i = 1;
+    lmax = Lmax[i];
+    lmin = Lmin[i];
+
+    lsat->number = 3;
+    sensor_MSS(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	lsat->band[i].esun = *(esun + j);
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+    }
+    return;
+}
+
+/****************************************************************************
+ * PURPOSE:     Store values of Landsat-4 MSS/TM
+ *              July 16, 1982 to June 15, 2001
+ *****************************************************************************/
+void set_MSS4(lsat_data * lsat)
+{
+    int i, j;
+    double julian, *lmax, *lmin;
+
+    /** Markham and Barker. EOSAT Landsat Technical Notes, No. 1, 1986;
+        Chander, Markham and Helder. Remote Sensing of Environment, 113 (2009)*/
+
+    /* Spectral radiances at detector */
+    double Lmax[][4] = {
+	{250., 180., 150., 133.},	/* before      August 26, 1982 */
+	{230., 180., 130., 133.},	/* between                     */
+	{238., 164., 142., 116.}	/* on or after April 1, 1983   */
+    };
+    double Lmin[][4] = {
+	{2., 4., 4., 3.},
+	{2., 4., 4., 3.},
+	{4., 4., 5., 4.}
+    };
+
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1827., 1569., 1260., 866.4 };
+
+    julian = julian_char(lsat->creation);
+    if (julian < julian_char("1982-08-26"))
+	i = 0;
+    else if (julian < julian_char("1983-03-31"))
+	i = 1;
+    else
+	i = 2;
+    lmax = Lmax[i];
+    lmin = Lmin[i];
+
+    lsat->number = 4;
+    sensor_MSS(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	lsat->band[i].esun = *(esun + j);
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+    }
+    return;
+}
+
+void set_TM4(lsat_data * lsat)
+{
+    int i, j;
+    double julian, *lmax, *lmin;
+
+    /** Brian L. Markham and John L. Barker.
+        EOSAT Landsat Technical Notes, No. 1, 1986 */
+    /* Spectral radiances at detector */
+    double Lmax[][7] = {
+	{158.42, 308.17, 234.63, 224.32, 32.42, 15.64, 17.00},	/* before August 1983      */
+	{142.86, 291.25, 225.00, 214.29, 30.00, 12.40, 15.93},	/* before January 15, 1984 */
+	{152.10, 296.81, 204.30, 206.20, 27.19, 15.3032, 14.38}	/* after  Jaunary 15, 1984 */
+    };
+    double Lmin[][7] = {
+	{-1.52, -2.84, -1.17, -1.51, -0.37, 2.00, -0.15},
+	{0.00, 0.00, 0.00, 0.00, 0.00, 4.84, 0.00},
+	{-1.50, -2.80, -1.20, -1.50, -0.37, 1.2378, -0.15}
+    };
+
+    /** Chander, Markham and Helder. Remote Sensing of Environment, 113 (2009) */
+
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1983., 1795., 1539., 1028., 219.8, 0., 83.49 };
+
+    /* Thermal band calibration constants: K1 = 671.62   K2 = 1284.30 */
+
+    julian = julian_char(lsat->creation);
+    if (julian < julian_char("1983-08-01"))
+	i = 0;
+    else if (julian < julian_char("1984-01-15"))
+	i = 1;
+    else
+	i = 2;
+    lmax = Lmax[i];
+    lmin = Lmin[i];
+
+    lsat->number = 4;
+    sensor_TM(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	lsat->band[i].esun = *(esun + j);
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+	if (lsat->band[i].thermal) {
+	    lsat->band[i].K1 = 671.62;
+	    lsat->band[i].K2 = 1284.30;
+	}
+    }
+    return;
+}
+
+
+/****************************************************************************
+ * PURPOSE:     Store values of Landsat-5 MSS/TM
+ *              March 1, 1984 to today
+ *****************************************************************************/
+void set_MSS5(lsat_data * lsat)
+{
+    int i, j;
+    double julian, *lmax, *lmin;
+
+    /** Brian L. Markham and John L. Barker.
+        EOSAT Landsat Technical Notes, No. 1, 1986 */
+    /* Spectral radiances at detector */
+    double Lmax[][4] = {
+	{240., 170., 150., 127.},	/* before   April 6, 1984    */
+	{268., 179., 159., 123.},	/* betweeen                  */
+	{268., 179., 148., 123.}	/* after    November 9, 1984 */
+    };
+    double Lmin[][4] = {
+	{4., 3., 4., 2.},
+	{3., 3., 4., 3.},
+	{3., 3., 5., 3.}
+    };
+
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1824., 1570., 1249., 853.4 };
+
+    julian = julian_char(lsat->creation);
+    if (julian < julian_char("1984-04-06"))
+	i = 0;
+    else if (julian < julian_char("1984-11-09"))
+	i = 1;
+    else
+	i = 2;
+    lmax = Lmax[i];
+    lmin = Lmin[i];
+
+    lsat->number = 5;
+    sensor_MSS(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	lsat->band[i].esun = *(esun + j);
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+    }
+    return;
+}
+
+void set_TM5(lsat_data * lsat)
+{
+    int i, j;
+    double julian, *lmax, *lmin, jbuf;
+
+    /** Gyanesh Chander and Brian Markham.
+        IEEE Transactions On Geoscience And Remote Sensing, Vol. 41, No. 11, November 2003 */
+
+    /* Spectral radiances at detector */
+    double Lmax[][7] = {
+	{152.10, 296.81, 204.30, 206.20, 27.19, 15.303, 14.38},	/* before May 4, 2003 */
+	{193.00, 365.00, 264.00, 221.00, 30.20, 15.303, 16.50},	/* after May 4, 2003 */
+	{169.00, 333.00, 264.00, 221.00, 30.20, 15.303, 16.50}	/* after April 2, 2007 */
+    };
+    double Lmin[][7] = {
+	{-1.52, -2.84, -1.17, -1.51, -0.37, 1.2378, -0.15},
+	{-1.52, -2.84, -1.17, -1.51, -0.37, 1.2378, -0.15},
+	{-1.52, -2.84, -1.17, -1.51, -0.37, 1.2378, -0.15}
+    };
+
+	/** Chander, Markham and Helder. Remote Sensing of Environment, 113 (2009) */
+
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1983., 1796., 1536., 1031., 220.0, 0., 83.44 };
+
+    /* Thermal band calibration constants: K1 = 607.76   K2 = 1260.56 */
+
+    julian = julian_char(lsat->creation);
+    if (julian < julian_char("2003-05-04"))
+	i = 0;
+    else if (julian < julian_char("2007-04-02"))
+	i = 1;
+    else
+	i = 2;
+    lmax = Lmax[i];
+    lmin = Lmin[i];
+    if (i == 2) {		/* in Chander, Markham and Barsi 2007 */
+	julian = julian_char(lsat->date);	/* Yes, here acquisition date */
+	if (julian >= julian_char("1992-01-01")) {
+	    lmax[0] = 193.0;
+	    lmax[1] = 365.0;
+	}
+    }
+
+    jbuf = julian_char("2004-04-04");
+    if (julian >= jbuf) {
+	G_warning
+	    ("Using QCalMin=1.0 as a NLAPS product processed after 4/4/2004");
+	G_warning("Discard this message if using the L5_MTL (-t) flag");
+    }
+    lsat->number = 5;
+    sensor_TM(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	if (julian >= jbuf)
+	    lsat->band[i].qcalmin = 1.;
+	lsat->band[i].esun = *(esun + j);
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+	if (lsat->band[i].thermal) {
+	    lsat->band[i].K1 = 607.76;
+	    lsat->band[i].K2 = 1260.56;
+	}
+    }
+    return;
+}
+
+
+/****************************************************************************
+ * PURPOSE:     Store values of Landsat-7 ETM+
+ *              April 15, 1999 to today
+ *****************************************************************************/
+void set_ETM(lsat_data * lsat, char gain[])
+{
+    int i, k, j;
+    double julian, *lmax, *lmin;
+
+    /** Richard Irish.
+        Landsat 7. Science Data Users Handbook. Last update: February 17, 2007 */
+
+    /* Spectral radiances at detector */
+    /* - LOW GAIN - */
+    double LmaxL[][8] = {
+	{297.5, 303.4, 235.5, 235.0, 47.70, 17.04, 16.60, 244.0},	/* before      July 1, 2000 */
+	{293.7, 300.9, 234.4, 241.1, 47.57, 17.04, 16.54, 243.1}	/* on or after July 1, 2000 */
+    };
+    double LminL[][8] = {
+	{-6.2, -6.0, -4.5, -4.5, -1.0, 0.0, -0.35, -5.0},
+	{-6.2, -6.4, -5.0, -5.1, -1.0, 0.0, -0.35, -4.7}
+    };
+    /* - HIGH GAIN - */
+    double LmaxH[][8] = {
+	{194.3, 202.4, 158.6, 157.5, 31.76, 12.65, 10.932, 158.4},
+	{191.6, 196.5, 152.9, 157.4, 31.06, 12.65, 10.80, 158.3}
+    };
+    double LminH[][8] = {
+	{-6.2, -6.0, -4.5, -4.5, -1.0, 3.2, -0.35, -5.0},
+	{-6.2, -6.4, -5.0, -5.1, -1.0, 3.2, -0.35, -4.7}
+    };
+
+	/** Chander, Markham and Helder. Remote Sensing of Environment, 113 (2009) */
+
+    /* Solar exoatmospheric spectral irradiances */
+    double esun[] = { 1997., 1812., 1533., 1039., 230.8, 0., 84.90, 1362. };
+
+    /*  Thermal band calibration constants: K1 = 666.09   K2 = 1282.71 */
+
+    julian = julian_char(lsat->creation);
+    if (julian < julian_char("2000-07-01"))
+	k = 0;
+    else
+	k = 1;
+
+    lsat->number = 7;
+    sensor_ETM(lsat);
+
+    lsat->dist_es = earth_sun(lsat->date);
+
+    for (i = 0; i < lsat->bands; i++) {
+	j = lsat->band[i].number - 1;
+	lsat->band[i].esun = *(esun + j);
+	if (gain[i] == 'H' || gain[i] == 'h') {
+	    lmax = LmaxH[k];
+	    lmin = LminH[k];
+	}
+	else {
+	    lmax = LmaxL[k];
+	    lmin = LminL[k];
+	}
+	lsat->band[i].lmax = *(lmax + j);
+	lsat->band[i].lmin = *(lmin + j);
+	if (lsat->band[i].thermal) {
+	    lsat->band[i].K1 = 666.09;
+	    lsat->band[i].K2 = 1282.71;
+	}
+    }
+    return;
+}

+ 21 - 0
imagery/i.landsat.toar/local_proto.h

@@ -0,0 +1,21 @@
+#ifndef _LOCAL_PROTO_H
+#define _LOCAL_PROTO_H
+
+#include <string.h>
+#include "landsat.h"
+
+void met_ETM(char *, lsat_data *);
+void met_TM5(char *, lsat_data *);
+void mtl_TM5(char *, lsat_data *);
+
+void set_MSS1(lsat_data *);
+void set_MSS2(lsat_data *);
+void set_MSS3(lsat_data *);
+void set_MSS4(lsat_data *);
+void set_MSS5(lsat_data *);
+
+void set_TM4(lsat_data *);
+void set_TM5(lsat_data *);
+void set_ETM(lsat_data *, char[]);
+
+#endif

+ 615 - 0
imagery/i.landsat.toar/main.c

@@ -0,0 +1,615 @@
+
+/****************************************************************************
+ *
+ * MODULE:       i.landsat.toar
+ *
+ * AUTHOR(S):    E. Jorge Tizado - ej.tizado@unileon.es
+ *		 Hamish Bowman (small grassification cleanups)
+ *               Yann Chemin (v7 + L5TM _MTL.txt support)
+ *
+ * PURPOSE:      Calculate TOA Radiance or Reflectance and Kinetic Temperature
+ *               for Landsat 1/2/3/4/5 MS, 4/5 TM or 7 ETM+
+ *
+ * COPYRIGHT:    (C) 2002, 2005 2008 by the GRASS Development Team
+ *
+ *               This program is free software under the GNU General Public
+ *               License (>=v2). Read the file COPYING that comes with GRASS
+ *               for details.
+ *
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <grass/gis.h>
+#include <grass/raster.h>
+#include <grass/glocale.h>
+
+#include "local_proto.h"
+
+int main(int argc, char *argv[])
+{
+    struct History history;
+    struct GModule *module;
+
+    struct Cell_head cellhd, window;
+    char *mapset;
+
+    void *inrast, *outrast;
+    int infd, outfd;
+    void *ptr;
+    int nrows, ncols, row, col;
+
+    RASTER_MAP_TYPE in_data_type;
+
+    int verbose = TRUE;
+    struct Option *input, *metfn, *sensor, *adate, *pdate, *elev,
+	*bgain, *metho, *perc, *dark, *satz, *atmo;
+    char *name, *met;
+    struct Flag *msss, *verbo, *frad, *l5_mtl;
+
+    lsat_data lsat;
+    char band_in[127], band_out[127];
+    int i, j, q, method, pixel, dn_dark[MAX_BANDS], dn_mode[MAX_BANDS];
+    int sensor_id;
+    double qcal, rad, ref, percent, ref_mode, sat_zenith, rayleigh;
+
+    struct Colors colors;
+    struct FPRange range;
+    double min, max;
+    unsigned long hist[256], h_max;
+
+    /* initialize GIS environment */
+    G_gisinit(argv[0]);
+
+    /* initialize module */
+    module = G_define_module();
+    module->description =
+	_("Calculates top-of-atmosphere radiance or reflectance and temperature for Landsat MSS/TM/ETM+.");
+    G_add_keyword(_("imagery"));
+    G_add_keyword(_("landsat"));
+    G_add_keyword(_("top-of-atmosphere radiance"));
+    G_add_keyword(_("top-of-atmosphere reflectance"));
+    G_add_keyword(_("dos-type simple atmospheric correction"));
+
+    /* It defines the different parameters */
+    input = G_define_option();
+    input->key = "band_prefix";
+    input->type = TYPE_STRING;
+    input->required = YES;
+    input->gisprompt = "input,cell,raster";
+    input->description = _("Base name of the landsat band rasters (.#)");
+
+    metfn = G_define_option();
+    metfn->key = "metfile";
+    metfn->type = TYPE_STRING;
+    metfn->required = NO;
+    metfn->gisprompt = "old_file,file,file";
+    metfn->description = _("Landsat ETM+ or TM5 header file (.met)");
+
+    metho = G_define_option();
+    metho->key = "method";
+    metho->type = TYPE_STRING;
+    metho->required = NO;
+    metho->options = "uncorrected,corrected,dos1,dos2,dos2b,dos3,dos4";
+    metho->description = _("Atmospheric correction method");
+    metho->answer = "uncorrected";
+
+    sensor = G_define_option();
+    sensor->key = "sensor";
+    sensor->type = TYPE_INTEGER;
+    sensor->description = _("Spacecraft sensor");
+    sensor->options = "1,2,3,4,5,7";
+    sensor->descriptions =
+	_("1;Landsat-1 MSS;"
+	  "2;Landsat-2 MSS;"
+	  "3;Landsat-3 MSS;"
+	  "4;Landsat-4 TM;" "5;Landsat-5 TM;" "7;Landsat-7 ETM+");
+    sensor->required = NO;
+
+    adate = G_define_option();
+    adate->key = "date";
+    adate->type = TYPE_STRING;
+    adate->required = NO;
+    adate->key_desc = "yyyy-mm-dd";
+    adate->description = _("Image acquisition date (yyyy-mm-dd)");
+
+    elev = G_define_option();
+    elev->key = "solar_elevation";
+    elev->type = TYPE_DOUBLE;
+    elev->required = NO;
+    elev->description = _("Solar elevation in degrees");
+
+    bgain = G_define_option();
+    bgain->key = "gain";
+    bgain->type = TYPE_STRING;
+    bgain->required = NO;
+    bgain->description =
+	_("Gain (H/L) of all Landsat ETM+ bands (1-5,61,62,7,8)");
+
+    pdate = G_define_option();
+    pdate->key = "product_date";
+    pdate->type = TYPE_STRING;
+    pdate->required = NO;
+    pdate->key_desc = "yyyy-mm-dd";
+    pdate->description = _("Image creation date (yyyy-mm-dd)");
+
+    perc = G_define_option();
+    perc->key = "percent";
+    perc->type = TYPE_DOUBLE;
+    perc->required = NO;
+    perc->description = _("Percent of solar radiance in path radiance");
+    perc->answer = "0.01";
+
+    dark = G_define_option();
+    dark->key = "pixel";
+    dark->type = TYPE_INTEGER;
+    dark->required = NO;
+    dark->description =
+	_("Minimum pixels to consider digital number as dark object");
+    dark->answer = "1000";
+
+    satz = G_define_option();
+    satz->key = "sat_zenith";
+    satz->type = TYPE_DOUBLE;
+    satz->required = NO;
+    satz->description = _("Satellite zenith in degrees");
+    satz->answer = "8.2000";
+
+    atmo = G_define_option();
+    atmo->key = "rayleigh";
+    atmo->type = TYPE_DOUBLE;
+    atmo->required = NO;
+    atmo->description = _("Rayleigh atmosphere");	/* scattering coefficient? */
+    atmo->answer = "0.0";
+
+    /* define the different flags */
+    frad = G_define_flag();
+    frad->key = 'r';
+    frad->description = _("Output at-sensor radiance for all bands");
+
+    msss = G_define_flag();
+    msss->key = 's';
+    msss->description = _("Set sensor of Landsat-4/5 to MSS");
+
+    l5_mtl = G_define_flag();
+    l5_mtl->key = 't';
+    l5_mtl->description = _("Landsat 5TM has a .MTL file instead of .met");
+
+    verbo = G_define_flag();
+    verbo->key = 'v';
+    verbo->description = _("Show parameters applied");
+
+    /* options and afters parser */
+    if (G_parser(argc, argv))
+	exit(EXIT_FAILURE);
+
+
+    /*****************************************
+     * ---------- START --------------------
+     * Stores options and flag to variables
+     *****************************************/
+    met = metfn->answer;
+    name = input->answer;
+
+    if (adate->answer != NULL) {
+	strncpy(lsat.date, adate->answer, 11);
+	lsat.date[10] = '\0';
+	if (strlen(lsat.date) != 10)
+	    G_fatal_error(_("Illegal date format: [%s] (yyyy-mm-dd)"),
+			  lsat.date);
+    }
+    else
+	lsat.date[0] = '\0';
+
+    if (pdate->answer != NULL) {
+	strncpy(lsat.creation, pdate->answer, 11);
+	lsat.creation[10] = '\0';
+	if (strlen(lsat.creation) != 10)
+	    G_fatal_error(_("Illegal date format: [%s] (yyyy-mm-dd)"),
+			  lsat.creation);
+    }
+    else
+	lsat.creation[0] = '\0';
+
+    lsat.sun_elev = elev->answer == NULL ? 0. : atof(elev->answer);
+    percent = atof(perc->answer);
+    pixel = atoi(dark->answer);
+    sat_zenith = atof(satz->answer);
+    rayleigh = atof(atmo->answer);
+
+    if (sensor->answer)
+	sensor_id = atoi(sensor->answer);
+    else
+	G_fatal_error(_("Must select type of satellite"));
+
+    /* Data from MET file: only Landsat-7 ETM+ and Landsat-5 TM  */
+    if (met != NULL) {
+	if (sensor_id == 7)
+	    met_ETM(met, &lsat);
+	else if (l5_mtl->answer)
+	    mtl_TM5(met, &lsat);
+	else
+	    met_TM5(met, &lsat);
+
+	G_debug(1, "lsat.number = %d, lsat.sensor = [%s]", lsat.number,
+		lsat.sensor);
+	if (!lsat.sensor || lsat.number > 7 || lsat.number < 1)
+	    G_fatal_error(_("Failed to identify satellite"));
+
+	G_message(_("Landsat-%d %s with data set in met file [%s]"),
+		  lsat.number, lsat.sensor, met);
+	if (elev->answer != NULL)
+	    lsat.sun_elev = atof(elev->answer);	/* Overwrite solar elevation of met file */
+    }
+    /* Data from date and solar elevation */
+    else if (adate->answer == NULL || elev->answer == NULL) {
+	G_fatal_error(_("Lacking date or solar elevation for this satellite"));
+    }
+    else {
+	if (sensor_id == 7) {	/* Need gain */
+	    if (bgain->answer != NULL && strlen(bgain->answer) == 9) {
+		set_ETM(&lsat, bgain->answer);
+		G_message("Landsat 7 ETM+");
+	    }
+	    else {
+		G_fatal_error(_("Landsat-7 requires band gain with 9 (H/L) data"));
+	    }
+	}
+	else {			/* Not need gain */
+	    if (sensor_id == 5) {
+		if (msss->answer)
+		    set_MSS5(&lsat);
+		else
+		    set_TM5(&lsat);
+		G_message("Landsat-5 %s", lsat.sensor);
+	    }
+	    else if (sensor_id == 4) {
+		if (msss->answer)
+		    set_MSS4(&lsat);
+		else
+		    set_TM4(&lsat);
+		G_message("Landsat-4 %s", lsat.sensor);
+	    }
+	    else if (sensor_id == 3) {
+		set_MSS3(&lsat);
+		G_message("Landsat-3 MSS");
+	    }
+	    else if (sensor_id == 2) {
+		set_MSS2(&lsat);
+		G_message("Landsat-2 MSS");
+	    }
+	    else if (sensor_id == 1) {
+		set_MSS1(&lsat);
+		G_message("Landsat-1 MSS");
+	    }
+	    else {
+		G_fatal_error(_("Unknown satellite type"));
+	    }
+	}
+    }
+
+	/*****************************************
+	* ------------ PREPARATION --------------
+	*****************************************/
+    if (G_strcasecmp(metho->answer, "corrected") == 0)
+	method = CORRECTED;
+    else if (G_strcasecmp(metho->answer, "dos1") == 0)
+	method = DOS1;
+    else if (G_strcasecmp(metho->answer, "dos2") == 0)
+	method = DOS2;
+    else if (G_strcasecmp(metho->answer, "dos2b") == 0)
+	method = DOS2b;
+    else if (G_strcasecmp(metho->answer, "dos3") == 0)
+	method = DOS3;
+    else if (G_strcasecmp(metho->answer, "dos4") == 0)
+	method = DOS4;
+    else
+	method = UNCORRECTED;
+
+    /*
+       if (metho->answer[3] == 'r')            method = CORRECTED;
+       else if (metho->answer[3] == '1')       method = DOS1;
+       else if (metho->answer[3] == '2')       method = (metho->answer[4] == '\0') ? DOS2 : DOS2b;
+       else if (metho->answer[3] == '3')       method = DOS3;
+       else if (metho->answer[3] == '4')       method = DOS4;
+       else method = UNCORRECTED;
+     */
+
+    for (i = 0; i < lsat.bands; i++) {
+	dn_mode[i] = 0;
+	dn_dark[i] = (int)lsat.band[i].qcalmin;
+	/* Calculate dark pixel */
+	if (method > DOS && !lsat.band[i].thermal) {
+	    for (j = 0; j < 256; j++)
+		hist[j] = 0L;
+
+	    snprintf(band_in, 127, "%s.%d", name, lsat.band[i].code);
+	    if ((infd = Rast_open_old(band_in, "")) < 0)
+		G_fatal_error(_("Unable to open raster map <%s>"), band_in);
+	    Rast_get_cellhd(band_in, "", &cellhd);
+	    G_set_window(&cellhd);
+
+	    in_data_type = Rast_get_map_type(infd);
+	    inrast = Rast_allocate_buf(in_data_type);
+
+	    nrows = Rast_window_rows();
+	    ncols = Rast_window_cols();
+
+	    G_message("Calculating dark pixel of [%s] ... ", band_in);
+	    for (row = 0; row < nrows; row++) {
+		Rast_get_row(infd, inrast, row, in_data_type);
+		for (col = 0; col < ncols; col++) {
+		    switch (in_data_type) {
+		    case CELL_TYPE:
+			ptr = (void *)((CELL *) inrast + col);
+			q = (int)*((CELL *) ptr);
+			break;
+		    case FCELL_TYPE:
+			ptr = (void *)((FCELL *) inrast + col);
+			q = (int)*((FCELL *) ptr);
+			break;
+		    case DCELL_TYPE:
+			ptr = (void *)((DCELL *) inrast + col);
+			q = (int)*((DCELL *) ptr);
+			break;
+		    }
+		    if (!Rast_is_null_value(ptr, in_data_type) &&
+			q >= lsat.band[i].qcalmin && q < 256)
+			hist[q]++;
+		}
+	    }
+	    /* DN of dark object */
+	    for (j = lsat.band[i].qcalmin; j < 256; j++) {
+		if (hist[j] >= pixel) {
+		    dn_dark[i] = j;
+		    break;
+		}
+	    }
+	    /* Mode of DN */
+	    h_max = 0L;
+	    for (j = lsat.band[i].qcalmin; j < 241; j++) {	/* Exclude ptentially saturated < 240 */
+		/* G_debug(5, "%d-%ld", j, hist[j]); */
+		if (hist[j] > h_max) {
+		    h_max = hist[j];
+		    dn_mode[i] = j;
+		}
+	    }
+	    G_message("... DN = %.2d [%d] : mode %.2d [%d] %s",
+		      dn_dark[i], hist[dn_dark[i]],
+		      dn_mode[i], hist[dn_mode[i]],
+		      hist[255] >
+		      hist[dn_mode[i]] ? ", excluding DN > 241" : "");
+
+	    G_free(inrast);
+	    Rast_close(infd);
+	}
+	/* Calculate transformation constants */
+	lsat_bandctes(&lsat, i, method, percent, dn_dark[i], sat_zenith,
+		      rayleigh);
+    }
+
+    if (strlen(lsat.creation) == 0)
+	G_fatal_error(_("Unknown production date"));
+
+	/*****************************************
+	 * ------------ VERBOSE ------------------
+	 *****************************************/
+    if (verbo->answer) {
+	fprintf(stdout, " ACQUISITION DATE %s [production date %s]\n",
+		lsat.date, lsat.creation);
+	fprintf(stdout, "   earth-sun distance    = %.8lf\n", lsat.dist_es);
+	fprintf(stdout, "   solar elevation angle = %.8lf\n", lsat.sun_elev);
+	fprintf(stdout, "   Method of calculus = %s\n",
+		(method == CORRECTED ? "CORRECTED"
+		 : (method == UNCORRECTED ? "UNCORRECTED" : metho->answer)));
+	if (method > DOS) {
+	    fprintf(stdout,
+		    "   percent of solar irradiance in path radiance = %.4lf\n",
+		    percent);
+	}
+	for (i = 0; i < lsat.bands; i++) {
+	    fprintf(stdout, "-------------------\n");
+	    fprintf(stdout, " BAND %d %s (code %d)\n",
+		    lsat.band[i].number,
+		    (lsat.band[i].thermal ? "thermal " : ""),
+		    lsat.band[i].code);
+	    fprintf(stdout,
+		    "   calibrated digital number (DN): %.1lf to %.1lf\n",
+		    lsat.band[i].qcalmin, lsat.band[i].qcalmax);
+	    fprintf(stdout, "   calibration constants (L): %.3lf to %.3lf\n",
+		    lsat.band[i].lmin, lsat.band[i].lmax);
+	    fprintf(stdout, "   at-%s radiance = %.5lf * DN + %.5lf\n",
+		    (method > DOS ? "surface" : "sensor"), lsat.band[i].gain,
+		    lsat.band[i].bias);
+	    if (lsat.band[i].thermal) {
+		fprintf(stdout,
+			"   at-sensor temperature = %.3lf / log[(%.3lf / radiance) + 1.0]\n",
+			lsat.band[i].K2, lsat.band[i].K1);
+	    }
+	    else {
+		fprintf(stdout,
+			"   mean solar exoatmospheric irradiance (ESUN): %.3lf\n",
+			lsat.band[i].esun);
+		fprintf(stdout, "   at-%s reflectance = radiance / %.5lf\n",
+			(method > DOS ? "surface" : "sensor"),
+			lsat.band[i].K2);
+		if (method > DOS) {
+		    fprintf(stdout,
+			    "   the darkness DN with a least %d pixels is %d\n",
+			    pixel, dn_dark[i]);
+		    fprintf(stdout, "   the mode of DN is %d\n", dn_mode[i]);
+		}
+	    }
+	}
+	fprintf(stdout, "-------------------\n");
+	fflush(stdout);
+    }
+
+	/*****************************************
+	 * ------------ CALCULUS -----------------
+	 *****************************************/
+
+    for (i = 0; i < lsat.bands; i++) {
+	snprintf(band_in, 127, "%s.%d", name, lsat.band[i].code);
+	snprintf(band_out, 127, "%s.toar.%d", name, lsat.band[i].code);
+
+	if ((infd = Rast_open_old(band_in, "")) < 0)
+	    G_fatal_error(_("Unable to open raster map <%s>"), band_in);
+	in_data_type = Rast_get_map_type(infd);
+	Rast_get_cellhd(band_in, mapset, &cellhd);
+
+	/* set same size as original band raster */
+	G_set_window(&cellhd);
+
+	/* controlling, if we can write the raster */
+	if (G_legal_filename(band_out) < 0)
+	    G_fatal_error(_("<%s> is an illegal file name"), band_out);
+
+	if ((outfd = Rast_open_new(band_out, DCELL_TYPE)) < 0)
+	    G_fatal_error(_("Unable to create raster map <%s>"), band_out);
+
+	/* Allocate input and output buffer */
+	inrast = Rast_allocate_buf(in_data_type);
+	outrast = Rast_allocate_buf(DCELL_TYPE);
+
+	nrows = Rast_window_rows();
+	ncols = Rast_window_cols();
+	/* ================================================================= */
+	G_message(_("Writing %s of <%s> to <%s> ..."),
+		  (frad->answer ? _("radiance")
+		   : (lsat.band[i].
+		      thermal) ? _("temperature") : _("reflectance")),
+		  band_in, band_out);
+	for (row = 0; row < nrows; row++) {
+	    if (verbose)
+		G_percent(row, nrows, 2);
+
+	    Rast_get_row(infd, inrast, row, in_data_type);
+	    for (col = 0; col < ncols; col++) {
+		switch (in_data_type) {
+		case CELL_TYPE:
+		    ptr = (void *)((CELL *) inrast + col);
+		    qcal = (double)((CELL *) inrast)[col];
+		    break;
+		case FCELL_TYPE:
+		    ptr = (void *)((FCELL *) inrast + col);
+		    qcal = (double)((FCELL *) inrast)[col];
+		    break;
+		case DCELL_TYPE:
+		    ptr = (void *)((DCELL *) inrast + col);
+		    qcal = (double)((DCELL *) inrast)[col];
+		    break;
+		}
+		if (Rast_is_null_value(ptr, in_data_type) ||
+		    qcal < lsat.band[i].qcalmin) {
+		    Rast_set_d_null_value((DCELL *) outrast + col, 1);
+		}
+		else {
+		    rad = lsat_qcal2rad(qcal, &lsat.band[i]);
+		    if (frad->answer) {
+			ref = rad;
+		    }
+		    else {
+			if (lsat.band[i].thermal) {
+			    ref = lsat_rad2temp(rad, &lsat.band[i]);
+			}
+			else {
+			    ref = lsat_rad2ref(rad, &lsat.band[i]);
+			    if (ref < 0. && method > DOS)
+				ref = 0.;
+			}
+		    }
+		    ((DCELL *) outrast)[col] = ref;
+		}
+	    }
+	    Rast_put_row(outfd, outrast, DCELL_TYPE);
+	}
+	ref_mode = 0.;
+	if (method > DOS && !lsat.band[i].thermal) {
+	    ref_mode = lsat_qcal2rad(dn_mode[i], &lsat.band[i]);
+	    ref_mode = lsat_rad2ref(ref_mode, &lsat.band[i]);
+	}
+
+	/* ================================================================= */
+
+	G_free(inrast);
+	Rast_close(infd);
+	G_free(outrast);
+	Rast_close(outfd);
+
+	/* needed ?
+	   if (out_type != CELL_TYPE)
+	   G_quantize_fp_map_range(band_out, G_mapset(), 0., 360., 0, 360);
+	 */
+	/* set grey255 colortable */
+	Rast_init_colors(&colors);
+	Rast_read_fp_range(band_out, G_mapset(), &range);
+	Rast_get_fp_range_min_max(&range, &min, &max);
+	Rast_make_grey_scale_fp_colors(&colors, min, max);
+	Rast_write_colors(band_out, G_mapset(), &colors);
+
+	/* Initialize the 'hist' structure with basic info */
+	Rast_short_history(band_out, "raster", &history);
+	/* Append a string to the 'history' structure */
+	Rast_append_format_history(&history,
+				   " %s of Landsat-%d %s (method %s)",
+				   lsat.band[i].
+				   thermal ? "Temperature" : "Reflectance",
+				   lsat.number, lsat.sensor, metho->answer);
+	Rast_append_history(&history,
+			    "----------------------------------------------------------------");
+	Rast_append_format_history(&history,
+				   " Acquisition date ...................... %s",
+				   lsat.date);
+	Rast_append_format_history(&history,
+				   " Production date ....................... %s\n",
+				   lsat.creation);
+	Rast_append_format_history(&history,
+				   " Earth-sun distance (d) ................ %.8lf",
+				   lsat.dist_es);
+	Rast_append_format_history(&history,
+				   " Digital number (DN) range ............. %.0lf to %.0lf",
+				   lsat.band[i].qcalmin,
+				   lsat.band[i].qcalmax);
+	Rast_append_format_history(&history,
+				   " Calibration constants (Lmin to Lmax) .. %+.3lf to %+.3lf",
+				   lsat.band[i].lmin, lsat.band[i].lmax);
+	Rast_append_format_history(&history,
+				   " DN to Radiance (gain and bias) ........ %+.5lf and %+.5lf",
+				   lsat.band[i].gain, lsat.band[i].bias);
+	if (lsat.band[i].thermal) {
+	    Rast_append_format_history(&history,
+				       " Temperature (K1 and K2) ............... %.3lf and %.3lf",
+				       lsat.band[i].K1, lsat.band[i].K2);
+	}
+	else {
+	    Rast_append_format_history(&history,
+				       " Mean solar irradiance (ESUN) .......... %.3lf",
+				       lsat.band[i].esun);
+	    Rast_append_format_history(&history,
+				       " Reflectance = Radiance divided by ..... %.5lf",
+				       lsat.band[i].K2);
+	    if (method > DOS) {
+		Rast_append_history(&history, " ");
+		Rast_append_format_history(&history,
+					   " Dark object (%4d pixels) DN = ........ %d",
+					   pixel, dn_dark[i]);
+		Rast_append_format_history(&history,
+					   " Mode in reflectance histogram ......... %.5lf",
+					   ref_mode);
+	    }
+	}
+	Rast_append_history(&history,
+			    "-----------------------------------------------------------------");
+
+	Rast_command_history(&history);
+	Rast_write_history(band_out, &history);
+
+	if (lsat.band[i].thermal)
+	    Rast_write_units(band_out, "Kelvin");
+	/* else units = ...? */
+	/* set raster timestamp from acq date? (see r.timestamp module) */
+    }
+
+    exit(EXIT_SUCCESS);
+}