├── .idea
├── misc.xml
├── modules.xml
├── vcs.xml
├── water-quality-gee.iml
└── workspace.xml
├── LICENSE
├── README.md
├── javascript
├── Ls8_FAI.js
├── Ls8_WQ_TimeSeries.js
├── Ls8_single_image.js
├── MODIS_WQ_TimeSeries.js
├── s2_FAI.js
├── s2_WQ_TimeSeries.js
└── s2_single_image.js
└── python
├── README.md
├── ls8_fai.py
├── ls8_single_image.py
├── ls8_wq_timeseries.py
├── modis_wq_timeseries.py
├── s2_fai.py
├── s2_single_image.py
└── s2_wq_timeseries.py
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/water-quality-gee.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # water-quality-gee
2 | This repository contains scripts for assessing water quality from remotely sensed data using Google Earth Engine
3 |
4 | If using any of these materials or scripts, please cite: Page, B.P. and D. Mishra (2018), A Modified Atmospheric Correction when Coupling Sentinel-2A and Landsat-8 for Inland Water Quality Monitoring, ISPRS J PHOTOGRAMM, *In Review*
5 |
6 |
7 | ## REQUIREMETS:
8 | - The scripts require a [Google Earth Engine](https://www.earthengine.google.com "Google Earth Engine Homepage") account.
9 |
10 | To use in the [GEE Code Editor](https://code.earthengine.google.com), download the javascript files and click run.
11 |
12 | To use in a Python environment, install the GEE Python API and run programs in the Python IDE of your choice.
13 |
14 |
15 | ## CONTENTS:
16 | - javascript/ contains javascript implementation of water quality processing code
17 | - python/ contains Python implementation of water quality processing code
18 |
19 |
20 | ## CONTRIBUTING:
21 | If you have any ideas, just [open an issue](https://github.com/SERVIR/water-quality-gee/issues) and tell us what you think.
22 |
23 | If you'd like to contribute, please fork the repository and make changes as
24 | you'd like. Pull requests are welcome.
25 |
26 |
27 | ## DISCLAIMERS:
28 | This is a work-in-progress. The scripts are offered as-is, although we will make every effort to test
29 | and fix any issues, we can't guarantee that these will be fit for your purpose.
30 |
--------------------------------------------------------------------------------
/javascript/Ls8_FAI.js:
--------------------------------------------------------------------------------
1 | // This script processes a single Landsat 8 Tier 1 Raw image to Rayleigh corrected reflectances.
2 | // The floating algal index (FAI) is calculated from Rrc over water bodies.
3 | // How to use:
4 | // (1) If a "geometry" variable exists in the imports window, delete it.
5 | // (2) Within the map, select the point button near the top left side.
6 | // (3) Create a new point by clicking on a location of interest and click run.
7 | // (4) The "available imagery" ImageCollection within the console displays all available imagery
8 | // over that location with the necessary filters described by the user below.
9 | // (5) Expand the features within the "available imagery" image collection and select an image
10 | // by highlighting the FILE_ID and pasting it here:
11 | var l8 = ee.Image('LANDSAT/LC08/C01/T1/LC08_170060_20171226');
12 | // (6) Click "Run"
13 | // (7) Export the image to your Google Drive by clicking on the "tasks" tab and clicking "RUN", be sure to specify
14 | // the proper folder.
15 |
16 | // Author: Benjamin Page //
17 | // Citations:
18 | // Page, B.P., Kumar, A. and Mishra, D.R., 2018. A novel cross-satellite based assessment of the spatio-temporal development of a cyanobacterial harmful algal bloom. International Journal of Applied Earth Observation and Geoinformation, 66, pp.69-81.
19 |
20 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
21 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
22 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
23 | // User Input //
24 |
25 | // begin date
26 | var iniDate = '2015-05-01';
27 |
28 | // end date
29 | var endDate = '2018-03-31';
30 |
31 | var oliCloudPerc = 5
32 |
33 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
34 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
35 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
36 |
37 | // Import Collections //
38 |
39 | // landsat 8 raw dn
40 | var OLI_DN = ee.ImageCollection('LANDSAT/LC08/C01/T1');
41 |
42 | // landsat-8 surface reflactance product (for masking purposes)
43 | var SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
44 |
45 | // toms / omi
46 | var ozone = ee.ImageCollection('TOMS/MERGED');
47 |
48 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
49 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
50 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
51 | // Filtering Collection and Masking //
52 |
53 | var pi = ee.Image(3.141592);
54 |
55 | // water mask
56 | var startMonth = 5;
57 | var endMonth = 9;
58 | var startYear = 2013;
59 | var endYear = 2017;
60 |
61 | var forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'));
62 | var mask = ee.Image(forMask.select('B6').median().lt(600))
63 | mask = mask.updateMask(mask)
64 | Map.addLayer(mask, {}, 'mask', false)
65 |
66 | // filter landsat 8 collection
67 | var FC_OLI = OLI_DN.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUD_COVER', "less_than", oliCloudPerc);
68 | print(FC_OLI, 'Available Imagery')
69 |
70 | print(l8, 'l8 image info')
71 |
72 | // oli image date
73 | var oliDate = l8.date();
74 | print(oliDate, 'l8 Date')
75 |
76 | var footprint = l8.geometry()
77 | Map.centerObject(footprint)
78 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
79 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
80 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
81 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
82 | // single Ls8 image Rayleigh Correction //
83 |
84 | // dem
85 | var DEM_OLI = ee.Image('USGS/SRTMGL1_003').clip(footprint);
86 |
87 | // ozone
88 | var DU_OLI = ee.Image(ozone.filterDate(iniDate,endDate).filterBounds(footprint).mean());
89 |
90 | //Julian Day
91 | var imgDate_OLI = ee.Date(l8.get('system:time_start'));
92 | var FOY_OLI = ee.Date.fromYMD(imgDate_OLI.get('year'),1,1);
93 | var JD_OLI = imgDate_OLI.difference(FOY_OLI,'day').int().add(1);
94 |
95 | // Earth-Sun distance
96 | var d_OLI = ee.Image.constant(l8.get('EARTH_SUN_DISTANCE'));
97 |
98 | //Sun elevation
99 | var SunEl_OLI = ee.Image.constant(l8.get('SUN_ELEVATION'));
100 |
101 | //Sun azimuth
102 | var SunAz_OLI = ee.Image.constant(l8.get('SUN_AZIMUTH'));
103 |
104 | //Satellite zenith
105 | var SatZe_OLI = ee.Image(0.0)
106 | var cosdSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).cos();
107 | var sindSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).sin();
108 |
109 | //Satellite azimuth
110 | var SatAz_OLI = ee.Image(0.0)
111 |
112 | //Sun zenith
113 | var SunZe_OLI = ee.Image(90).subtract(SunEl_OLI)
114 | var cosdSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image.constant(180))).cos(); // in degrees
115 | var sindSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image(180))).sin(); // in degrees
116 |
117 | //Relative azimuth
118 | var RelAz_OLI = ee.Image(SunAz_OLI);
119 | var cosdRelAz_OLI = RelAz_OLI.multiply(pi.divide(ee.Image(180))).cos();
120 |
121 | //Pressure calculation
122 | var P_OLI = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM_OLI)).pow(5.25588)).multiply(0.01);
123 | var Po_OLI = ee.Image(1013.25);
124 |
125 | // Radiometric Calibration //
126 | //define bands to be converted to radiance
127 | var bands_OLI = ['B1','B2','B3','B4','B5','B6','B7'];
128 |
129 | // radiance_mult_bands
130 | var rad_mult_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_MULT_BAND_1')),
131 | ee.Image(l8.get('RADIANCE_MULT_BAND_2')),
132 | ee.Image(l8.get('RADIANCE_MULT_BAND_3')),
133 | ee.Image(l8.get('RADIANCE_MULT_BAND_4')),
134 | ee.Image(l8.get('RADIANCE_MULT_BAND_5')),
135 | ee.Image(l8.get('RADIANCE_MULT_BAND_6')),
136 | ee.Image(l8.get('RADIANCE_MULT_BAND_7'))]
137 | )).toArray(1);
138 |
139 | // radiance add band
140 | var rad_add_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_ADD_BAND_1')),
141 | ee.Image(l8.get('RADIANCE_ADD_BAND_2')),
142 | ee.Image(l8.get('RADIANCE_ADD_BAND_3')),
143 | ee.Image(l8.get('RADIANCE_ADD_BAND_4')),
144 | ee.Image(l8.get('RADIANCE_ADD_BAND_5')),
145 | ee.Image(l8.get('RADIANCE_ADD_BAND_6')),
146 | ee.Image(l8.get('RADIANCE_ADD_BAND_7'))]
147 | )).toArray(1);
148 |
149 | //create an empty image to save new radiance bands to
150 | var imgArr_OLI = l8.select(bands_OLI).toArray().toArray(1);
151 | var Ltoa_OLI = imgArr_OLI.multiply(rad_mult_OLI).add(rad_add_OLI)
152 |
153 | // esun
154 | var ESUN_OLI = ee.Image.constant(197.24790954589844)
155 | .addBands(ee.Image.constant(201.98426818847656))
156 | .addBands(ee.Image.constant(186.12677001953125))
157 | .addBands(ee.Image.constant(156.95257568359375))
158 | .addBands(ee.Image.constant(96.04714965820312))
159 | .addBands(ee.Image.constant(23.8833221450863))
160 | .addBands(ee.Image.constant(8.04995873449635)).toArray().toArray(1);
161 | ESUN_OLI = ESUN_OLI.multiply(ee.Image(1))
162 |
163 | var ESUNImg_OLI = ESUN_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
164 |
165 | // Ozone Correction //
166 | // Ozone coefficients
167 | var koz_OLI = ee.Image.constant(0.0039).addBands(ee.Image.constant(0.0218))
168 | .addBands(ee.Image.constant(0.1078))
169 | .addBands(ee.Image.constant(0.0608))
170 | .addBands(ee.Image.constant(0.0019))
171 | .addBands(ee.Image.constant(0))
172 | .addBands(ee.Image.constant(0))
173 | .toArray().toArray(1);
174 |
175 | // Calculate ozone optical thickness
176 | var Toz_OLI = koz_OLI.multiply(DU_OLI).divide(ee.Image.constant(1000));
177 |
178 | // Calculate TOA radiance in the absense of ozone
179 | var Lt_OLI = Ltoa_OLI.multiply(((Toz_OLI)).multiply((ee.Image.constant(1).divide(cosdSunZe_OLI)).add(ee.Image.constant(1).divide(cosdSatZe_OLI))).exp());
180 |
181 | // Rayleigh optical thickness
182 | var bandCenter_OLI = ee.Image(443).divide(1000).addBands(ee.Image(483).divide(1000))
183 | .addBands(ee.Image(561).divide(1000))
184 | .addBands(ee.Image(655).divide(1000))
185 | .addBands(ee.Image(865).divide(1000))
186 | .addBands(ee.Image(1609).divide(1000))
187 | .addBands(ee.Number(2201).divide(1000))
188 | .toArray().toArray(1);
189 |
190 | // create an empty image to save new Tr values to
191 | var Tr_OLI = (P_OLI.divide(Po_OLI)).multiply(ee.Image(0.008569).multiply(bandCenter_OLI.pow(-4))).multiply((ee.Image(1).add(ee.Image(0.0113).multiply(bandCenter_OLI.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter_OLI.pow(-4)))));
192 |
193 | // Fresnel Reflection //
194 | // Specular reflection (s- and p- polarization states)
195 | var theta_V_OLI = ee.Image(0.0000000001);
196 | var sin_theta_j_OLI = sindSunZe_OLI.divide(ee.Image(1.333));
197 |
198 | var theta_j_OLI = sin_theta_j_OLI.asin().multiply(ee.Image(180).divide(pi));
199 |
200 | var theta_SZ_OLI = SunZe_OLI;
201 |
202 | var R_theta_SZ_s_OLI = (((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).subtract(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).add(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)));
203 |
204 | var R_theta_V_s_OLI = ee.Image(0.0000000001);
205 |
206 | var R_theta_SZ_p_OLI = (((theta_SZ_OLI.multiply(pi.divide(180))).subtract(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(180))).add(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)));
207 |
208 | var R_theta_V_p_OLI = ee.Image(0.0000000001);
209 |
210 | var R_theta_SZ_OLI = ee.Image(0.5).multiply(R_theta_SZ_s_OLI.add(R_theta_SZ_p_OLI));
211 |
212 | var R_theta_V_OLI = ee.Image(0.5).multiply(R_theta_V_s_OLI.add(R_theta_V_p_OLI));
213 |
214 | // Rayleigh scattering phase function //
215 | // Sun-sensor geometry
216 | var theta_neg_OLI = ((cosdSunZe_OLI.multiply(ee.Image(-1))).multiply(cosdSatZe_OLI)).subtract((sindSunZe_OLI).multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI));
217 |
218 | var theta_neg_inv_OLI = theta_neg_OLI.acos().multiply(ee.Image(180).divide(pi));
219 |
220 | var theta_pos_OLI = (cosdSunZe_OLI.multiply(cosdSatZe_OLI)).subtract(sindSunZe_OLI.multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI));
221 |
222 | var theta_pos_inv_OLI = theta_pos_OLI.acos().multiply(ee.Image(180).divide(pi));
223 |
224 | var cosd_tni_OLI = theta_neg_inv_OLI.multiply(pi.divide(180)).cos(); // in degrees
225 |
226 | var cosd_tpi_OLI = theta_pos_inv_OLI.multiply(pi.divide(180)).cos(); // in degrees
227 |
228 | var Pr_neg_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni_OLI.pow(2))));
229 |
230 | var Pr_pos_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi_OLI.pow(2))));
231 |
232 | // Rayleigh scattering phase function
233 | var Pr_OLI = Pr_neg_OLI.add((R_theta_SZ_OLI.add(R_theta_V_OLI)).multiply(Pr_pos_OLI));
234 |
235 | // Calulate Lr,
236 | var denom_OLI = ee.Image(4).multiply(pi).multiply(cosdSatZe_OLI);
237 | var Lr_OLI = (ESUN_OLI.multiply(Tr_OLI)).multiply(Pr_OLI.divide(denom_OLI));
238 |
239 | // Rayleigh corrected radiance
240 | var Lrc_OLI = (Lt_OLI.divide(ee.Image(10))).subtract(Lr_OLI);
241 | var LrcImg_OLI = Lrc_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
242 |
243 | // Rayleigh corrected reflectance
244 | var prc_OLI = Lrc_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI));
245 | var pc = prc_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
246 |
247 | // Calculate FAI
248 | var NIRprime = (pc.select('B4')).add((pc.select('B6').subtract(pc.select('B4'))).multiply((ee.Image(865).subtract(ee.Image(655))).divide((ee.Image(1609).subtract(ee.Image(655))))));
249 | var fai = ((pc.select('B5').subtract(NIRprime))).multiply(mask);
250 |
251 | // Map Layers //
252 | // Map Layers
253 | Map.addLayer(footprint, {}, 'footprint', false);
254 | Map.addLayer(pc.multiply(mask), {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.1}, 'pc rgb', false)
255 | Map.addLayer(fai, {min: -0.05, max: 0.2, palette: ['000080','0080FF','7BFF7B','FF9700','800000']}, 'FAI', true);
256 |
257 | // Export Image to Drive
258 | Export.image.toDrive({
259 | image: fai,
260 | description: 'l8_FAI',
261 | scale: 30,
262 | region: footprint
263 | });
--------------------------------------------------------------------------------
/javascript/Ls8_WQ_TimeSeries.js:
--------------------------------------------------------------------------------
1 | // This script processes and charts WQ parameter valuse from a collection of Lansdat 8 archive for a particular pixel throughout time.
2 | // WQ parameters such secchi depth, tropshic state index, and lake temperature
3 | // How to use:
4 | // (1) If a "geometry" variable exists in the imports window, delete it.
5 | // (2) Within the map, select the point button near the top left side.
6 | // (3) Create a new point by clicking on a location of interest.
7 | // (4) Adjust the time frame you wish to browse by adding here:
8 | // begin date
9 | var iniDate = '2016-04-01';
10 | // end date
11 | var endDate = '2016-10-31';
12 | // (5) Adjust a cloud % threshold here:
13 | var oliCloudPerc = 10
14 | // (5) Click Run
15 | // (5) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
16 | // image from this collection, highlight the FILE_ID and copy and paste it into the proper location within the "Ls8 Single Image" script.
17 | // (6) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
18 | // (6) Click "Run"
19 | // (7) Export each chart as a csv by clicking on the export icon near the top-right of the chart.
20 |
21 | // Author: Benjamin Page //
22 | // Citations:
23 | // Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring
24 |
25 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
26 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
27 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
28 | Map.centerObject(geometry, 7)
29 |
30 | // Import Collections //
31 |
32 | // landsat 8 raw dn
33 | var OLI_DN = ee.ImageCollection('LANDSAT/LC08/C01/T1');
34 |
35 | // landsat-8 surface reflactance product (for masking purposes)
36 | var SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
37 |
38 | // toms / omi
39 | var ozone = ee.ImageCollection('TOMS/MERGED');
40 |
41 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
42 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
43 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
44 | // Filtering Collection and Masking //
45 |
46 | var pi = ee.Image(3.141592);
47 |
48 | // water mask
49 | var startMonth = 5;
50 | var endMonth = 9;
51 | var startYear = 2013;
52 | var endYear = 2017;
53 |
54 | var forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'));
55 | var mask = ee.Image(forMask.select('B6').median().lt(600))
56 | mask = mask.updateMask(mask)
57 |
58 | // filter landsat 8 collection
59 | var FC_OLI = OLI_DN.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUD_COVER', "less_than", oliCloudPerc);
60 | print(FC_OLI, 'Available Imagery')
61 |
62 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
63 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
64 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
65 | // Processing Collection //
66 | var Rrs_coll = FC_OLI.map(l8Correction);
67 |
68 | var sd_coll = Rrs_coll.map(secchi);
69 |
70 | var tsi_coll = sd_coll.map(trophicState)
71 |
72 | var tsi_collR = tsi_coll.map(reclassify);
73 |
74 | var lst_coll = FC_OLI.map(LST)
75 |
76 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
77 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
78 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
79 | // Mapping Functions //
80 |
81 | function l8Correction(img){
82 |
83 | // tile geometry
84 |
85 | var l8Footprint = img.geometry()
86 |
87 | // dem
88 | var DEM_OLI = ee.Image('USGS/SRTMGL1_003').clip(l8Footprint);
89 |
90 | // ozone
91 | var DU_OLI = ee.Image(ozone.filterDate(iniDate,endDate).filterBounds(l8Footprint).mean());
92 |
93 | //Julian Day
94 | var imgDate_OLI = ee.Date(img.get('system:time_start'));
95 | var FOY_OLI = ee.Date.fromYMD(imgDate_OLI.get('year'),1,1);
96 | var JD_OLI = imgDate_OLI.difference(FOY_OLI,'day').int().add(1);
97 |
98 | // Earth-Sun distance
99 | var d_OLI = ee.Image.constant(img.get('EARTH_SUN_DISTANCE'));
100 |
101 | //Sun elevation
102 | var SunEl_OLI = ee.Image.constant(img.get('SUN_ELEVATION'));
103 |
104 | //Sun azimuth
105 | var SunAz_OLI = ee.Image.constant(img.get('SUN_AZIMUTH'));
106 |
107 | //Satellite zenith
108 | var SatZe_OLI = ee.Image(0.0)
109 | var cosdSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).cos();
110 | var sindSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).sin();
111 |
112 | //Satellite azimuth
113 | var SatAz_OLI = ee.Image(0.0).clip(l8Footprint);
114 |
115 | //Sun zenith
116 | var SunZe_OLI = ee.Image(90).subtract(SunEl_OLI)
117 | var cosdSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image.constant(180))).cos(); // in degrees
118 | var sindSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image(180))).sin(); // in degrees
119 |
120 | //Relative azimuth
121 | var RelAz_OLI = ee.Image(SunAz_OLI);
122 | var cosdRelAz_OLI = RelAz_OLI.multiply(pi.divide(ee.Image(180))).cos();
123 |
124 | //Pressure calculation
125 | var P_OLI = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM_OLI)).pow(5.25588)).multiply(0.01);
126 | var Po_OLI = ee.Image(1013.25);
127 |
128 | // Radiometric Calibration //
129 | //define bands to be converted to radiance
130 | var bands_OLI = ['B1','B2','B3','B4','B5','B6','B7'];
131 |
132 | // radiance_mult_bands
133 | var rad_mult_OLI = ee.Image(ee.Array([ee.Image(img.get('RADIANCE_MULT_BAND_1')),
134 | ee.Image(img.get('RADIANCE_MULT_BAND_2')),
135 | ee.Image(img.get('RADIANCE_MULT_BAND_3')),
136 | ee.Image(img.get('RADIANCE_MULT_BAND_4')),
137 | ee.Image(img.get('RADIANCE_MULT_BAND_5')),
138 | ee.Image(img.get('RADIANCE_MULT_BAND_6')),
139 | ee.Image(img.get('RADIANCE_MULT_BAND_7'))]
140 | )).toArray(1);
141 |
142 | // radiance add band
143 | var rad_add_OLI = ee.Image(ee.Array([ee.Image(img.get('RADIANCE_ADD_BAND_1')),
144 | ee.Image(img.get('RADIANCE_ADD_BAND_2')),
145 | ee.Image(img.get('RADIANCE_ADD_BAND_3')),
146 | ee.Image(img.get('RADIANCE_ADD_BAND_4')),
147 | ee.Image(img.get('RADIANCE_ADD_BAND_5')),
148 | ee.Image(img.get('RADIANCE_ADD_BAND_6')),
149 | ee.Image(img.get('RADIANCE_ADD_BAND_7'))]
150 | )).toArray(1);
151 |
152 | //create an empty image to save new radiance bands to
153 | var imgArr_OLI = img.select(bands_OLI).toArray().toArray(1);
154 | var Ltoa_OLI = imgArr_OLI.multiply(rad_mult_OLI).add(rad_add_OLI)
155 |
156 | // esun
157 | var ESUN_OLI = ee.Image.constant(197.24790954589844)
158 | .addBands(ee.Image.constant(201.98426818847656))
159 | .addBands(ee.Image.constant(186.12677001953125))
160 | .addBands(ee.Image.constant(156.95257568359375))
161 | .addBands(ee.Image.constant(96.04714965820312))
162 | .addBands(ee.Image.constant(23.8833221450863))
163 | .addBands(ee.Image.constant(8.04995873449635)).toArray().toArray(1);
164 | ESUN_OLI = ESUN_OLI.multiply(ee.Image(1))
165 |
166 | var ESUNImg_OLI = ESUN_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
167 |
168 | // Ozone Correction //
169 | // Ozone coefficients
170 | var koz_OLI = ee.Image.constant(0.0039).addBands(ee.Image.constant(0.0218))
171 | .addBands(ee.Image.constant(0.1078))
172 | .addBands(ee.Image.constant(0.0608))
173 | .addBands(ee.Image.constant(0.0019))
174 | .addBands(ee.Image.constant(0))
175 | .addBands(ee.Image.constant(0))
176 | .toArray().toArray(1);
177 |
178 | // Calculate ozone optical thickness
179 | var Toz_OLI = koz_OLI.multiply(DU_OLI).divide(ee.Image.constant(1000));
180 |
181 | // Calculate TOA radiance in the absense of ozone
182 | var Lt_OLI = Ltoa_OLI.multiply(((Toz_OLI)).multiply((ee.Image.constant(1).divide(cosdSunZe_OLI)).add(ee.Image.constant(1).divide(cosdSatZe_OLI))).exp());
183 |
184 | // Rayleigh optical thickness
185 | var bandCenter_OLI = ee.Image(443).divide(1000).addBands(ee.Image(483).divide(1000))
186 | .addBands(ee.Image(561).divide(1000))
187 | .addBands(ee.Image(655).divide(1000))
188 | .addBands(ee.Image(865).divide(1000))
189 | .addBands(ee.Image(1609).divide(1000))
190 | .addBands(ee.Number(2201).divide(1000))
191 | .toArray().toArray(1);
192 |
193 | // create an empty image to save new Tr values to
194 | var Tr_OLI = (P_OLI.divide(Po_OLI)).multiply(ee.Image(0.008569).multiply(bandCenter_OLI.pow(-4))).multiply((ee.Image(1).add(ee.Image(0.0113).multiply(bandCenter_OLI.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter_OLI.pow(-4)))));
195 |
196 | // Fresnel Reflection //
197 | // Specular reflection (s- and p- polarization states)
198 | var theta_V_OLI = ee.Image(0.0000000001);
199 | var sin_theta_j_OLI = sindSunZe_OLI.divide(ee.Image(1.333));
200 |
201 | var theta_j_OLI = sin_theta_j_OLI.asin().multiply(ee.Image(180).divide(pi));
202 |
203 | var theta_SZ_OLI = SunZe_OLI;
204 |
205 | var R_theta_SZ_s_OLI = (((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).subtract(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).add(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)));
206 |
207 | var R_theta_V_s_OLI = ee.Image(0.0000000001);
208 |
209 | var R_theta_SZ_p_OLI = (((theta_SZ_OLI.multiply(pi.divide(180))).subtract(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(180))).add(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)));
210 |
211 | var R_theta_V_p_OLI = ee.Image(0.0000000001);
212 |
213 | var R_theta_SZ_OLI = ee.Image(0.5).multiply(R_theta_SZ_s_OLI.add(R_theta_SZ_p_OLI));
214 |
215 | var R_theta_V_OLI = ee.Image(0.5).multiply(R_theta_V_s_OLI.add(R_theta_V_p_OLI));
216 |
217 | // Rayleigh scattering phase function //
218 | // Sun-sensor geometry
219 | var theta_neg_OLI = ((cosdSunZe_OLI.multiply(ee.Image(-1))).multiply(cosdSatZe_OLI)).subtract((sindSunZe_OLI).multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI));
220 |
221 | var theta_neg_inv_OLI = theta_neg_OLI.acos().multiply(ee.Image(180).divide(pi));
222 |
223 | var theta_pos_OLI = (cosdSunZe_OLI.multiply(cosdSatZe_OLI)).subtract(sindSunZe_OLI.multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI));
224 |
225 | var theta_pos_inv_OLI = theta_pos_OLI.acos().multiply(ee.Image(180).divide(pi));
226 |
227 | var cosd_tni_OLI = theta_neg_inv_OLI.multiply(pi.divide(180)).cos(); // in degrees
228 |
229 | var cosd_tpi_OLI = theta_pos_inv_OLI.multiply(pi.divide(180)).cos(); // in degrees
230 |
231 | var Pr_neg_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni_OLI.pow(2))));
232 |
233 | var Pr_pos_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi_OLI.pow(2))));
234 |
235 | // Rayleigh scattering phase function
236 | var Pr_OLI = Pr_neg_OLI.add((R_theta_SZ_OLI.add(R_theta_V_OLI)).multiply(Pr_pos_OLI));
237 |
238 | // Calulate Lr,
239 | var denom_OLI = ee.Image(4).multiply(pi).multiply(cosdSatZe_OLI);
240 | var Lr_OLI = (ESUN_OLI.multiply(Tr_OLI)).multiply(Pr_OLI.divide(denom_OLI));
241 |
242 | // Rayleigh corrected radiance
243 | var Lrc_OLI = (Lt_OLI.divide(ee.Image(10))).subtract(Lr_OLI);
244 | var LrcImg_OLI = Lrc_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
245 |
246 | // Rayleigh corrected reflectance
247 | var prc_OLI = Lrc_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI));
248 | var prcImg_OLI = prc_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
249 |
250 | // Aerosol Correction //
251 | // Bands in nm
252 | var bands_nm_OLI = ee.Image(443).addBands(ee.Image(483))
253 | .addBands(ee.Image(561))
254 | .addBands(ee.Image(655))
255 | .addBands(ee.Image(865))
256 | .addBands(ee.Image(0))
257 | .addBands(ee.Image(0))
258 | .toArray().toArray(1);
259 |
260 | // Lam in SWIR bands
261 | var Lam_6_OLI = LrcImg_OLI.select('B6');
262 | var Lam_7_OLI = LrcImg_OLI.select('B7');
263 |
264 | // Calculate aerosol type
265 | var eps_OLI = (((((Lam_7_OLI).divide(ESUNImg_OLI.select('B7'))).log()).subtract(((Lam_6_OLI).divide(ESUNImg_OLI.select('B6'))).log())).divide(ee.Image(2201).subtract(ee.Image(1609)))).multiply(mask);
266 |
267 | // Calculate multiple scattering of aerosols for each band
268 | var Lam_OLI = (Lam_7_OLI).multiply(((ESUN_OLI).divide(ESUNImg_OLI.select('B7')))).multiply((eps_OLI.multiply(ee.Image(-1))).multiply((bands_nm_OLI.divide(ee.Image(2201)))).exp());
269 |
270 | // diffuse transmittance
271 | var trans_OLI = Tr_OLI.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe_OLI)).exp();
272 |
273 | // Compute water-leaving radiance
274 | var Lw_OLI = Lrc_OLI.subtract(Lam_OLI).divide(trans_OLI);
275 |
276 | // water-leaving reflectance
277 | var pw_OLI = (Lw_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI)));
278 | var pwImg_OLI = pw_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
279 |
280 | // Rrs
281 | var Rrs_coll = (pw_OLI.divide(pi).arrayProject([0]).arrayFlatten([bands_OLI]).slice(0,5));
282 |
283 | return(Rrs_coll.set('system:time_start',img.get('system:time_start')));
284 |
285 | }
286 | function LST(img){
287 | var TIRS_1 = img.select('B10');
288 |
289 | var b10_add_band = ee.Number(img.get('RADIANCE_ADD_BAND_10'));
290 | var b10_mult_band = ee.Number(img.get('RADIANCE_MULT_BAND_10'));
291 |
292 | var Oi = ee.Number(0.29);
293 |
294 | var TIRS_cal = (TIRS_1.multiply(b10_mult_band).add(b10_add_band).subtract(Oi)); //.multiply(lakes);
295 |
296 | var K1_b10 = ee.Number(TIRS_1.get('K1_CONSTANT_BAND_10'));
297 | var K2_b10 = ee.Number(TIRS_1.get('K2_CONSTANT_BAND_10'));
298 |
299 | var lst_coll = ((ee.Image(K2_b10).divide(((ee.Image(K1_b10).divide(ee.Image(TIRS_cal))).add(ee.Image(1))).log())).subtract(ee.Image(273))).multiply(mask); // Celsius
300 | lst_coll = (ee.Image(0.7745).multiply(lst_coll)).add(ee.Image(9.6502));
301 |
302 | // Define a square kernel with radius 1.5
303 | var sq_kernel = ee.Kernel.square(1.5,'pixels');
304 |
305 | // focal convolution
306 | lst_coll = lst_coll.focal_median({kernel: sq_kernel, iterations: 1});
307 |
308 | return(lst_coll.set('system:time_start',img.get('system:time_start')))
309 |
310 | }
311 | function secchi(img){
312 | var blueRed_coll = (img.select('B2').divide(img.select('B4'))).log()
313 | var lnMOSD_coll = (ee.Image(1.4856).multiply(blueRed_coll)).add(ee.Image(0.2734)); // R2 = 0.8748 with Anthony's in-situ data
314 | var MOSD_coll = ee.Image(10).pow(lnMOSD_coll);
315 | var sd_coll = (ee.Image(0.1777).multiply(MOSD_coll)).add(ee.Image(1.0813));
316 | return(sd_coll.updateMask(sd_coll.lt(10)).set('system:time_start',img.get('system:time_start')))
317 | }
318 | function trophicState(img){
319 | var tsi_coll = ee.Image(60).subtract(ee.Image(14.41).multiply(img.log()));
320 | return(tsi_coll.updateMask(tsi_coll.lt(200)).set('system:time_start',img.get('system:time_start')))
321 |
322 | }
323 | function reclassify(img){
324 |
325 | // Create conditions
326 | var mask1 = img.lt(30); // (1)
327 | var mask2 = img.gte(30).and(img.lt(40));// (2)
328 | var mask3 = img.gte(40).and(img.lt(50)); // (3)
329 | var mask4 = img.gte(50).and(img.lt(60)); // (4)
330 | var mask5 = img.gte(60).and(img.lt(70)); // (5)
331 | var mask6 = img.gte(70).and(img.lt(80)); // (6)
332 | var mask7 = img.gte(70); // (7)
333 |
334 | // Reclassify conditions into new values
335 | var img1 = img.where(mask1.eq(1), 1).mask(mask1);
336 | var img2 = img.where(mask2.eq(1), 2).mask(mask2);
337 | var img3 = img.where(mask3.eq(1), 3).mask(mask3);
338 | var img4 = img.where(mask4.eq(1), 4).mask(mask4);
339 | var img5 = img.where(mask5.eq(1), 5).mask(mask5);
340 | var img6 = img.where(mask6.eq(1), 6).mask(mask6);
341 | var img7 = img.where(mask7.eq(1), 7).mask(mask7);
342 |
343 | // Ouput of reclassified image
344 | var tsi_collR = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7);
345 | return(tsi_collR.updateMask(tsi_collR.set('system:time_start',img.get('system:time_start'))))
346 | }
347 |
348 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
349 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
350 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
351 | // Map Layers //
352 | Map.addLayer(mask, {}, 'mask', false)
353 |
354 | // l8
355 | Map.addLayer(Rrs_coll.mean(), {bands: ['B4', 'B3', 'B1'], min: 0, max: 0.04}, 'l8 Rrs RGB', false);
356 | Map.addLayer(lst_coll.mean(), {min: 23, max: 27, palette: ['darkblue', 'blue', 'white', 'red', 'darkred']}, 'LST [c]', false)
357 | Map.addLayer(sd_coll.mean(), {min: 0, max: 3, palette: ['800000', 'FF9700', '7BFF7B', '0080FF', '000080']}, 'Zsd [m]', false);
358 | Map.addLayer(tsi_coll.mean(), {min: 30, max: 70, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'darkred']}, 'TSI [0-100]', false);
359 | Map.addLayer(tsi_collR.mode(), {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']}, 'TSI Reclass', true);
360 |
361 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
362 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
363 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
364 | // Time Series //
365 |
366 | // LST time series
367 | var lstTimeSeries = ui.Chart.image.seriesByRegion(
368 | lst_coll, geometry, ee.Reducer.mean())
369 | .setChartType('ScatterChart')
370 | .setOptions({
371 | title: 'Mean LST',
372 | vAxis: {title: 'LST [c]'},
373 | lineWidth: 1,
374 | pointSize: 4,
375 | });
376 |
377 | // SD time series
378 | var sdTimeSeries = ui.Chart.image.seriesByRegion(
379 | sd_coll, geometry, ee.Reducer.mean())
380 | .setChartType('ScatterChart')
381 | .setOptions({
382 | title: 'Mean Secchi Depth',
383 | vAxis: {title: 'Zsd [m]'},
384 | lineWidth: 1,
385 | pointSize: 4,
386 | });
387 |
388 |
389 | // TSI time series
390 | var tsiTimeSeries = ui.Chart.image.seriesByRegion(
391 | tsi_coll, geometry, ee.Reducer.mean())
392 | .setChartType('ScatterChart')
393 | .setOptions({
394 | title: 'Mean Trophic State Index',
395 | vAxis: {title: 'TSI [1-100]'},
396 | lineWidth: 1,
397 | pointSize: 4,
398 | });
399 |
400 | // TSI Reclass time series
401 | var tsiRTimeSeries = ui.Chart.image.seriesByRegion(
402 | tsi_collR, geometry, ee.Reducer.mean())
403 | .setChartType('ScatterChart')
404 | .setOptions({
405 | title: 'Mode Trophic State Index Class',
406 | vAxis: {title: 'TSI Class'},
407 | lineWidth: 1,
408 | pointSize: 4,
409 | });
410 |
411 | print(lstTimeSeries)
412 | print(sdTimeSeries)
413 | print(tsiTimeSeries)
414 | print(tsiRTimeSeries)
--------------------------------------------------------------------------------
/javascript/Ls8_single_image.js:
--------------------------------------------------------------------------------
1 | // This script processes a single Landsat 8 image of interest.
2 | // WQ parameters such chlor-a, secchi depth, trophic state index are calculated
3 | // How to use:
4 | // (1) If a "geometry" variable exists in the imports window, delete it.
5 | // (2) Within the map, select the point button near the top left side.
6 | // (3) Create a new point by clicking on a location of interest.
7 | // (4) Adjust the time frame you wish to browse by adding here:
8 | // begin date
9 | var iniDate = '2015-05-01';
10 | // end date
11 | var endDate = '2018-03-31';
12 | // (5) Adjust a cloud % threshold here:
13 | var oliCloudPerc = 5
14 | // (6) Click Run
15 | // (7) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
16 | // image from this collection, find the FILE_ID within the features and copy and paste it here:
17 | var l8 = ee.Image('LANDSAT/LC08/C01/T1/LC08_170060_20171226');
18 | // (8) Click "Run"
19 | // (9) Export each image by clicking on the run button within the "tasks" tab.
20 |
21 | // Author: Benjamin Page //
22 | // Citations:
23 | // Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring, In Review
24 |
25 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
26 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
27 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
28 |
29 | // Import Collections //
30 |
31 | // landsat 8 raw dn
32 | var OLI_DN = ee.ImageCollection('LANDSAT/LC08/C01/T1');
33 |
34 | // landsat-8 surface reflactance product (for masking purposes)
35 | var SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
36 |
37 | // toms / omi
38 | var ozone = ee.ImageCollection('TOMS/MERGED');
39 |
40 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
41 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
42 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
43 | // Filtering Collection and Masking //
44 |
45 | var pi = ee.Image(3.141592);
46 |
47 | // water mask
48 | var startMonth = 5;
49 | var endMonth = 9;
50 | var startYear = 2013;
51 | var endYear = 2017;
52 |
53 | var forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'));
54 | var mask = ee.Image(forMask.select('B6').median().lt(600))
55 | mask = mask.updateMask(mask)
56 |
57 | // filter landsat 8 collection
58 | var FC_OLI = OLI_DN.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUD_COVER', "less_than", oliCloudPerc);
59 | print(FC_OLI, 'Available Imagery')
60 |
61 | print(l8, 'l8 image info')
62 |
63 | // oli image date
64 | var oliDate = l8.date();
65 | print(oliDate, 'l8 Date')
66 |
67 | var footprint = l8.geometry()
68 |
69 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
70 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
71 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
72 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
73 | // single OLI image ATMOSPHERIC CORRECTION //
74 |
75 | // dem
76 | var DEM_OLI = ee.Image('USGS/SRTMGL1_003').clip(footprint);
77 |
78 | // ozone
79 | var DU_OLI = ee.Image(ozone.filterDate(iniDate,endDate).filterBounds(footprint).mean());
80 |
81 | //Julian Day
82 | var imgDate_OLI = ee.Date(l8.get('system:time_start'));
83 | var FOY_OLI = ee.Date.fromYMD(imgDate_OLI.get('year'),1,1);
84 | var JD_OLI = imgDate_OLI.difference(FOY_OLI,'day').int().add(1);
85 |
86 | // Earth-Sun distance
87 | var d_OLI = ee.Image.constant(l8.get('EARTH_SUN_DISTANCE'));
88 |
89 | //Sun elevation
90 | var SunEl_OLI = ee.Image.constant(l8.get('SUN_ELEVATION'));
91 |
92 | //Sun azimuth
93 | var SunAz_OLI = ee.Image.constant(l8.get('SUN_AZIMUTH'));
94 |
95 | //Satellite zenith
96 | var SatZe_OLI = ee.Image(0.0)
97 | var cosdSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).cos();
98 | var sindSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).sin();
99 |
100 | //Satellite azimuth
101 | var SatAz_OLI = ee.Image(0.0)
102 |
103 | //Sun zenith
104 | var SunZe_OLI = ee.Image(90).subtract(SunEl_OLI)
105 | var cosdSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image.constant(180))).cos(); // in degrees
106 | var sindSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image(180))).sin(); // in degrees
107 |
108 | //Relative azimuth
109 | var RelAz_OLI = ee.Image(SunAz_OLI);
110 | var cosdRelAz_OLI = RelAz_OLI.multiply(pi.divide(ee.Image(180))).cos();
111 |
112 | //Pressure calculation
113 | var P_OLI = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM_OLI)).pow(5.25588)).multiply(0.01);
114 | var Po_OLI = ee.Image(1013.25);
115 |
116 | // Radiometric Calibration //
117 | //define bands to be converted to radiance
118 | var bands_OLI = ['B1','B2','B3','B4','B5','B6','B7'];
119 |
120 | // radiance_mult_bands
121 | var rad_mult_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_MULT_BAND_1')),
122 | ee.Image(l8.get('RADIANCE_MULT_BAND_2')),
123 | ee.Image(l8.get('RADIANCE_MULT_BAND_3')),
124 | ee.Image(l8.get('RADIANCE_MULT_BAND_4')),
125 | ee.Image(l8.get('RADIANCE_MULT_BAND_5')),
126 | ee.Image(l8.get('RADIANCE_MULT_BAND_6')),
127 | ee.Image(l8.get('RADIANCE_MULT_BAND_7'))]
128 | )).toArray(1);
129 |
130 | // radiance add band
131 | var rad_add_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_ADD_BAND_1')),
132 | ee.Image(l8.get('RADIANCE_ADD_BAND_2')),
133 | ee.Image(l8.get('RADIANCE_ADD_BAND_3')),
134 | ee.Image(l8.get('RADIANCE_ADD_BAND_4')),
135 | ee.Image(l8.get('RADIANCE_ADD_BAND_5')),
136 | ee.Image(l8.get('RADIANCE_ADD_BAND_6')),
137 | ee.Image(l8.get('RADIANCE_ADD_BAND_7'))]
138 | )).toArray(1);
139 |
140 | //create an empty image to save new radiance bands to
141 | var imgArr_OLI = l8.select(bands_OLI).toArray().toArray(1);
142 | var Ltoa_OLI = imgArr_OLI.multiply(rad_mult_OLI).add(rad_add_OLI)
143 |
144 | // esun
145 | var ESUN_OLI = ee.Image.constant(197.24790954589844)
146 | .addBands(ee.Image.constant(201.98426818847656))
147 | .addBands(ee.Image.constant(186.12677001953125))
148 | .addBands(ee.Image.constant(156.95257568359375))
149 | .addBands(ee.Image.constant(96.04714965820312))
150 | .addBands(ee.Image.constant(23.8833221450863))
151 | .addBands(ee.Image.constant(8.04995873449635)).toArray().toArray(1);
152 | ESUN_OLI = ESUN_OLI.multiply(ee.Image(1))
153 |
154 | var ESUNImg_OLI = ESUN_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
155 |
156 | // Ozone Correction //
157 | // Ozone coefficients
158 | var koz_OLI = ee.Image.constant(0.0039).addBands(ee.Image.constant(0.0218))
159 | .addBands(ee.Image.constant(0.1078))
160 | .addBands(ee.Image.constant(0.0608))
161 | .addBands(ee.Image.constant(0.0019))
162 | .addBands(ee.Image.constant(0))
163 | .addBands(ee.Image.constant(0))
164 | .toArray().toArray(1);
165 |
166 | // Calculate ozone optical thickness
167 | var Toz_OLI = koz_OLI.multiply(DU_OLI).divide(ee.Image.constant(1000));
168 |
169 | // Calculate TOA radiance in the absense of ozone
170 | var Lt_OLI = Ltoa_OLI.multiply(((Toz_OLI)).multiply((ee.Image.constant(1).divide(cosdSunZe_OLI)).add(ee.Image.constant(1).divide(cosdSatZe_OLI))).exp());
171 |
172 | // Rayleigh optical thickness
173 | var bandCenter_OLI = ee.Image(443).divide(1000).addBands(ee.Image(483).divide(1000))
174 | .addBands(ee.Image(561).divide(1000))
175 | .addBands(ee.Image(655).divide(1000))
176 | .addBands(ee.Image(865).divide(1000))
177 | .addBands(ee.Image(1609).divide(1000))
178 | .addBands(ee.Number(2201).divide(1000))
179 | .toArray().toArray(1);
180 |
181 | // create an empty image to save new Tr values to
182 | var Tr_OLI = (P_OLI.divide(Po_OLI)).multiply(ee.Image(0.008569).multiply(bandCenter_OLI.pow(-4))).multiply((ee.Image(1).add(ee.Image(0.0113).multiply(bandCenter_OLI.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter_OLI.pow(-4)))));
183 |
184 | // Fresnel Reflection //
185 | // Specular reflection (s- and p- polarization states)
186 | var theta_V_OLI = ee.Image(0.0000000001);
187 | var sin_theta_j_OLI = sindSunZe_OLI.divide(ee.Image(1.333));
188 |
189 | var theta_j_OLI = sin_theta_j_OLI.asin().multiply(ee.Image(180).divide(pi));
190 |
191 | var theta_SZ_OLI = SunZe_OLI;
192 |
193 | var R_theta_SZ_s_OLI = (((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).subtract(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).add(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)));
194 |
195 | var R_theta_V_s_OLI = ee.Image(0.0000000001);
196 |
197 | var R_theta_SZ_p_OLI = (((theta_SZ_OLI.multiply(pi.divide(180))).subtract(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(180))).add(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)));
198 |
199 | var R_theta_V_p_OLI = ee.Image(0.0000000001);
200 |
201 | var R_theta_SZ_OLI = ee.Image(0.5).multiply(R_theta_SZ_s_OLI.add(R_theta_SZ_p_OLI));
202 |
203 | var R_theta_V_OLI = ee.Image(0.5).multiply(R_theta_V_s_OLI.add(R_theta_V_p_OLI));
204 |
205 | // Rayleigh scattering phase function //
206 | // Sun-sensor geometry
207 | var theta_neg_OLI = ((cosdSunZe_OLI.multiply(ee.Image(-1))).multiply(cosdSatZe_OLI)).subtract((sindSunZe_OLI).multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI));
208 |
209 | var theta_neg_inv_OLI = theta_neg_OLI.acos().multiply(ee.Image(180).divide(pi));
210 |
211 | var theta_pos_OLI = (cosdSunZe_OLI.multiply(cosdSatZe_OLI)).subtract(sindSunZe_OLI.multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI));
212 |
213 | var theta_pos_inv_OLI = theta_pos_OLI.acos().multiply(ee.Image(180).divide(pi));
214 |
215 | var cosd_tni_OLI = theta_neg_inv_OLI.multiply(pi.divide(180)).cos(); // in degrees
216 |
217 | var cosd_tpi_OLI = theta_pos_inv_OLI.multiply(pi.divide(180)).cos(); // in degrees
218 |
219 | var Pr_neg_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni_OLI.pow(2))));
220 |
221 | var Pr_pos_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi_OLI.pow(2))));
222 |
223 | // Rayleigh scattering phase function
224 | var Pr_OLI = Pr_neg_OLI.add((R_theta_SZ_OLI.add(R_theta_V_OLI)).multiply(Pr_pos_OLI));
225 |
226 | // Calulate Lr,
227 | var denom_OLI = ee.Image(4).multiply(pi).multiply(cosdSatZe_OLI);
228 | var Lr_OLI = (ESUN_OLI.multiply(Tr_OLI)).multiply(Pr_OLI.divide(denom_OLI));
229 |
230 | // Rayleigh corrected radiance
231 | var Lrc_OLI = (Lt_OLI.divide(ee.Image(10))).subtract(Lr_OLI);
232 | var LrcImg_OLI = Lrc_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
233 |
234 | // Rayleigh corrected reflectance
235 | var prc_OLI = Lrc_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI));
236 | var prcImg_OLI = prc_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
237 |
238 | // Aerosol Correction //
239 | // Bands in nm
240 | var bands_nm_OLI = ee.Image(443).addBands(ee.Image(483))
241 | .addBands(ee.Image(561))
242 | .addBands(ee.Image(655))
243 | .addBands(ee.Image(865))
244 | .addBands(ee.Image(0))
245 | .addBands(ee.Image(0))
246 | .toArray().toArray(1);
247 |
248 | // Lam in SWIR bands
249 | var Lam_6_OLI = LrcImg_OLI.select('B6')
250 | var Lam_7_OLI = LrcImg_OLI.select('B7')
251 |
252 | // Calculate aerosol type
253 | var eps_OLI = (((((Lam_7_OLI).divide(ESUNImg_OLI.select('B7'))).log()).subtract(((Lam_6_OLI).divide(ESUNImg_OLI.select('B6'))).log())).divide(ee.Image(2201).subtract(ee.Image(1609)))).multiply(mask)
254 |
255 | // Calculate multiple scattering of aerosols for each band
256 | var Lam_OLI = (Lam_7_OLI).multiply(((ESUN_OLI).divide(ESUNImg_OLI.select('B7')))).multiply((eps_OLI.multiply(ee.Image(-1))).multiply((bands_nm_OLI.divide(ee.Image(2201)))).exp());
257 |
258 | // diffuse transmittance
259 | var trans_OLI = Tr_OLI.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe_OLI)).exp();
260 |
261 | // Compute water-leaving radiance
262 | var Lw_OLI = Lrc_OLI.subtract(Lam_OLI).divide(trans_OLI);
263 |
264 | // water-leaving reflectance
265 | var pw_OLI = (Lw_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI)));
266 | var pwImg_OLI = pw_OLI.arrayProject([0]).arrayFlatten([bands_OLI]);
267 |
268 | // Rrs
269 | var Rrs = (pw_OLI.divide(pi).arrayProject([0]).arrayFlatten([bands_OLI]).slice(0,5));
270 |
271 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
272 | // Models //
273 |
274 | // Surface water temperature
275 | var TIRS_1 = l8.select('B10');
276 |
277 | var b10_add_band = ee.Number(TIRS_1.get('RADIANCE_ADD_BAND_10'));
278 | var b10_mult_band = ee.Number(TIRS_1.get('RADIANCE_MULT_BAND_10'));
279 |
280 | var Oi = ee.Number(0.29);
281 |
282 | var TIRS_cal = (TIRS_1.multiply(b10_mult_band).add(b10_add_band).subtract(Oi)); //.multiply(lakes);
283 |
284 | var K1_b10 = ee.Number(TIRS_1.get('K1_CONSTANT_BAND_10'));
285 | var K2_b10 = ee.Number(TIRS_1.get('K2_CONSTANT_BAND_10'));
286 |
287 | var LST = ((ee.Image(K2_b10).divide(((ee.Image(K1_b10).divide(ee.Image(TIRS_cal))).add(ee.Image(1))).log())).subtract(ee.Image(273))).multiply(mask); // Celsius
288 | LST = (ee.Image(0.7745).multiply(LST)).add(ee.Image(9.6502)); // calibration R2 = 0.8599
289 |
290 | // chlor_a
291 | // Chlorophyll-a OC3
292 | var a0 = ee.Image(0.2412);
293 | var a1 = ee.Image(-2.0546);
294 | var a2 = ee.Image(1.1776);
295 | var a3 = ee.Image(-0.5538);
296 | var a4 = ee.Image(-0.4570);
297 |
298 | var log_BG = (Rrs.select('B1').divide(Rrs.select('B3'))).log10();
299 |
300 | var a1a = a1.multiply(log_BG.pow(1));
301 | var a2a = a2.multiply(log_BG.pow(2));
302 | var a3a = a3.multiply(log_BG.pow(3));
303 | var a4a = a4.multiply(log_BG.pow(4));
304 |
305 | var sum = a1a.add(a2a).add(a3a).add(a4a);
306 |
307 | var log10_chlor_a = a0.add(sum);
308 |
309 | var chlor_a = ee.Image(10).pow(log10_chlor_a);
310 | var chlor_a_cal = ee.Image(4.0752).multiply(chlor_a).subtract(ee.Image(3.9617));
311 |
312 | // SD
313 | var ln_BlueRed = (Rrs.select('B2').divide(Rrs.select('B4'))).log()
314 | var lnMOSD = (ee.Image(1.4856).multiply(ln_BlueRed)).add(ee.Image(0.2734)); // R2 = 0.8748 with in-situ
315 | var MOSD = ee.Image(10).pow(lnMOSD); // log space to (m)
316 | var SD = (ee.Image(0.1777).multiply(MOSD)).add(ee.Image(1.0813));
317 |
318 | // tsi
319 | var TSI_c = ee.Image(30.6).add(ee.Image(9.81)).multiply(chlor_a_cal.log())
320 | var TSI_s = ee.Image(60.0).subtract(ee.Image(14.41)).multiply(SD.log())
321 | var TSI = (TSI_c.add(TSI_s)).divide(ee.Image(2));
322 |
323 | // Reclassify TSI
324 |
325 | // Create conditions
326 | var mask1 = TSI.lt(30); // (1)
327 | var mask2 = TSI.gte(30).and(TSI.lt(40));// (2)
328 | var mask3 = TSI.gte(40).and(TSI.lt(50)); // (3)
329 | var mask4 = TSI.gte(50).and(TSI.lt(60)); // (4)
330 | var mask5 = TSI.gte(60).and(TSI.lt(70)); // (5)
331 | var mask6 = TSI.gte(70).and(TSI.lt(80)); // (6)
332 | var mask7 = TSI.gte(80); // (7)
333 |
334 | // Reclassify conditions into new values
335 | var img1 = TSI.where(mask1.eq(1), 1).mask(mask1);
336 | var img2 = TSI.where(mask2.eq(1), 2).mask(mask2);
337 | var img3 = TSI.where(mask3.eq(1), 3).mask(mask3);
338 | var img4 = TSI.where(mask4.eq(1), 4).mask(mask4);
339 | var img5 = TSI.where(mask5.eq(1), 5).mask(mask5);
340 | var img6 = TSI.where(mask6.eq(1), 6).mask(mask6);
341 | var img7 = TSI.where(mask7.eq(1), 7).mask(mask7);
342 |
343 | // Ouput of reclassified image
344 | var TSI_R = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7);
345 |
346 | // Map Layers
347 | Map.addLayer(footprint, {}, 'footprint')
348 | Map.addLayer(l8.multiply(mask), {bands: ['B4', 'B3', 'B2'], min: 0, max: 15000}, 'rgb', false) // rgb
349 | Map.addLayer(LST, {min: 20, max: 30, palette: ['darkblue', 'blue', 'white', 'red', 'darkred']}, 'LST', false)
350 | Map.addLayer(chlor_a_cal, {min: 0, max: 80, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange' , 'darkred']}, 'chlor_a', false)
351 | Map.addLayer(SD, {min: 0, max: 3, palette: ['darkred', 'orange', 'yellow', 'limegreen', 'cyan', 'blue']}, 'SD', false)
352 | Map.addLayer(TSI, {min: 30, max: 70, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange' , 'darkred']}, 'TSI', false)
353 | Map.addLayer(TSI_R, {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']}, 'TSI_R', true)
354 | Map.centerObject(footprint, 7)
355 |
356 | // EXPORT IMAGES //
357 |
358 | // Export LST
359 | Export.image.toDrive({
360 | image: LST,
361 | description: 'LST',
362 | scale: 30,
363 | region: footprint
364 | });
365 |
366 | // Export chlor_a
367 | Export.image.toDrive({
368 | image: chlor_a,
369 | description: 'chlor_a',
370 | scale: 30,
371 | region: footprint
372 | });
373 |
374 | // Export SD
375 | Export.image.toDrive({
376 | image: SD,
377 | description: 'SD',
378 | scale: 30,
379 | region: footprint
380 | });
381 |
382 | // Export TSI
383 | Export.image.toDrive({
384 | image: TSI,
385 | description: 'TSI',
386 | scale: 30,
387 | region: footprint
388 | });
389 |
390 | // Export TSI_R
391 | Export.image.toDrive({
392 | image: TSI_R,
393 | description: 'TSI_R',
394 | scale: 30,
395 | region: footprint
396 | });
--------------------------------------------------------------------------------
/javascript/MODIS_WQ_TimeSeries.js:
--------------------------------------------------------------------------------
1 | // This script processes and charts WQ parameter values from a collection of MODIS L3 OC archive for a particular pixel throughout time.
2 | // WQ parameters such as chlorophyll-a, secchi depth and the trophic state index
3 | // How to use:
4 | // (1) If a "geometry" variable exists in the imports window, delete it.
5 | // (2) Within the map, select the polygon button near the top left side.
6 | // (3) Create a new polygon by drawing around the area of interest
7 | // (4) Adjust the time frame you wish to browse by adding here:
8 | // begin date
9 | var startDate = '2016-04-01';
10 | // end date
11 | var endDate = '2016-10-31';
12 | // (5) Click Run
13 | // (5) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
14 | // image from this collection, highlight the FILE_ID and copy and paste it into the proper location within the "Ls8 Single Image" script.
15 | // (6) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
16 | // (6) Click "Run"
17 | // (7) Export each chart as a csv by clicking on the export icon near the top-right of the chart.
18 | // (8) If you are interested in a single image from the collection, copy and paste the FILE_ID here:
19 | var singleImage = ee.Image('NASA/OCEANDATA/MODIS-Aqua/L3SMI/A2017183').clip(geometry);
20 |
21 | // Author: Benjamin Page //
22 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
23 | // Import Colelction
24 | var MODIS = ee.ImageCollection('NASA/OCEANDATA/MODIS-Aqua/L3SMI');
25 |
26 | // filter collection
27 | var FC = MODIS.filterDate(startDate, endDate).filterBounds(geometry);
28 | print(FC, 'available imagery')
29 |
30 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
31 |
32 | ////// Models ///////
33 |
34 | // chlor_a
35 | var chlor_a = FC.select('chlor_a');
36 |
37 | // SD
38 | var SD = ee.ImageCollection(FC.map(secchi).copyProperties(FC, ['system:time_start']))
39 |
40 | // TSI
41 | var TSI = SD.map(trophicState);
42 |
43 | ////// Map Functions //////
44 |
45 | function secchi(img){
46 |
47 | var Rrs_488 = img.select('Rrs_488')
48 | var Rrs_667 = img.select('Rrs_667')
49 | var ln_blueRed = (Rrs_488.divide(Rrs_667)).log()
50 | var lnMOSD = (ee.Image(1.4856).multiply(ln_blueRed)).add(ee.Image(0.2734)); // R2 = 0.8748 with Anthony's in-situ data
51 | var MOSD = ee.Image(10).pow(lnMOSD);
52 |
53 | var SD = (ee.Image(0.1777).multiply(MOSD)).add(ee.Image(1.0813));
54 |
55 | return(SD.set('system:time_start', img.get('system:time_start')))
56 |
57 | }
58 | function trophicState(img){
59 | var TSI = ee.Image(60).subtract((ee.Image(14.41)).multiply((img.log())));
60 | return(TSI.set('system:time_start', img.get('system:time_start')))
61 |
62 | }
63 |
64 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
65 | // Time Series
66 |
67 | var chlorTimeSeries = ui.Chart.image.seriesByRegion(
68 | chlor_a, geometry, ee.Reducer.mean())
69 | .setChartType('ScatterChart')
70 | .setOptions({
71 | title: 'Mean chlor_a',
72 | vAxis: {title: 'chlor_a (ug / L)'},
73 | lineWidth: 1,
74 | pointSize: 4,
75 | });
76 |
77 | var sdTimeSeries = ui.Chart.image.seriesByRegion(
78 | SD, geometry, ee.Reducer.mean())
79 | .setChartType('ScatterChart')
80 | .setOptions({
81 | title: 'Mean Secchi Depth',
82 | vAxis: {title: 'Zsd [m]'},
83 | lineWidth: 1,
84 | pointSize: 4,
85 | });
86 |
87 | var tsiTimeSeries = ui.Chart.image.seriesByRegion(
88 | TSI, geometry, ee.Reducer.mean())
89 | .setChartType('ScatterChart')
90 | .setOptions({
91 | title: 'Mean TSI',
92 | vAxis: {title: 'TSI value)'},
93 | lineWidth: 1,
94 | pointSize: 4,
95 | });
96 |
97 | print(chlorTimeSeries)
98 | print(sdTimeSeries)
99 | print(tsiTimeSeries)
100 |
101 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
102 |
103 | // Map layers //
104 |
105 | Map.centerObject(geometry, 7)
106 | Map.addLayer(chlor_a.mean().clip(geometry), {min:0, max: 100, palette: ['darkblue','blue','limegreen','yellow','orange', 'orangered', 'darkred']}, 'chlor_a', false)
107 | Map.addLayer(SD.mean().clip(geometry), {min:0, max: 2, palette: ['red','orangered','orange','yellow','limegreen', 'blue', 'darkblue']}, 'SD', false)
108 | Map.addLayer(TSI.mean().clip(geometry), {min:30, max: 80, palette: ['darkblue','blue','limegreen','yellow','orange', 'orangered', 'red']}, 'TSI', true)
109 | Map.addLayer(singleImage.select('chlor_a'), {min: 0, max:100, palette: ['darkblue','blue','limegreen','yellow','orange', 'orangered', 'darkred']}, 'singleImage_chlor_a', false);
110 |
111 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
112 | // Exporting Images //
113 |
114 | // Export mean chlor_a
115 | Export.image.toDrive({
116 | image: chlor_a.mean().clip(geometry),
117 | description: 'Mean Chlorophyll-a',
118 | scale: 250,
119 | region: geometry
120 | });
121 |
122 | // Export mean SD
123 | Export.image.toDrive({
124 | image: SD.mean().clip(geometry),
125 | description: 'Mean Secchi Depth',
126 | scale: 250,
127 | region: geometry
128 | });
129 |
130 | // Export mean TSI
131 | Export.image.toDrive({
132 | image: TSI.mean().clip(geometry),
133 | description: 'Mean Trophic State (from index)',
134 | scale: 250,
135 | region: geometry
136 | });
--------------------------------------------------------------------------------
/javascript/s2_FAI.js:
--------------------------------------------------------------------------------
1 | // This script processes a single Sentinel-2 TOA to Rayleigh corrected reflectances.
2 | // The floating algal index (FAI) is calculated from Rrc over water bodies.
3 | // How to use:
4 | // (1) If a "geometry" variable exists in the imports window, delete it.
5 | // (2) Within the map, select the point button near the top left side.
6 | // (3) Create a new point by clicking on a location of interest and click run.
7 | // (4) The "available imagery" ImageCollection within the console displays all available imagery
8 | // over that location with the necessary filters described by the user below.
9 | // (5) Expand the features within the "available imagery" image collection and select an image
10 | // by highlighting the FILE_ID and pasting it here:
11 | var s2 = ee.Image('COPERNICUS/S2/20180216T075011_20180216T080852_T36MXE')
12 | // (6) Click "Run"
13 | // (7) Export the image to your Google Drive by clicking on the "tasks" tab and clicking "RUN", be sure to specify
14 | // the proper folder.
15 |
16 | // Author: Benjamin Page //
17 | // Citations:
18 | // Page, B.P., Kumar, A. and Mishra, D.R., 2018. A novel cross-satellite based assessment of the spatio-temporal development of a cyanobacterial harmful algal bloom. International Journal of Applied Earth Observation and Geoinformation, 66, pp.69-81.
19 |
20 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
21 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
22 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
23 | // User Input //
24 |
25 | // begin date
26 | var iniDate = '2015-01-01';
27 |
28 | // end date
29 | var endDate = '2018-02-28';
30 |
31 | var cloudPerc = 5
32 |
33 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
34 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
35 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
36 | // Import Collections //
37 |
38 | // sentinel-2
39 | var MSI = ee.ImageCollection('COPERNICUS/S2');
40 |
41 | // toms / omi
42 | var ozone = ee.ImageCollection('TOMS/MERGED');
43 |
44 | var SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
45 |
46 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
47 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
48 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
49 | // water mask
50 | var startMonth = 5;
51 | var endMonth = 9;
52 | var startYear = 2013;
53 | var endYear = 2017;
54 |
55 | var forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'));
56 | var mask = ee.Image(forMask.select('B6').median().lt(300))
57 | mask = mask.updateMask(mask)
58 |
59 | // constants
60 | var pi = ee.Image(3.141592);
61 | var imDate = s2.date();
62 |
63 | // filter sentinel 2 collection
64 | var FC = MSI.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUDY_PIXEL_PERCENTAGE', "less_than", cloudPerc);
65 | print(FC, 'Sentinel 2 Collection')
66 | print(imDate, 'image date')
67 |
68 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
69 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
70 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
71 | // MSI Atmospheric Correction //
72 |
73 | var bands = ['B1','B2','B3','B4','B5','B6','B7', 'B8', 'B8A', 'B11', 'B12'];
74 |
75 | // rescale
76 | var rescale = ee.Image(s2.divide(10000).copyProperties(s2)).select(bands);
77 |
78 | // tile footprint
79 | var footprint = s2.geometry()
80 | Map.centerObject(footprint)
81 |
82 | // dem
83 | var DEM = ee.Image('USGS/SRTMGL1_003').clip(footprint);
84 |
85 | // ozone
86 | var DU = ee.Image(ozone.filterDate(iniDate,endDate).filterBounds(geometry).mean());
87 |
88 | //Julian Day
89 | var imgDate = ee.Date(s2.get('system:time_start'));
90 | var FOY = ee.Date.fromYMD(imgDate.get('year'),1,1);
91 | var JD = imgDate.difference(FOY,'day').int().add(1);
92 |
93 | // earth-sun distance
94 | var myCos = ((ee.Image(0.0172).multiply(ee.Image(JD).subtract(ee.Image(2)))).cos()).pow(2)
95 | var cosd = myCos.multiply(pi.divide(ee.Image(180))).cos();
96 | var d = ee.Image(1).subtract(ee.Image(0.01673)).multiply(cosd).clip(footprint)
97 |
98 | // sun azimuth
99 | var SunAz = ee.Image.constant(s2.get('MEAN_SOLAR_AZIMUTH_ANGLE')).clip(footprint);
100 |
101 | // sun zenith
102 | var SunZe = ee.Image.constant(s2.get('MEAN_SOLAR_ZENITH_ANGLE')).clip(footprint);
103 | var cosdSunZe = SunZe.multiply(pi.divide(ee.Image(180))).cos(); // in degrees
104 | var sindSunZe = SunZe.multiply(pi.divide(ee.Image(180))).sin(); // in degrees
105 |
106 | // sat zenith
107 | var SatZe = ee.Image.constant(s2.get('MEAN_INCIDENCE_ZENITH_ANGLE_B5')).clip(footprint);
108 | var cosdSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).cos();
109 | var sindSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).sin();
110 |
111 | // sat azimuth
112 | var SatAz = ee.Image.constant(s2.get('MEAN_INCIDENCE_AZIMUTH_ANGLE_B5')).clip(footprint);
113 |
114 | // relative azimuth
115 | var RelAz = SatAz.subtract(SunAz);
116 | var cosdRelAz = RelAz.multiply(pi.divide(ee.Image(180))).cos();
117 |
118 | // Pressure
119 | var P = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM)).pow(5.25588)).multiply(0.01)
120 | var Po = ee.Image(1013.25);
121 |
122 | // esun
123 | var ESUN = ee.Image(ee.Array([ee.Image(s2.get('SOLAR_IRRADIANCE_B1')),
124 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2')),
125 | ee.Image(s2.get('SOLAR_IRRADIANCE_B3')),
126 | ee.Image(s2.get('SOLAR_IRRADIANCE_B4')),
127 | ee.Image(s2.get('SOLAR_IRRADIANCE_B5')),
128 | ee.Image(s2.get('SOLAR_IRRADIANCE_B6')),
129 | ee.Image(s2.get('SOLAR_IRRADIANCE_B7')),
130 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8')),
131 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8A')),
132 | ee.Image(s2.get('SOLAR_IRRADIANCE_B11')),
133 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2'))]
134 | )).toArray().toArray(1);
135 |
136 | ESUN = ESUN.multiply(ee.Image(1))
137 |
138 | var ESUNImg = ESUN.arrayProject([0]).arrayFlatten([bands]);
139 |
140 | // create empty array for the images
141 | var imgArr = rescale.select(bands).toArray().toArray(1);
142 |
143 | // pTOA to Ltoa
144 | var Ltoa = imgArr.multiply(ESUN).multiply(cosdSunZe).divide(pi.multiply(d.pow(2)));
145 |
146 | // band centers
147 | var bandCenter = ee.Image(443).divide(1000).addBands(ee.Image(490).divide(1000))
148 | .addBands(ee.Image(560).divide(1000))
149 | .addBands(ee.Image(665).divide(1000))
150 | .addBands(ee.Image(705).divide(1000))
151 | .addBands(ee.Image(740).divide(1000))
152 | .addBands(ee.Number(783).divide(1000))
153 | .addBands(ee.Number(842).divide(1000))
154 | .addBands(ee.Number(865).divide(1000))
155 | .addBands(ee.Number(1610).divide(1000))
156 | .addBands(ee.Number(2190).divide(1000))
157 | .toArray().toArray(1);
158 |
159 | // ozone coefficients
160 | var koz = ee.Image(0.0039).addBands(ee.Image(0.0213))
161 | .addBands(ee.Image(0.1052))
162 | .addBands(ee.Image(0.0505))
163 | .addBands(ee.Image(0.0205))
164 | .addBands(ee.Image(0.0112))
165 | .addBands(ee.Image(0.0075))
166 | .addBands(ee.Image(0.0021))
167 | .addBands(ee.Image(0.0019))
168 | .addBands(ee.Image(0))
169 | .addBands(ee.Image(0))
170 | .toArray().toArray(1);
171 |
172 | //////////////////////////////////////
173 |
174 | // Calculate ozone optical thickness
175 | var Toz = koz.multiply(DU).divide(ee.Image(1000));
176 |
177 | // Calculate TOA radiance in the absense of ozone
178 | var Lt = Ltoa.multiply(((Toz)).multiply((ee.Image(1).divide(cosdSunZe)).add(ee.Image(1).divide(cosdSatZe))).exp());
179 |
180 | // Rayleigh optical thickness
181 | var Tr = (P.divide(Po)).multiply(ee.Image(0.008569).multiply(bandCenter.pow(-4))).multiply((ee.Image(1).add(ee.Image(0.0113).multiply(bandCenter.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter.pow(-4)))));
182 |
183 | // Specular reflection (s- and p- polarization states)
184 | var theta_V = ee.Image(0.0000000001);
185 | var sin_theta_j = sindSunZe.divide(ee.Image(1.333));
186 |
187 | var theta_j = sin_theta_j.asin().multiply(ee.Image(180).divide(pi));
188 |
189 | var theta_SZ = SunZe;
190 |
191 | var R_theta_SZ_s = (((theta_SZ.multiply(pi.divide(ee.Image(180)))).subtract(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ.multiply(pi.divide(ee.Image(180)))).add(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)));
192 |
193 | var R_theta_V_s = ee.Image(0.0000000001);
194 |
195 | var R_theta_SZ_p = (((theta_SZ.multiply(pi.divide(180))).subtract(theta_j.multiply(pi.divide(180)))).tan().pow(2)).divide((((theta_SZ.multiply(pi.divide(180))).add(theta_j.multiply(pi.divide(180)))).tan().pow(2)));
196 |
197 | var R_theta_V_p = ee.Image(0.0000000001);
198 |
199 | var R_theta_SZ = ee.Image(0.5).multiply(R_theta_SZ_s.add(R_theta_SZ_p));
200 |
201 | var R_theta_V = ee.Image(0.5).multiply(R_theta_V_s.add(R_theta_V_p));
202 |
203 | // Sun-sensor geometry
204 | var theta_neg = ((cosdSunZe.multiply(ee.Image(-1))).multiply(cosdSatZe)).subtract((sindSunZe).multiply(sindSatZe).multiply(cosdRelAz));
205 |
206 | var theta_neg_inv = theta_neg.acos().multiply(ee.Image(180).divide(pi));
207 |
208 | var theta_pos = (cosdSunZe.multiply(cosdSatZe)).subtract(sindSunZe.multiply(sindSatZe).multiply(cosdRelAz));
209 |
210 | var theta_pos_inv = theta_pos.acos().multiply(ee.Image(180).divide(pi));
211 |
212 | var cosd_tni = theta_neg_inv.multiply(pi.divide(180)).cos(); // in degrees
213 |
214 | var cosd_tpi = theta_pos_inv.multiply(pi.divide(180)).cos(); // in degrees
215 |
216 | var Pr_neg = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni.pow(2))));
217 |
218 | var Pr_pos = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi.pow(2))));
219 |
220 | // Rayleigh scattering phase function
221 | var Pr = Pr_neg.add((R_theta_SZ.add(R_theta_V)).multiply(Pr_pos)); // for water Rayleigh correction
222 | //var Pr = ee.Image(1); // for terrestrial Rayleigh correction
223 |
224 | // rayleigh radiance contribution
225 | var denom = ee.Image(4).multiply(pi).multiply(cosdSatZe);
226 | var Lr = (ESUN.multiply(Tr)).multiply(Pr.divide(denom));
227 |
228 | // rayleigh corrected radiance
229 | var Lrc = Lt.subtract(Lr);
230 | var LrcImg = Lrc.arrayProject([0]).arrayFlatten([bands]);
231 |
232 | // rayleigh corrected reflectance
233 | var prc = (Lrc.multiply(pi).multiply(d.pow(2)).divide(ESUN.multiply(cosdSunZe)));
234 | var prcImg = prc.arrayProject([0]).arrayFlatten([bands]);
235 | print(prcImg, 'prc')
236 |
237 | ////////////////////////////////////////////////
238 | // models //
239 |
240 | // Calculate FAI
241 | var NIRprime = (prcImg.select('B4')).add((prcImg.select('B11').subtract(prcImg.select('B4'))).multiply((ee.Image(865).subtract(ee.Image(665))).divide((ee.Image(1610).subtract(ee.Image(665))))));
242 | var FAI = ((prcImg.select('B8A').subtract(NIRprime))).multiply(mask);
243 |
244 | // Map Layers
245 | Map.addLayer(footprint, {}, 'footprint', true);
246 | Map.addLayer(prcImg.multiply(mask), {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.4}, 'prc rgb', false)
247 | Map.addLayer(FAI, {min: -0.05, max: 0.2, palette: ['000080','0080FF','7BFF7B','FF9700','800000']}, 'FAI', true);
248 |
249 | // export image //
250 |
251 | Export.image.toDrive({
252 | image: FAI,
253 | description: 's2_FAI',
254 | scale: 30,
255 | region: footprint
256 | });
257 |
258 |
--------------------------------------------------------------------------------
/javascript/s2_WQ_TimeSeries.js:
--------------------------------------------------------------------------------
1 | // This script processes and charts WQ parameter valuse from a collection of Sentinel-2 archive for a particular pixel throughout time.
2 | // WQ parameters such chlor-a, secchi depth, trophic state index
3 | // How to use:
4 | // (1) If a "geometry" variable exists in the imports window, delete it.
5 | // (2) Within the map, select the point button near the top left side.
6 | // (3) Create a new point by clicking on a location of interest.
7 | // (4) Adjust the time frame you wish to browse by adding here:
8 | // begin date
9 | var iniDate = '2015-05-01';
10 | // end date
11 | var endDate = '2018-03-31';
12 | // (5) Adjust a cloud % threshold here:
13 | var cloudPerc = 5
14 | // (5) Click Run
15 | // (5) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
16 | // image from this collection, highlight the FILE_ID and copy and paste it into the proper location within the "s2 Single Image" script.
17 | // (6) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
18 | // (6) Click "Run"
19 | // (7) Export each chart as a csv by clicking on the export icon near the top-right of the chart.
20 |
21 | // Author: Benjamin Page //
22 | // Citations:
23 | // Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring
24 |
25 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
26 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
27 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
28 | Map.centerObject(geometry, 7)
29 |
30 | // Import Collections //
31 |
32 | // sentinel-2
33 | var MSI = ee.ImageCollection('COPERNICUS/S2');
34 |
35 | // landsat-8 surface reflactance product (for masking purposes)
36 | var SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
37 |
38 | // toms / omi
39 | var ozone = ee.ImageCollection('TOMS/MERGED');
40 |
41 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
42 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
43 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
44 |
45 | var pi = ee.Image(3.141592);
46 |
47 | // water mask
48 | var startMonth = 5;
49 | var endMonth = 9;
50 | var startYear = 2013;
51 | var endYear = 2017;
52 |
53 | var forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'));
54 | var mask = ee.Image(forMask.select('B6').median().lt(300))
55 | mask = mask.updateMask(mask)
56 |
57 | // filter sentinel 2 collection
58 | var FC = MSI.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUDY_PIXEL_PERCENTAGE', "less_than", cloudPerc);
59 | print(FC, 'Sentinel 2 Collection')
60 |
61 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
62 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
63 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
64 | // Collection Processing //
65 |
66 | // atmospheric correction
67 | var Rrs_coll = FC.map(s2Correction);
68 |
69 |
70 | // chlorophyll-a
71 | var chlor_a_coll = Rrs_coll.map(chlorophyll);
72 |
73 | // sd
74 | var sd_coll = Rrs_coll.map(secchi);
75 |
76 | // tsi
77 | var tsi_coll = chlor_a_coll.map(trophicState);
78 |
79 | // tsi reclass
80 | var tsi_collR = tsi_coll.map(reclassify);
81 |
82 | // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
83 | // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
84 | // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
85 | // Mapping functions //
86 |
87 | function s2Correction(img){
88 |
89 | // msi bands
90 | var bands = ['B1','B2','B3','B4','B5','B6','B7', 'B8', 'B8A', 'B11', 'B12'];
91 |
92 | // rescale
93 | var rescale = img.select(bands).divide(10000).multiply(mask)
94 |
95 | // tile footprint
96 | var footprint = rescale.geometry()
97 |
98 | // dem
99 | var DEM = ee.Image('USGS/SRTMGL1_003').clip(footprint);
100 |
101 | // ozone
102 | var DU = ee.Image(ozone.filterDate(iniDate,endDate).filterBounds(footprint).mean());
103 |
104 | //Julian Day
105 | var imgDate = ee.Date(img.get('system:time_start'));
106 | var FOY = ee.Date.fromYMD(imgDate.get('year'),1,1);
107 | var JD = imgDate.difference(FOY,'day').int().add(1);
108 |
109 | // earth-sun distance
110 | var myCos = ((ee.Image(0.0172).multiply(ee.Image(JD).subtract(ee.Image(2)))).cos()).pow(2)
111 | var cosd = myCos.multiply(pi.divide(ee.Image(180))).cos();
112 | var d = ee.Image(1).subtract(ee.Image(0.01673)).multiply(cosd).clip(footprint)
113 |
114 | // sun azimuth
115 | var SunAz = ee.Image.constant(img.get('MEAN_SOLAR_AZIMUTH_ANGLE')).clip(footprint);
116 |
117 | // sun zenith
118 | var SunZe = ee.Image.constant(img.get('MEAN_SOLAR_ZENITH_ANGLE')).clip(footprint);
119 | var cosdSunZe = SunZe.multiply(pi.divide(ee.Image(180))).cos(); // in degrees
120 | var sindSunZe = SunZe.multiply(pi.divide(ee.Image(180))).sin(); // in degrees
121 |
122 | // sat zenith
123 | var SatZe = ee.Image.constant(img.get('MEAN_INCIDENCE_ZENITH_ANGLE_B5')).clip(footprint);
124 | var cosdSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).cos();
125 | var sindSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).sin();
126 |
127 | // sat azimuth
128 | var SatAz = ee.Image.constant(img.get('MEAN_INCIDENCE_AZIMUTH_ANGLE_B5')).clip(footprint);
129 |
130 | // relative azimuth
131 | var RelAz = SatAz.subtract(SunAz);
132 | var cosdRelAz = RelAz.multiply(pi.divide(ee.Image(180))).cos();
133 |
134 | // Pressure
135 | var P = (ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM)).pow(5.25588)).multiply(0.01)).multiply(mask);
136 | var Po = ee.Image(1013.25);
137 |
138 | // esun
139 | var ESUN = ee.Image(ee.Array([ee.Image(img.get('SOLAR_IRRADIANCE_B1')),
140 | ee.Image(img.get('SOLAR_IRRADIANCE_B2')),
141 | ee.Image(img.get('SOLAR_IRRADIANCE_B3')),
142 | ee.Image(img.get('SOLAR_IRRADIANCE_B4')),
143 | ee.Image(img.get('SOLAR_IRRADIANCE_B5')),
144 | ee.Image(img.get('SOLAR_IRRADIANCE_B6')),
145 | ee.Image(img.get('SOLAR_IRRADIANCE_B7')),
146 | ee.Image(img.get('SOLAR_IRRADIANCE_B8')),
147 | ee.Image(img.get('SOLAR_IRRADIANCE_B8A')),
148 | ee.Image(img.get('SOLAR_IRRADIANCE_B11')),
149 | ee.Image(img.get('SOLAR_IRRADIANCE_B2'))]
150 | )).toArray().toArray(1);
151 |
152 | ESUN = ESUN.multiply(ee.Image(1))
153 |
154 | var ESUNImg = ESUN.arrayProject([0]).arrayFlatten([bands]);
155 |
156 | // create empty array for the images
157 | var imgArr = rescale.select(bands).toArray().toArray(1);
158 |
159 | // pTOA to Ltoa
160 | var Ltoa = imgArr.multiply(ESUN).multiply(cosdSunZe).divide(pi.multiply(d.pow(2)));
161 |
162 | // band centers
163 | var bandCenter = ee.Image(443).divide(1000).addBands(ee.Image(490).divide(1000))
164 | .addBands(ee.Image(560).divide(1000))
165 | .addBands(ee.Image(665).divide(1000))
166 | .addBands(ee.Image(705).divide(1000))
167 | .addBands(ee.Image(740).divide(1000))
168 | .addBands(ee.Image(783).divide(1000))
169 | .addBands(ee.Image(842).divide(1000))
170 | .addBands(ee.Image(865).divide(1000))
171 | .addBands(ee.Image(1610).divide(1000))
172 | .addBands(ee.Image(2190).divide(1000))
173 | .toArray().toArray(1);
174 |
175 | // ozone coefficients
176 | var koz = ee.Image(0.0039).addBands(ee.Image(0.0213))
177 | .addBands(ee.Image(0.1052))
178 | .addBands(ee.Image(0.0505))
179 | .addBands(ee.Image(0.0205))
180 | .addBands(ee.Image(0.0112))
181 | .addBands(ee.Image(0.0075))
182 | .addBands(ee.Image(0.0021))
183 | .addBands(ee.Image(0.0019))
184 | .addBands(ee.Image(0))
185 | .addBands(ee.Image(0))
186 | .toArray().toArray(1);
187 |
188 | // Calculate ozone optical thickness
189 | var Toz = koz.multiply(DU).divide(ee.Image(1000));
190 |
191 | // Calculate TOA radiance in the absense of ozone
192 | var Lt = Ltoa.multiply(((Toz)).multiply((ee.Image(1).divide(cosdSunZe)).add(ee.Image(1).divide(cosdSatZe))).exp());
193 |
194 | // Rayleigh optical thickness
195 | var Tr = (P.divide(Po)).multiply(ee.Image(0.008569).multiply(bandCenter.pow(-4))).multiply((ee.Image(1).add(ee.Image(0.0113).multiply(bandCenter.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter.pow(-4)))));
196 |
197 | // Specular reflection (s- and p- polarization states)
198 | var theta_V = ee.Image(0.0000000001);
199 | var sin_theta_j = sindSunZe.divide(ee.Image(1.333));
200 |
201 | var theta_j = sin_theta_j.asin().multiply(ee.Image(180).divide(pi));
202 |
203 | var theta_SZ = SunZe;
204 |
205 | var R_theta_SZ_s = (((theta_SZ.multiply(pi.divide(ee.Image(180)))).subtract(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ.multiply(pi.divide(ee.Image(180)))).add(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)));
206 |
207 | var R_theta_V_s = ee.Image(0.0000000001);
208 |
209 | var R_theta_SZ_p = (((theta_SZ.multiply(pi.divide(180))).subtract(theta_j.multiply(pi.divide(180)))).tan().pow(2)).divide((((theta_SZ.multiply(pi.divide(180))).add(theta_j.multiply(pi.divide(180)))).tan().pow(2)));
210 |
211 | var R_theta_V_p = ee.Image(0.0000000001);
212 |
213 | var R_theta_SZ = ee.Image(0.5).multiply(R_theta_SZ_s.add(R_theta_SZ_p));
214 |
215 | var R_theta_V = ee.Image(0.5).multiply(R_theta_V_s.add(R_theta_V_p));
216 |
217 | // Sun-sensor geometry
218 | var theta_neg = ((cosdSunZe.multiply(ee.Image(-1))).multiply(cosdSatZe)).subtract((sindSunZe).multiply(sindSatZe).multiply(cosdRelAz));
219 |
220 | var theta_neg_inv = theta_neg.acos().multiply(ee.Image(180).divide(pi));
221 |
222 | var theta_pos = (cosdSunZe.multiply(cosdSatZe)).subtract(sindSunZe.multiply(sindSatZe).multiply(cosdRelAz));
223 |
224 | var theta_pos_inv = theta_pos.acos().multiply(ee.Image(180).divide(pi));
225 |
226 | var cosd_tni = theta_neg_inv.multiply(pi.divide(180)).cos(); // in degrees
227 |
228 | var cosd_tpi = theta_pos_inv.multiply(pi.divide(180)).cos(); // in degrees
229 |
230 | var Pr_neg = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni.pow(2))));
231 |
232 | var Pr_pos = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi.pow(2))));
233 |
234 | // Rayleigh scattering phase function
235 | var Pr = Pr_neg.add((R_theta_SZ.add(R_theta_V)).multiply(Pr_pos));
236 |
237 | // rayleigh radiance contribution
238 | var denom = ee.Image(4).multiply(pi).multiply(cosdSatZe);
239 | var Lr = (ESUN.multiply(Tr)).multiply(Pr.divide(denom));
240 |
241 | // rayleigh corrected radiance
242 | var Lrc = Lt.subtract(Lr);
243 | var LrcImg = Lrc.arrayProject([0]).arrayFlatten([bands]);
244 |
245 | // Aerosol Correction //
246 |
247 | // Bands in nm
248 | var bands_nm = ee.Image(443).addBands(ee.Image(490))
249 | .addBands(ee.Image(560))
250 | .addBands(ee.Image(665))
251 | .addBands(ee.Image(705))
252 | .addBands(ee.Image(740))
253 | .addBands(ee.Image(783))
254 | .addBands(ee.Image(842))
255 | .addBands(ee.Image(865))
256 | .addBands(ee.Image(0))
257 | .addBands(ee.Image(0))
258 | .toArray().toArray(1);
259 |
260 | // Lam in SWIR bands
261 | var Lam_10 = LrcImg.select('B11');
262 | var Lam_11 = LrcImg.select('B12');
263 |
264 | // Calculate aerosol type
265 | var eps = ((((Lam_11).divide(ESUNImg.select('B12'))).log()).subtract(((Lam_10).divide(ESUNImg.select('B11'))).log())).divide(ee.Image(2190).subtract(ee.Image(1610)));
266 |
267 | // Calculate multiple scattering of aerosols for each band
268 | var Lam = (Lam_11).multiply(((ESUN).divide(ESUNImg.select('B12')))).multiply((eps.multiply(ee.Image(-1))).multiply((bands_nm.divide(ee.Image(2190)))).exp());
269 |
270 | // diffuse transmittance
271 | var trans = Tr.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe)).exp();
272 |
273 | // Compute water-leaving radiance
274 | var Lw = Lrc.subtract(Lam).divide(trans);
275 |
276 | // water-leaving reflectance
277 | var pw = (Lw.multiply(pi).multiply(d.pow(2)).divide(ESUN.multiply(cosdSunZe)));
278 |
279 | // remote sensing reflectance
280 | var Rrs_coll = (pw.divide(pi).arrayProject([0]).arrayFlatten([bands]).slice(0,9));
281 |
282 | return(Rrs_coll.set('system:time_start',img.get('system:time_start')));
283 |
284 | }
285 | function chlorophyll(img){
286 | var NDCI_coll = (img.select('B5').subtract(img.select('B4'))).divide(img.select('B5').add(img.select('B4')));
287 | var chlor_a_coll = ee.Image(14.039).add(ee.Image(86.115).multiply(NDCI_coll)).add(ee.Image(194.325).multiply(NDCI_coll.pow(ee.Image(2))));
288 | return(chlor_a_coll.updateMask(chlor_a_coll.lt(100)).set('system:time_start',img.get('system:time_start')))
289 | }
290 | function secchi(img){
291 | var blueRed_coll = (img.select('B2').divide(img.select('B4'))).log()
292 | var lnMOSD_coll = (ee.Image(1.4856).multiply(blueRed_coll)).add(ee.Image(0.2734)); // R2 = 0.8748 with Anthony's in-situ data
293 | var MOSD_coll = ee.Image(10).pow(lnMOSD_coll);
294 | var sd_coll = (ee.Image(0.1777).multiply(MOSD_coll)).add(ee.Image(1.0813));
295 | return(sd_coll.updateMask(sd_coll.lt(10)).set('system:time_start',img.get('system:time_start')))
296 | }
297 | function trophicState(img){
298 | var tsi_coll = ee.Image(30.6).add(ee.Image(9.81).multiply(img.log()));
299 | return(tsi_coll.updateMask(tsi_coll.lt(200)).set('system:time_start',img.get('system:time_start')))
300 | }
301 | function reclassify(img){
302 |
303 | // Create conditions
304 | var mask1 = img.lt(30); // (1)
305 | var mask2 = img.gte(30).and(img.lt(40));// (2)
306 | var mask3 = img.gte(40).and(img.lt(50)); // (3)
307 | var mask4 = img.gte(50).and(img.lt(60)); // (4)
308 | var mask5 = img.gte(60).and(img.lt(70)); // (5)
309 | var mask6 = img.gte(70).and(img.lt(80)); // (6)
310 | var mask7 = img.gte(80); // (7)
311 |
312 | // Reclassify conditions into new values
313 | var img1 = img.where(mask1.eq(1), 1).mask(mask1);
314 | var img2 = img.where(mask2.eq(1), 2).mask(mask2);
315 | var img3 = img.where(mask3.eq(1), 3).mask(mask3);
316 | var img4 = img.where(mask4.eq(1), 4).mask(mask4);
317 | var img5 = img.where(mask5.eq(1), 5).mask(mask5);
318 | var img6 = img.where(mask6.eq(1), 6).mask(mask6);
319 | var img7 = img.where(mask7.eq(1), 7).mask(mask7);
320 |
321 | // Ouput of reclassified image
322 | var tsi_collR = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7);
323 | return(tsi_collR.updateMask(tsi_collR.set('system:time_start',img.get('system:time_start'))))
324 | }
325 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
326 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
327 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
328 | // Time Series //
329 |
330 | // Chlorophyll-a time series
331 | var chlorTimeSeries = ui.Chart.image.seriesByRegion(
332 | chlor_a_coll, geometry, ee.Reducer.mean())
333 | .setChartType('ScatterChart')
334 | .setOptions({
335 | title: 'Mean Chlorphyll-a',
336 | vAxis: {title: 'Chlor-a [micrograms/L]'},
337 | lineWidth: 1,
338 | pointSize: 4,
339 | });
340 |
341 | // SD time series
342 | var sdTimeSeries = ui.Chart.image.seriesByRegion(
343 | sd_coll, geometry, ee.Reducer.mean())
344 | .setChartType('ScatterChart')
345 | .setOptions({
346 | title: 'Mean Secchi Depth',
347 | vAxis: {title: 'Zsd [m]'},
348 | lineWidth: 1,
349 | pointSize: 4,
350 | });
351 |
352 | // TSI time series
353 | var tsiTimeSeries = ui.Chart.image.seriesByRegion(
354 | tsi_coll, geometry, ee.Reducer.mean())
355 | .setChartType('ScatterChart')
356 | .setOptions({
357 | title: 'Mean Trophic State Index',
358 | vAxis: {title: 'TSI [1-100]'},
359 | lineWidth: 1,
360 | pointSize: 4,
361 | });
362 |
363 | // TSI Reclass time series
364 | var tsiRTimeSeries = ui.Chart.image.seriesByRegion(
365 | tsi_collR, geometry, ee.Reducer.mode())
366 | .setChartType('ScatterChart')
367 | .setOptions({
368 | title: 'Mode Trophic State Index Class',
369 | vAxis: {title: 'TSI Class'},
370 | lineWidth: 1,
371 | pointSize: 4,
372 | });
373 |
374 |
375 | print(chlorTimeSeries)
376 | print(sdTimeSeries)
377 | print(tsiTimeSeries)
378 | print(tsiRTimeSeries)
379 |
380 | // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
381 | // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
382 | // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
383 | // Map Layers //
384 | Map.addLayer(mask, {}, 'mask', false)
385 | Map.addLayer(Rrs_coll.mean(), {min: 0, max: 0.03, bands: ['B4', 'B3', 'B2']}, 'Mean RGB', false);
386 | Map.addLayer(chlor_a_coll.mean(), {min: 0, max: 40, palette: ['darkblue','blue','cyan','limegreen','yellow', 'orange', 'orangered', 'darkred']}, 'Mean chlor-a', false);
387 | Map.addLayer(sd_coll.mean(), {min: 0, max: 2, palette: ['800000', 'FF9700', '7BFF7B', '0080FF', '000080']}, 'Mean Zsd', false);
388 | Map.addLayer(tsi_coll.mean(), {min: 30, max: 80, palette: ['darkblue','blue','cyan','limegreen','yellow', 'orange', 'orangered', 'darkred']}, 'Mean TSI', false);
389 | Map.addLayer(tsi_collR.mode(), {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']}, 'Mode TSI Class', true);
390 |
--------------------------------------------------------------------------------
/javascript/s2_single_image.js:
--------------------------------------------------------------------------------
1 | // This script processes a single Sentinel-2 image of interest.
2 | // WQ parameters such chlor-a, secchi depth, trophic state index are calculated
3 | // How to use:
4 | // (1) If a "geometry" variable exists in the imports window, delete it.
5 | // (2) Within the map, select the point button near the top left side.
6 | // (3) Create a new point by clicking on a location of interest.
7 | // (4) Adjust the time frame you wish to browse by adding here:
8 | // begin date
9 | var iniDate = '2015-05-01';
10 | // end date
11 | var endDate = '2018-03-31';
12 | // (5) Adjust a cloud % threshold here:
13 | var cloudPerc = 5
14 | // (6) Click Run
15 | // (7) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
16 | // image from this collection, highlight the FILE_ID in the available imagery features and copy and paste it here:
17 | var s2 = ee.Image('COPERNICUS/S2/20171128T075251_20171128T080644_T36MXE')
18 | // (8) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
19 | // (9) Click "Run"
20 | // (10) Export each image by clicking on the run button within the "tasks" tab.
21 |
22 | // Author: Benjamin Page //
23 | // Citations:
24 | // Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring
25 |
26 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
27 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
28 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
29 | // Import Collections //
30 |
31 | // sentinel-2
32 | var MSI = ee.ImageCollection('COPERNICUS/S2');
33 |
34 | // landsat-8 surface reflactance product (for masking purposes)
35 | var SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR');
36 |
37 | // toms / omi
38 | var ozone = ee.ImageCollection('TOMS/MERGED');
39 |
40 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
41 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
42 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
43 |
44 | var pi = ee.Image(3.141592);
45 | var imDate = s2.date();
46 |
47 | var footprint = s2.geometry()
48 |
49 | // water mask
50 | var startMonth = 5;
51 | var endMonth = 9;
52 | var startYear = 2013;
53 | var endYear = 2017;
54 |
55 | var forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'));
56 | var mask = ee.Image(forMask.select('B6').median().lt(300))
57 | mask = mask.updateMask(mask).clip(footprint)
58 |
59 | // filter sentinel 2 collection
60 | var FC = MSI.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUDY_PIXEL_PERCENTAGE', "less_than", cloudPerc);
61 | print(FC, 'Sentinel 2 Collection')
62 | print(imDate, 'image date')
63 |
64 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
65 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
66 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
67 | // MSI Atmospheric Correction //
68 |
69 | var bands = ['B1','B2','B3','B4','B5','B6','B7', 'B8', 'B8A', 'B11', 'B12'];
70 |
71 | // rescale*
72 | var rescale = ee.Image(s2.divide(10000).multiply(mask).copyProperties(s2)).select(bands);
73 |
74 | // dem*
75 | var DEM = ee.Image('USGS/SRTMGL1_003').clip(footprint);
76 |
77 | // ozone*
78 | var DU = ee.Image(ozone.filterDate(iniDate,endDate).filterBounds(footprint).mean());
79 |
80 | //Julian Day
81 | var imgDate = ee.Date(s2.get('system:time_start'));
82 | var FOY = ee.Date.fromYMD(imgDate.get('year'),1,1);
83 | var JD = imgDate.difference(FOY,'day').int().add(1);
84 |
85 | // earth-sun distance
86 | var myCos = ((ee.Image(0.0172).multiply(ee.Image(JD).subtract(ee.Image(2)))).cos()).pow(2)
87 | var cosd = myCos.multiply(pi.divide(ee.Image(180))).cos();
88 | var d = ee.Image(1).subtract(ee.Image(0.01673)).multiply(cosd).clip(footprint)
89 |
90 | // sun azimuth
91 | var SunAz = ee.Image.constant(s2.get('MEAN_SOLAR_AZIMUTH_ANGLE')).clip(footprint);
92 |
93 | // sun zenith
94 | var SunZe = ee.Image.constant(s2.get('MEAN_SOLAR_ZENITH_ANGLE')).clip(footprint);
95 | var cosdSunZe = SunZe.multiply(pi.divide(ee.Image(180))).cos(); // in degrees
96 | var sindSunZe = SunZe.multiply(pi.divide(ee.Image(180))).sin(); // in degrees
97 |
98 | // sat zenith
99 | var SatZe = ee.Image.constant(s2.get('MEAN_INCIDENCE_ZENITH_ANGLE_B5')).clip(footprint);
100 | var cosdSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).cos();
101 | var sindSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).sin();
102 |
103 | // sat azimuth
104 | var SatAz = ee.Image.constant(s2.get('MEAN_INCIDENCE_AZIMUTH_ANGLE_B5')).clip(footprint);
105 |
106 | // relative azimuth
107 | var RelAz = SatAz.subtract(SunAz);
108 | var cosdRelAz = RelAz.multiply(pi.divide(ee.Image(180))).cos();
109 |
110 | // Pressure
111 | var P = (ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM)).pow(5.25588)).multiply(0.01)).multiply(mask);
112 | var Po = ee.Image(1013.25);
113 |
114 | // esun
115 | var ESUN = ee.Image(ee.Array([ee.Image(s2.get('SOLAR_IRRADIANCE_B1')),
116 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2')),
117 | ee.Image(s2.get('SOLAR_IRRADIANCE_B3')),
118 | ee.Image(s2.get('SOLAR_IRRADIANCE_B4')),
119 | ee.Image(s2.get('SOLAR_IRRADIANCE_B5')),
120 | ee.Image(s2.get('SOLAR_IRRADIANCE_B6')),
121 | ee.Image(s2.get('SOLAR_IRRADIANCE_B7')),
122 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8')),
123 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8A')),
124 | ee.Image(s2.get('SOLAR_IRRADIANCE_B11')),
125 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2'))]
126 | )).toArray().toArray(1);
127 |
128 | ESUN = ESUN.multiply(ee.Image(1))
129 |
130 | var ESUNImg = ESUN.arrayProject([0]).arrayFlatten([bands]);
131 |
132 | // create empty array for the images
133 | var imgArr = rescale.select(bands).toArray().toArray(1);
134 |
135 | // pTOA to Ltoa
136 | var Ltoa = imgArr.multiply(ESUN).multiply(cosdSunZe).divide(pi.multiply(d.pow(2)));
137 |
138 | // band centers
139 | var bandCenter = ee.Image(443).divide(1000).addBands(ee.Image(490).divide(1000))
140 | .addBands(ee.Image(560).divide(1000))
141 | .addBands(ee.Image(665).divide(1000))
142 | .addBands(ee.Image(705).divide(1000))
143 | .addBands(ee.Image(740).divide(1000))
144 | .addBands(ee.Number(783).divide(1000))
145 | .addBands(ee.Number(842).divide(1000))
146 | .addBands(ee.Number(865).divide(1000))
147 | .addBands(ee.Number(1610).divide(1000))
148 | .addBands(ee.Number(2190).divide(1000))
149 | .toArray().toArray(1);
150 |
151 | // ozone coefficients
152 | var koz = ee.Image(0.0039).addBands(ee.Image(0.0213))
153 | .addBands(ee.Image(0.1052))
154 | .addBands(ee.Image(0.0505))
155 | .addBands(ee.Image(0.0205))
156 | .addBands(ee.Image(0.0112))
157 | .addBands(ee.Image(0.0075))
158 | .addBands(ee.Image(0.0021))
159 | .addBands(ee.Image(0.0019))
160 | .addBands(ee.Image(0))
161 | .addBands(ee.Image(0))
162 | .toArray().toArray(1);
163 |
164 | //////////////////////////////////////
165 |
166 | // Calculate ozone optical thickness
167 | var Toz = koz.multiply(DU).divide(ee.Image(1000));
168 |
169 | // Calculate TOA radiance in the absense of ozone
170 | var Lt = Ltoa.multiply(((Toz)).multiply((ee.Image(1).divide(cosdSunZe)).add(ee.Image(1).divide(cosdSatZe))).exp());
171 |
172 | // Rayleigh optical thickness
173 | var Tr = (P.divide(Po)).multiply(ee.Image(0.008569).multiply(bandCenter.pow(-4))).multiply((ee.Image(1).add(ee.Image(0.0113).multiply(bandCenter.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter.pow(-4)))));
174 |
175 | // Specular reflection (s- and p- polarization states)
176 | var theta_V = ee.Image(0.0000000001);
177 | var sin_theta_j = sindSunZe.divide(ee.Image(1.333));
178 |
179 | var theta_j = sin_theta_j.asin().multiply(ee.Image(180).divide(pi));
180 |
181 | var theta_SZ = SunZe;
182 |
183 | var R_theta_SZ_s = (((theta_SZ.multiply(pi.divide(ee.Image(180)))).subtract(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ.multiply(pi.divide(ee.Image(180)))).add(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)));
184 |
185 | var R_theta_V_s = ee.Image(0.0000000001);
186 |
187 | var R_theta_SZ_p = (((theta_SZ.multiply(pi.divide(180))).subtract(theta_j.multiply(pi.divide(180)))).tan().pow(2)).divide((((theta_SZ.multiply(pi.divide(180))).add(theta_j.multiply(pi.divide(180)))).tan().pow(2)));
188 |
189 | var R_theta_V_p = ee.Image(0.0000000001);
190 |
191 | var R_theta_SZ = ee.Image(0.5).multiply(R_theta_SZ_s.add(R_theta_SZ_p));
192 |
193 | var R_theta_V = ee.Image(0.5).multiply(R_theta_V_s.add(R_theta_V_p));
194 |
195 | // Sun-sensor geometry
196 | var theta_neg = ((cosdSunZe.multiply(ee.Image(-1))).multiply(cosdSatZe)).subtract((sindSunZe).multiply(sindSatZe).multiply(cosdRelAz));
197 |
198 | var theta_neg_inv = theta_neg.acos().multiply(ee.Image(180).divide(pi));
199 |
200 | var theta_pos = (cosdSunZe.multiply(cosdSatZe)).subtract(sindSunZe.multiply(sindSatZe).multiply(cosdRelAz));
201 |
202 | var theta_pos_inv = theta_pos.acos().multiply(ee.Image(180).divide(pi));
203 |
204 | var cosd_tni = theta_neg_inv.multiply(pi.divide(180)).cos(); // in degrees
205 |
206 | var cosd_tpi = theta_pos_inv.multiply(pi.divide(180)).cos(); // in degrees
207 |
208 | var Pr_neg = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni.pow(2))));
209 |
210 | var Pr_pos = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi.pow(2))));
211 |
212 | // Rayleigh scattering phase function
213 | var Pr = Pr_neg.add((R_theta_SZ.add(R_theta_V)).multiply(Pr_pos));
214 |
215 | // rayleigh radiance contribution
216 | var denom = ee.Image(4).multiply(pi).multiply(cosdSatZe);
217 | var Lr = (ESUN.multiply(Tr)).multiply(Pr.divide(denom));
218 |
219 | // rayleigh corrected radiance
220 | var Lrc = Lt.subtract(Lr);
221 | var LrcImg = Lrc.arrayProject([0]).arrayFlatten([bands]);
222 |
223 | // Aerosol Correction //
224 |
225 | // Bands in nm
226 | var bands_nm = ee.Image(443).addBands(ee.Image(490))
227 | .addBands(ee.Image(560))
228 | .addBands(ee.Image(665))
229 | .addBands(ee.Image(705))
230 | .addBands(ee.Image(740))
231 | .addBands(ee.Image(783))
232 | .addBands(ee.Image(842))
233 | .addBands(ee.Image(865))
234 | .addBands(ee.Image(0))
235 | .addBands(ee.Image(0))
236 | .toArray().toArray(1);
237 |
238 | // Lam in SWIR bands
239 | var Lam_10 = LrcImg.select('B11');
240 | var Lam_11 = LrcImg.select('B12');
241 |
242 | // Calculate aerosol type
243 | var eps = ((((Lam_11).divide(ESUNImg.select('B12'))).log()).subtract(((Lam_10).divide(ESUNImg.select('B11'))).log())).divide(ee.Image(2190).subtract(ee.Image(1610)));
244 |
245 | // Calculate multiple scattering of aerosols for each band
246 | var Lam = (Lam_11).multiply(((ESUN).divide(ESUNImg.select('B12')))).multiply((eps.multiply(ee.Image(-1))).multiply((bands_nm.divide(ee.Image(2190)))).exp());
247 |
248 | // diffuse transmittance
249 | var trans = Tr.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe)).exp();
250 |
251 | // Compute water-leaving radiance
252 | var Lw = Lrc.subtract(Lam).divide(trans);
253 |
254 | // water-leaving reflectance
255 | var pw = (Lw.multiply(pi).multiply(d.pow(2)).divide(ESUN.multiply(cosdSunZe)));
256 | var pwImg = pw.arrayProject([0]).arrayFlatten([bands]);
257 |
258 | // remote sensing reflectance
259 | var Rrs = (pw.divide(pi).arrayProject([0]).arrayFlatten([bands]).slice(0,9));
260 | print(Rrs, 'Rrs')
261 |
262 | /// Bio optical Models ///
263 | // chlor_a
264 | var NDCI = (Rrs.select('B5').subtract(Rrs.select('B4'))).divide(Rrs.select('B5').add(Rrs.select('B4'))) // ndci
265 | var chlor_a = ee.Image(14.039).add(ee.Image(86.115).multiply(NDCI)).add(ee.Image(194.325).multiply(NDCI.pow(ee.Image(2)))); // chlor_a
266 |
267 | // SD
268 | var ln_BlueRed = (Rrs.select('B2').divide(Rrs.select('B4'))).log()
269 | var lnMOSD = (ee.Image(1.4856).multiply(ln_BlueRed)).add(ee.Image(0.2734)); // R2 = 0.8748 with in-situ
270 | var MOSD = ee.Image(10).pow(lnMOSD); // log space to (m)
271 | var SD = (ee.Image(0.1777).multiply(MOSD)).add(ee.Image(1.0813));
272 |
273 | // tsi
274 | var TSI_c = ee.Image(30.6).add(ee.Image(9.81)).multiply(chlor_a.log())
275 | var TSI_s = ee.Image(60.0).subtract(ee.Image(14.41)).multiply(SD.log())
276 | var TSI = (TSI_c.add(TSI_s)).divide(ee.Image(2));
277 |
278 | // tsi reclassified
279 | // Create conditions
280 | var mask1 = TSI.lt(30); // classical oligitrophy (1)
281 | var mask2 = TSI.gte(30).and(TSI.lt(40));// (2)
282 | var mask3 = TSI.gte(40).and(TSI.lt(50)); // (3)
283 | var mask4 = TSI.gte(50).and(TSI.lt(60)); // (4)
284 | var mask5 = TSI.gte(60).and(TSI.lt(70)); // (5)
285 | var mask6 = TSI.gte(70).and(TSI.lt(80)); // (6)
286 | var mask7 = TSI.gte(80); // (7)
287 |
288 |
289 | // Reclassify conditions into new values
290 | var img1 = TSI.where(mask1.eq(1), 1).mask(mask1);
291 | var img2 = TSI.where(mask2.eq(1), 2).mask(mask2);
292 | var img3 = TSI.where(mask3.eq(1), 3).mask(mask3);
293 | var img4 = TSI.where(mask4.eq(1), 4).mask(mask4);
294 | var img5 = TSI.where(mask5.eq(1), 5).mask(mask5);
295 | var img6 = TSI.where(mask6.eq(1), 6).mask(mask6);
296 | var img7 = TSI.where(mask7.eq(1), 7).mask(mask7);
297 |
298 |
299 | // Ouput of reclassified image
300 | var TSI_R = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7);
301 |
302 | // Map Layers //
303 | Map.centerObject(footprint, 7);
304 | Map.addLayer(footprint, {}, 'footprint', true) // footprint
305 | Map.addLayer(s2.multiply(mask), {bands: ['B4', 'B3', 'B2'], min: 0, max: 1000}, 'rgb', false) // rgb
306 | Map.addLayer(SD, {min: 0, max: 3, palette: ['darkred', 'orange', 'yellow', 'limegreen', 'cyan' , 'blue']}, 'SD', false);
307 | Map.addLayer(chlor_a, {min: 0, max: 40, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange' , 'darkred']}, 'chlor_a', false);
308 | Map.addLayer(TSI, {min: 30, max: 70, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange' , 'darkred']}, 'TSI', false);
309 | Map.addLayer(TSI_R, {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']}, 'TSI_R', true);
310 |
311 | // EXPORT IMAGES //
312 |
313 | // Export SD
314 | Export.image.toDrive({
315 | image: SD,
316 | description: 'SD',
317 | scale: 20,
318 | region: footprint
319 | });
320 |
321 | // Export chlor_a
322 | Export.image.toDrive({
323 | image: chlor_a,
324 | description: 'chlor_a',
325 | scale: 20,
326 | region: footprint
327 | });
328 |
329 | // Export TSI
330 | Export.image.toDrive({
331 | image: TSI,
332 | description: 'TSI',
333 | scale: 20,
334 | region: footprint
335 | });
336 |
337 | // Export TSI_R
338 | Export.image.toDrive({
339 | image: TSI_R,
340 | description: 'TSI_R',
341 | scale: 20,
342 | region: footprint
343 | });
--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------
1 | #Coming soon...
2 |
--------------------------------------------------------------------------------
/python/ls8_fai.py:
--------------------------------------------------------------------------------
1 | import ee
2 | from ee.ee_exception import EEException
3 |
4 | try:
5 | ee.Initialize()
6 | except EEException as e:
7 | from oauth2client.service_account import ServiceAccountCredentials
8 | credentials = ServiceAccountCredentials.from_p12_keyfile(
9 | service_account_email='',
10 | filename='',
11 | private_key_password='notasecret',
12 | scopes=ee.oauth.SCOPE + ' https://www.googleapis.com/auth/drive ')
13 | ee.Initialize(credentials)
14 |
15 | geometry = ee.Geometry.Polygon([[[30.76171875, 0.9049611504960419],
16 | [30.8935546875, -3.487377195492663],
17 | [35.5517578125, -3.2680324702882952],
18 | [35.5517578125, 1.9593043032313748]]])
19 | l8 = ee.Image('LANDSAT/LC08/C01/T1/LC08_170060_20171226')
20 |
21 | #Start date
22 | iniDate = '2015-05-01'
23 |
24 | #End Date
25 | endDate = '2018-03-31'
26 |
27 | oliCloudPerc = 5
28 |
29 | #Landsat 8 raw dn
30 | OLI_DN = ee.ImageCollection('LANDSAT/LC08/C01/T1')
31 |
32 | #Landsat-8 surface reflactance product (for masking purposes)
33 | SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
34 |
35 | #toms / omi
36 | ozone = ee.ImageCollection('TOMS/MERGED')
37 |
38 | #Filtering Collection and Masking
39 | pi = ee.Image(3.141592)
40 |
41 | #Water Mask
42 | startMonth = 5
43 | endMonth = 9
44 | startYear = 2013
45 | endYear = 2017
46 |
47 | forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'))
48 | mask = ee.Image(forMask.select('B6').median().lt(600))
49 | mask = mask.updateMask(mask)
50 | FC_OLI = OLI_DN.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUD_COVER', "less_than", oliCloudPerc)
51 |
52 | #OLI image date
53 | oliDate = l8.date()
54 | footprint = l8.geometry()
55 |
56 | #DEM
57 | DEM_OLI = ee.Image('USGS/SRTMGL1_003').clip(footprint)
58 |
59 | #Ozone
60 | DU_OLI = ee.Image(ozone.filterDate(iniDate,endDate).filterBounds(footprint).mean())
61 |
62 | #Julian Day
63 | imgDate_OLI = ee.Date(l8.get('system:time_start'))
64 | FOY_OLI = ee.Date.fromYMD(imgDate_OLI.get('year'),1,1)
65 | JD_OLI = imgDate_OLI.difference(FOY_OLI,'day').int().add(1)
66 |
67 | #Earth-Sun distance
68 | d_OLI = ee.Image.constant(l8.get('EARTH_SUN_DISTANCE'))
69 |
70 | #Sun elevation
71 | SunEl_OLI = ee.Image.constant(l8.get('SUN_ELEVATION'))
72 |
73 | #Sun azimuth
74 | SunAz_OLI = ee.Image.constant(l8.get('SUN_AZIMUTH'))
75 |
76 | #Satellite Zenith
77 | SatZe_OLI = ee.Image(0.0)
78 | cosdSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).cos()
79 | sindSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).sin()
80 |
81 | #Satellite Azimuth
82 | SatAz_OLI = ee.Image(0.0)
83 |
84 | #Sun zenith
85 | SunZe_OLI = ee.Image(90).subtract(SunEl_OLI)
86 | cosdSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image.constant(180))).cos()
87 | sindSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image(180))).sin()
88 |
89 | #Relative azimuth
90 | RelAz_OLI = ee.Image(SunAz_OLI)
91 | cosdRelAz_OLI = RelAz_OLI.multiply(pi.divide(ee.Image(180))).cos()
92 |
93 | #Pressure calculation
94 | P_OLI = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM_OLI)).pow(5.25588)).multiply(0.01)
95 | Po_OLI = ee.Image(1013.25)
96 |
97 | #Radiometric Calibration
98 | #define bands to be converted to radiance
99 | bands_OLI = ['B1','B2','B3','B4','B5','B6','B7']
100 |
101 | #Radiance Mult Bands
102 | rad_mult_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_MULT_BAND_1')),
103 | ee.Image(l8.get('RADIANCE_MULT_BAND_2')),
104 | ee.Image(l8.get('RADIANCE_MULT_BAND_3')),
105 | ee.Image(l8.get('RADIANCE_MULT_BAND_4')),
106 | ee.Image(l8.get('RADIANCE_MULT_BAND_5')),
107 | ee.Image(l8.get('RADIANCE_MULT_BAND_6')),
108 | ee.Image(l8.get('RADIANCE_MULT_BAND_7'))]
109 | )).toArray(1)
110 |
111 | #Radiance add band
112 | rad_add_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_ADD_BAND_1')),
113 | ee.Image(l8.get('RADIANCE_ADD_BAND_2')),
114 | ee.Image(l8.get('RADIANCE_ADD_BAND_3')),
115 | ee.Image(l8.get('RADIANCE_ADD_BAND_4')),
116 | ee.Image(l8.get('RADIANCE_ADD_BAND_5')),
117 | ee.Image(l8.get('RADIANCE_ADD_BAND_6')),
118 | ee.Image(l8.get('RADIANCE_ADD_BAND_7'))]
119 | )).toArray(1)
120 |
121 | # Create an empty image to save new radiance bands to
122 | imgArr_OLI = l8.select(bands_OLI).toArray().toArray(1)
123 | Ltoa_OLI = imgArr_OLI.multiply(rad_mult_OLI).add(rad_add_OLI)
124 |
125 | #esun
126 | ESUN_OLI = ee.Image.constant(197.24790954589844) \
127 | .addBands(ee.Image.constant(201.98426818847656))\
128 | .addBands(ee.Image.constant(186.12677001953125))\
129 | .addBands(ee.Image.constant(156.95257568359375))\
130 | .addBands(ee.Image.constant(96.04714965820312))\
131 | .addBands(ee.Image.constant(23.8833221450863))\
132 | .addBands(ee.Image.constant(8.04995873449635)).toArray().toArray(1)
133 |
134 | ESUN_OLI = ESUN_OLI.multiply(ee.Image(1))
135 |
136 | ESUNImg_OLI = ESUN_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
137 |
138 | #Ozone correction
139 | #Ozone coefficients
140 | koz_OLI = ee.Image.constant(0.0039).addBands(ee.Image.constant(0.0218))\
141 | .addBands(ee.Image.constant(0.1078))\
142 | .addBands(ee.Image.constant(0.0608))\
143 | .addBands(ee.Image.constant(0.0019))\
144 | .addBands(ee.Image.constant(0))\
145 | .addBands(ee.Image.constant(0))\
146 | .toArray().toArray(1)
147 |
148 | #Calculate ozone optical thickness
149 | Toz_OLI = koz_OLI.multiply(DU_OLI).divide(ee.Image.constant(1000))
150 | # Calculate TOA radiance in the absense of ozone
151 | Lt_OLI = Ltoa_OLI.multiply(((Toz_OLI)).multiply((ee.Image.constant(1).divide(cosdSunZe_OLI)).add(ee.Image.constant(1).divide(cosdSatZe_OLI))).exp())
152 |
153 | # Rayleigh optical thickness
154 | bandCenter_OLI = ee.Image(443).divide(1000).addBands(ee.Image(483).divide(1000))\
155 | .addBands(ee.Image(561).divide(1000))\
156 | .addBands(ee.Image(655).divide(1000))\
157 | .addBands(ee.Image(865).divide(1000))\
158 | .addBands(ee.Image(1609).divide(1000))\
159 | .addBands(ee.Number(2201).divide(1000))\
160 | .toArray().toArray(1)
161 |
162 | # create an empty image to save new Tr values to
163 | Tr_OLI = (P_OLI.divide(Po_OLI)).multiply(ee.Image(0.008569).multiply(bandCenter_OLI.pow(-4))).multiply((ee.Image(1).add(ee.Image(0.0113).multiply(bandCenter_OLI.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter_OLI.pow(-4)))))
164 |
165 | # Fresnel Reflection #
166 | # Specular reflection (s- and p- polarization states)
167 | theta_V_OLI = ee.Image(0.0000000001)
168 | sin_theta_j_OLI = sindSunZe_OLI.divide(ee.Image(1.333))
169 |
170 | theta_j_OLI = sin_theta_j_OLI.asin().multiply(ee.Image(180).divide(pi))
171 |
172 | theta_SZ_OLI = SunZe_OLI
173 |
174 | R_theta_SZ_s_OLI = (((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).subtract(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).add(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)))
175 |
176 | R_theta_V_s_OLI = ee.Image(0.0000000001)
177 | R_theta_SZ_p_OLI = (((theta_SZ_OLI.multiply(pi.divide(180))).subtract(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)).divide((((theta_SZ_OLI.multiply(pi.divide(180))).add(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)))
178 | R_theta_V_p_OLI = ee.Image(0.0000000001)
179 | R_theta_SZ_OLI = ee.Image(0.5).multiply(R_theta_SZ_s_OLI.add(R_theta_SZ_p_OLI))
180 | R_theta_V_OLI = ee.Image(0.5).multiply(R_theta_V_s_OLI.add(R_theta_V_p_OLI))
181 |
182 | # Rayleigh scattering phase function #
183 | # Sun-sensor geometry
184 | theta_neg_OLI = ((cosdSunZe_OLI.multiply(ee.Image(-1))).multiply(cosdSatZe_OLI)).subtract((sindSunZe_OLI).multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI))
185 |
186 | theta_neg_inv_OLI = theta_neg_OLI.acos().multiply(ee.Image(180).divide(pi))
187 |
188 | theta_pos_OLI = (cosdSunZe_OLI.multiply(cosdSatZe_OLI)).subtract(sindSunZe_OLI.multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI))
189 |
190 | theta_pos_inv_OLI = theta_pos_OLI.acos().multiply(ee.Image(180).divide(pi))
191 |
192 | cosd_tni_OLI = theta_neg_inv_OLI.multiply(pi.divide(180)).cos() # in degrees
193 |
194 | cosd_tpi_OLI = theta_pos_inv_OLI.multiply(pi.divide(180)).cos() # in degrees
195 |
196 | Pr_neg_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni_OLI.pow(2))))
197 |
198 | Pr_pos_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi_OLI.pow(2))))
199 |
200 | # Rayleigh scattering phase function
201 | Pr_OLI = Pr_neg_OLI.add((R_theta_SZ_OLI.add(R_theta_V_OLI)).multiply(Pr_pos_OLI))
202 |
203 | # Calulate Lr,
204 | denom_OLI = ee.Image(4).multiply(pi).multiply(cosdSatZe_OLI)
205 | Lr_OLI = (ESUN_OLI.multiply(Tr_OLI)).multiply(Pr_OLI.divide(denom_OLI))
206 |
207 | # Rayleigh corrected radiance
208 | Lrc_OLI = (Lt_OLI.divide(ee.Image(10))).subtract(Lr_OLI)
209 | LrcImg_OLI = Lrc_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
210 |
211 | # Rayleigh corrected reflectance
212 | prc_OLI = Lrc_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI))
213 | pc = prc_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
214 |
215 | pcVisParams = {'bands': 'B4,B3,B2','min': 0,'max': 0.1}
216 | pc = pc.multiply(mask)
217 | pcImgID = pc.getMapId(pcVisParams)
218 | # Calculate FAI
219 | NIRprime = (pc.select('B4')).add((pc.select('B6').subtract(pc.select('B4'))).multiply((ee.Image(865).subtract(ee.Image(655))).divide((ee.Image(1609).subtract(ee.Image(655))))))
220 | fai = (pc.select('B5').subtract(NIRprime))
221 | fai = fai.multiply(mask)
222 |
223 | faiVisParams = {'min': -0.05, 'max': 0.2, 'palette': '#000080,#0080FF,#7BFF7B,#FF9700,#800000'}
224 | faiImgID = fai.getMapId(faiVisParams)
225 |
226 | print pcImgID
227 | print faiImgID
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
--------------------------------------------------------------------------------
/python/ls8_single_image.py:
--------------------------------------------------------------------------------
1 | import ee
2 | from ee.ee_exception import EEException
3 |
4 | try:
5 | ee.Initialize()
6 | except EEException as e:
7 | from oauth2client.service_account import ServiceAccountCredentials
8 | credentials = ServiceAccountCredentials.from_p12_keyfile(
9 | service_account_email='',
10 | filename='',
11 | private_key_password='notasecret',
12 | scopes=ee.oauth.SCOPE + ' https://www.googleapis.com/auth/drive ')
13 | ee.Initialize(credentials)
14 | # This script processes a single Landsat 8 image of interest.
15 | # WQ parameters such chlor-a, secchi depth, trophic state index are calculated
16 | # How to use:
17 | # (1) If a "geometry" variable exists in the imports window, delete it.
18 | # (2) Within the map, select the point button near the top left side.
19 | # (3) Create a new point by clicking on a location of interest.
20 | # (4) Adjust the time frame you wish to browse by adding here:
21 |
22 | geometry = ee.Geometry.Polygon([[[30.76171875, 0.9049611504960419],
23 | [30.8935546875, -3.487377195492663],
24 | [35.5517578125, -3.2680324702882952],
25 | [35.5517578125, 1.9593043032313748]]])
26 | # begin date
27 | iniDate = '2015-05-01'
28 | # end date
29 | endDate = '2018-03-31'
30 | # (5) Adjust a cloud % threshold here:
31 | oliCloudPerc = 5
32 | # (6) Click Run
33 | # (7) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
34 | # image from this collection, find the FILE_ID within the features and copy and paste it here:
35 | l8 = ee.Image('LANDSAT/LC08/C01/T1/LC08_170060_20171226')
36 | # (8) Click "Run"
37 | # (9) Export each image by clicking on the run button within the "tasks" tab.
38 |
39 | # Author: Benjamin Page #
40 | # Citations:
41 | # Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring, In Review
42 |
43 | #########################################################/
44 | #########################################################/
45 | #########################################################/
46 |
47 | # Import Collections #
48 |
49 | # landsat 8 raw dn
50 | OLI_DN = ee.ImageCollection('LANDSAT/LC08/C01/T1')
51 |
52 | # landsat-8 surface reflactance product (for masking purposes)
53 | SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
54 |
55 | # toms / omi
56 | ozone = ee.ImageCollection('TOMS/MERGED')
57 |
58 | #########################################################/
59 | #########################################################/
60 | #########################################################/
61 | # Filtering Collection and Masking #
62 |
63 | pi = ee.Image(3.141592)
64 |
65 | # water mask
66 | startMonth = 5
67 | endMonth = 9
68 | startYear = 2013
69 | endYear = 2017
70 |
71 | forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter \
72 | (ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'))
73 | mask = ee.Image(forMask.select('B6').median().lt(600))
74 | mask = mask.updateMask(mask)
75 |
76 | # filter landsat 8 collection
77 | FC_OLI = OLI_DN.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUD_COVER', "less_than", oliCloudPerc)
78 |
79 | # oli image date
80 | oliDate = l8.date()
81 |
82 | footprint = l8.geometry()
83 |
84 | #########################################################/
85 | #########################################################/
86 | #########################################################/
87 | ################################################################################
88 | # single OLI image ATMOSPHERIC CORRECTION #
89 |
90 | # dem
91 | DEM_OLI = ee.Image('USGS/SRTMGL1_003').clip(footprint)
92 |
93 | # ozone
94 | DU_OLI = ee.Image(ozone.filterDate(iniDate ,endDate).filterBounds(footprint).mean())
95 |
96 | # Julian Day
97 | imgDate_OLI = ee.Date(l8.get('system:time_start'))
98 | FOY_OLI = ee.Date.fromYMD(imgDate_OLI.get('year') ,1 ,1)
99 | JD_OLI = imgDate_OLI.difference(FOY_OLI ,'day').int().add(1)
100 |
101 | # Earth-Sun distance
102 | d_OLI = ee.Image.constant(l8.get('EARTH_SUN_DISTANCE'))
103 |
104 | # Sun elevation
105 | SunEl_OLI = ee.Image.constant(l8.get('SUN_ELEVATION'))
106 |
107 | # Sun azimuth
108 | SunAz_OLI = ee.Image.constant(l8.get('SUN_AZIMUTH'))
109 |
110 | # Satellite zenith
111 | SatZe_OLI = ee.Image(0.0)
112 | cosdSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).cos()
113 | sindSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).sin()
114 |
115 | # Satellite azimuth
116 | SatAz_OLI = ee.Image(0.0)
117 |
118 | # Sun zenith
119 | SunZe_OLI = ee.Image(90).subtract(SunEl_OLI)
120 | cosdSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image.constant(180))).cos() # in degrees
121 | sindSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image(180))).sin() # in degrees
122 |
123 | # Relative azimuth
124 | RelAz_OLI = ee.Image(SunAz_OLI)
125 | cosdRelAz_OLI = RelAz_OLI.multiply(pi.divide(ee.Image(180))).cos()
126 |
127 | # Pressure calculation
128 | P_OLI = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM_OLI)).pow(5.25588)).multiply \
129 | (0.01)
130 | Po_OLI = ee.Image(1013.25)
131 |
132 | # Radiometric Calibration #
133 | # define bands to be converted to radiance
134 | bands_OLI = ['B1' ,'B2' ,'B3' ,'B4' ,'B5' ,'B6' ,'B7']
135 |
136 | # radiance_mult_bands
137 | rad_mult_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_MULT_BAND_1')),
138 | ee.Image(l8.get('RADIANCE_MULT_BAND_2')),
139 | ee.Image(l8.get('RADIANCE_MULT_BAND_3')),
140 | ee.Image(l8.get('RADIANCE_MULT_BAND_4')),
141 | ee.Image(l8.get('RADIANCE_MULT_BAND_5')),
142 | ee.Image(l8.get('RADIANCE_MULT_BAND_6')),
143 | ee.Image(l8.get('RADIANCE_MULT_BAND_7'))]
144 | )).toArray(1)
145 |
146 | # radiance add band
147 | rad_add_OLI = ee.Image(ee.Array([ee.Image(l8.get('RADIANCE_ADD_BAND_1')),
148 | ee.Image(l8.get('RADIANCE_ADD_BAND_2')),
149 | ee.Image(l8.get('RADIANCE_ADD_BAND_3')),
150 | ee.Image(l8.get('RADIANCE_ADD_BAND_4')),
151 | ee.Image(l8.get('RADIANCE_ADD_BAND_5')),
152 | ee.Image(l8.get('RADIANCE_ADD_BAND_6')),
153 | ee.Image(l8.get('RADIANCE_ADD_BAND_7'))]
154 | )).toArray(1)
155 |
156 | # create an empty image to save new radiance bands to
157 | imgArr_OLI = l8.select(bands_OLI).toArray().toArray(1)
158 | Ltoa_OLI = imgArr_OLI.multiply(rad_mult_OLI).add(rad_add_OLI)
159 |
160 | # esun
161 | ESUN_OLI = ee.Image.constant(197.24790954589844)\
162 | .addBands(ee.Image.constant(201.98426818847656))\
163 | .addBands(ee.Image.constant(186.12677001953125))\
164 | .addBands(ee.Image.constant(156.95257568359375))\
165 | .addBands(ee.Image.constant(96.04714965820312))\
166 | .addBands(ee.Image.constant(23.8833221450863))\
167 | .addBands(ee.Image.constant(8.04995873449635)).toArray().toArray(1)
168 | ESUN_OLI = ESUN_OLI.multiply(ee.Image(1))
169 |
170 | ESUNImg_OLI = ESUN_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
171 |
172 | # Ozone Correction #
173 | # Ozone coefficients
174 | koz_OLI = ee.Image.constant(0.0039).addBands(ee.Image.constant(0.0218))\
175 | .addBands(ee.Image.constant(0.1078))\
176 | .addBands(ee.Image.constant(0.0608))\
177 | .addBands(ee.Image.constant(0.0019))\
178 | .addBands(ee.Image.constant(0))\
179 | .addBands(ee.Image.constant(0))\
180 | .toArray().toArray(1)
181 |
182 | # Calculate ozone optical thickness
183 | Toz_OLI = koz_OLI.multiply(DU_OLI).divide(ee.Image.constant(1000))
184 |
185 | # Calculate TOA radiance in the absense of ozone
186 | Lt_OLI = Ltoa_OLI.multiply(((Toz_OLI)).multiply
187 | ((ee.Image.constant(1).divide(cosdSunZe_OLI)).add(ee.Image.constant(1).divide(cosdSatZe_OLI))).exp())
188 |
189 | # Rayleigh optical thickness
190 | bandCenter_OLI = ee.Image(443).divide(1000).addBands(ee.Image(483).divide(1000))\
191 | .addBands(ee.Image(561).divide(1000))\
192 | .addBands(ee.Image(655).divide(1000))\
193 | .addBands(ee.Image(865).divide(1000))\
194 | .addBands(ee.Image(1609).divide(1000))\
195 | .addBands(ee.Number(2201).divide(1000))\
196 | .toArray().toArray(1)
197 |
198 | # create an empty image to save new Tr values to
199 | Tr_OLI = (P_OLI.divide(Po_OLI)).multiply(ee.Image(0.008569).multiply(bandCenter_OLI.pow(-4))).multiply((ee.Image(1).add\
200 | (ee.Image(0.0113).multiply(bandCenter_OLI.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter_OLI.pow(-4)))))
201 |
202 | # Fresnel Reflection #
203 | # Specular reflection (s- and p- polarization states)
204 | theta_V_OLI = ee.Image(0.0000000001)
205 | sin_theta_j_OLI = sindSunZe_OLI.divide(ee.Image(1.333))
206 |
207 | theta_j_OLI = sin_theta_j_OLI.asin().multiply(ee.Image(180).divide(pi))
208 |
209 | theta_SZ_OLI = SunZe_OLI
210 |
211 | R_theta_SZ_s_OLI = (((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).subtract
212 | (theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ_OLI.multiply
213 | (pi.divide(ee.Image(180)))).add(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)))
214 |
215 | R_theta_V_s_OLI = ee.Image(0.0000000001)
216 |
217 | R_theta_SZ_p_OLI = (((theta_SZ_OLI.multiply(pi.divide(180))).subtract(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)).\
218 | divide((((theta_SZ_OLI.multiply(pi.divide(180))).add(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)))
219 |
220 | R_theta_V_p_OLI = ee.Image(0.0000000001)
221 |
222 | R_theta_SZ_OLI = ee.Image(0.5).multiply(R_theta_SZ_s_OLI.add(R_theta_SZ_p_OLI))
223 |
224 | R_theta_V_OLI = ee.Image(0.5).multiply(R_theta_V_s_OLI.add(R_theta_V_p_OLI))
225 |
226 | # Rayleigh scattering phase function #
227 | # Sun-sensor geometry
228 | theta_neg_OLI = ((cosdSunZe_OLI.multiply(ee.Image(-1))).multiply(cosdSatZe_OLI)).\
229 | subtract((sindSunZe_OLI).multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI))
230 |
231 | theta_neg_inv_OLI = theta_neg_OLI.acos().multiply(ee.Image(180).divide(pi))
232 |
233 | theta_pos_OLI = (cosdSunZe_OLI.multiply(cosdSatZe_OLI)).\
234 | subtract(sindSunZe_OLI.multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI))
235 |
236 | theta_pos_inv_OLI = theta_pos_OLI.acos().multiply(ee.Image(180).divide(pi))
237 |
238 | cosd_tni_OLI = theta_neg_inv_OLI.multiply(pi.divide(180)).cos() # in degrees
239 |
240 | cosd_tpi_OLI = theta_pos_inv_OLI.multiply(pi.divide(180)).cos() # in degrees
241 |
242 | Pr_neg_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni_OLI.pow(2))))
243 |
244 | Pr_pos_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi_OLI.pow(2))))
245 |
246 | # Rayleigh scattering phase function
247 | Pr_OLI = Pr_neg_OLI.add((R_theta_SZ_OLI.add(R_theta_V_OLI)).multiply(Pr_pos_OLI))
248 |
249 | # Calulate Lr,
250 | denom_OLI = ee.Image(4).multiply(pi).multiply(cosdSatZe_OLI)
251 | Lr_OLI = (ESUN_OLI.multiply(Tr_OLI)).multiply(Pr_OLI.divide(denom_OLI))
252 |
253 | # Rayleigh corrected radiance
254 | Lrc_OLI = (Lt_OLI.divide(ee.Image(10))).subtract(Lr_OLI)
255 | LrcImg_OLI = Lrc_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
256 |
257 | # Rayleigh corrected reflectance
258 | prc_OLI = Lrc_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI))
259 | prcImg_OLI = prc_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
260 |
261 | # Aerosol Correction #
262 | # Bands in nm
263 | bands_nm_OLI = ee.Image(443).addBands(ee.Image(483))\
264 | .addBands(ee.Image(561))\
265 | .addBands(ee.Image(655))\
266 | .addBands(ee.Image(865))\
267 | .addBands(ee.Image(0))\
268 | .addBands(ee.Image(0))\
269 | .toArray().toArray(1)
270 |
271 | # Lam in SWIR bands
272 | Lam_6_OLI = LrcImg_OLI.select('B6')
273 | Lam_7_OLI = LrcImg_OLI.select('B7')
274 |
275 | # Calculate aerosol type
276 | eps_OLI = (((((Lam_7_OLI).divide(ESUNImg_OLI.select('B7'))).log()).\
277 | subtract(((Lam_6_OLI).divide(ESUNImg_OLI.select('B6'))).log())).divide(ee.Image(2201).subtract(ee.Image(1609))))\
278 | .multiply(mask)
279 |
280 | # Calculate multiple scattering of aerosols for each band
281 | Lam_OLI = (Lam_7_OLI).multiply(((ESUN_OLI).divide(ESUNImg_OLI.select('B7'))))\
282 | .multiply((eps_OLI.multiply(ee.Image(-1))).multiply((bands_nm_OLI.divide(ee.Image(2201)))).exp())
283 |
284 | # diffuse transmittance
285 | trans_OLI = Tr_OLI.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe_OLI)).exp()
286 |
287 | # Compute water-leaving radiance
288 | Lw_OLI = Lrc_OLI.subtract(Lam_OLI).divide(trans_OLI)
289 |
290 | # water-leaving reflectance
291 | pw_OLI = (Lw_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI)))
292 | pwImg_OLI = pw_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
293 |
294 | # Rrs
295 | Rrs = (pw_OLI.divide(pi).arrayProject([0]).arrayFlatten([bands_OLI]).slice(0, 5))
296 |
297 | ###########################################################/
298 | # Models #
299 |
300 | # Surface water temperature
301 | TIRS_1 = l8.select('B10')
302 |
303 | b10_add_band = ee.Number(TIRS_1.get('RADIANCE_ADD_BAND_10'))
304 | b10_mult_band = ee.Number(TIRS_1.get('RADIANCE_MULT_BAND_10'))
305 |
306 | Oi = ee.Number(0.29)
307 |
308 | TIRS_cal = (TIRS_1.multiply(b10_mult_band).add(b10_add_band).subtract(Oi)) # .multiply(lakes)
309 |
310 | K1_b10 = ee.Number(TIRS_1.get('K1_CONSTANT_BAND_10'))
311 | K2_b10 = ee.Number(TIRS_1.get('K2_CONSTANT_BAND_10'))
312 |
313 | LST = ((ee.Image(K2_b10).divide(((ee.Image(K1_b10).divide(ee.Image(TIRS_cal))).add(ee.Image(1))).log()))\
314 | .subtract(ee.Image(273))).multiply(mask) # Celsius
315 | LST = (ee.Image(0.7745).multiply(LST)).add(ee.Image(9.6502)) # calibration R2 = 0.8599
316 |
317 | # chlor_a
318 | # Chlorophyll-a OC3
319 | a0 = ee.Image(0.2412)
320 | a1 = ee.Image(-2.0546)
321 | a2 = ee.Image(1.1776)
322 | a3 = ee.Image(-0.5538)
323 | a4 = ee.Image(-0.4570)
324 |
325 | log_BG = (Rrs.select('B1').divide(Rrs.select('B3'))).log10()
326 |
327 | a1a = a1.multiply(log_BG.pow(1))
328 | a2a = a2.multiply(log_BG.pow(2))
329 | a3a = a3.multiply(log_BG.pow(3))
330 | a4a = a4.multiply(log_BG.pow(4))
331 |
332 | sum = a1a.add(a2a).add(a3a).add(a4a)
333 |
334 | log10_chlor_a = a0.add(sum)
335 |
336 | chlor_a = ee.Image(10).pow(log10_chlor_a)
337 | chlor_a_cal = ee.Image(4.0752).multiply(chlor_a).subtract(ee.Image(3.9617))
338 |
339 | # SD
340 | ln_BlueRed = (Rrs.select('B2').divide(Rrs.select('B4'))).log()
341 | lnMOSD = (ee.Image(1.4856).multiply(ln_BlueRed)).add(ee.Image(0.2734)) # R2 = 0.8748 with in-situ
342 | MOSD = ee.Image(10).pow(lnMOSD) # log space to (m)
343 | SD = (ee.Image(0.1777).multiply(MOSD)).add(ee.Image(1.0813))
344 |
345 | # tsi
346 | TSI_c = ee.Image(30.6).add(ee.Image(9.81)).multiply(chlor_a_cal.log())
347 | TSI_s = ee.Image(60.0).subtract(ee.Image(14.41)).multiply(SD.log())
348 | TSI = (TSI_c.add(TSI_s)).divide(ee.Image(2))
349 |
350 | # Reclassify TSI
351 |
352 | # Create conditions
353 | mask1 = TSI.lt(30) # (1)
354 | mask2 = TSI.gte(30).And(TSI.lt(40)) # (2)
355 | mask3 = TSI.gte(40).And(TSI.lt(50)) # (3)
356 | mask4 = TSI.gte(50).And(TSI.lt(60)) # (4)
357 | mask5 = TSI.gte(60).And(TSI.lt(70)) # (5)
358 | mask6 = TSI.gte(70).And(TSI.lt(80)) # (6)
359 | mask7 = TSI.gte(80) # (7)
360 |
361 | # Reclassify conditions into new values
362 | img1 = TSI.where(mask1.eq(1), 1).mask(mask1)
363 | img2 = TSI.where(mask2.eq(1), 2).mask(mask2)
364 | img3 = TSI.where(mask3.eq(1), 3).mask(mask3)
365 | img4 = TSI.where(mask4.eq(1), 4).mask(mask4)
366 | img5 = TSI.where(mask5.eq(1), 5).mask(mask5)
367 | img6 = TSI.where(mask6.eq(1), 6).mask(mask6)
368 | img7 = TSI.where(mask7.eq(1), 7).mask(mask7)
369 |
370 | # Ouput of reclassified image
371 | TSI_R = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7)
372 |
373 | l8Lyr = l8.multiply(mask)
374 | l8VisParams = {'bands': 'B4,B3,B2', 'min': 0, 'max': 15000}
375 | l8MapId = l8Lyr.getMapId(l8VisParams)
376 |
377 | LSTvisParams = {'min': 20, 'max': 30, 'palette': 'darkblue,blue,white,red,darkred'}
378 | LSTMapId = LST.getMapId(LSTvisParams)
379 |
380 | chlor_a_cal_visParams = {'min': 0, 'max': 80, 'palette':'blue,cyan,limegreen,yellow,orange,darkred'}
381 | chlor_a_cal_mapid = chlor_a_cal.getMapId(chlor_a_cal_visParams)
382 |
383 | SDvisParams = {'min': 0, 'max': 3, 'palette': 'darkred,orange,yellow,limegreen,cyan,blue'}
384 | SDMapId = SD.getMapId(SDvisParams)
385 |
386 | TSIvisParams = {'min': 30, 'max': 70, 'palette': 'blue,cyan,limegreen,yellow,orange,darkred'}
387 | TSIMapId = TSI.getMapId(TSIvisParams)
388 |
389 | TSI_R_visParams = {'min': 1, 'max': 7, 'palette': 'purple,blue,limegreen,yellow,orange,orangered,darkred'}
390 | TSI_R_MapId = TSI_R.getMapId(TSI_R_visParams)
391 |
392 | print 'L8',l8MapId
393 | print 'LST',LSTMapId
394 | print 'Chlor A',chlor_a_cal_mapid
395 | print 'SD',SDMapId
396 | print 'TSI',TSIMapId
397 | print 'TSI R',TSI_R_MapId
398 | # Map Layers
399 |
400 | # Map.addLayer(l8.multiply(mask), {bands: ['B4', 'B3', 'B2'], min: 0, max: 15000}, 'rgb', false) # rgb
401 | # Map.addLayer(LST, {min: 20, max: 30, palette: ['darkblue', 'blue', 'white', 'red', 'darkred']}, 'LST', false)
402 | # Map.addLayer(chlor_a_cal, {min: 0, max: 80, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange', 'darkred']},
403 | # 'chlor_a', false)
404 | # Map.addLayer(SD, {min: 0, max: 3, palette: ['darkred', 'orange', 'yellow', 'limegreen', 'cyan', 'blue']}, 'SD', false)
405 | # Map.addLayer(TSI, {min: 30, max: 70, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange', 'darkred']}, 'TSI',
406 | # false)
407 | # Map.addLayer(TSI_R,
408 | # {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']},
409 | # 'TSI_R', true)
410 | #
411 | #
412 |
--------------------------------------------------------------------------------
/python/ls8_wq_timeseries.py:
--------------------------------------------------------------------------------
1 | import ee
2 | from ee.ee_exception import EEException
3 | import datetime
4 |
5 | try:
6 | ee.Initialize()
7 | except EEException as e:
8 | from oauth2client.service_account import ServiceAccountCredentials
9 | credentials = ServiceAccountCredentials.from_p12_keyfile(
10 | service_account_email='',
11 | filename='',
12 | private_key_password='notasecret',
13 | scopes=ee.oauth.SCOPE + ' https://www.googleapis.com/auth/drive ')
14 | ee.Initialize(credentials)
15 |
16 | geometry = ee.Geometry.Polygon([[[78.41079711914062, 17.465297700926097],
17 | [78.41629028320312, 17.334252606951086],
18 | [78.59893798828125, 17.33490806580805],
19 | [78.55087280273438, 17.494769882318828]]])
20 |
21 | # This script processes and charts WQ parameter valuse from a collection of Lansdat 8 archive for a particular pixel throughout time.
22 | # WQ parameters such secchi depth, tropshic state index, and lake temperature
23 | # How to use:
24 | # (1) If a "geometry" iable exists in the imports window, delete it.
25 | # (2) Within the map, select the point button near the top left side.
26 | # (3) Create a new point by clicking on a location of interest.
27 | # (4) Adjust the time frame you wish to browse by adding here:
28 | # begin date
29 | iniDate = '2016-04-01'
30 | # end date
31 | endDate = '2016-10-31'
32 | # (5) Adjust a cloud % threshold here:
33 | oliCloudPerc = 10
34 | # (5) Click Run
35 | # (5) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
36 | # image from this collection, highlight the FILE_ID and copy and paste it into the proper location within the "Ls8 Single Image" script.
37 | # (6) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
38 | # (6) Click "Run"
39 | # (7) Export each chart as a csv by clicking on the export icon near the top-right of the chart.
40 |
41 | # Author: Benjamin Page #
42 | # Citations:
43 | # Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring
44 |
45 | #########################################################/
46 | #########################################################/
47 | #########################################################/
48 |
49 |
50 | # Import Collections #
51 |
52 | # landsat 8 raw dn
53 | OLI_DN = ee.ImageCollection('LANDSAT/LC08/C01/T1')
54 |
55 | # landsat-8 surface reflactance product (for masking purposes)
56 | SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
57 |
58 | # toms / omi
59 | ozone = ee.ImageCollection('TOMS/MERGED')
60 |
61 | #########################################################/
62 | #########################################################/
63 | #########################################################/
64 | # Filtering Collection and Masking #
65 |
66 | pi = ee.Image(3.141592)
67 |
68 | # water mask
69 | startMonth = 5
70 | endMonth = 9
71 | startYear = 2013
72 | endYear = 2017
73 |
74 | forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10)\
75 | .filter(ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'))
76 | mask = ee.Image(forMask.select('B6').median().lt(600))
77 | mask = mask.updateMask(mask)
78 |
79 | # filter landsat 8 collection
80 | FC_OLI = OLI_DN.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUD_COVER', "less_than",oliCloudPerc)
81 |
82 | # Mapping Functions #
83 |
84 | def l8Correction(img):
85 |
86 | # tile geometry
87 |
88 | l8Footprint = img.geometry()
89 |
90 | # dem
91 | DEM_OLI = ee.Image('USGS/SRTMGL1_003').clip(l8Footprint)
92 |
93 | # ozone
94 | DU_OLI = ee.Image(ozone.filterDate(iniDate, endDate).filterBounds(l8Footprint).mean())
95 |
96 | # Julian Day
97 | imgDate_OLI = ee.Date(img.get('system:time_start'))
98 | FOY_OLI = ee.Date.fromYMD(imgDate_OLI.get('year'), 1, 1)
99 | JD_OLI = imgDate_OLI.difference(FOY_OLI, 'day').int().add(1)
100 |
101 | # Earth-Sun distance
102 | d_OLI = ee.Image.constant(img.get('EARTH_SUN_DISTANCE'))
103 |
104 | # Sun elevation
105 | SunEl_OLI = ee.Image.constant(img.get('SUN_ELEVATION'))
106 |
107 | # Sun azimuth
108 | SunAz_OLI = ee.Image.constant(img.get('SUN_AZIMUTH'))
109 |
110 | # Satellite zenith
111 | SatZe_OLI = ee.Image(0.0)
112 | cosdSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).cos()
113 | sindSatZe_OLI = (SatZe_OLI).multiply(pi.divide(ee.Image(180))).sin()
114 |
115 | # Satellite azimuth
116 | SatAz_OLI = ee.Image(0.0).clip(l8Footprint)
117 |
118 | # Sun zenith
119 | SunZe_OLI = ee.Image(90).subtract(SunEl_OLI)
120 | cosdSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image.constant(180))).cos() # in degrees
121 | sindSunZe_OLI = SunZe_OLI.multiply(pi.divide(ee.Image(180))).sin() # in degrees
122 |
123 | # Relative azimuth
124 | RelAz_OLI = ee.Image(SunAz_OLI)
125 | cosdRelAz_OLI = RelAz_OLI.multiply(pi.divide(ee.Image(180))).cos()
126 |
127 | # Pressure calculation
128 | P_OLI = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM_OLI)).pow(5.25588)).multiply(
129 | 0.01)
130 | Po_OLI = ee.Image(1013.25)
131 |
132 | # Radiometric Calibration #
133 | # define bands to be converted to radiance
134 | bands_OLI = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']
135 |
136 | # radiance_mult_bands
137 | rad_mult_OLI = ee.Image(ee.Array([ee.Image(img.get('RADIANCE_MULT_BAND_1')),
138 | ee.Image(img.get('RADIANCE_MULT_BAND_2')),
139 | ee.Image(img.get('RADIANCE_MULT_BAND_3')),
140 | ee.Image(img.get('RADIANCE_MULT_BAND_4')),
141 | ee.Image(img.get('RADIANCE_MULT_BAND_5')),
142 | ee.Image(img.get('RADIANCE_MULT_BAND_6')),
143 | ee.Image(img.get('RADIANCE_MULT_BAND_7'))]
144 | )).toArray(1)
145 |
146 | # radiance add band
147 | rad_add_OLI = ee.Image(ee.Array([ee.Image(img.get('RADIANCE_ADD_BAND_1')),
148 | ee.Image(img.get('RADIANCE_ADD_BAND_2')),
149 | ee.Image(img.get('RADIANCE_ADD_BAND_3')),
150 | ee.Image(img.get('RADIANCE_ADD_BAND_4')),
151 | ee.Image(img.get('RADIANCE_ADD_BAND_5')),
152 | ee.Image(img.get('RADIANCE_ADD_BAND_6')),
153 | ee.Image(img.get('RADIANCE_ADD_BAND_7'))]
154 | )).toArray(1)
155 |
156 | # create an empty image to save new radiance bands to
157 | imgArr_OLI = img.select(bands_OLI).toArray().toArray(1)
158 | Ltoa_OLI = imgArr_OLI.multiply(rad_mult_OLI).add(rad_add_OLI)
159 |
160 | # esun
161 | ESUN_OLI = ee.Image.constant(197.24790954589844)\
162 | .addBands(ee.Image.constant(201.98426818847656))\
163 | .addBands(ee.Image.constant(186.12677001953125))\
164 | .addBands(ee.Image.constant(156.95257568359375))\
165 | .addBands(ee.Image.constant(96.04714965820312))\
166 | .addBands(ee.Image.constant(23.8833221450863))\
167 | .addBands(ee.Image.constant(8.04995873449635)).toArray().toArray(1)
168 | ESUN_OLI = ESUN_OLI.multiply(ee.Image(1))
169 |
170 | ESUNImg_OLI = ESUN_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
171 |
172 | # Ozone Correction #
173 | # Ozone coefficients
174 | koz_OLI = ee.Image.constant(0.0039).addBands(ee.Image.constant(0.0218))\
175 | .addBands(ee.Image.constant(0.1078))\
176 | .addBands(ee.Image.constant(0.0608))\
177 | .addBands(ee.Image.constant(0.0019))\
178 | .addBands(ee.Image.constant(0))\
179 | .addBands(ee.Image.constant(0))\
180 | .toArray().toArray(1)
181 |
182 | # Calculate ozone optical thickness
183 | Toz_OLI = koz_OLI.multiply(DU_OLI).divide(ee.Image.constant(1000))
184 |
185 | # Calculate TOA radiance in the absense of ozone
186 | Lt_OLI = Ltoa_OLI.multiply(((Toz_OLI)).multiply(
187 | (ee.Image.constant(1).divide(cosdSunZe_OLI)).add(ee.Image.constant(1).divide(cosdSatZe_OLI))).exp())
188 |
189 | # Rayleigh optical thickness
190 | bandCenter_OLI = ee.Image(443).divide(1000).addBands(ee.Image(483).divide(1000))\
191 | .addBands(ee.Image(561).divide(1000))\
192 | .addBands(ee.Image(655).divide(1000))\
193 | .addBands(ee.Image(865).divide(1000))\
194 | .addBands(ee.Image(1609).divide(1000))\
195 | .addBands(ee.Number(2201).divide(1000))\
196 | .toArray().toArray(1)
197 |
198 | # create an empty image to save new Tr values to
199 | Tr_OLI = (P_OLI.divide(Po_OLI)).multiply(ee.Image(0.008569).multiply(bandCenter_OLI.pow(-4))).multiply((ee.Image(1).add(
200 | ee.Image(0.0113).multiply(bandCenter_OLI.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter_OLI.pow(-4)))))
201 |
202 | # Fresnel Reflection #
203 | # Specular reflection (s- and p- polarization states)
204 | theta_V_OLI = ee.Image(0.0000000001)
205 | sin_theta_j_OLI = sindSunZe_OLI.divide(ee.Image(1.333))
206 |
207 | theta_j_OLI = sin_theta_j_OLI.asin().multiply(ee.Image(180).divide(pi))
208 |
209 | theta_SZ_OLI = SunZe_OLI
210 |
211 | R_theta_SZ_s_OLI = (((theta_SZ_OLI.multiply(pi.divide(ee.Image(180)))).subtract(
212 | theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)).divide((((theta_SZ_OLI.multiply(
213 | pi.divide(ee.Image(180)))).add(theta_j_OLI.multiply(pi.divide(ee.Image(180))))).sin().pow(2)))
214 |
215 | R_theta_V_s_OLI = ee.Image(0.0000000001)
216 |
217 | R_theta_SZ_p_OLI = (
218 | ((theta_SZ_OLI.multiply(pi.divide(180))).subtract(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)).divide(
219 | (((theta_SZ_OLI.multiply(pi.divide(180))).add(theta_j_OLI.multiply(pi.divide(180)))).tan().pow(2)))
220 |
221 | R_theta_V_p_OLI = ee.Image(0.0000000001)
222 |
223 | R_theta_SZ_OLI = ee.Image(0.5).multiply(R_theta_SZ_s_OLI.add(R_theta_SZ_p_OLI))
224 |
225 | R_theta_V_OLI = ee.Image(0.5).multiply(R_theta_V_s_OLI.add(R_theta_V_p_OLI))
226 |
227 | # Rayleigh scattering phase function #
228 | # Sun-sensor geometry
229 | theta_neg_OLI = ((cosdSunZe_OLI.multiply(ee.Image(-1))).multiply(cosdSatZe_OLI)).subtract(
230 | (sindSunZe_OLI).multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI))
231 |
232 | theta_neg_inv_OLI = theta_neg_OLI.acos().multiply(ee.Image(180).divide(pi))
233 |
234 | theta_pos_OLI = (cosdSunZe_OLI.multiply(cosdSatZe_OLI)).subtract(
235 | sindSunZe_OLI.multiply(sindSatZe_OLI).multiply(cosdRelAz_OLI))
236 |
237 | theta_pos_inv_OLI = theta_pos_OLI.acos().multiply(ee.Image(180).divide(pi))
238 |
239 | cosd_tni_OLI = theta_neg_inv_OLI.multiply(pi.divide(180)).cos() # in degrees
240 |
241 | cosd_tpi_OLI = theta_pos_inv_OLI.multiply(pi.divide(180)).cos() # in degrees
242 |
243 | Pr_neg_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni_OLI.pow(2))))
244 |
245 | Pr_pos_OLI = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi_OLI.pow(2))))
246 |
247 | # Rayleigh scattering phase function
248 | Pr_OLI = Pr_neg_OLI.add((R_theta_SZ_OLI.add(R_theta_V_OLI)).multiply(Pr_pos_OLI))
249 |
250 | # Calulate Lr,
251 | denom_OLI = ee.Image(4).multiply(pi).multiply(cosdSatZe_OLI)
252 | Lr_OLI = (ESUN_OLI.multiply(Tr_OLI)).multiply(Pr_OLI.divide(denom_OLI))
253 |
254 | # Rayleigh corrected radiance
255 | Lrc_OLI = (Lt_OLI.divide(ee.Image(10))).subtract(Lr_OLI)
256 | LrcImg_OLI = Lrc_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
257 |
258 | # Rayleigh corrected reflectance
259 | prc_OLI = Lrc_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI))
260 | prcImg_OLI = prc_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
261 |
262 | # Aerosol Correction #
263 | # Bands in nm
264 | bands_nm_OLI = ee.Image(443).addBands(ee.Image(483))\
265 | .addBands(ee.Image(561))\
266 | .addBands(ee.Image(655))\
267 | .addBands(ee.Image(865))\
268 | .addBands(ee.Image(0))\
269 | .addBands(ee.Image(0))\
270 | .toArray().toArray(1)
271 |
272 | # Lam in SWIR bands
273 | Lam_6_OLI = LrcImg_OLI.select('B6')
274 | Lam_7_OLI = LrcImg_OLI.select('B7')
275 |
276 | # Calculate aerosol type
277 | eps_OLI = (((((Lam_7_OLI).divide(ESUNImg_OLI.select('B7'))).log()).subtract(
278 | ((Lam_6_OLI).divide(ESUNImg_OLI.select('B6'))).log())).divide(ee.Image(2201).subtract(ee.Image(1609)))).multiply(
279 | mask)
280 |
281 | # Calculate multiple scattering of aerosols for each band
282 | Lam_OLI = (Lam_7_OLI).multiply(((ESUN_OLI).divide(ESUNImg_OLI.select('B7')))).multiply(
283 | (eps_OLI.multiply(ee.Image(-1))).multiply((bands_nm_OLI.divide(ee.Image(2201)))).exp())
284 |
285 | # diffuse transmittance
286 | trans_OLI = Tr_OLI.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe_OLI)).exp()
287 |
288 | # Compute water-leaving radiance
289 | Lw_OLI = Lrc_OLI.subtract(Lam_OLI).divide(trans_OLI)
290 |
291 | # water-leaving reflectance
292 | pw_OLI = (Lw_OLI.multiply(pi).multiply(d_OLI.pow(2)).divide(ESUN_OLI.multiply(cosdSunZe_OLI)))
293 | pwImg_OLI = pw_OLI.arrayProject([0]).arrayFlatten([bands_OLI])
294 |
295 | # Rrs
296 | Rrs_coll = (pw_OLI.divide(pi).arrayProject([0]).arrayFlatten([bands_OLI]).slice(0, 5))
297 |
298 | return (Rrs_coll.set('system:time_start', img.get('system:time_start')))
299 |
300 |
301 | def LST(img):
302 |
303 | TIRS_1 = img.select('B10')
304 |
305 | b10_add_band = ee.Number(img.get('RADIANCE_ADD_BAND_10'))
306 | b10_mult_band = ee.Number(img.get('RADIANCE_MULT_BAND_10'))
307 |
308 | Oi = ee.Number(0.29)
309 |
310 | TIRS_cal = (TIRS_1.multiply(b10_mult_band).add(b10_add_band).subtract(Oi)) # .multiply(lakes)
311 |
312 | K1_b10 = ee.Number(TIRS_1.get('K1_CONSTANT_BAND_10'))
313 | K2_b10 = ee.Number(TIRS_1.get('K2_CONSTANT_BAND_10'))
314 |
315 | lst_coll = ((ee.Image(K2_b10).divide(((ee.Image(K1_b10).divide(ee.Image(TIRS_cal))).add(ee.Image(1))).log())).subtract(
316 | ee.Image(273))).multiply(mask) # Celsius
317 | lst_coll = (ee.Image(0.7745).multiply(lst_coll)).add(ee.Image(9.6502))
318 |
319 | # Define a square kernel with radius 1.5
320 | sq_kernel = ee.Kernel.square(1.5, 'pixels')
321 |
322 | # focal convolution
323 | lst_coll = lst_coll.focal_median(kernel=sq_kernel,iterations=ee.Number(1))
324 | # lst_coll = lst_coll.focal_median({'kernel': sq_kernel, 'iterations': 1})
325 |
326 | return (lst_coll.set('system:time_start', img.get('system:time_start')))
327 |
328 | def secchi(img):
329 |
330 | blueRed_coll = (img.select('B2').divide(img.select('B4'))).log()
331 | lnMOSD_coll = (ee.Image(1.4856).multiply(blueRed_coll)).add(ee.Image(0.2734)) # R2 = 0.8748 with Anthony's in-situ data
332 | MOSD_coll = ee.Image(10).pow(lnMOSD_coll)
333 | sd_coll = (ee.Image(0.1777).multiply(MOSD_coll)).add(ee.Image(1.0813))
334 | return (sd_coll.updateMask(sd_coll.lt(10)).set('system:time_start', img.get('system:time_start')))
335 |
336 | def trophicState(img):
337 |
338 | tsi_coll = ee.Image(60).subtract(ee.Image(14.41).multiply(img.log()))
339 | return (tsi_coll.updateMask(tsi_coll.lt(200)).set('system:time_start', img.get('system:time_start')))
340 |
341 |
342 | def reclassify(img):
343 |
344 | # Create conditions
345 | mask1 = img.lt(30) # (1)
346 | mask2 = img.gte(30).And(img.lt(40)) # (2)
347 | mask3 = img.gte(40).And(img.lt(50)) # (3)
348 | mask4 = img.gte(50).And(img.lt(60)) # (4)
349 | mask5 = img.gte(60).And(img.lt(70)) # (5)
350 | mask6 = img.gte(70).And(img.lt(80)) # (6)
351 | mask7 = img.gte(70) # (7)
352 |
353 | # Reclassify conditions into new values
354 | img1 = img.where(mask1.eq(1), 1).mask(mask1)
355 | img2 = img.where(mask2.eq(1), 2).mask(mask2)
356 | img3 = img.where(mask3.eq(1), 3).mask(mask3)
357 | img4 = img.where(mask4.eq(1), 4).mask(mask4)
358 | img5 = img.where(mask5.eq(1), 5).mask(mask5)
359 | img6 = img.where(mask6.eq(1), 6).mask(mask6)
360 | img7 = img.where(mask7.eq(1), 7).mask(mask7)
361 |
362 | # Ouput of reclassified image
363 | tsi_collR = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7)
364 | return (tsi_collR.updateMask(tsi_collR.set('system:time_start', img.get('system:time_start'))))
365 |
366 | #########################################################/
367 | #########################################################/
368 | #########################################################/
369 | # Processing Collection #
370 | Rrs_coll = FC_OLI.map(l8Correction)
371 |
372 | sd_coll = Rrs_coll.map(secchi)
373 |
374 | tsi_coll = sd_coll.map(trophicState)
375 |
376 | tsi_collR = tsi_coll.map(reclassify)
377 |
378 | lst_coll = FC_OLI.map(LST)
379 |
380 | ##########################################################################################
381 | ##########################################################################################
382 | ##########################################################################################
383 |
384 |
385 | ##########################################################################################
386 | ##########################################################################################
387 | ##########################################################################################
388 | # Map Layers #
389 |
390 | # l8
391 |
392 | Rrs_coll_mean = Rrs_coll.mean()
393 | l8_rsr_vis = {'bands': 'B4,B3,B1', 'min': 0, 'max': 0.04}
394 | l8_mapid = Rrs_coll_mean.getMapId(l8_rsr_vis)
395 |
396 | lst_coll_mean = lst_coll.mean()
397 | lst_vis = {'min': 23, 'max': 27, 'palette': 'darkblue,blue,white,red,darkred'}
398 | lst_mapid = lst_coll_mean.getMapId(lst_vis)
399 |
400 | tsi_coll_mean = tsi_coll.mean()
401 | tsi_vis = {'min': 30, 'max': 70, 'palette': 'blue,cyan,limegreen,yellow,darkred'}
402 | tsi_mapid = tsi_coll_mean.getMapId(tsi_vis)
403 |
404 | tsi_collR_mean = tsi_collR.mean()
405 | tsir_vis = {'min': 1, 'max': 7, 'palette': 'purple,blue,limegreen,yellow,orange,orangered,darkred'}
406 | tsir_mapid = tsi_collR_mean.getMapId(tsir_vis)
407 |
408 | print 'L8',l8_mapid
409 | print 'LST',lst_mapid
410 | print 'TSI',tsi_mapid
411 | print 'TSI R',tsir_mapid
412 |
413 |
414 | #
415 | # Map.addLayer(Rrs_coll.mean(), {bands: ['B4', 'B3', 'B1'], min: 0, max: 0.04}, 'l8 Rrs RGB', false)
416 | # Map.addLayer(lst_coll.mean(), {min: 23, max: 27, palette: ['darkblue', 'blue', 'white', 'red', 'darkred']}, 'LST [c]',
417 | # false)
418 | # Map.addLayer(sd_coll.mean(), {min: 0, max: 3, palette: ['800000', 'FF9700', '7BFF7B', '0080FF', '000080']}, 'Zsd [m]',
419 | # false)
420 | # Map.addLayer(tsi_coll.mean(), {min: 30, max: 70, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'darkred']},
421 | # 'TSI [0-100]', false)
422 | # Map.addLayer(tsi_collR.mode(),
423 | # {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']},
424 | # 'TSI Reclass', true)
425 |
426 | #########################################################/
427 | #########################################################/
428 | #########################################################/
429 | # Time Series #
430 | # def makeTimeSeries(collection,geometry,key=None):
431 | #
432 | # def reducerMapping(img):
433 | # reduction = img.reduceRegion(
434 | # ee.Reducer.mean(), geometry, 30)
435 | #
436 | # time = img.get('system:time_start')
437 | #
438 | # return img.set('indexVal',[ee.Number(time),reduction.get(key)])
439 | #
440 | # collection = collection.filterBounds(geometry) #.getInfo()
441 | #
442 | # indexCollection = collection.map(reducerMapping)
443 | #
444 | # indexSeries = indexCollection.aggregate_array('indexVal').getInfo()
445 | #
446 | # formattedSeries = [[x[0],round(float(x[1]),3)] for x in indexSeries]
447 | #
448 | # days_with_data = [[datetime.datetime.fromtimestamp((int(x[0]) / 1000)).strftime('%Y %B %d'),round(float(x[1]),3)] for x in indexSeries if x[1] > 0 ]
449 | #
450 | # return sorted(formattedSeries)
451 | # # LST time series
452 | # print lst_coll
453 | # lstTimeSeries = makeTimeSeries(lst_coll,geometry,key='radiance')
454 | #
455 | # print lstTimeSeries
456 | # lstTimeSeries = ui.Chart.image.seriesByRegion(
457 | # lst_coll, geometry, ee.Reducer.mean())
458 | # .setChartType('ScatterChart')
459 | # .setOptions({
460 | # title: 'Mean LST',
461 | # vAxis: {title: 'LST [c]'},
462 | # lineWidth: 1,
463 | # pointSize: 4,
464 | # })
465 | #
466 | # # SD time series
467 | # sdTimeSeries = ui.Chart.image.seriesByRegion(
468 | # sd_coll, geometry, ee.Reducer.mean())
469 | # .setChartType('ScatterChart')
470 | # .setOptions({
471 | # title: 'Mean Secchi Depth',
472 | # vAxis: {title: 'Zsd [m]'},
473 | # lineWidth: 1,
474 | # pointSize: 4,
475 | # })
476 | #
477 | # # TSI time series
478 | # tsiTimeSeries = ui.Chart.image.seriesByRegion(
479 | # tsi_coll, geometry, ee.Reducer.mean())
480 | # .setChartType('ScatterChart')
481 | # .setOptions({
482 | # title: 'Mean Trophic State Index',
483 | # vAxis: {title: 'TSI [1-100]'},
484 | # lineWidth: 1,
485 | # pointSize: 4,
486 | # })
487 | #
488 | # # TSI Reclass time series
489 | # tsiRTimeSeries = ui.Chart.image.seriesByRegion(
490 | # tsi_collR, geometry, ee.Reducer.mean())
491 | # .setChartType('ScatterChart')
492 | # .setOptions({
493 | # title: 'Mode Trophic State Index Class',
494 | # vAxis: {title: 'TSI Class'},
495 | # lineWidth: 1,
496 | # pointSize: 4,
497 | # })
498 | #
499 | # print(lstTimeSeries)
500 | # print(sdTimeSeries)
501 | # print(tsiTimeSeries)
502 | # print(tsiRTimeSeries)
--------------------------------------------------------------------------------
/python/modis_wq_timeseries.py:
--------------------------------------------------------------------------------
1 | import ee
2 | from ee.ee_exception import EEException
3 | import datetime
4 |
5 | ####Still under development#########
6 | try:
7 | ee.Initialize()
8 | except EEException as e:
9 | from oauth2client.service_account import ServiceAccountCredentials
10 | credentials = ServiceAccountCredentials.from_p12_keyfile(
11 | service_account_email='',
12 | filename='',
13 | private_key_password='notasecret',
14 | scopes=ee.oauth.SCOPE + ' https://www.googleapis.com/auth/drive ')
15 | ee.Initialize(credentials)
16 |
17 | geometry = ee.Geometry.Polygon([[[78.41079711914062, 17.465297700926097],
18 | [78.41629028320312, 17.334252606951086],
19 | [78.59893798828125, 17.33490806580805],
20 | [78.55087280273438, 17.494769882318828]]])
21 |
22 | # This script processes and charts WQ parameter values from a collection of MODIS L3 OC archive for a particular pixel throughout time.
23 | # WQ parameters such as chlorophyll-a, secchi depth and the trophic state index
24 | # How to use:
25 | # (1) If a "geometry" iable exists in the imports window, delete it.
26 | # (2) Within the map, select the polygon button near the top left side.
27 | # (3) Create a new polygon by drawing around the area of interest
28 | # (4) Adjust the time frame you wish to browse by adding here:
29 | # begin date
30 | startDate = '2016-04-01'
31 | # end date
32 | endDate = '2016-10-31'
33 | # (5) Click Run
34 | # (5) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
35 | # image from this collection, highlight the FILE_ID and copy and paste it into the proper location within the "Ls8 Single Image" script.
36 | # (6) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
37 | # (6) Click "Run"
38 | # (7) Export each chart as a csv by clicking on the export icon near the top-right of the chart.
39 | # (8) If you are interested in a single image from the collection, copy and paste the FILE_ID here:
40 | singleImage = ee.Image('NASA/OCEANDATA/MODIS-Aqua/L3SMI/A2017183').clip(geometry)
41 |
42 | # Author: Benjamin Page #
43 | ###################################################################/
44 | # Import Colelction
45 | MODIS = ee.ImageCollection('NASA/OCEANDATA/MODIS-Aqua/L3SMI')
46 |
47 | # filter collection
48 | FC = MODIS.filterDate(startDate, endDate).filterBounds(geometry)
49 |
50 |
51 | ###################################################################/
52 | ### Map Functions ###
53 |
54 | def secchi(img):
55 | Rrs_488 = img.select('Rrs_488')
56 | Rrs_667 = img.select('Rrs_667')
57 | ln_blueRed = (Rrs_488.divide(Rrs_667)).log()
58 | lnMOSD = (ee.Image(1.4856).multiply(ln_blueRed)).add(ee.Image(0.2734)) # R2 = 0.8748 with Anthony's in-situ data
59 | MOSD = ee.Image(10).pow(lnMOSD)
60 |
61 | SD = (ee.Image(0.1777).multiply(MOSD)).add(ee.Image(1.0813))
62 |
63 | return (SD.set('system:time_start', img.get('system:time_start')))
64 |
65 |
66 | def trophicState(img):
67 | TSI = ee.Image(60).subtract((ee.Image(14.41)).multiply((img.log())))
68 | return (TSI.set('system:time_start', img.get('system:time_start')))
69 |
70 | ### Models ###/
71 |
72 | # chlor_a
73 | chlor_a = FC.select('chlor_a')
74 |
75 | # SD
76 | SD = ee.ImageCollection(FC.map(secchi).copyProperties(FC, ['system:time_start']))
77 |
78 | # TSI
79 | TSI = SD.map(trophicState)
80 |
81 |
82 |
83 |
84 |
85 | ###################################################################/
86 | # Time Series
87 |
88 | # chlorTimeSeries = ui.Chart.image.seriesByRegion(
89 | # chlor_a, geometry, ee.Reducer.mean())
90 | # .setChartType('ScatterChart')
91 | # .setOptions({
92 | # title: 'Mean chlor_a',
93 | # vAxis: {title: 'chlor_a (ug / L)'},
94 | # lineWidth: 1,
95 | # pointSize: 4,
96 | # })
97 | #
98 | # sdTimeSeries = ui.Chart.image.seriesByRegion(
99 | # SD, geometry, ee.Reducer.mean())
100 | # .setChartType('ScatterChart')
101 | # .setOptions({
102 | # title: 'Mean Secchi Depth',
103 | # vAxis: {title: 'Zsd [m]'},
104 | # lineWidth: 1,
105 | # pointSize: 4,
106 | # })
107 | #
108 | # tsiTimeSeries = ui.Chart.image.seriesByRegion(
109 | # TSI, geometry, ee.Reducer.mean())
110 | # .setChartType('ScatterChart')
111 | # .setOptions({
112 | # title: 'Mean TSI',
113 | # vAxis: {title: 'TSI value)'},
114 | # lineWidth: 1,
115 | # pointSize: 4,
116 | # })
117 | #
118 | # print(chlorTimeSeries)
119 | # print(sdTimeSeries)
120 | # print(tsiTimeSeries)
121 |
122 | ###################################################################/
123 |
124 | # Map layers #
125 |
126 | chlor_a_mean = chlor_a.mean()
127 | # chlor_a_mean = chlor_a_mean.clip(geometry)
128 | chlor_a_vis = {'min': 0, 'max': 100, 'palette': 'darkblue,blue,limegreen,yellow,orange,orangered,darkred'}
129 | chlor_a_mapid = chlor_a_mean.getMapId(chlor_a_vis)
130 |
131 | SD_mean = SD.mean()
132 | # SD_mean = SD_mean.clip(geometry)
133 | SD_vis = {'min': 0, 'max': 2, 'palette': 'red,orangered,orange,yellow,limegreen,blue,darkblue'}
134 | SD_mapid = SD_mean.getMapId(SD_vis)
135 |
136 | TSI_mean = TSI.mean()
137 | # TDI_mean = TSI_mean.clip(geometry)
138 | TSI_vis = {'min': 0, 'max': 2, 'palette': 'red,orangered,orange,yellow,limegreen,blue,darkblue'}
139 | TSI_mapid = TSI_mean.getMapId(TSI_vis)
140 |
141 | singleImageLyr = singleImage.select('chlor_a')
142 | singleImage_vis = {'min': 0, 'max': 100, 'palette': 'darkblue,blue,limegreen,yellow,orange,orangered,darkred'}
143 | singleImage_mapid = singleImageLyr.getMapId(singleImage_vis)
144 |
145 | print "CHLOR A",chlor_a_mapid
146 | print "SD",SD_mapid
147 | print "TSI",TSI_mapid
148 | print "Single", singleImage_mapid
149 | # Map.addLayer(chlor_a.mean().clip(geometry),
150 | # {min: 0, max: 100, palette: ['darkblue', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']},
151 | # 'chlor_a', false)
152 | # Map.addLayer(SD.mean().clip(geometry),
153 | # {min: 0, max: 2, palette: ['red', 'orangered', 'orange', 'yellow', 'limegreen', 'blue', 'darkblue']}, 'SD',
154 | # false)
155 | # Map.addLayer(TSI.mean().clip(geometry),
156 | # {min: 30, max: 80, palette: ['darkblue', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'red']},
157 | # 'TSI', true)
158 | # Map.addLayer(singleImage.select('chlor_a'),
159 | # {min: 0, max: 100, palette: ['darkblue', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']},
160 | # 'singleImage_chlor_a', false)
161 |
162 | ################################################################/
163 | # Exporting Images #
164 |
165 | # # Export mean chlor_a
166 | # Export.image.toDrive({
167 | # image: chlor_a.mean().clip(geometry),
168 | # description: 'Mean Chlorophyll-a',
169 | # scale: 250,
170 | # region: geometry
171 | # })
172 | #
173 | # # Export mean SD
174 | # Export.image.toDrive({
175 | # image: SD.mean().clip(geometry),
176 | # description: 'Mean Secchi Depth',
177 | # scale: 250,
178 | # region: geometry
179 | # })
180 | #
181 | # # Export mean TSI
182 | # Export.image.toDrive({
183 | # image: TSI.mean().clip(geometry),
184 | # description: 'Mean Trophic State (from index)',
185 | # scale: 250,
186 | # region: geometry
187 | # })
--------------------------------------------------------------------------------
/python/s2_fai.py:
--------------------------------------------------------------------------------
1 | import ee
2 | from ee.ee_exception import EEException
3 | import datetime
4 |
5 | try:
6 | ee.Initialize()
7 | except EEException as e:
8 | from oauth2client.service_account import ServiceAccountCredentials
9 | credentials = ServiceAccountCredentials.from_p12_keyfile(
10 | service_account_email='',
11 | filename='',
12 | private_key_password='notasecret',
13 | scopes=ee.oauth.SCOPE + ' https://www.googleapis.com/auth/drive ')
14 | ee.Initialize(credentials)
15 |
16 | # geometry = ee.Geometry.Polygon([[[78.41079711914062, 17.465297700926097],
17 | # [78.41629028320312, 17.334252606951086],
18 | # [78.59893798828125, 17.33490806580805],
19 | # [78.55087280273438, 17.494769882318828]]])
20 |
21 | geometry = ee.Geometry.Polygon([[[30.76171875, 0.9049611504960419],
22 | [30.8935546875, -3.487377195492663],
23 | [35.5517578125, -3.2680324702882952],
24 | [35.5517578125, 1.9593043032313748]]])
25 |
26 | # This script processes a single Sentinel-2 TOA to Rayleigh corrected reflectances.
27 | # The floating algal index (FAI) is calculated from Rrc over water bodies.
28 | # How to use:
29 | # (1) If a "geometry" iable exists in the imports window, delete it.
30 | # (2) Within the map, select the point button near the top left side.
31 | # (3) Create a new point by clicking on a location of interest and click run.
32 | # (4) The "available imagery" ImageCollection within the console displays all available imagery
33 | # over that location with the necessary filters described by the user below.
34 | # (5) Expand the features within the "available imagery" image collection and select an image
35 | # by highlighting the FILE_ID and pasting it here:
36 | s2 = ee.Image('COPERNICUS/S2/20180216T075011_20180216T080852_T36MXE')
37 | # (6) Click "Run"
38 | # (7) Export the image to your Google Drive by clicking on the "tasks" tab and clicking "RUN", be sure to specify
39 | # the proper folder.
40 |
41 | # Author: Benjamin Page #
42 | # Citations:
43 | # Page, B.P., Kumar, A. and Mishra, D.R., 2018. A novel cross-satellite based assessment of the spatio-temporal development of a cyanobacterial harmful algal bloom. International Journal of Applied Earth Observation and Geoinformation, 66, pp.69-81.
44 |
45 | #########################################################/
46 | #########################################################/
47 | #########################################################/
48 | # User Input #
49 |
50 | # begin date
51 | iniDate = '2015-01-01'
52 |
53 | # end date
54 | endDate = '2018-02-28'
55 |
56 | cloudPerc = 5
57 |
58 | #########################################################/
59 | #########################################################/
60 | #########################################################/
61 | # Import Collections #
62 |
63 | # sentinel-2
64 | MSI = ee.ImageCollection('COPERNICUS/S2')
65 |
66 | # toms / omi
67 | ozone = ee.ImageCollection('TOMS/MERGED')
68 |
69 | SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
70 |
71 | #########################################################/
72 | #########################################################/
73 | #########################################################/
74 | # water mask
75 | startMonth = 5
76 | endMonth = 9
77 | startYear = 2013
78 | endYear = 2017
79 |
80 | forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(
81 | ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'))
82 | mask = ee.Image(forMask.select('B6').median().lt(300))
83 | mask = mask.updateMask(mask)
84 |
85 | # constants
86 | pi = ee.Image(3.141592)
87 | imDate = s2.date()
88 |
89 | # filter sentinel 2 collection
90 | FC = MSI.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUDY_PIXEL_PERCENTAGE', "less_than",
91 | cloudPerc)
92 |
93 |
94 | #########################################################/
95 | #########################################################/
96 | #########################################################/
97 | # MSI Atmospheric Correction #
98 |
99 | bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B11', 'B12']
100 |
101 | # rescale
102 | rescale = ee.Image(s2.divide(10000).copyProperties(s2)).select(bands)
103 |
104 | # tile footprint
105 | footprint = s2.geometry()
106 |
107 | # dem
108 | DEM = ee.Image('USGS/SRTMGL1_003').clip(footprint)
109 |
110 | # ozone
111 | DU = ee.Image(ozone.filterDate(iniDate, endDate).filterBounds(geometry).mean())
112 |
113 | # Julian Day
114 | imgDate = ee.Date(s2.get('system:time_start'))
115 | FOY = ee.Date.fromYMD(imgDate.get('year'), 1, 1)
116 | JD = imgDate.difference(FOY, 'day').int().add(1)
117 |
118 | # earth-sun distance
119 | myCos = ((ee.Image(0.0172).multiply(ee.Image(JD).subtract(ee.Image(2)))).cos()).pow(2)
120 | cosd = myCos.multiply(pi.divide(ee.Image(180))).cos()
121 | d = ee.Image(1).subtract(ee.Image(0.01673)).multiply(cosd).clip(footprint)
122 |
123 | # sun azimuth
124 | SunAz = ee.Image.constant(s2.get('MEAN_SOLAR_AZIMUTH_ANGLE')).clip(footprint)
125 |
126 | # sun zenith
127 | SunZe = ee.Image.constant(s2.get('MEAN_SOLAR_ZENITH_ANGLE')).clip(footprint)
128 | cosdSunZe = SunZe.multiply(pi.divide(ee.Image(180))).cos() # in degrees
129 | sindSunZe = SunZe.multiply(pi.divide(ee.Image(180))).sin() # in degrees
130 |
131 | # sat zenith
132 | SatZe = ee.Image.constant(s2.get('MEAN_INCIDENCE_ZENITH_ANGLE_B5')).clip(footprint)
133 | cosdSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).cos()
134 | sindSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).sin()
135 |
136 | # sat azimuth
137 | SatAz = ee.Image.constant(s2.get('MEAN_INCIDENCE_AZIMUTH_ANGLE_B5')).clip(footprint)
138 |
139 | # relative azimuth
140 | RelAz = SatAz.subtract(SunAz)
141 | cosdRelAz = RelAz.multiply(pi.divide(ee.Image(180))).cos()
142 |
143 | # Pressure
144 | P = ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM)).pow(5.25588)).multiply(0.01)
145 | Po = ee.Image(1013.25)
146 |
147 | # esun
148 | ESUN = ee.Image(ee.Array([ee.Image(s2.get('SOLAR_IRRADIANCE_B1')),
149 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2')),
150 | ee.Image(s2.get('SOLAR_IRRADIANCE_B3')),
151 | ee.Image(s2.get('SOLAR_IRRADIANCE_B4')),
152 | ee.Image(s2.get('SOLAR_IRRADIANCE_B5')),
153 | ee.Image(s2.get('SOLAR_IRRADIANCE_B6')),
154 | ee.Image(s2.get('SOLAR_IRRADIANCE_B7')),
155 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8')),
156 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8A')),
157 | ee.Image(s2.get('SOLAR_IRRADIANCE_B11')),
158 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2'))]
159 | )).toArray().toArray(1)
160 |
161 | ESUN = ESUN.multiply(ee.Image(1))
162 |
163 | ESUNImg = ESUN.arrayProject([0]).arrayFlatten([bands])
164 |
165 | # create empty array for the images
166 | imgArr = rescale.select(bands).toArray().toArray(1)
167 |
168 | # pTOA to Ltoa
169 | Ltoa = imgArr.multiply(ESUN).multiply(cosdSunZe).divide(pi.multiply(d.pow(2)))
170 |
171 | # band centers
172 | bandCenter = ee.Image(443).divide(1000).addBands(ee.Image(490).divide(1000))\
173 | .addBands(ee.Image(560).divide(1000))\
174 | .addBands(ee.Image(665).divide(1000))\
175 | .addBands(ee.Image(705).divide(1000))\
176 | .addBands(ee.Image(740).divide(1000))\
177 | .addBands(ee.Number(783).divide(1000))\
178 | .addBands(ee.Number(842).divide(1000))\
179 | .addBands(ee.Number(865).divide(1000))\
180 | .addBands(ee.Number(1610).divide(1000))\
181 | .addBands(ee.Number(2190).divide(1000))\
182 | .toArray().toArray(1)
183 |
184 | # ozone coefficients
185 | koz = ee.Image(0.0039).addBands(ee.Image(0.0213))\
186 | .addBands(ee.Image(0.1052))\
187 | .addBands(ee.Image(0.0505))\
188 | .addBands(ee.Image(0.0205))\
189 | .addBands(ee.Image(0.0112))\
190 | .addBands(ee.Image(0.0075))\
191 | .addBands(ee.Image(0.0021))\
192 | .addBands(ee.Image(0.0019))\
193 | .addBands(ee.Image(0))\
194 | .addBands(ee.Image(0))\
195 | .toArray().toArray(1)
196 |
197 | ###################
198 |
199 | # Calculate ozone optical thickness
200 | Toz = koz.multiply(DU).divide(ee.Image(1000))
201 |
202 | # Calculate TOA radiance in the absense of ozone
203 | Lt = Ltoa.multiply(((Toz)).multiply((ee.Image(1).divide(cosdSunZe)).add(ee.Image(1).divide(cosdSatZe))).exp())
204 |
205 | # Rayleigh optical thickness
206 | Tr = (P.divide(Po)).multiply(ee.Image(0.008569).multiply(bandCenter.pow(-4))).multiply((ee.Image(1).add(
207 | ee.Image(0.0113).multiply(bandCenter.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter.pow(-4)))))
208 |
209 | # Specular reflection (s- and p- polarization states)
210 | theta_V = ee.Image(0.0000000001)
211 | sin_theta_j = sindSunZe.divide(ee.Image(1.333))
212 |
213 | theta_j = sin_theta_j.asin().multiply(ee.Image(180).divide(pi))
214 |
215 | theta_SZ = SunZe
216 |
217 | R_theta_SZ_s = (
218 | ((theta_SZ.multiply(pi.divide(ee.Image(180)))).subtract(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(
219 | 2)).divide(
220 | (((theta_SZ.multiply(pi.divide(ee.Image(180)))).add(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)))
221 |
222 | R_theta_V_s = ee.Image(0.0000000001)
223 |
224 | R_theta_SZ_p = (((theta_SZ.multiply(pi.divide(180))).subtract(theta_j.multiply(pi.divide(180)))).tan().pow(2)).divide(
225 | (((theta_SZ.multiply(pi.divide(180))).add(theta_j.multiply(pi.divide(180)))).tan().pow(2)))
226 |
227 | R_theta_V_p = ee.Image(0.0000000001)
228 |
229 | R_theta_SZ = ee.Image(0.5).multiply(R_theta_SZ_s.add(R_theta_SZ_p))
230 |
231 | R_theta_V = ee.Image(0.5).multiply(R_theta_V_s.add(R_theta_V_p))
232 |
233 | # Sun-sensor geometry
234 | theta_neg = ((cosdSunZe.multiply(ee.Image(-1))).multiply(cosdSatZe)).subtract(
235 | (sindSunZe).multiply(sindSatZe).multiply(cosdRelAz))
236 |
237 | theta_neg_inv = theta_neg.acos().multiply(ee.Image(180).divide(pi))
238 |
239 | theta_pos = (cosdSunZe.multiply(cosdSatZe)).subtract(sindSunZe.multiply(sindSatZe).multiply(cosdRelAz))
240 |
241 | theta_pos_inv = theta_pos.acos().multiply(ee.Image(180).divide(pi))
242 |
243 | cosd_tni = theta_neg_inv.multiply(pi.divide(180)).cos() # in degrees
244 |
245 | cosd_tpi = theta_pos_inv.multiply(pi.divide(180)).cos() # in degrees
246 |
247 | Pr_neg = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni.pow(2))))
248 |
249 | Pr_pos = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi.pow(2))))
250 |
251 | # Rayleigh scattering phase function
252 | Pr = Pr_neg.add((R_theta_SZ.add(R_theta_V)).multiply(Pr_pos)) # for water Rayleigh correction
253 | # Pr = ee.Image(1) # for terrestrial Rayleigh correction
254 |
255 | # rayleigh radiance contribution
256 | denom = ee.Image(4).multiply(pi).multiply(cosdSatZe)
257 | Lr = (ESUN.multiply(Tr)).multiply(Pr.divide(denom))
258 |
259 | # rayleigh corrected radiance
260 | Lrc = Lt.subtract(Lr)
261 | LrcImg = Lrc.arrayProject([0]).arrayFlatten([bands])
262 |
263 | # rayleigh corrected reflectance
264 | prc = (Lrc.multiply(pi).multiply(d.pow(2)).divide(ESUN.multiply(cosdSunZe)))
265 | prcImg = prc.arrayProject([0]).arrayFlatten([bands])
266 |
267 |
268 | ########################
269 | # models #
270 |
271 | # Calculate FAI
272 | NIRprime = (prcImg.select('B4')).add((prcImg.select('B11').subtract(prcImg.select('B4'))).multiply(
273 | (ee.Image(865).subtract(ee.Image(665))).divide((ee.Image(1610).subtract(ee.Image(665))))))
274 | FAI = ((prcImg.select('B8A').subtract(NIRprime))).multiply(mask)
275 |
276 | prcLyr = prcImg.multiply(mask)
277 | prc_vis = {'bands': 'B4,B3,B2', 'min': 0, 'max': 0.4}
278 | prc_mapid = prcLyr.getMapId(prc_vis)
279 |
280 | FAI_vis = {'min': -0.05, 'max': 0.2, 'palette': '#000080,#0080FF,#7BFF7B,#FF9700,#800000'}
281 | FAI_mapid = FAI.getMapId(FAI_vis)
282 |
283 | print 'PRC',prc_mapid
284 | print 'FAI',FAI_mapid
285 | # Map Layers
286 | # Map.addLayer(footprint, {}, 'footprint', true)
287 | # Map.addLayer(prcImg.multiply(mask), {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.4}, 'prc rgb', false)
288 | # Map.addLayer(FAI, {min: -0.05, max: 0.2, palette: ['000080', '0080FF', '7BFF7B', 'FF9700', '800000']}, 'FAI', true)
289 | #
290 | # # export image #
291 | #
292 | # Export.image.toDrive({
293 | # image: FAI,
294 | # description: 's2_FAI',
295 | # scale: 30,
296 | # region: footprint
297 | # })
298 |
299 |
--------------------------------------------------------------------------------
/python/s2_single_image.py:
--------------------------------------------------------------------------------
1 | import ee
2 | from ee.ee_exception import EEException
3 | import datetime
4 |
5 | try:
6 | ee.Initialize()
7 | except EEException as e:
8 | from oauth2client.service_account import ServiceAccountCredentials
9 | credentials = ServiceAccountCredentials.from_p12_keyfile(
10 | service_account_email='',
11 | filename='',
12 | private_key_password='notasecret',
13 | scopes=ee.oauth.SCOPE + ' https://www.googleapis.com/auth/drive ')
14 | ee.Initialize(credentials)
15 |
16 | # geometry = ee.Geometry.Polygon([[[78.41079711914062, 17.465297700926097],
17 | # [78.41629028320312, 17.334252606951086],
18 | # [78.59893798828125, 17.33490806580805],
19 | # [78.55087280273438, 17.494769882318828]]])
20 |
21 | geometry = ee.Geometry.Polygon([[[30.76171875, 0.9049611504960419],
22 | [30.8935546875, -3.487377195492663],
23 | [35.5517578125, -3.2680324702882952],
24 | [35.5517578125, 1.9593043032313748]]])
25 |
26 | # This script processes a single Sentinel-2 image of interest.
27 | # WQ parameters such chlor-a, secchi depth, trophic state index are calculated
28 | # How to use:
29 | # (1) If a "geometry" iable exists in the imports window, delete it.
30 | # (2) Within the map, select the point button near the top left side.
31 | # (3) Create a new point by clicking on a location of interest.
32 | # (4) Adjust the time frame you wish to browse by adding here:
33 | # begin date
34 | iniDate = '2015-05-01'
35 | # end date
36 | endDate = '2018-03-31'
37 | # (5) Adjust a cloud % threshold here:
38 | cloudPerc = 5
39 | # (6) Click Run
40 | # (7) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
41 | # image from this collection, highlight the FILE_ID in the available imagery features and copy and paste it here:
42 | s2 = ee.Image('COPERNICUS/S2/20171128T075251_20171128T080644_T36MXE')
43 | # (8) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
44 | # (9) Click "Run"
45 | # (10) Export each image by clicking on the run button within the "tasks" tab.
46 |
47 | # Author: Benjamin Page #
48 | # Citations:
49 | # Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring
50 |
51 | #########################################################/
52 | #########################################################/
53 | #########################################################/
54 | # Import Collections #
55 |
56 | # sentinel-2
57 | MSI = ee.ImageCollection('COPERNICUS/S2')
58 |
59 | # landsat-8 surface reflactance product (for masking purposes)
60 | SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
61 |
62 | # toms / omi
63 | ozone = ee.ImageCollection('TOMS/MERGED')
64 |
65 | #########################################################/
66 | #########################################################/
67 | #########################################################/
68 |
69 | pi = ee.Image(3.141592)
70 | imDate = s2.date()
71 |
72 | footprint = s2.geometry()
73 |
74 | # water mask
75 | startMonth = 5
76 | endMonth = 9
77 | startYear = 2013
78 | endYear = 2017
79 |
80 | forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(
81 | ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'))
82 | mask = ee.Image(forMask.select('B6').median().lt(300))
83 | mask = mask.updateMask(mask).clip(footprint)
84 |
85 | # filter sentinel 2 collection
86 | FC = MSI.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUDY_PIXEL_PERCENTAGE', "less_than",
87 | cloudPerc)
88 |
89 |
90 | #########################################################/
91 | #########################################################/
92 | #########################################################/
93 | # MSI Atmospheric Correction #
94 |
95 | bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B11', 'B12']
96 |
97 | # rescale*
98 | rescale = ee.Image(s2.divide(10000).multiply(mask).copyProperties(s2)).select(bands)
99 |
100 | # dem*
101 | DEM = ee.Image('USGS/SRTMGL1_003').clip(footprint)
102 |
103 | # ozone*
104 | DU = ee.Image(ozone.filterDate(iniDate, endDate).filterBounds(footprint).mean())
105 |
106 | # Julian Day
107 | imgDate = ee.Date(s2.get('system:time_start'))
108 | FOY = ee.Date.fromYMD(imgDate.get('year'), 1, 1)
109 | JD = imgDate.difference(FOY, 'day').int().add(1)
110 |
111 | # earth-sun distance
112 | myCos = ((ee.Image(0.0172).multiply(ee.Image(JD).subtract(ee.Image(2)))).cos()).pow(2)
113 | cosd = myCos.multiply(pi.divide(ee.Image(180))).cos()
114 | d = ee.Image(1).subtract(ee.Image(0.01673)).multiply(cosd).clip(footprint)
115 |
116 | # sun azimuth
117 | SunAz = ee.Image.constant(s2.get('MEAN_SOLAR_AZIMUTH_ANGLE')).clip(footprint)
118 |
119 | # sun zenith
120 | SunZe = ee.Image.constant(s2.get('MEAN_SOLAR_ZENITH_ANGLE')).clip(footprint)
121 | cosdSunZe = SunZe.multiply(pi.divide(ee.Image(180))).cos() # in degrees
122 | sindSunZe = SunZe.multiply(pi.divide(ee.Image(180))).sin() # in degrees
123 |
124 | # sat zenith
125 | SatZe = ee.Image.constant(s2.get('MEAN_INCIDENCE_ZENITH_ANGLE_B5')).clip(footprint)
126 | cosdSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).cos()
127 | sindSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).sin()
128 |
129 | # sat azimuth
130 | SatAz = ee.Image.constant(s2.get('MEAN_INCIDENCE_AZIMUTH_ANGLE_B5')).clip(footprint)
131 |
132 | # relative azimuth
133 | RelAz = SatAz.subtract(SunAz)
134 | cosdRelAz = RelAz.multiply(pi.divide(ee.Image(180))).cos()
135 |
136 | # Pressure
137 | P = (ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM)).pow(5.25588)).multiply(
138 | 0.01)).multiply(mask)
139 | Po = ee.Image(1013.25)
140 |
141 | # esun
142 | ESUN = ee.Image(ee.Array([ee.Image(s2.get('SOLAR_IRRADIANCE_B1')),
143 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2')),
144 | ee.Image(s2.get('SOLAR_IRRADIANCE_B3')),
145 | ee.Image(s2.get('SOLAR_IRRADIANCE_B4')),
146 | ee.Image(s2.get('SOLAR_IRRADIANCE_B5')),
147 | ee.Image(s2.get('SOLAR_IRRADIANCE_B6')),
148 | ee.Image(s2.get('SOLAR_IRRADIANCE_B7')),
149 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8')),
150 | ee.Image(s2.get('SOLAR_IRRADIANCE_B8A')),
151 | ee.Image(s2.get('SOLAR_IRRADIANCE_B11')),
152 | ee.Image(s2.get('SOLAR_IRRADIANCE_B2'))]
153 | )).toArray().toArray(1)
154 |
155 | ESUN = ESUN.multiply(ee.Image(1))
156 |
157 | ESUNImg = ESUN.arrayProject([0]).arrayFlatten([bands])
158 |
159 | # create empty array for the images
160 | imgArr = rescale.select(bands).toArray().toArray(1)
161 |
162 | # pTOA to Ltoa
163 | Ltoa = imgArr.multiply(ESUN).multiply(cosdSunZe).divide(pi.multiply(d.pow(2)))
164 |
165 | # band centers
166 | bandCenter = ee.Image(443).divide(1000).addBands(ee.Image(490).divide(1000))\
167 | .addBands(ee.Image(560).divide(1000))\
168 | .addBands(ee.Image(665).divide(1000))\
169 | .addBands(ee.Image(705).divide(1000))\
170 | .addBands(ee.Image(740).divide(1000))\
171 | .addBands(ee.Number(783).divide(1000))\
172 | .addBands(ee.Number(842).divide(1000))\
173 | .addBands(ee.Number(865).divide(1000))\
174 | .addBands(ee.Number(1610).divide(1000))\
175 | .addBands(ee.Number(2190).divide(1000))\
176 | .toArray().toArray(1)
177 |
178 | # ozone coefficients
179 | koz = ee.Image(0.0039).addBands(ee.Image(0.0213))\
180 | .addBands(ee.Image(0.1052))\
181 | .addBands(ee.Image(0.0505))\
182 | .addBands(ee.Image(0.0205))\
183 | .addBands(ee.Image(0.0112))\
184 | .addBands(ee.Image(0.0075))\
185 | .addBands(ee.Image(0.0021))\
186 | .addBands(ee.Image(0.0019))\
187 | .addBands(ee.Image(0))\
188 | .addBands(ee.Image(0))\
189 | .toArray().toArray(1)
190 |
191 | ###################
192 |
193 | # Calculate ozone optical thickness
194 | Toz = koz.multiply(DU).divide(ee.Image(1000))
195 |
196 | # Calculate TOA radiance in the absense of ozone
197 | Lt = Ltoa.multiply(((Toz)).multiply((ee.Image(1).divide(cosdSunZe)).add(ee.Image(1).divide(cosdSatZe))).exp())
198 |
199 | # Rayleigh optical thickness
200 | Tr = (P.divide(Po)).multiply(ee.Image(0.008569).multiply(bandCenter.pow(-4))).multiply((ee.Image(1).add(
201 | ee.Image(0.0113).multiply(bandCenter.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter.pow(-4)))))
202 |
203 | # Specular reflection (s- and p- polarization states)
204 | theta_V = ee.Image(0.0000000001)
205 | sin_theta_j = sindSunZe.divide(ee.Image(1.333))
206 |
207 | theta_j = sin_theta_j.asin().multiply(ee.Image(180).divide(pi))
208 |
209 | theta_SZ = SunZe
210 |
211 | R_theta_SZ_s = (
212 | ((theta_SZ.multiply(pi.divide(ee.Image(180)))).subtract(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(
213 | 2)).divide(
214 | (((theta_SZ.multiply(pi.divide(ee.Image(180)))).add(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)))
215 |
216 | R_theta_V_s = ee.Image(0.0000000001)
217 |
218 | R_theta_SZ_p = (((theta_SZ.multiply(pi.divide(180))).subtract(theta_j.multiply(pi.divide(180)))).tan().pow(2)).divide(
219 | (((theta_SZ.multiply(pi.divide(180))).add(theta_j.multiply(pi.divide(180)))).tan().pow(2)))
220 |
221 | R_theta_V_p = ee.Image(0.0000000001)
222 |
223 | R_theta_SZ = ee.Image(0.5).multiply(R_theta_SZ_s.add(R_theta_SZ_p))
224 |
225 | R_theta_V = ee.Image(0.5).multiply(R_theta_V_s.add(R_theta_V_p))
226 |
227 | # Sun-sensor geometry
228 | theta_neg = ((cosdSunZe.multiply(ee.Image(-1))).multiply(cosdSatZe)).subtract(
229 | (sindSunZe).multiply(sindSatZe).multiply(cosdRelAz))
230 |
231 | theta_neg_inv = theta_neg.acos().multiply(ee.Image(180).divide(pi))
232 |
233 | theta_pos = (cosdSunZe.multiply(cosdSatZe)).subtract(sindSunZe.multiply(sindSatZe).multiply(cosdRelAz))
234 |
235 | theta_pos_inv = theta_pos.acos().multiply(ee.Image(180).divide(pi))
236 |
237 | cosd_tni = theta_neg_inv.multiply(pi.divide(180)).cos() # in degrees
238 |
239 | cosd_tpi = theta_pos_inv.multiply(pi.divide(180)).cos() # in degrees
240 |
241 | Pr_neg = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni.pow(2))))
242 |
243 | Pr_pos = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi.pow(2))))
244 |
245 | # Rayleigh scattering phase function
246 | Pr = Pr_neg.add((R_theta_SZ.add(R_theta_V)).multiply(Pr_pos))
247 |
248 | # rayleigh radiance contribution
249 | denom = ee.Image(4).multiply(pi).multiply(cosdSatZe)
250 | Lr = (ESUN.multiply(Tr)).multiply(Pr.divide(denom))
251 |
252 | # rayleigh corrected radiance
253 | Lrc = Lt.subtract(Lr)
254 | LrcImg = Lrc.arrayProject([0]).arrayFlatten([bands])
255 |
256 | # Aerosol Correction #
257 |
258 | # Bands in nm
259 | bands_nm = ee.Image(443).addBands(ee.Image(490))\
260 | .addBands(ee.Image(560))\
261 | .addBands(ee.Image(665))\
262 | .addBands(ee.Image(705))\
263 | .addBands(ee.Image(740))\
264 | .addBands(ee.Image(783))\
265 | .addBands(ee.Image(842))\
266 | .addBands(ee.Image(865))\
267 | .addBands(ee.Image(0))\
268 | .addBands(ee.Image(0))\
269 | .toArray().toArray(1)
270 |
271 | # Lam in SWIR bands
272 | Lam_10 = LrcImg.select('B11')
273 | Lam_11 = LrcImg.select('B12')
274 |
275 | # Calculate aerosol type
276 | eps = (
277 | (((Lam_11).divide(ESUNImg.select('B12'))).log()).subtract(((Lam_10).divide(ESUNImg.select('B11'))).log())).divide(
278 | ee.Image(2190).subtract(ee.Image(1610)))
279 |
280 | # Calculate multiple scattering of aerosols for each band
281 | Lam = (Lam_11).multiply(((ESUN).divide(ESUNImg.select('B12')))).multiply(
282 | (eps.multiply(ee.Image(-1))).multiply((bands_nm.divide(ee.Image(2190)))).exp())
283 |
284 | # diffuse transmittance
285 | trans = Tr.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe)).exp()
286 |
287 | # Compute water-leaving radiance
288 | Lw = Lrc.subtract(Lam).divide(trans)
289 |
290 | # water-leaving reflectance
291 | pw = (Lw.multiply(pi).multiply(d.pow(2)).divide(ESUN.multiply(cosdSunZe)))
292 | pwImg = pw.arrayProject([0]).arrayFlatten([bands])
293 |
294 | # remote sensing reflectance
295 | Rrs = (pw.divide(pi).arrayProject([0]).arrayFlatten([bands]).slice(0, 9))
296 |
297 |
298 | # / Bio optical Models #/
299 | # chlor_a
300 | NDCI = (Rrs.select('B5').subtract(Rrs.select('B4'))).divide(Rrs.select('B5').add(Rrs.select('B4'))) # ndci
301 | chlor_a = ee.Image(14.039).add(ee.Image(86.115).multiply(NDCI)).add(
302 | ee.Image(194.325).multiply(NDCI.pow(ee.Image(2)))) # chlor_a
303 |
304 | # SD
305 | ln_BlueRed = (Rrs.select('B2').divide(Rrs.select('B4'))).log()
306 | lnMOSD = (ee.Image(1.4856).multiply(ln_BlueRed)).add(ee.Image(0.2734)) # R2 = 0.8748 with in-situ
307 | MOSD = ee.Image(10).pow(lnMOSD) # log space to (m)
308 | SD = (ee.Image(0.1777).multiply(MOSD)).add(ee.Image(1.0813))
309 |
310 | # tsi
311 | TSI_c = ee.Image(30.6).add(ee.Image(9.81)).multiply(chlor_a.log())
312 | TSI_s = ee.Image(60.0).subtract(ee.Image(14.41)).multiply(SD.log())
313 | TSI = (TSI_c.add(TSI_s)).divide(ee.Image(2))
314 |
315 | # tsi reclassified
316 | # Create conditions
317 | mask1 = TSI.lt(30) # classical oligitrophy (1)
318 | mask2 = TSI.gte(30).And(TSI.lt(40)) # (2)
319 | mask3 = TSI.gte(40).And(TSI.lt(50)) # (3)
320 | mask4 = TSI.gte(50).And(TSI.lt(60)) # (4)
321 | mask5 = TSI.gte(60).And(TSI.lt(70)) # (5)
322 | mask6 = TSI.gte(70).And(TSI.lt(80)) # (6)
323 | mask7 = TSI.gte(80) # (7)
324 |
325 | # Reclassify conditions into new values
326 | img1 = TSI.where(mask1.eq(1), 1).mask(mask1)
327 | img2 = TSI.where(mask2.eq(1), 2).mask(mask2)
328 | img3 = TSI.where(mask3.eq(1), 3).mask(mask3)
329 | img4 = TSI.where(mask4.eq(1), 4).mask(mask4)
330 | img5 = TSI.where(mask5.eq(1), 5).mask(mask5)
331 | img6 = TSI.where(mask6.eq(1), 6).mask(mask6)
332 | img7 = TSI.where(mask7.eq(1), 7).mask(mask7)
333 |
334 | # Ouput of reclassified image
335 | TSI_R = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7)
336 |
337 | s2Lyr = s2.multiply(mask)
338 | s2_vis = {'bands': 'B4,B3,B2', 'min': 0, 'max': 1000}
339 | s2_mapid = s2Lyr.getMapId(s2_vis)
340 |
341 | chlor_a_vis = {'min': 0, 'max': 40, 'palette': 'blue,cyan,limegreen,yellow,orange,darkred'}
342 | chlor_a_mapid = chlor_a.getMapId(chlor_a_vis)
343 |
344 | TSI_vis = {'min': 30, 'max': 70, 'palette': 'blue,cyan,limegreen,yellow,orange,darkred'}
345 | TSI_mapid = TSI.getMapId(TSI_vis)
346 |
347 | TSI_R_vis = {'min': 1, 'max': 7, 'palette': 'purple,blue,limegreen,yellow,orange,orangered,darkred'}
348 | TSI_R_mapid = TSI_R.getMapId(TSI_R_vis)
349 |
350 | print 'S2',s2_mapid
351 | print 'Chlor A',chlor_a_mapid
352 | print 'TSI',TSI_mapid
353 | print 'TSI R',TSI_R_mapid
354 | # Map Layers #
355 | # Map.centerObject(footprint, 7)
356 | # Map.addLayer(footprint, {}, 'footprint', true) # footprint
357 | # Map.addLayer(s2.multiply(mask), {bands: ['B4', 'B3', 'B2'], min: 0, max: 1000}, 'rgb', false) # rgb
358 | # Map.addLayer(SD, {min: 0, max: 3, palette: ['darkred', 'orange', 'yellow', 'limegreen', 'cyan', 'blue']}, 'SD', false)
359 | # Map.addLayer(chlor_a, {min: 0, max: 40, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange', 'darkred']},
360 | # 'chlor_a', false)
361 | # Map.addLayer(TSI, {min: 30, max: 70, palette: ['blue', 'cyan', 'limegreen', 'yellow', 'orange', 'darkred']}, 'TSI',
362 | # false)
363 | # Map.addLayer(TSI_R,
364 | # {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']},
365 | # 'TSI_R', true)
366 |
367 | # EXPORT IMAGES #
368 |
369 | # # Export SD
370 | # Export.image.toDrive({
371 | # image: SD,
372 | # description: 'SD',
373 | # scale: 20,
374 | # region: footprint
375 | # })
376 | #
377 | # # Export chlor_a
378 | # Export.image.toDrive({
379 | # image: chlor_a,
380 | # description: 'chlor_a',
381 | # scale: 20,
382 | # region: footprint
383 | # })
384 | #
385 | # # Export TSI
386 | # Export.image.toDrive({
387 | # image: TSI,
388 | # description: 'TSI',
389 | # scale: 20,
390 | # region: footprint
391 | # })
392 | #
393 | # # Export TSI_R
394 | # Export.image.toDrive({
395 | # image: TSI_R,
396 | # description: 'TSI_R',
397 | # scale: 20,
398 | # region: footprint
399 | # })
--------------------------------------------------------------------------------
/python/s2_wq_timeseries.py:
--------------------------------------------------------------------------------
1 | import ee
2 | from ee.ee_exception import EEException
3 | import datetime
4 |
5 | try:
6 | ee.Initialize()
7 | except EEException as e:
8 | from oauth2client.service_account import ServiceAccountCredentials
9 | credentials = ServiceAccountCredentials.from_p12_keyfile(
10 | service_account_email='',
11 | filename='',
12 | private_key_password='notasecret',
13 | scopes=ee.oauth.SCOPE + ' https://www.googleapis.com/auth/drive ')
14 | ee.Initialize(credentials)
15 |
16 | # geometry = ee.Geometry.Polygon([[[78.41079711914062, 17.465297700926097],
17 | # [78.41629028320312, 17.334252606951086],
18 | # [78.59893798828125, 17.33490806580805],
19 | # [78.55087280273438, 17.494769882318828]]])
20 |
21 | geometry = ee.Geometry.Polygon([[[30.76171875, 0.9049611504960419],
22 | [30.8935546875, -3.487377195492663],
23 | [35.5517578125, -3.2680324702882952],
24 | [35.5517578125, 1.9593043032313748]]])
25 |
26 | # This script processes and charts WQ parameter valuse from a collection of Sentinel-2 archive for a particular pixel throughout time.
27 | # WQ parameters such chlor-a, secchi depth, trophic state index
28 | # How to use:
29 | # (1) If a "geometry" iable exists in the imports window, delete it.
30 | # (2) Within the map, select the point button near the top left side.
31 | # (3) Create a new point by clicking on a location of interest.
32 | # (4) Adjust the time frame you wish to browse by adding here:
33 | # begin date
34 | iniDate = '2015-05-01'
35 | # end date
36 | endDate = '2018-03-31'
37 | # (5) Adjust a cloud % threshold here:
38 | cloudPerc = 5
39 | # (5) Click Run
40 | # (5) The "available imagery" ImageCollection within the console displays all available imagery. If you wish to investigate a single
41 | # image from this collection, highlight the FILE_ID and copy and paste it into the proper location within the "s2 Single Image" script.
42 | # (6) The charts will appear on the right hand side of the interface, and will display the mean map in the map user interface.
43 | # (6) Click "Run"
44 | # (7) Export each chart as a csv by clicking on the export icon near the top-right of the chart.
45 |
46 | # Author: Benjamin Page #
47 | # Citations:
48 | # Page, B.P. and Mishra, D.R., 2018. A modified atmospheric correction when coupling sentinel-2 and landsat-8 for inland water quality monitoring
49 |
50 | #########################################################/
51 | #########################################################/
52 | #########################################################/
53 | # Map.centerObject(geometry, 7)
54 |
55 | # Import Collections #
56 |
57 | # sentinel-2
58 | MSI = ee.ImageCollection('COPERNICUS/S2')
59 |
60 | # landsat-8 surface reflactance product (for masking purposes)
61 | SRP = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
62 |
63 | # toms / omi
64 | ozone = ee.ImageCollection('TOMS/MERGED')
65 |
66 | #########################################################/
67 | #########################################################/
68 | #########################################################/
69 |
70 | pi = ee.Image(3.141592)
71 |
72 | # water mask
73 | startMonth = 5
74 | endMonth = 9
75 | startYear = 2013
76 | endYear = 2017
77 |
78 | forMask = SRP.filterBounds(geometry).select('B6').filterMetadata('CLOUD_COVER', "less_than", 10).filter(
79 | ee.Filter.calendarRange(startMonth, endMonth, 'month')).filter(ee.Filter.calendarRange(startYear, endYear, 'year'))
80 | mask = ee.Image(forMask.select('B6').median().lt(300))
81 | mask = mask.updateMask(mask)
82 |
83 | # filter sentinel 2 collection
84 | FC = MSI.filterDate(iniDate, endDate).filterBounds(geometry).filterMetadata('CLOUDY_PIXEL_PERCENTAGE', "less_than",
85 | cloudPerc)
86 |
87 |
88 | # #########################################################/
89 | # #########################################################/
90 | # #########################################################/
91 | # Mapping functions #
92 |
93 | def s2Correction(img):
94 | # msi bands
95 | bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B11', 'B12']
96 |
97 | # rescale
98 | rescale = img.select(bands).divide(10000).multiply(mask)
99 |
100 | # tile footprint
101 | footprint = rescale.geometry()
102 |
103 | # dem
104 | DEM = ee.Image('USGS/SRTMGL1_003').clip(footprint)
105 |
106 | # ozone
107 | DU = ee.Image(ozone.filterDate(iniDate, endDate).filterBounds(footprint).mean())
108 |
109 | # Julian Day
110 | imgDate = ee.Date(img.get('system:time_start'))
111 | FOY = ee.Date.fromYMD(imgDate.get('year'), 1, 1)
112 | JD = imgDate.difference(FOY, 'day').int().add(1)
113 |
114 | # earth-sun distance
115 | myCos = ((ee.Image(0.0172).multiply(ee.Image(JD).subtract(ee.Image(2)))).cos()).pow(2)
116 | cosd = myCos.multiply(pi.divide(ee.Image(180))).cos()
117 | d = ee.Image(1).subtract(ee.Image(0.01673)).multiply(cosd).clip(footprint)
118 |
119 | # sun azimuth
120 | SunAz = ee.Image.constant(img.get('MEAN_SOLAR_AZIMUTH_ANGLE')).clip(footprint)
121 |
122 | # sun zenith
123 | SunZe = ee.Image.constant(img.get('MEAN_SOLAR_ZENITH_ANGLE')).clip(footprint)
124 | cosdSunZe = SunZe.multiply(pi.divide(ee.Image(180))).cos() # in degrees
125 | sindSunZe = SunZe.multiply(pi.divide(ee.Image(180))).sin() # in degrees
126 |
127 | # sat zenith
128 | SatZe = ee.Image.constant(img.get('MEAN_INCIDENCE_ZENITH_ANGLE_B5')).clip(footprint)
129 | cosdSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).cos()
130 | sindSatZe = (SatZe).multiply(pi.divide(ee.Image(180))).sin()
131 |
132 | # sat azimuth
133 | SatAz = ee.Image.constant(img.get('MEAN_INCIDENCE_AZIMUTH_ANGLE_B5')).clip(footprint)
134 |
135 | # relative azimuth
136 | RelAz = SatAz.subtract(SunAz)
137 | cosdRelAz = RelAz.multiply(pi.divide(ee.Image(180))).cos()
138 |
139 | # Pressure
140 | P = (ee.Image(101325).multiply(ee.Image(1).subtract(ee.Image(0.0000225577).multiply(DEM)).pow(5.25588)).multiply(
141 | 0.01)).multiply(mask)
142 | Po = ee.Image(1013.25)
143 |
144 | # esun
145 | ESUN = ee.Image(ee.Array([ee.Image(img.get('SOLAR_IRRADIANCE_B1')),
146 | ee.Image(img.get('SOLAR_IRRADIANCE_B2')),
147 | ee.Image(img.get('SOLAR_IRRADIANCE_B3')),
148 | ee.Image(img.get('SOLAR_IRRADIANCE_B4')),
149 | ee.Image(img.get('SOLAR_IRRADIANCE_B5')),
150 | ee.Image(img.get('SOLAR_IRRADIANCE_B6')),
151 | ee.Image(img.get('SOLAR_IRRADIANCE_B7')),
152 | ee.Image(img.get('SOLAR_IRRADIANCE_B8')),
153 | ee.Image(img.get('SOLAR_IRRADIANCE_B8A')),
154 | ee.Image(img.get('SOLAR_IRRADIANCE_B11')),
155 | ee.Image(img.get('SOLAR_IRRADIANCE_B2'))]
156 | )).toArray().toArray(1)
157 |
158 | ESUN = ESUN.multiply(ee.Image(1))
159 |
160 | ESUNImg = ESUN.arrayProject([0]).arrayFlatten([bands])
161 |
162 | # create empty array for the images
163 | imgArr = rescale.select(bands).toArray().toArray(1)
164 |
165 | # pTOA to Ltoa
166 | Ltoa = imgArr.multiply(ESUN).multiply(cosdSunZe).divide(pi.multiply(d.pow(2)))
167 |
168 | # band centers
169 | bandCenter = ee.Image(443).divide(1000).addBands(ee.Image(490).divide(1000)) \
170 | .addBands(ee.Image(560).divide(1000)) \
171 | .addBands(ee.Image(665).divide(1000)) \
172 | .addBands(ee.Image(705).divide(1000)) \
173 | .addBands(ee.Image(740).divide(1000)) \
174 | .addBands(ee.Image(783).divide(1000)) \
175 | .addBands(ee.Image(842).divide(1000)) \
176 | .addBands(ee.Image(865).divide(1000)) \
177 | .addBands(ee.Image(1610).divide(1000)) \
178 | .addBands(ee.Image(2190).divide(1000)) \
179 | .toArray().toArray(1)
180 |
181 | # ozone coefficients
182 | koz = ee.Image(0.0039).addBands(ee.Image(0.0213)) \
183 | .addBands(ee.Image(0.1052)) \
184 | .addBands(ee.Image(0.0505)) \
185 | .addBands(ee.Image(0.0205)) \
186 | .addBands(ee.Image(0.0112)) \
187 | .addBands(ee.Image(0.0075)) \
188 | .addBands(ee.Image(0.0021)) \
189 | .addBands(ee.Image(0.0019)) \
190 | .addBands(ee.Image(0)) \
191 | .addBands(ee.Image(0)) \
192 | .toArray().toArray(1)
193 |
194 | # Calculate ozone optical thickness
195 | Toz = koz.multiply(DU).divide(ee.Image(1000))
196 |
197 | # Calculate TOA radiance in the absense of ozone
198 | Lt = Ltoa.multiply(((Toz)).multiply((ee.Image(1).divide(cosdSunZe)).add(ee.Image(1).divide(cosdSatZe))).exp())
199 |
200 | # Rayleigh optical thickness
201 | Tr = (P.divide(Po)).multiply(ee.Image(0.008569).multiply(bandCenter.pow(-4))).multiply((ee.Image(1).add(
202 | ee.Image(0.0113).multiply(bandCenter.pow(-2))).add(ee.Image(0.00013).multiply(bandCenter.pow(-4)))))
203 |
204 | # Specular reflection (s- and p- polarization states)
205 | theta_V = ee.Image(0.0000000001)
206 | sin_theta_j = sindSunZe.divide(ee.Image(1.333))
207 |
208 | theta_j = sin_theta_j.asin().multiply(ee.Image(180).divide(pi))
209 |
210 | theta_SZ = SunZe
211 |
212 | R_theta_SZ_s = (
213 | ((theta_SZ.multiply(pi.divide(ee.Image(180)))).subtract(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(
214 | 2)).divide(
215 | (((theta_SZ.multiply(pi.divide(ee.Image(180)))).add(theta_j.multiply(pi.divide(ee.Image(180))))).sin().pow(2)))
216 |
217 | R_theta_V_s = ee.Image(0.0000000001)
218 |
219 | R_theta_SZ_p = (
220 | ((theta_SZ.multiply(pi.divide(180))).subtract(theta_j.multiply(pi.divide(180)))).tan().pow(2)).divide(
221 | (((theta_SZ.multiply(pi.divide(180))).add(theta_j.multiply(pi.divide(180)))).tan().pow(2)))
222 |
223 | R_theta_V_p = ee.Image(0.0000000001)
224 |
225 | R_theta_SZ = ee.Image(0.5).multiply(R_theta_SZ_s.add(R_theta_SZ_p))
226 |
227 | R_theta_V = ee.Image(0.5).multiply(R_theta_V_s.add(R_theta_V_p))
228 |
229 | # Sun-sensor geometry
230 | theta_neg = ((cosdSunZe.multiply(ee.Image(-1))).multiply(cosdSatZe)).subtract(
231 | (sindSunZe).multiply(sindSatZe).multiply(cosdRelAz))
232 |
233 | theta_neg_inv = theta_neg.acos().multiply(ee.Image(180).divide(pi))
234 |
235 | theta_pos = (cosdSunZe.multiply(cosdSatZe)).subtract(sindSunZe.multiply(sindSatZe).multiply(cosdRelAz))
236 |
237 | theta_pos_inv = theta_pos.acos().multiply(ee.Image(180).divide(pi))
238 |
239 | cosd_tni = theta_neg_inv.multiply(pi.divide(180)).cos() # in degrees
240 |
241 | cosd_tpi = theta_pos_inv.multiply(pi.divide(180)).cos() # in degrees
242 |
243 | Pr_neg = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tni.pow(2))))
244 |
245 | Pr_pos = ee.Image(0.75).multiply((ee.Image(1).add(cosd_tpi.pow(2))))
246 |
247 | # Rayleigh scattering phase function
248 | Pr = Pr_neg.add((R_theta_SZ.add(R_theta_V)).multiply(Pr_pos))
249 |
250 | # rayleigh radiance contribution
251 | denom = ee.Image(4).multiply(pi).multiply(cosdSatZe)
252 | Lr = (ESUN.multiply(Tr)).multiply(Pr.divide(denom))
253 |
254 | # rayleigh corrected radiance
255 | Lrc = Lt.subtract(Lr)
256 | LrcImg = Lrc.arrayProject([0]).arrayFlatten([bands])
257 |
258 | # Aerosol Correction #
259 |
260 | # Bands in nm
261 | bands_nm = ee.Image(443).addBands(ee.Image(490)) \
262 | .addBands(ee.Image(560)) \
263 | .addBands(ee.Image(665)) \
264 | .addBands(ee.Image(705)) \
265 | .addBands(ee.Image(740)) \
266 | .addBands(ee.Image(783)) \
267 | .addBands(ee.Image(842)) \
268 | .addBands(ee.Image(865)) \
269 | .addBands(ee.Image(0)) \
270 | .addBands(ee.Image(0)) \
271 | .toArray().toArray(1)
272 |
273 | # Lam in SWIR bands
274 | Lam_10 = LrcImg.select('B11')
275 | Lam_11 = LrcImg.select('B12')
276 |
277 | # Calculate aerosol type
278 | eps = (
279 | (((Lam_11).divide(ESUNImg.select('B12'))).log()).subtract(
280 | ((Lam_10).divide(ESUNImg.select('B11'))).log())).divide(
281 | ee.Image(2190).subtract(ee.Image(1610)))
282 |
283 | # Calculate multiple scattering of aerosols for each band
284 | Lam = (Lam_11).multiply(((ESUN).divide(ESUNImg.select('B12')))).multiply(
285 | (eps.multiply(ee.Image(-1))).multiply((bands_nm.divide(ee.Image(2190)))).exp())
286 |
287 | # diffuse transmittance
288 | trans = Tr.multiply(ee.Image(-1)).divide(ee.Image(2)).multiply(ee.Image(1).divide(cosdSatZe)).exp()
289 |
290 | # Compute water-leaving radiance
291 | Lw = Lrc.subtract(Lam).divide(trans)
292 |
293 | # water-leaving reflectance
294 | pw = (Lw.multiply(pi).multiply(d.pow(2)).divide(ESUN.multiply(cosdSunZe)))
295 |
296 | # remote sensing reflectance
297 | Rrs_coll = (pw.divide(pi).arrayProject([0]).arrayFlatten([bands]).slice(0, 9))
298 |
299 | return (Rrs_coll.set('system:time_start', img.get('system:time_start')))
300 |
301 |
302 | def chlorophyll(img):
303 | NDCI_coll = (img.select('B5').subtract(img.select('B4'))).divide(img.select('B5').add(img.select('B4')))
304 | chlor_a_coll = ee.Image(14.039).add(ee.Image(86.115).multiply(NDCI_coll)).add(
305 | ee.Image(194.325).multiply(NDCI_coll.pow(ee.Image(2))))
306 | return (chlor_a_coll.updateMask(chlor_a_coll.lt(100)).set('system:time_start', img.get('system:time_start')))
307 |
308 |
309 | def secchi(img):
310 |
311 | blueRed_coll = (img.select('B2').divide(img.select('B4'))).log()
312 | lnMOSD_coll = (ee.Image(1.4856).multiply(blueRed_coll)).add(
313 | ee.Image(0.2734)) # R2 = 0.8748 with Anthony's in-situ data
314 | MOSD_coll = ee.Image(10).pow(lnMOSD_coll)
315 | sd_coll = (ee.Image(0.1777).multiply(MOSD_coll)).add(ee.Image(1.0813))
316 | return (sd_coll.updateMask(sd_coll.lt(10)).set('system:time_start', img.get('system:time_start')))
317 |
318 |
319 | def trophicState(img):
320 | tsi_coll = ee.Image(30.6).add(ee.Image(9.81).multiply(img.log()))
321 | return (tsi_coll.updateMask(tsi_coll.lt(200)).set('system:time_start', img.get('system:time_start')))
322 |
323 |
324 | def reclassify(img):
325 | # Create conditions
326 | mask1 = img.lt(30) # (1)
327 | mask2 = img.gte(30).And(img.lt(40)) # (2)
328 | mask3 = img.gte(40).And(img.lt(50)) # (3)
329 | mask4 = img.gte(50).And(img.lt(60)) # (4)
330 | mask5 = img.gte(60).And(img.lt(70)) # (5)
331 | mask6 = img.gte(70).And(img.lt(80)) # (6)
332 | mask7 = img.gte(80) # (7)
333 |
334 | # Reclassify conditions into new values
335 | img1 = img.where(mask1.eq(1), 1).mask(mask1)
336 | img2 = img.where(mask2.eq(1), 2).mask(mask2)
337 | img3 = img.where(mask3.eq(1), 3).mask(mask3)
338 | img4 = img.where(mask4.eq(1), 4).mask(mask4)
339 | img5 = img.where(mask5.eq(1), 5).mask(mask5)
340 | img6 = img.where(mask6.eq(1), 6).mask(mask6)
341 | img7 = img.where(mask7.eq(1), 7).mask(mask7)
342 |
343 | # Ouput of reclassified image
344 | tsi_collR = img1.unmask(img2).unmask(img3).unmask(img4).unmask(img5).unmask(img6).unmask(img7)
345 | return (tsi_collR.updateMask(tsi_collR.set('system:time_start', img.get('system:time_start'))))
346 |
347 |
348 | #########################################################/
349 | #########################################################/
350 | #########################################################/
351 | # Collection Processing #
352 |
353 | # atmospheric correction
354 | Rrs_coll = FC.map(s2Correction)
355 |
356 | # chlorophyll-a
357 | chlor_a_coll = Rrs_coll.map(chlorophyll)
358 |
359 | # sd
360 | sd_coll = Rrs_coll.map(secchi)
361 |
362 | # tsi
363 | tsi_coll = chlor_a_coll.map(trophicState)
364 |
365 | # tsi reclass
366 | tsi_collR = tsi_coll.map(reclassify)
367 |
368 | Rsr = Rrs_coll.mean()
369 | Rsr_vis = {'min': 0, 'max': 0.03, 'bands': 'B4,B3,B2'}
370 | Rsr_mapid = Rsr.getMapId(Rsr_vis)
371 |
372 | SD = sd_coll.mean()
373 | SD_vis = {'min': 0, 'max': 2, 'palette': '#800000,#FF9700,#7BFF7B,#0080FF,#000080'}
374 | SD_mapid = SD.getMapId(SD_vis)
375 |
376 | TSI = tsi_coll.mean()
377 | TSI_vis = {'min': 30, 'max': 80,'palette':'darkblue,blue,cyan,limegreen,yellow,orange,orangered,darkred'}
378 | TSI_mapid = TSI.getMapId(TSI_vis)
379 |
380 | TSI_R = tsi_collR.mean()
381 | TSI_R_vis = {'min': 1, 'max': 7, 'palette': 'purple,blue,limegreen,yellow,orange,orangered,darkred'}
382 | TSI_R_mapid = TSI_R.getMapId(TSI_R_vis)
383 |
384 | print 'RSR',Rsr_mapid
385 | print 'SD',SD_mapid
386 | print 'TSI',TSI_mapid
387 | print 'TSI R',TSI_R_mapid
388 |
389 | # #########################################################/
390 | # #########################################################/
391 | # #########################################################/
392 | # Map Layers #
393 | # Map.addLayer(mask, {}, 'mask', false)
394 | # Map.addLayer(Rrs_coll.mean(), {min: 0, max: 0.03, bands: ['B4', 'B3', 'B2']}, 'Mean RGB', false)
395 | # Map.addLayer(chlor_a_coll.mean(), {min: 0, max: 40,
396 | # palette: ['darkblue', 'blue', 'cyan', 'limegreen', 'yellow', 'orange', 'orangered',
397 | # 'darkred']}, 'Mean chlor-a', false)
398 | # Map.addLayer(sd_coll.mean(), {min: 0, max: 2, palette: ['800000', 'FF9700', '7BFF7B', '0080FF', '000080']}, 'Mean Zsd',
399 | # false)
400 | # Map.addLayer(tsi_coll.mean(), {min: 30, max: 80,
401 | # palette: ['darkblue', 'blue', 'cyan', 'limegreen', 'yellow', 'orange', 'orangered',
402 | # 'darkred']}, 'Mean TSI', false)
403 | # Map.addLayer(tsi_collR.mode(),
404 | # {min: 1, max: 7, palette: ['purple', 'blue', 'limegreen', 'yellow', 'orange', 'orangered', 'darkred']},
405 | # 'Mode TSI Class', true)
406 |
407 | #########################################################/
408 | #########################################################/
409 | #########################################################/
410 | # Time Series #
411 |
412 | # # Chlorophyll-a time series
413 | # chlorTimeSeries = ui.Chart.image.seriesByRegion(
414 | # chlor_a_coll, geometry, ee.Reducer.mean())
415 | # .setChartType('ScatterChart')
416 | # .setOptions({
417 | # title: 'Mean Chlorphyll-a',
418 | # vAxis: {title: 'Chlor-a [micrograms/L]'},
419 | # lineWidth: 1,
420 | # pointSize: 4,
421 | # })
422 | #
423 | # # SD time series
424 | # sdTimeSeries = ui.Chart.image.seriesByRegion(
425 | # sd_coll, geometry, ee.Reducer.mean())
426 | # .setChartType('ScatterChart')
427 | # .setOptions({
428 | # title: 'Mean Secchi Depth',
429 | # vAxis: {title: 'Zsd [m]'},
430 | # lineWidth: 1,
431 | # pointSize: 4,
432 | # })
433 | #
434 | # # TSI time series
435 | # tsiTimeSeries = ui.Chart.image.seriesByRegion(
436 | # tsi_coll, geometry, ee.Reducer.mean())
437 | # .setChartType('ScatterChart')
438 | # .setOptions({
439 | # title: 'Mean Trophic State Index',
440 | # vAxis: {title: 'TSI [1-100]'},
441 | # lineWidth: 1,
442 | # pointSize: 4,
443 | # })
444 | #
445 | # # TSI Reclass time series
446 | # tsiRTimeSeries = ui.Chart.image.seriesByRegion(
447 | # tsi_collR, geometry, ee.Reducer.mode())
448 | # .setChartType('ScatterChart')
449 | # .setOptions({
450 | # title: 'Mode Trophic State Index Class',
451 | # vAxis: {title: 'TSI Class'},
452 | # lineWidth: 1,
453 | # pointSize: 4,
454 | # })
455 | #
456 | # print(chlorTimeSeries)
457 | # print(sdTimeSeries)
458 | # print(tsiTimeSeries)
459 | # print(tsiRTimeSeries)
460 |
461 |
462 |
--------------------------------------------------------------------------------