danielle-losos commited on
Commit
68efdc9
·
verified ·
1 Parent(s): 9108523

Upload 16 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM jupyter/base-notebook:latest
2
+
3
+ # Install required packages
4
+ RUN mamba install -c conda-forge leafmap geopandas localtileserver -y && \
5
+ fix-permissions "${CONDA_DIR}" && \
6
+ fix-permissions "/home/${NB_USER}"
7
+
8
+ # Copy the requirements file and install dependencies
9
+ COPY requirements.txt .
10
+ RUN pip install -r requirements.txt
11
+
12
+ # Copy the entire project directory into the container
13
+ COPY . /home/${NB_USER}
14
+
15
+ # Set the working directory
16
+ WORKDIR /home/${NB_USER}
17
+
18
+ # Set the PROJ_LIB environment variable
19
+ ENV PROJ_LIB='/opt/conda/share/proj'
20
+
21
+ # Ensure the notebook user owns the home directory
22
+ USER root
23
+ RUN chown -R ${NB_UID} ${HOME}
24
+ USER ${NB_USER}
25
+
26
+ # Expose the port for Solara
27
+ EXPOSE 8765
28
+
29
+ # Run the Solara app
30
+ CMD ["solara", "run", "pages", "--host=0.0.0.0"]
Home.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import solara
2
+
3
+ @solara.component
4
+ def Page():
5
+ with solara.Column(align="center"):
6
+ markdown = """
7
+ ## Real-time wildfire burn mapping
8
+
9
+ ### About the project
10
+
11
+ **A proof of concept illustrating wildfire burn severity maps with emerging clarity while the fires progress.
12
+ Target users are forecasters and emergency managers responding to post-fire risks including debris flows and landslides.**
13
+
14
+ More project description, etc, etc.
15
+
16
+ **Case Studies from 2020 and 2021 Western US wildfire seasons **
17
+
18
+ - August Complex, CA (2020)
19
+ - Cameron Peak, CO (2020)
20
+ - Dixie Fire, CA (2021)
21
+ - North Complex, CA (2020)
22
+
23
+
24
+ **Current 2024 wildfires over 10,000 acres **
25
+
26
+ ### How to use the app
27
+
28
+ 1. Select the fire from the drop-down menu
29
+
30
+ 2. Export image to Google Drive as a geotiff
31
+
32
+ 3.
33
+
34
+ ### Support
35
+
36
+ Initial funding for wildland burn scar mapping came through the NOAA JPSS/RRPG Fire and Smoke Initiative.
37
+ This supported the initial tests of BRIDGE maps using dNDVI. Subsequent funding supported the development of dNBR mapping and an effort
38
+ to tie support the near real-time distribution of incident-based fire detection and related satellite imagery products through the Next Generation Fire System (NGFS).
39
+ Current funding from the NOAA Weather Program Office (WPO) is supporting the refinement of our Google Earth Engine App (GEE)
40
+ and integration of GEE burn scar output with AWIPS (see example above) for Weather Forecast Offices, Regional Offices, and the Weather Prediction Center.
41
+
42
+ """
43
+
44
+ solara.Markdown(markdown)
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Open Geospatial Solutions
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.
README.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Solara Geemap
3
+ emoji: 🏃
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ app_port: 8765
10
+ ---
11
+
12
+ ## Earth Engine Web Apps
13
+
14
+ ### Introduction
15
+
16
+ **A collection of Earth Engine web apps developed using [Solara](https://github.com/widgetti/solara) and geemap**
17
+
18
+ - Web App: <https://giswqs-solara-geemap.hf.space>
19
+ - GitHub: <https://github.com/opengeos/solara-geemap>
20
+ - Hugging Face: <https://huggingface.co/spaces/giswqs/solara-geemap>
21
+
22
+ ### How to deploy this app on Hugging Face Spaces
23
+
24
+ 1. Go to <https://huggingface.co/spaces/giswqs/solara-geemap/tree/main> and duplicate the space to your own space.
25
+
26
+ ![](https://i.imgur.com/gTg4V2x.png)
27
+
28
+ 2. You need to set `EARTHENGINE_TOKEN` in order to use Earth Engine. The token value should be copied from the following file depending on your operating system:
29
+
30
+ ```text
31
+ Windows: C:\\Users\\USERNAME\\.config\\earthengine\\credentials
32
+ Linux: /home/USERNAME/.config/earthengine/credentials
33
+ MacOS: /Users/USERNAME/.config/earthengine/credentials
34
+ ```
35
+
36
+ Simply open the file and copy **ALL** the content to the `EARTHENGINE_TOKEN` environment variable.
37
+
38
+ ![](https://i.imgur.com/i04gzyH.png)
39
+
40
+ ![](https://i.imgur.com/Ex37Ut7.png)
41
+
42
+ Alternatively, you can run the following code to retrieve your Earth Engine token:
43
+
44
+ ```python
45
+ import geemap
46
+ geemap.get_ee_token()
47
+ ```
48
+
49
+ Copy all the content of the printed token and set it as the `EARTHENGINE_TOKEN` environment variable.
50
+
51
+ 3. After the space is built successfully, click the `Embed this Space` menu and find the `Direct URL` for the app, such as <https://giswqs-solara-geemap.hf.space>.
52
+
53
+ ![](https://i.imgur.com/DNM36sk.png)
54
+
55
+ ![](https://i.imgur.com/KX82lSf.png)
56
+
57
+ 4. Add your own apps (\*.py) to the `pages` folder.
58
+ 5. Commit and push your changes to the repository. Wait for the space to be built successfully.
__init__.py ADDED
File without changes
__pycache__/NBR_calculations.cpython-312.pyc ADDED
Binary file (6.87 kB). View file
 
current_fires.py ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ee
2
+ import geemap
3
+ import solara
4
+ import ipywidgets as widgets
5
+ import datetime
6
+
7
+ #from NBR_calculations import GcalcNBR, VcalcNBR, LcalcNBR, ScalcNBR, GcalcCCsingle
8
+ import requests
9
+
10
+ # Bit-masking
11
+ BitMask_0 = 1 << 0
12
+ BitMask_1 = 1 << 1
13
+ BitMask_2 = 1 << 2
14
+ BitMask_3 = 1 << 3
15
+ BitMask_4 = 1 << 4
16
+ BitMask_5 = 1 << 5
17
+ BitMask_6 = 1 << 6
18
+ BitMask_7 = 1 << 7
19
+ BitMask_8 = 1 << 8
20
+ BitMask_9 = 1 << 9
21
+
22
+ def GcalcCCsingle (goesImg):
23
+
24
+ fireDQF = goesImg.select('DQF').int()
25
+ CMI_QF3 = goesImg.select('DQF_C03').int()
26
+ CMI_QF6 = goesImg.select('DQF_C06').int()
27
+
28
+ #Right now, cloud mask is excluding clouds and water; active fire, bad data and fire free are unmasked. NBR mask exlcudes fire
29
+ F_Mask = fireDQF.eq(0)
30
+ C_Mask = (fireDQF.lt(2).Or(fireDQF.gt(2))).rename('C_Mask')
31
+ #.And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('C_Mask')
32
+ QF_Mask = (fireDQF.eq(1).Or(fireDQF.gt(3)))\
33
+ .And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('QFmask')
34
+
35
+ GOESmasked = goesImg.select(['CMI_C03','CMI_C06']).updateMask(QF_Mask)
36
+ NBRmasked = GOESmasked.normalizedDifference(['CMI_C03', 'CMI_C06']).toFloat().rename('NBR')
37
+ cloudMasked = goesImg.select('CMI_C03').updateMask(C_Mask).toFloat().rename('CC')
38
+ fireMasked = goesImg.select('CMI_C03').updateMask(F_Mask).toFloat().rename('FC')
39
+
40
+ return goesImg.addBands([NBRmasked,cloudMasked, fireMasked,QF_Mask,C_Mask])
41
+
42
+ '''Parameter Array Name Value Bit(s) = Value
43
+ Sun Glint QF1 Surface Reflectance None 6-7 = 00
44
+ Low Sun Mask QF1 Surface Reflectance High 5 = 0
45
+ Day/Night QF1 Surface Reflectance Day 4 =0
46
+ Cloud Detection QF1 Surface Reflectance Confident Clear 2-3 = 00 or Problably Clear 2-3 = 01
47
+ Cloud Mask Quality QF1 Surface Reflectance High or Medium 0-1 = 10 or 11
48
+ Snow/Ice QF2 Surface Reflectance No Snow or Ice 5 = 0
49
+ Cloud Shadow QF2 Surface Reflectance No Cloud Shadow 3 = 0
50
+ LandWater QF2 Surface Reflectance Land, Snow, Arctic, Antarctic or Greenland, Desert 0-2 = 011, 100, 101, 110, 111
51
+ Thin Cirrus Flag QF7 Surface Reflectance No Thin Cirrus 4 = 0
52
+ Aerosol Quantity QF7 Surface Reflectance Climatology, Low or Medium 2-3 = 00, 01 or 10
53
+ Adjacent to Cloud QF7 Surface Reflectance Not Adjacent to Cloud 1 = 0'''
54
+
55
+ def VcalcNBR (VIIRSimg):
56
+
57
+ QF1 = VIIRSimg.select('QF1').int()
58
+ QF2 = VIIRSimg.select('QF2').int()
59
+ QF7 = VIIRSimg.select('QF7').int()
60
+
61
+ QF_Mask = (QF1.bitwiseAnd(BitMask_3).eq(0)).And\
62
+ ((QF2.bitwiseAnd(BitMask_2).eq(4)).Or((QF2.bitwiseAnd(BitMask_1).eq(0)))).And\
63
+ (QF2.bitwiseAnd(BitMask_5).eq(0)).rename('QFmask');
64
+
65
+ VIIRSm = VIIRSimg.select(['I2','M11']).updateMask(QF_Mask);
66
+ NBR = VIIRSm.normalizedDifference(['I2','M11']).toFloat().rename('NBR')
67
+ return VIIRSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
68
+
69
+ ''' Bit 1: Dilated Cloud
70
+ Bit 2: Cirrus (high confidence)
71
+ Bit 3: Cloud
72
+ Bit 4: Cloud Shadow
73
+ Bit 5: Snow
74
+ Bit 6: Clear (0: Cloud or Dilated Cloud bits are set, 1: Cloud and Dilated Cloud bits are not set)
75
+ Bit 7: Water
76
+ Bits 8-9: Cloud Confidence (0: None, 1: Low, 2: Medium, 3: High)
77
+ Bits 10-11: Cloud Shadow Confidence (0: None, 1: Low, 2: Medium, 3: High)
78
+ Bits 12-13: Snow/Ice Confidence (0: None, 1: Low, 2: Medium, 3: High)
79
+ Bits 14-15: Cirrus Confidence (0: None, 1: Low, 2: Medium, 3: High)'''
80
+
81
+ def LcalcNBR (LSimg):
82
+ QApixel = LSimg.select('QA_PIXEL').int()
83
+ QF_Mask =(QApixel.bitwiseAnd(BitMask_3).eq(0)).And\
84
+ (QApixel.bitwiseAnd(BitMask_5).eq(0)).And\
85
+ (QApixel.bitwiseAnd(BitMask_7).eq(0)).rename('QFmask');
86
+
87
+ LSmasked = LSimg.select(['SR_B5','SR_B7']).updateMask(QF_Mask);
88
+ NBR = LSmasked.normalizedDifference(['SR_B5','SR_B7']).toFloat().rename('NBR')
89
+ return LSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
90
+
91
+ ''' 1 Saturated or defective
92
+ 2 Dark Area Pixels
93
+ 3 Cloud Shadows
94
+ 4 Vegetation
95
+ 5 Bare Soils
96
+ 6 Water
97
+ 7 Clouds Low Probability / Unclassified
98
+ 8 Clouds Medium Probability
99
+ 9 Clouds High Probability
100
+ 10 Cirrus
101
+ 11 Snow / Ice'''
102
+
103
+ def ScalcNBR (sentImg):
104
+ SCL = sentImg.select('SCL');
105
+ QF_Mask =(SCL.neq(6)).And\
106
+ (SCL.neq(8)).And\
107
+ (SCL.neq(9)).And\
108
+ (SCL.neq(11))\
109
+ .rename('QFmask');
110
+ sentMasked = sentImg.select(['B8A','B12']).updateMask(QF_Mask); #B8 is another option- broadband NIR
111
+ NBR = sentMasked.normalizedDifference(['B8A','B12']).toFloat().rename('NBR')
112
+ return sentImg.addBands(NBR).addBands(QF_Mask).addBands(SCL)#.set('avgNBR', avgNBR)
113
+
114
+ #createDates = NIFC_perims_716.aggregate_array('attr_Cre_1')
115
+ #incidentIDs = NIFC_perims_716.aggregate_array('poly_Incid')
116
+ #fireList = incidentIDs.getInfo()
117
+ fireList = wildfire_names = [ "FRESNO JUNE LIGHTNING COMPLEX", "Larch Creek","Deadman","Cow Valley","0404 RV LONE ROCK",
118
+ "PIONEER","South Fork", "Deer Springs","Basin","Lake","Horse Gulch","Falls","Silver King","Indios"]
119
+ selected_fire = solara.reactive(fireList[6])
120
+ dNBRvisParams = {'min': 0.0,'max': 0.8, 'palette': ['green', 'yellow','orange','red']}
121
+ today = datetime.datetime.today().strftime('%Y-%m-%d')
122
+
123
+ class Map(geemap.Map):
124
+ def __init__(self, **kwargs):
125
+ super().__init__(**kwargs)
126
+ self.add_basemap('OpenStreetMap')
127
+
128
+ self.customize_ee_data(selected_fire.value, today)
129
+ self.add_selector()
130
+ self.add_dwnldButton()
131
+ self.add("layer_manager")
132
+ self.remove("draw_control")
133
+
134
+
135
+ def customize_ee_data(self, fireID, elapDays):
136
+ NIFC_perims_716 = ee.FeatureCollection('projects/ovcrge-ssec-burn-scar-map-c116/assets/NIFC_perimeters_7-16')
137
+ fire = NIFC_perims_716.filter(ee.Filter.eq('poly_Incid',fireID)).first()
138
+ timestamp = fire.get('attr_Cre_1')
139
+ geom = fire.geometry()
140
+
141
+ startDate = ee.Date(timestamp)#.format('YYYY-MM-dd')
142
+ endDate = ee.Date.parse('YYYY-MM-dd', str(today))
143
+
144
+ boundingBox = ee.Geometry(geom.buffer(5000).bounds())
145
+
146
+ elapDayNum = ee.Number(10)
147
+ elapDay_plusOne = elapDayNum.add(ee.Number(1))
148
+
149
+ def calc_nbr(pre_start, pre_stop, post_start, post_stop, bbox, goes):
150
+
151
+ def MergeBands (eachImage):
152
+ oneImage = ee.Image.cat(eachImage.get('CMI'), eachImage.get('FDC'))
153
+ return oneImage
154
+ displacementImg18 = ee.Image.load('projects/ee-losos/assets/G18-F-meter-offset_GEE')
155
+ y_dif = displacementImg18.select([1])
156
+ x_dif = displacementImg18.select([0]).multiply(-1)
157
+ displacement18 = ee.Image([x_dif, y_dif])
158
+
159
+ displacementImg16 = ee.Image.load('projects/ee-losos/assets/G16-F-meter-offset_GEE')
160
+ y_dif = displacementImg16.select([1])
161
+ x_dif = displacementImg16.select([0]).multiply(-1)
162
+ displacement16 = ee.Image([x_dif, y_dif]);
163
+
164
+ preCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
165
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
166
+ preFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
167
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
168
+ postCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
169
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
170
+ postFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(post_start, post_stop))\
171
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
172
+
173
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
174
+ primary = preCMIcol,
175
+ secondary = preFDCcol,
176
+ condition = ee.Filter.maxDifference(
177
+ difference = 10, #milliseconds
178
+ leftField = 'system:time_start',
179
+ rightField = 'system:time_start',))
180
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
181
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
182
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
183
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
184
+
185
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
186
+ primary = postCMIcol,
187
+ secondary = postFDCcol,
188
+ condition = ee.Filter.maxDifference(
189
+ difference = 10, #milliseconds
190
+ leftField = 'system:time_start',
191
+ rightField = 'system:time_start',))
192
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
193
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
194
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
195
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
196
+
197
+ dNBR_goes17 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
198
+
199
+
200
+ #GOES-16
201
+ preCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
202
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
203
+ preFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
204
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
205
+
206
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
207
+ primary = preCMIcol,
208
+ secondary = preFDCcol,
209
+ condition = ee.Filter.maxDifference(
210
+ difference = 10, #milliseconds
211
+ leftField = 'system:time_start',
212
+ rightField = 'system:time_start',))
213
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
214
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
215
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
216
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
217
+
218
+
219
+ postCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
220
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
221
+ postFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(post_start, post_stop))\
222
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
223
+
224
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
225
+ primary = postCMIcol,
226
+ secondary = postFDCcol,
227
+ condition = ee.Filter.maxDifference(
228
+ difference = 10, #milliseconds
229
+ leftField = 'system:time_start',
230
+ rightField = 'system:time_start',))
231
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
232
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
233
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
234
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
235
+
236
+ dNBR_goes16 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
237
+
238
+ dNBRclip_goes17= dNBR_goes17.clip(bbox)
239
+ dNBRclip_goes16= dNBR_goes16.clip(bbox)
240
+ dNBRdisp_goes17 = dNBRclip_goes17.displace(displacement18, 'bicubic')
241
+ dNBRdisp_goes16 = dNBRclip_goes16.displace(displacement16, 'bicubic')
242
+ dNBRgoes_compos = ee.ImageCollection([dNBRdisp_goes17,dNBRdisp_goes16]).mean()
243
+
244
+ #ACTIVE fire
245
+ activeFire18 = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
246
+ activeFire16 = ee.ImageCollection(f"NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
247
+ sumFRP18 = activeFire18.select('Power').sum().rename('sumFRP')
248
+ sumFRP16 = activeFire16.select('Power').sum().rename('sumFRP')
249
+ maskNoFire18 = sumFRP18.gt(200).displace(displacement18, 'bicubic')
250
+ maskNoFire16 = sumFRP16.gt(200).displace(displacement16, 'bicubic')
251
+ maskNoFire = ee.ImageCollection([maskNoFire18,maskNoFire16]).sum().gt(0)
252
+
253
+ '''
254
+ activeSNPP = ee.ImageCollection("NASA/LANCE/SNPP_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
255
+ activeNOAA20 = ee.ImageCollection("NASA/LANCE/NOAA20_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
256
+ sumFRP_SNPP = activeSNPP.select('confidence').max().rename('sumFRP')
257
+ sumFRP_NOAA20 = activeNOAA20.select('confidence').max().rename('sumFRP')
258
+ #maskNoFire = ee.ImageCollection([sumFRP_SNPP,sumFRP_NOAA20]).sum().gt(0)
259
+ maskNoFire = sumFRP_SNPP.gt(0)
260
+ '''
261
+
262
+ #VIIRS
263
+ preVIIRSimg = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(pre_start, pre_stop)).mean()
264
+ postVIIRSimgCol = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(post_start, post_stop)) #TO FIX ON JUNE 18 sfork_startDate.advance(24, 'day'), sfork_startDate.advance(25,'day')
265
+
266
+ #Landsat
267
+ prelandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
268
+ postlandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
269
+ prelandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
270
+ postlandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
271
+ prelandsatcol = prelandsat8col.merge(prelandsat9col)
272
+ postlandsatcol = postlandsat8col.merge(postlandsat9col)
273
+
274
+ #Sentinel
275
+ presentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
276
+ postsentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(post_start, post_stop).filterBounds(bbox) #TO FIX on JULY 5: sfork_startDate.advance(32, 'day'), sfork_startDate.advance(33,'day')
277
+ #olderPostSentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(sfork_startDate.advance(37, 'day'), sfork_startDate.advance(38,'day')).filterBounds(bbox)
278
+
279
+ #SAR
280
+ #SARimg = ee.Image('projects/ovcrge-ssec-burn-scar-map-c116/assets/burned_20200907_20200919_test')
281
+ #SARmask = SARimg.eq(1)
282
+
283
+ if postVIIRSimgCol.size().getInfo() > 0:
284
+ postVIIRSimg = postVIIRSimgCol.mean()
285
+ preVIIRSimg = VcalcNBR(preVIIRSimg)
286
+ postVIIRSimg = VcalcNBR(postVIIRSimg)
287
+ dNBR_viirs = preVIIRSimg.subtract(postVIIRSimg).select('NBR')
288
+ dNBRclip_viirs = dNBR_viirs.clip(bbox)
289
+ else:
290
+ dNBR_composite = dNBRgoes_compos
291
+ if postsentCol.size().getInfo() > 0:
292
+ presentMean = presentCol.mean()
293
+ postsentMean = postsentCol.mean()
294
+ presentImg = ScalcNBR(presentMean)
295
+ postsentImg = ScalcNBR(postsentMean)
296
+ dnbr_sent = presentImg.subtract(postsentImg).multiply(1.3).add(0.05).select('NBR')
297
+ dNBRclip_sent = dnbr_sent.clip(bbox)
298
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_sent]).mosaic() #dNBRclip_viirs SHOULD GO IN IF UP TO DATE
299
+ elif postlandsatcol.size().getInfo() > 0:
300
+ prelandsat = prelandsatcol.mean()
301
+ prelandsatImg = LcalcNBR(prelandsat)
302
+ postlandsat = postlandsatcol.mean()
303
+ postlandsatImg = LcalcNBR(postlandsat)
304
+ dNBR_landsat = prelandsatImg.subtract(postlandsatImg).multiply(3.23).add(0.01).select('NBR')
305
+ dNBRclip_ls = dNBR_landsat.clip(bbox)
306
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_ls]).mosaic() #dNBRclip_viirs SHOULD GO IN IF UP TO DATE
307
+ else:
308
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos]).mosaic() #dNBRclip_viirs SHOULD GO IN IF UP TO DATE
309
+
310
+ masked_compos = dNBR_composite.updateMask(maskNoFire) #(SARmask)
311
+ #doubleMasked_compos = masked_compos.updateMask(maskNoFire)
312
+ doubleMasked_compos = masked_compos.mask(masked_compos.mask()).float()
313
+ downloadArgs = {'name': 'VIIRS_burnMap',
314
+ 'crs': 'EPSG:4326',
315
+ 'scale': 60,
316
+ 'region': bbox}
317
+ url = doubleMasked_compos.getDownloadURL(downloadArgs)
318
+
319
+ print(url)
320
+ noDataVal = -9999
321
+ unmaskedImage = doubleMasked_compos.unmask(noDataVal, False)
322
+
323
+ task = ee.batch.Export.image.toDrive(**{
324
+ 'image': unmaskedImage,
325
+ 'description': "Composite_burnMap6",
326
+ 'folder': "Earth Engine Outputs",
327
+ 'fileNamePrefix': "Composite_burnMap_noData_VIIRS_June18_espg3857_60m",
328
+ 'region': bbox,
329
+ 'crs': 'EPSG:3857',
330
+ 'scale': 60,})
331
+ task.start()
332
+ return masked_compos
333
+
334
+
335
+ self.clear_specific_layers()
336
+
337
+ fireImg = calc_nbr(startDate.advance(-7, 'day'), startDate, endDate.advance(-3, 'day'), endDate, boundingBox, 18)
338
+ self.addLayer(fireImg, dNBRvisParams, fireID, True)
339
+ self.centerObject(boundingBox, 10)
340
+ file = fireImg
341
+
342
+
343
+ def clear_specific_layers(self):
344
+ layers_to_keep = ['OpenStreetMap']
345
+ layers = list(self.layers)
346
+ for layer in layers:
347
+ if layer.name not in layers_to_keep:
348
+ self.remove_layer(layer)
349
+
350
+
351
+ def add_selector(self):
352
+ selector = widgets.Dropdown(options=fireList, value=fireList[6], description='Current wildfire :', style={'description_width': '125px'}, layout=widgets.Layout(width='400px'))
353
+
354
+ def on_selector_change(change):
355
+ if change['name'] == 'value':
356
+ selected_fire.value = change['new']
357
+ self.customize_ee_data(selected_fire.value, today)
358
+
359
+ selector.observe(on_selector_change, names='value')
360
+ self.add_widget(selector, position="topleft")
361
+
362
+
363
+ def add_dwnldButton(self):
364
+ button = widgets.Button(description='Export to Drive',icon='cloud-arrow-down')
365
+
366
+ #def on_button_click(change, file):
367
+ # if change['name'] == 'value':
368
+ # selected_days.value = change['new']
369
+ # self.download_ee_image(file, "trial_file.tif", scale=30)
370
+ def on_button_click(b):
371
+ # Get the currently selected fire and elapsed days
372
+ fire = selected_fire.value
373
+ elapDays = today
374
+
375
+ # Customize the EE data and download the image
376
+ file = self.customize_ee_data(fire, elapDays)
377
+ #self.download_ee_image(file, f"{fire}_NBR_{elapDays}days.tif", scale=30)
378
+
379
+ button.observe(on_button_click)
380
+ self.add_widget(button, position="topleft")
381
+
382
+
383
+
384
+ @solara.component
385
+ def Page():
386
+
387
+ with solara.Column(align="center"):
388
+ markdown = """
389
+ ## Current 2024 wildfires over 10,000 acres"""
390
+ solara.Markdown(markdown)
391
+
392
+ # Isolation is required to prevent the map from overlapping navigation (when screen width < 960px)
393
+ with solara.Column(style={"isolation": "isolate"}):
394
+ map_widget = Map.element(
395
+ center=[39, -120.5],
396
+ zoom=8,
397
+ height="600px",
398
+ toolbar_ctrl=False
399
+ )
historical_fires.py ADDED
@@ -0,0 +1,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ee
2
+ import geemap
3
+ import solara
4
+ import ipywidgets as widgets
5
+ #from NBR_calculations import GcalcNBR, VcalcNBR, LcalcNBR, ScalcNBR, GcalcCCsingle
6
+ import requests
7
+
8
+ # Bit-masking
9
+ BitMask_0 = 1 << 0
10
+ BitMask_1 = 1 << 1
11
+ BitMask_2 = 1 << 2
12
+ BitMask_3 = 1 << 3
13
+ BitMask_4 = 1 << 4
14
+ BitMask_5 = 1 << 5
15
+ BitMask_6 = 1 << 6
16
+ BitMask_7 = 1 << 7
17
+ BitMask_8 = 1 << 8
18
+ BitMask_9 = 1 << 9
19
+
20
+ def GcalcCCsingle (goesImg):
21
+
22
+ fireDQF = goesImg.select('DQF').int()
23
+ CMI_QF3 = goesImg.select('DQF_C03').int()
24
+ CMI_QF6 = goesImg.select('DQF_C06').int()
25
+
26
+ #Right now, cloud mask is excluding clouds and water; active fire, bad data and fire free are unmasked. NBR mask exlcudes fire
27
+ F_Mask = fireDQF.eq(0)
28
+ C_Mask = (fireDQF.lt(2).Or(fireDQF.gt(2))).rename('C_Mask')
29
+ #.And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('C_Mask')
30
+ QF_Mask = (fireDQF.eq(1).Or(fireDQF.gt(3)))\
31
+ .And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('QFmask')
32
+
33
+ GOESmasked = goesImg.select(['CMI_C03','CMI_C06']).updateMask(QF_Mask)
34
+ NBRmasked = GOESmasked.normalizedDifference(['CMI_C03', 'CMI_C06']).toFloat().rename('NBR')
35
+ cloudMasked = goesImg.select('CMI_C03').updateMask(C_Mask).toFloat().rename('CC')
36
+ fireMasked = goesImg.select('CMI_C03').updateMask(F_Mask).toFloat().rename('FC')
37
+
38
+ return goesImg.addBands([NBRmasked,cloudMasked, fireMasked,QF_Mask,C_Mask])
39
+
40
+ '''Parameter Array Name Value Bit(s) = Value
41
+ Sun Glint QF1 Surface Reflectance None 6-7 = 00
42
+ Low Sun Mask QF1 Surface Reflectance High 5 = 0
43
+ Day/Night QF1 Surface Reflectance Day 4 =0
44
+ Cloud Detection QF1 Surface Reflectance Confident Clear 2-3 = 00 or Problably Clear 2-3 = 01
45
+ Cloud Mask Quality QF1 Surface Reflectance High or Medium 0-1 = 10 or 11
46
+ Snow/Ice QF2 Surface Reflectance No Snow or Ice 5 = 0
47
+ Cloud Shadow QF2 Surface Reflectance No Cloud Shadow 3 = 0
48
+ LandWater QF2 Surface Reflectance Land, Snow, Arctic, Antarctic or Greenland, Desert 0-2 = 011, 100, 101, 110, 111
49
+ Thin Cirrus Flag QF7 Surface Reflectance No Thin Cirrus 4 = 0
50
+ Aerosol Quantity QF7 Surface Reflectance Climatology, Low or Medium 2-3 = 00, 01 or 10
51
+ Adjacent to Cloud QF7 Surface Reflectance Not Adjacent to Cloud 1 = 0'''
52
+
53
+ def VcalcNBR (VIIRSimg):
54
+
55
+ QF1 = VIIRSimg.select('QF1').int()
56
+ QF2 = VIIRSimg.select('QF2').int()
57
+ QF7 = VIIRSimg.select('QF7').int()
58
+
59
+ QF_Mask = (QF1.bitwiseAnd(BitMask_3).eq(0)).And\
60
+ ((QF2.bitwiseAnd(BitMask_2).eq(4)).Or((QF2.bitwiseAnd(BitMask_1).eq(0)))).And\
61
+ (QF2.bitwiseAnd(BitMask_5).eq(0)).rename('QFmask');
62
+
63
+ VIIRSm = VIIRSimg.select(['I2','M11']).updateMask(QF_Mask);
64
+ NBR = VIIRSm.normalizedDifference(['I2','M11']).toFloat().rename('NBR')
65
+ return VIIRSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
66
+
67
+ ''' Bit 1: Dilated Cloud
68
+ Bit 2: Cirrus (high confidence)
69
+ Bit 3: Cloud
70
+ Bit 4: Cloud Shadow
71
+ Bit 5: Snow
72
+ Bit 6: Clear (0: Cloud or Dilated Cloud bits are set, 1: Cloud and Dilated Cloud bits are not set)
73
+ Bit 7: Water
74
+ Bits 8-9: Cloud Confidence (0: None, 1: Low, 2: Medium, 3: High)
75
+ Bits 10-11: Cloud Shadow Confidence (0: None, 1: Low, 2: Medium, 3: High)
76
+ Bits 12-13: Snow/Ice Confidence (0: None, 1: Low, 2: Medium, 3: High)
77
+ Bits 14-15: Cirrus Confidence (0: None, 1: Low, 2: Medium, 3: High)'''
78
+
79
+ def LcalcNBR (LSimg):
80
+ QApixel = LSimg.select('QA_PIXEL').int()
81
+ QF_Mask =(QApixel.bitwiseAnd(BitMask_3).eq(0)).And\
82
+ (QApixel.bitwiseAnd(BitMask_5).eq(0)).And\
83
+ (QApixel.bitwiseAnd(BitMask_7).eq(0)).rename('QFmask');
84
+
85
+ LSmasked = LSimg.select(['SR_B5','SR_B7']).updateMask(QF_Mask);
86
+ NBR = LSmasked.normalizedDifference(['SR_B5','SR_B7']).toFloat().rename('NBR')
87
+ return LSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
88
+
89
+ ''' 1 Saturated or defective
90
+ 2 Dark Area Pixels
91
+ 3 Cloud Shadows
92
+ 4 Vegetation
93
+ 5 Bare Soils
94
+ 6 Water
95
+ 7 Clouds Low Probability / Unclassified
96
+ 8 Clouds Medium Probability
97
+ 9 Clouds High Probability
98
+ 10 Cirrus
99
+ 11 Snow / Ice'''
100
+
101
+ def ScalcNBR (sentImg):
102
+ SCL = sentImg.select('SCL');
103
+ QF_Mask =(SCL.neq(6)).And\
104
+ (SCL.neq(8)).And\
105
+ (SCL.neq(9)).And\
106
+ (SCL.neq(11))\
107
+ .rename('QFmask');
108
+ sentMasked = sentImg.select(['B8A','B12']).updateMask(QF_Mask); #B8 is another option- broadband NIR
109
+ NBR = sentMasked.normalizedDifference(['B8A','B12']).toFloat().rename('NBR')
110
+ return sentImg.addBands(NBR).addBands(QF_Mask).addBands(SCL)#.set('avgNBR', avgNBR)
111
+
112
+
113
+ fireList = ["North Complex", "Dixie", "Cameron Peak", "August Complex", "South Fork"]
114
+ selected_fire = solara.reactive(fireList[4])
115
+ selected_days = solara.reactive(25) #30
116
+ dNBRvisParams = {'min': 0.0,'max': 0.8, 'palette': ['green', 'yellow','orange','red']}
117
+
118
+
119
+ class Map(geemap.Map):
120
+ def __init__(self, **kwargs):
121
+ super().__init__(**kwargs)
122
+ self.add_basemap('OpenStreetMap')
123
+ self.customize_ee_data(selected_fire.value, selected_days.value)
124
+ self.add_selector()
125
+ self.add_intSlider()
126
+ self.add_dwnldButton()
127
+ self.add("layer_manager")
128
+ self.remove("draw_control")
129
+
130
+
131
+ def customize_ee_data(self, fire, elapDays):
132
+ elapDayNum = ee.Number(elapDays)
133
+ elapDay_plusOne = elapDayNum.add(ee.Number(1))
134
+
135
+ north_startDate = ee.Date('2020-08-16')
136
+ dixie_startDate = ee.Date('2021-07-13')
137
+ cam_startDate = ee.Date('2020-08-13')
138
+ aug_startDate = ee.Date('2020-08-15')
139
+ sfork_startDate = ee.Date('2024-05-25')
140
+
141
+ north_complex_bb = ee.Geometry.BBox(-121.616097, 39.426723, -120.668526, 40.030845)
142
+ dixie_bb = ee.Geometry.BBox(-121.680467, 39.759303, -120.065477, 40.873387)
143
+ cam_peak_bb = ee.Geometry.BBox(-106.014784, 40.377907, -105.116651, 40.822094)
144
+ aug_complex_bb = ee.Geometry.BBox(-123.668726, 39.337654, -122.355860, 40.498304)
145
+ sfork_bb = ee.Geometry.BBox(-106.192, 33.1, -105.065, 33.782)
146
+
147
+ def calc_nbr(pre_start, pre_stop, post_start, post_stop, bbox, goes):
148
+ def MergeBands (eachImage):
149
+ oneImage = ee.Image.cat(eachImage.get('CMI'), eachImage.get('FDC'))
150
+ return oneImage
151
+ displacementImg18 = ee.Image.load('projects/ee-losos/assets/G18-F-meter-offset_GEE')
152
+ y_dif = displacementImg18.select([1])
153
+ x_dif = displacementImg18.select([0]).multiply(-1)
154
+ displacement18 = ee.Image([x_dif, y_dif])
155
+
156
+ displacementImg16 = ee.Image.load('projects/ee-losos/assets/G16-F-meter-offset_GEE')
157
+ y_dif = displacementImg16.select([1])
158
+ x_dif = displacementImg16.select([0]).multiply(-1)
159
+ displacement16 = ee.Image([x_dif, y_dif]);
160
+
161
+ preCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
162
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
163
+ preFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
164
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
165
+ postCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
166
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
167
+ postFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(post_start, post_stop))\
168
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
169
+
170
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
171
+ primary = preCMIcol,
172
+ secondary = preFDCcol,
173
+ condition = ee.Filter.maxDifference(
174
+ difference = 10, #milliseconds
175
+ leftField = 'system:time_start',
176
+ rightField = 'system:time_start',))
177
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
178
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
179
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
180
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
181
+
182
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
183
+ primary = postCMIcol,
184
+ secondary = postFDCcol,
185
+ condition = ee.Filter.maxDifference(
186
+ difference = 10, #milliseconds
187
+ leftField = 'system:time_start',
188
+ rightField = 'system:time_start',))
189
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
190
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
191
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
192
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
193
+
194
+ dNBR_goes17 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
195
+
196
+
197
+ #GOES-16
198
+ preCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
199
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
200
+ preFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
201
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
202
+
203
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
204
+ primary = preCMIcol,
205
+ secondary = preFDCcol,
206
+ condition = ee.Filter.maxDifference(
207
+ difference = 10, #milliseconds
208
+ leftField = 'system:time_start',
209
+ rightField = 'system:time_start',))
210
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
211
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
212
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
213
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
214
+
215
+
216
+ postCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
217
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
218
+ postFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(post_start, post_stop))\
219
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
220
+
221
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
222
+ primary = postCMIcol,
223
+ secondary = postFDCcol,
224
+ condition = ee.Filter.maxDifference(
225
+ difference = 10, #milliseconds
226
+ leftField = 'system:time_start',
227
+ rightField = 'system:time_start',))
228
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
229
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
230
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
231
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
232
+
233
+ dNBR_goes16 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
234
+
235
+ dNBRclip_goes17= dNBR_goes17.clip(bbox)
236
+ dNBRclip_goes16= dNBR_goes16.clip(bbox)
237
+ dNBRdisp_goes17 = dNBRclip_goes17.displace(displacement18, 'bicubic')
238
+ dNBRdisp_goes16 = dNBRclip_goes16.displace(displacement16, 'bicubic')
239
+ dNBRgoes_compos = ee.ImageCollection([dNBRdisp_goes17,dNBRdisp_goes16]).mean()
240
+
241
+ #ACTIVE fire
242
+ activeFire18 = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
243
+ activeFire16 = ee.ImageCollection(f"NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
244
+ sumFRP18 = activeFire18.select('Power').sum().rename('sumFRP')
245
+ sumFRP16 = activeFire16.select('Power').sum().rename('sumFRP')
246
+ maskNoFire18 = sumFRP18.gt(200).displace(displacement18, 'bicubic')
247
+ maskNoFire16 = sumFRP16.gt(200).displace(displacement16, 'bicubic')
248
+ maskNoFire = ee.ImageCollection([maskNoFire18,maskNoFire16]).sum().gt(0)
249
+
250
+ '''
251
+ activeSNPP = ee.ImageCollection("NASA/LANCE/SNPP_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
252
+ activeNOAA20 = ee.ImageCollection("NASA/LANCE/NOAA20_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
253
+ sumFRP_SNPP = activeSNPP.select('confidence').max().rename('sumFRP')
254
+ sumFRP_NOAA20 = activeNOAA20.select('confidence').max().rename('sumFRP')
255
+ #maskNoFire = ee.ImageCollection([sumFRP_SNPP,sumFRP_NOAA20]).sum().gt(0)
256
+ maskNoFire = sumFRP_SNPP.gt(0)
257
+ '''
258
+
259
+ #VIIRS
260
+ preVIIRSimg = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(pre_start, pre_stop)).mean()
261
+ #postVIIRSimgCol = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(post_start, post_stop))
262
+ postVIIRSimgCol = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(post_start, post_stop)) #TO FIX ON JUNE 18 sfork_startDate.advance(24, 'day'), sfork_startDate.advance(25,'day')
263
+
264
+ #Landsat
265
+ prelandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
266
+ postlandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
267
+ prelandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
268
+ postlandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
269
+ prelandsatcol = prelandsat8col.merge(prelandsat9col)
270
+ postlandsatcol = postlandsat8col.merge(postlandsat9col)
271
+
272
+ #Sentinel
273
+ presentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
274
+ postsentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(post_start, post_stop).filterBounds(bbox) #TO FIX on JULY 5: sfork_startDate.advance(32, 'day'), sfork_startDate.advance(33,'day')
275
+ olderPostSentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(sfork_startDate.advance(37, 'day'), sfork_startDate.advance(38,'day')).filterBounds(bbox)
276
+ #SAR
277
+ #SARimg = ee.Image('projects/ovcrge-ssec-burn-scar-map-c116/assets/burned_20200907_20200919_test')
278
+ #SARmask = SARimg.eq(1)
279
+ if postVIIRSimgCol.size().getInfo() > 0:
280
+ postVIIRSimg = postVIIRSimgCol.mean()
281
+ preVIIRSimg = VcalcNBR(preVIIRSimg)
282
+ postVIIRSimg = VcalcNBR(postVIIRSimg)
283
+ dNBR_viirs = preVIIRSimg.subtract(postVIIRSimg).select('NBR')
284
+ dNBRclip_viirs = dNBR_viirs.clip(bbox)
285
+ else:
286
+ dNBR_composite = dNBRgoes_compos
287
+ if postsentCol.size().getInfo() > 0:
288
+ presentMean = presentCol.mean()
289
+ postsentMean = postsentCol.mean()
290
+ postsent2Mean = olderPostSentCol.mean()
291
+ presentImg = ScalcNBR(presentMean)
292
+ postsentImg = ScalcNBR(postsentMean)
293
+ postsentImg2 = ScalcNBR(postsent2Mean)
294
+ postSentCombo = ee.ImageCollection([postsentImg,postsentImg2]).mosaic()
295
+ dnbr_sent = presentImg.subtract(postSentCombo).multiply(1.3).add(0.05).select('NBR')
296
+ dNBRclip_sent = dnbr_sent.clip(bbox)
297
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_viirs,dNBRclip_sent]).mosaic()
298
+ elif postlandsatcol.size().getInfo() > 0:
299
+ print(postlandsatcol.size().getInfo())
300
+ prelandsat = prelandsatcol.mean()
301
+ prelandsatImg = LcalcNBR(prelandsat)
302
+ postlandsat = postlandsatcol.mean()
303
+ postlandsatImg = LcalcNBR(postlandsat)
304
+ dNBR_landsat = prelandsatImg.subtract(postlandsatImg).multiply(3.23).add(0.01).select('NBR')
305
+ dNBRclip_ls = dNBR_landsat.clip(bbox)
306
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_viirs,dNBRclip_ls]).mosaic()
307
+ else:
308
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_viirs]).mosaic()
309
+
310
+ masked_compos = dNBR_composite.updateMask(maskNoFire) #(SARmask)
311
+ #doubleMasked_compos = masked_compos.updateMask(maskNoFire)
312
+ doubleMasked_compos = masked_compos.mask(masked_compos.mask()).float()
313
+ downloadArgs = {'name': 'VIIRS_burnMap',
314
+ 'crs': 'EPSG:4326',
315
+ 'scale': 60,
316
+ 'region': bbox}
317
+ url = doubleMasked_compos.getDownloadURL(downloadArgs)
318
+
319
+ print(url)
320
+ noDataVal = -9999
321
+ unmaskedImage = doubleMasked_compos.unmask(noDataVal, False)
322
+
323
+ task = ee.batch.Export.image.toDrive(**{
324
+ 'image': unmaskedImage,
325
+ 'description': "Composite_burnMap6",
326
+ 'folder': "Earth Engine Outputs",
327
+ 'fileNamePrefix': "Composite_burnMap_noData_VIIRS_June18_espg3857_60m",
328
+ 'region': bbox,
329
+ 'crs': 'EPSG:3857',
330
+ 'scale': 60,})
331
+ #task.start()
332
+ return masked_compos
333
+
334
+
335
+ self.clear_specific_layers()
336
+
337
+ if fire == "North Complex":
338
+ north_complex = calc_nbr(north_startDate.advance(-7, 'day'), north_startDate, north_startDate.advance(elapDayNum, 'day'), north_startDate.advance(elapDay_plusOne,'day'), north_complex_bb, 17)
339
+ self.addLayer(north_complex, dNBRvisParams, 'North Complex GOES NBR', True)
340
+ self.centerObject(north_complex_bb, 9)
341
+ file = north_complex
342
+ elif fire == "Dixie":
343
+ dixie = calc_nbr(dixie_startDate.advance(-7, 'day'), dixie_startDate, dixie_startDate.advance(elapDayNum, 'day'), dixie_startDate.advance(elapDay_plusOne,'day'), dixie_bb, 17)
344
+ self.addLayer(dixie, dNBRvisParams, 'Dixie Complex GOES NBR', True)
345
+ self.centerObject(dixie_bb, 9)
346
+ file = dixie
347
+ elif fire == "Cameron Peak":
348
+ cam_peak = calc_nbr(cam_startDate.advance(-7, 'day'), cam_startDate, cam_startDate.advance(elapDayNum, 'day'), cam_startDate.advance(elapDay_plusOne,'day'), cam_peak_bb, 17)
349
+ self.addLayer(cam_peak, dNBRvisParams, 'Cameron Peak GOES NBR', True)
350
+ self.centerObject(cam_peak_bb, 9)
351
+ file = cam_peak
352
+ elif fire == "August Complex":
353
+ aug_complex = calc_nbr(aug_startDate.advance(-7, 'day'), aug_startDate, aug_startDate.advance(elapDayNum, 'day'), aug_startDate.advance(elapDay_plusOne,'day'), aug_complex_bb, 17)
354
+ self.addLayer(aug_complex, dNBRvisParams, 'August Complex GOES NBR', True)
355
+ self.centerObject(aug_complex_bb, 9)
356
+ file = aug_complex
357
+ elif fire == "South Fork":
358
+ sfork = calc_nbr(sfork_startDate.advance(-7, 'day'), sfork_startDate, sfork_startDate.advance(elapDayNum, 'day'), sfork_startDate.advance(elapDay_plusOne,'day'), sfork_bb, 18)
359
+ self.addLayer(sfork, dNBRvisParams, 'South Fork GOES NBR', True)
360
+ self.centerObject(sfork_bb, 9)
361
+ file = sfork
362
+
363
+ def clear_specific_layers(self):
364
+ layers_to_keep = ['OpenStreetMap']
365
+ layers = list(self.layers)
366
+ for layer in layers:
367
+ if layer.name not in layers_to_keep:
368
+ self.remove_layer(layer)
369
+
370
+
371
+ def add_selector(self):
372
+ selector = widgets.Dropdown(options=fireList, value="South Fork", description='Wildfire Case Study:', style={'description_width': '125px'}, layout=widgets.Layout(width='400px'))
373
+
374
+ def on_selector_change(change):
375
+ if change['name'] == 'value':
376
+ selected_fire.value = change['new']
377
+ self.customize_ee_data(selected_fire.value, selected_days.value)
378
+
379
+ selector.observe(on_selector_change, names='value')
380
+ self.add_widget(selector, position="topleft")
381
+
382
+ def add_intSlider(self):
383
+ slider = widgets.IntSlider(value=selected_days.value,min=1,max=40,step=1,description='Elapsed days:',style={'description_width': '125px'}, layout=widgets.Layout(width='400px'))
384
+
385
+ def on_slider_change(change):
386
+ if change['name'] == 'value':
387
+ selected_days.value = change['new']
388
+ self.customize_ee_data(selected_fire.value, selected_days.value)
389
+
390
+ slider.observe(on_slider_change, names='value')
391
+ self.add_widget(slider, position="topleft")
392
+
393
+ def add_dwnldButton(self):
394
+ button = widgets.Button(description='Download',icon='cloud-arrow-down')
395
+
396
+ #def on_button_click(change, file):
397
+ # if change['name'] == 'value':
398
+ # selected_days.value = change['new']
399
+ # self.download_ee_image(file, "trial_file.tif", scale=30)
400
+ def on_button_click(b):
401
+ # Get the currently selected fire and elapsed days
402
+ fire = selected_fire.value
403
+ elapDays = selected_days.value
404
+
405
+ # Customize the EE data and download the image
406
+ file = self.customize_ee_data(fire, elapDays)
407
+ #self.download_ee_image(file, f"{fire}_NBR_{elapDays}days.tif", scale=30)
408
+
409
+ button.observe(on_button_click)
410
+ self.add_widget(button, position="topleft")
411
+
412
+
413
+
414
+ @solara.component
415
+ def Page():
416
+
417
+ with solara.Column(align="center"):
418
+ markdown = """
419
+ ## Historical Western US wildfires from 2020-2021 """
420
+ solara.Markdown(markdown)
421
+
422
+ # Isolation is required to prevent the map from overlapping navigation (when screen width < 960px)
423
+ with solara.Column(style={"isolation": "isolate"}):
424
+ map_widget = Map.element(
425
+ center=[39, -120.5],
426
+ zoom=8,
427
+ height="600px",
428
+ toolbar_ctrl=False
429
+ )
pages/Home.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import solara
2
+
3
+ @solara.component
4
+ def Page():
5
+ with solara.Column(align="center"):
6
+ markdown = """
7
+ ## Real-time wildfire burn mapping
8
+
9
+ ### About the project
10
+
11
+ **A proof of concept illustrating wildfire burn severity maps with emerging clarity while the fires progress.
12
+ Target users are forecasters and emergency managers responding to post-fire risks including debris flows and landslides.**
13
+
14
+ More project description, etc, etc.
15
+
16
+ **Case Studies from 2020 and 2021 Western US wildfire seasons **
17
+
18
+ - August Complex, CA (2020)
19
+ - Cameron Peak, CO (2020)
20
+ - Dixie Fire, CA (2021)
21
+ - North Complex, CA (2020)
22
+
23
+
24
+ **Current 2024 wildfires over 10,000 acres **
25
+
26
+ ### How to use the app
27
+
28
+ 1. Select the fire from the drop-down menu
29
+
30
+ 2. Export image to Google Drive as a geotiff
31
+
32
+ 3.
33
+
34
+ ### Support
35
+
36
+ Initial funding for wildland burn scar mapping came through the NOAA JPSS/RRPG Fire and Smoke Initiative.
37
+ This supported the initial tests of BRIDGE maps using dNDVI. Subsequent funding supported the development of dNBR mapping and an effort
38
+ to tie support the near real-time distribution of incident-based fire detection and related satellite imagery products through the Next Generation Fire System (NGFS).
39
+ Current funding from the NOAA Weather Program Office (WPO) is supporting the refinement of our Google Earth Engine App (GEE)
40
+ and integration of GEE burn scar output with AWIPS (see example above) for Weather Forecast Offices, Regional Offices, and the Weather Prediction Center.
41
+
42
+ """
43
+
44
+ solara.Markdown(markdown)
pages/__init__.py ADDED
File without changes
pages/__pycache__/NBR_calculations.cpython-312.pyc ADDED
Binary file (6.87 kB). View file
 
pages/current_fires.py ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ee
2
+ import geemap
3
+ import solara
4
+ import ipywidgets as widgets
5
+ import datetime
6
+
7
+ #from NBR_calculations import GcalcNBR, VcalcNBR, LcalcNBR, ScalcNBR, GcalcCCsingle
8
+ import requests
9
+
10
+ # Bit-masking
11
+ BitMask_0 = 1 << 0
12
+ BitMask_1 = 1 << 1
13
+ BitMask_2 = 1 << 2
14
+ BitMask_3 = 1 << 3
15
+ BitMask_4 = 1 << 4
16
+ BitMask_5 = 1 << 5
17
+ BitMask_6 = 1 << 6
18
+ BitMask_7 = 1 << 7
19
+ BitMask_8 = 1 << 8
20
+ BitMask_9 = 1 << 9
21
+
22
+ def GcalcCCsingle (goesImg):
23
+
24
+ fireDQF = goesImg.select('DQF').int()
25
+ CMI_QF3 = goesImg.select('DQF_C03').int()
26
+ CMI_QF6 = goesImg.select('DQF_C06').int()
27
+
28
+ #Right now, cloud mask is excluding clouds and water; active fire, bad data and fire free are unmasked. NBR mask exlcudes fire
29
+ F_Mask = fireDQF.eq(0)
30
+ C_Mask = (fireDQF.lt(2).Or(fireDQF.gt(2))).rename('C_Mask')
31
+ #.And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('C_Mask')
32
+ QF_Mask = (fireDQF.eq(1).Or(fireDQF.gt(3)))\
33
+ .And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('QFmask')
34
+
35
+ GOESmasked = goesImg.select(['CMI_C03','CMI_C06']).updateMask(QF_Mask)
36
+ NBRmasked = GOESmasked.normalizedDifference(['CMI_C03', 'CMI_C06']).toFloat().rename('NBR')
37
+ cloudMasked = goesImg.select('CMI_C03').updateMask(C_Mask).toFloat().rename('CC')
38
+ fireMasked = goesImg.select('CMI_C03').updateMask(F_Mask).toFloat().rename('FC')
39
+
40
+ return goesImg.addBands([NBRmasked,cloudMasked, fireMasked,QF_Mask,C_Mask])
41
+
42
+ '''Parameter Array Name Value Bit(s) = Value
43
+ Sun Glint QF1 Surface Reflectance None 6-7 = 00
44
+ Low Sun Mask QF1 Surface Reflectance High 5 = 0
45
+ Day/Night QF1 Surface Reflectance Day 4 =0
46
+ Cloud Detection QF1 Surface Reflectance Confident Clear 2-3 = 00 or Problably Clear 2-3 = 01
47
+ Cloud Mask Quality QF1 Surface Reflectance High or Medium 0-1 = 10 or 11
48
+ Snow/Ice QF2 Surface Reflectance No Snow or Ice 5 = 0
49
+ Cloud Shadow QF2 Surface Reflectance No Cloud Shadow 3 = 0
50
+ LandWater QF2 Surface Reflectance Land, Snow, Arctic, Antarctic or Greenland, Desert 0-2 = 011, 100, 101, 110, 111
51
+ Thin Cirrus Flag QF7 Surface Reflectance No Thin Cirrus 4 = 0
52
+ Aerosol Quantity QF7 Surface Reflectance Climatology, Low or Medium 2-3 = 00, 01 or 10
53
+ Adjacent to Cloud QF7 Surface Reflectance Not Adjacent to Cloud 1 = 0'''
54
+
55
+ def VcalcNBR (VIIRSimg):
56
+
57
+ QF1 = VIIRSimg.select('QF1').int()
58
+ QF2 = VIIRSimg.select('QF2').int()
59
+ QF7 = VIIRSimg.select('QF7').int()
60
+
61
+ QF_Mask = (QF1.bitwiseAnd(BitMask_3).eq(0)).And\
62
+ ((QF2.bitwiseAnd(BitMask_2).eq(4)).Or((QF2.bitwiseAnd(BitMask_1).eq(0)))).And\
63
+ (QF2.bitwiseAnd(BitMask_5).eq(0)).rename('QFmask');
64
+
65
+ VIIRSm = VIIRSimg.select(['I2','M11']).updateMask(QF_Mask);
66
+ NBR = VIIRSm.normalizedDifference(['I2','M11']).toFloat().rename('NBR')
67
+ return VIIRSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
68
+
69
+ ''' Bit 1: Dilated Cloud
70
+ Bit 2: Cirrus (high confidence)
71
+ Bit 3: Cloud
72
+ Bit 4: Cloud Shadow
73
+ Bit 5: Snow
74
+ Bit 6: Clear (0: Cloud or Dilated Cloud bits are set, 1: Cloud and Dilated Cloud bits are not set)
75
+ Bit 7: Water
76
+ Bits 8-9: Cloud Confidence (0: None, 1: Low, 2: Medium, 3: High)
77
+ Bits 10-11: Cloud Shadow Confidence (0: None, 1: Low, 2: Medium, 3: High)
78
+ Bits 12-13: Snow/Ice Confidence (0: None, 1: Low, 2: Medium, 3: High)
79
+ Bits 14-15: Cirrus Confidence (0: None, 1: Low, 2: Medium, 3: High)'''
80
+
81
+ def LcalcNBR (LSimg):
82
+ QApixel = LSimg.select('QA_PIXEL').int()
83
+ QF_Mask =(QApixel.bitwiseAnd(BitMask_3).eq(0)).And\
84
+ (QApixel.bitwiseAnd(BitMask_5).eq(0)).And\
85
+ (QApixel.bitwiseAnd(BitMask_7).eq(0)).rename('QFmask');
86
+
87
+ LSmasked = LSimg.select(['SR_B5','SR_B7']).updateMask(QF_Mask);
88
+ NBR = LSmasked.normalizedDifference(['SR_B5','SR_B7']).toFloat().rename('NBR')
89
+ return LSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
90
+
91
+ ''' 1 Saturated or defective
92
+ 2 Dark Area Pixels
93
+ 3 Cloud Shadows
94
+ 4 Vegetation
95
+ 5 Bare Soils
96
+ 6 Water
97
+ 7 Clouds Low Probability / Unclassified
98
+ 8 Clouds Medium Probability
99
+ 9 Clouds High Probability
100
+ 10 Cirrus
101
+ 11 Snow / Ice'''
102
+
103
+ def ScalcNBR (sentImg):
104
+ SCL = sentImg.select('SCL');
105
+ QF_Mask =(SCL.neq(6)).And\
106
+ (SCL.neq(8)).And\
107
+ (SCL.neq(9)).And\
108
+ (SCL.neq(11))\
109
+ .rename('QFmask');
110
+ sentMasked = sentImg.select(['B8A','B12']).updateMask(QF_Mask); #B8 is another option- broadband NIR
111
+ NBR = sentMasked.normalizedDifference(['B8A','B12']).toFloat().rename('NBR')
112
+ return sentImg.addBands(NBR).addBands(QF_Mask).addBands(SCL)#.set('avgNBR', avgNBR)
113
+
114
+ #createDates = NIFC_perims_716.aggregate_array('attr_Cre_1')
115
+ #incidentIDs = NIFC_perims_716.aggregate_array('poly_Incid')
116
+ #fireList = incidentIDs.getInfo()
117
+ fireList = wildfire_names = [ "FRESNO JUNE LIGHTNING COMPLEX", "Larch Creek","Deadman","Cow Valley","0404 RV LONE ROCK",
118
+ "PIONEER","South Fork", "Deer Springs","Basin","Lake","Horse Gulch","Falls","Silver King","Indios"]
119
+ selected_fire = solara.reactive(fireList[6])
120
+ dNBRvisParams = {'min': 0.0,'max': 0.8, 'palette': ['green', 'yellow','orange','red']}
121
+ today = datetime.datetime.today().strftime('%Y-%m-%d')
122
+
123
+ class Map(geemap.Map):
124
+ def __init__(self, **kwargs):
125
+ super().__init__(**kwargs)
126
+ self.add_basemap('OpenStreetMap')
127
+
128
+ self.customize_ee_data(selected_fire.value, today)
129
+ self.add_selector()
130
+ self.add_dwnldButton()
131
+ self.add("layer_manager")
132
+ self.remove("draw_control")
133
+
134
+
135
+ def customize_ee_data(self, fireID, elapDays):
136
+ NIFC_perims_716 = ee.FeatureCollection('projects/ovcrge-ssec-burn-scar-map-c116/assets/NIFC_perimeters_7-16')
137
+ fire = NIFC_perims_716.filter(ee.Filter.eq('poly_Incid',fireID)).first()
138
+ timestamp = fire.get('attr_Cre_1')
139
+ geom = fire.geometry()
140
+
141
+ startDate = ee.Date(timestamp)#.format('YYYY-MM-dd')
142
+ endDate = ee.Date.parse('YYYY-MM-dd', str(today))
143
+
144
+ boundingBox = ee.Geometry(geom.buffer(5000).bounds())
145
+
146
+ elapDayNum = ee.Number(10)
147
+ elapDay_plusOne = elapDayNum.add(ee.Number(1))
148
+
149
+ def calc_nbr(pre_start, pre_stop, post_start, post_stop, bbox, goes):
150
+
151
+ def MergeBands (eachImage):
152
+ oneImage = ee.Image.cat(eachImage.get('CMI'), eachImage.get('FDC'))
153
+ return oneImage
154
+ displacementImg18 = ee.Image.load('projects/ee-losos/assets/G18-F-meter-offset_GEE')
155
+ y_dif = displacementImg18.select([1])
156
+ x_dif = displacementImg18.select([0]).multiply(-1)
157
+ displacement18 = ee.Image([x_dif, y_dif])
158
+
159
+ displacementImg16 = ee.Image.load('projects/ee-losos/assets/G16-F-meter-offset_GEE')
160
+ y_dif = displacementImg16.select([1])
161
+ x_dif = displacementImg16.select([0]).multiply(-1)
162
+ displacement16 = ee.Image([x_dif, y_dif]);
163
+
164
+ preCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
165
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
166
+ preFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
167
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
168
+ postCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
169
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
170
+ postFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(post_start, post_stop))\
171
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
172
+
173
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
174
+ primary = preCMIcol,
175
+ secondary = preFDCcol,
176
+ condition = ee.Filter.maxDifference(
177
+ difference = 10, #milliseconds
178
+ leftField = 'system:time_start',
179
+ rightField = 'system:time_start',))
180
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
181
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
182
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
183
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
184
+
185
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
186
+ primary = postCMIcol,
187
+ secondary = postFDCcol,
188
+ condition = ee.Filter.maxDifference(
189
+ difference = 10, #milliseconds
190
+ leftField = 'system:time_start',
191
+ rightField = 'system:time_start',))
192
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
193
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
194
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
195
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
196
+
197
+ dNBR_goes17 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
198
+
199
+
200
+ #GOES-16
201
+ preCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
202
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
203
+ preFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
204
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
205
+
206
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
207
+ primary = preCMIcol,
208
+ secondary = preFDCcol,
209
+ condition = ee.Filter.maxDifference(
210
+ difference = 10, #milliseconds
211
+ leftField = 'system:time_start',
212
+ rightField = 'system:time_start',))
213
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
214
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
215
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
216
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
217
+
218
+
219
+ postCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
220
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
221
+ postFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(post_start, post_stop))\
222
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
223
+
224
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
225
+ primary = postCMIcol,
226
+ secondary = postFDCcol,
227
+ condition = ee.Filter.maxDifference(
228
+ difference = 10, #milliseconds
229
+ leftField = 'system:time_start',
230
+ rightField = 'system:time_start',))
231
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
232
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
233
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
234
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
235
+
236
+ dNBR_goes16 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
237
+
238
+ dNBRclip_goes17= dNBR_goes17.clip(bbox)
239
+ dNBRclip_goes16= dNBR_goes16.clip(bbox)
240
+ dNBRdisp_goes17 = dNBRclip_goes17.displace(displacement18, 'bicubic')
241
+ dNBRdisp_goes16 = dNBRclip_goes16.displace(displacement16, 'bicubic')
242
+ dNBRgoes_compos = ee.ImageCollection([dNBRdisp_goes17,dNBRdisp_goes16]).mean()
243
+
244
+ #ACTIVE fire
245
+ activeFire18 = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
246
+ activeFire16 = ee.ImageCollection(f"NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
247
+ sumFRP18 = activeFire18.select('Power').sum().rename('sumFRP')
248
+ sumFRP16 = activeFire16.select('Power').sum().rename('sumFRP')
249
+ maskNoFire18 = sumFRP18.gt(200).displace(displacement18, 'bicubic')
250
+ maskNoFire16 = sumFRP16.gt(200).displace(displacement16, 'bicubic')
251
+ maskNoFire = ee.ImageCollection([maskNoFire18,maskNoFire16]).sum().gt(0)
252
+
253
+ '''
254
+ activeSNPP = ee.ImageCollection("NASA/LANCE/SNPP_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
255
+ activeNOAA20 = ee.ImageCollection("NASA/LANCE/NOAA20_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
256
+ sumFRP_SNPP = activeSNPP.select('confidence').max().rename('sumFRP')
257
+ sumFRP_NOAA20 = activeNOAA20.select('confidence').max().rename('sumFRP')
258
+ #maskNoFire = ee.ImageCollection([sumFRP_SNPP,sumFRP_NOAA20]).sum().gt(0)
259
+ maskNoFire = sumFRP_SNPP.gt(0)
260
+ '''
261
+
262
+ #VIIRS
263
+ preVIIRSimg = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(pre_start, pre_stop)).mean()
264
+ postVIIRSimgCol = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(post_start, post_stop)) #TO FIX ON JUNE 18 sfork_startDate.advance(24, 'day'), sfork_startDate.advance(25,'day')
265
+
266
+ #Landsat
267
+ prelandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
268
+ postlandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
269
+ prelandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
270
+ postlandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
271
+ prelandsatcol = prelandsat8col.merge(prelandsat9col)
272
+ postlandsatcol = postlandsat8col.merge(postlandsat9col)
273
+
274
+ #Sentinel
275
+ presentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
276
+ postsentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(post_start, post_stop).filterBounds(bbox) #TO FIX on JULY 5: sfork_startDate.advance(32, 'day'), sfork_startDate.advance(33,'day')
277
+ #olderPostSentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(sfork_startDate.advance(37, 'day'), sfork_startDate.advance(38,'day')).filterBounds(bbox)
278
+
279
+ #SAR
280
+ #SARimg = ee.Image('projects/ovcrge-ssec-burn-scar-map-c116/assets/burned_20200907_20200919_test')
281
+ #SARmask = SARimg.eq(1)
282
+
283
+ if postVIIRSimgCol.size().getInfo() > 0:
284
+ postVIIRSimg = postVIIRSimgCol.mean()
285
+ preVIIRSimg = VcalcNBR(preVIIRSimg)
286
+ postVIIRSimg = VcalcNBR(postVIIRSimg)
287
+ dNBR_viirs = preVIIRSimg.subtract(postVIIRSimg).select('NBR')
288
+ dNBRclip_viirs = dNBR_viirs.clip(bbox)
289
+ else:
290
+ dNBR_composite = dNBRgoes_compos
291
+ if postsentCol.size().getInfo() > 0:
292
+ presentMean = presentCol.mean()
293
+ postsentMean = postsentCol.mean()
294
+ presentImg = ScalcNBR(presentMean)
295
+ postsentImg = ScalcNBR(postsentMean)
296
+ dnbr_sent = presentImg.subtract(postsentImg).multiply(1.3).add(0.05).select('NBR')
297
+ dNBRclip_sent = dnbr_sent.clip(bbox)
298
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_sent]).mosaic() #dNBRclip_viirs SHOULD GO IN IF UP TO DATE
299
+ elif postlandsatcol.size().getInfo() > 0:
300
+ prelandsat = prelandsatcol.mean()
301
+ prelandsatImg = LcalcNBR(prelandsat)
302
+ postlandsat = postlandsatcol.mean()
303
+ postlandsatImg = LcalcNBR(postlandsat)
304
+ dNBR_landsat = prelandsatImg.subtract(postlandsatImg).multiply(3.23).add(0.01).select('NBR')
305
+ dNBRclip_ls = dNBR_landsat.clip(bbox)
306
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_ls]).mosaic() #dNBRclip_viirs SHOULD GO IN IF UP TO DATE
307
+ else:
308
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos]).mosaic() #dNBRclip_viirs SHOULD GO IN IF UP TO DATE
309
+
310
+ masked_compos = dNBR_composite.updateMask(maskNoFire) #(SARmask)
311
+ #doubleMasked_compos = masked_compos.updateMask(maskNoFire)
312
+ doubleMasked_compos = masked_compos.mask(masked_compos.mask()).float()
313
+ downloadArgs = {'name': 'VIIRS_burnMap',
314
+ 'crs': 'EPSG:4326',
315
+ 'scale': 60,
316
+ 'region': bbox}
317
+ url = doubleMasked_compos.getDownloadURL(downloadArgs)
318
+
319
+ print(url)
320
+ noDataVal = -9999
321
+ unmaskedImage = doubleMasked_compos.unmask(noDataVal, False)
322
+
323
+ task = ee.batch.Export.image.toDrive(**{
324
+ 'image': unmaskedImage,
325
+ 'description': "Composite_burnMap6",
326
+ 'folder': "Earth Engine Outputs",
327
+ 'fileNamePrefix': "Composite_burnMap_noData_VIIRS_June18_espg3857_60m",
328
+ 'region': bbox,
329
+ 'crs': 'EPSG:3857',
330
+ 'scale': 60,})
331
+ task.start()
332
+ return masked_compos
333
+
334
+
335
+ self.clear_specific_layers()
336
+
337
+ fireImg = calc_nbr(startDate.advance(-7, 'day'), startDate, endDate.advance(-3, 'day'), endDate, boundingBox, 18)
338
+ self.addLayer(fireImg, dNBRvisParams, fireID, True)
339
+ self.centerObject(boundingBox, 10)
340
+ file = fireImg
341
+
342
+
343
+ def clear_specific_layers(self):
344
+ layers_to_keep = ['OpenStreetMap']
345
+ layers = list(self.layers)
346
+ for layer in layers:
347
+ if layer.name not in layers_to_keep:
348
+ self.remove_layer(layer)
349
+
350
+
351
+ def add_selector(self):
352
+ selector = widgets.Dropdown(options=fireList, value=fireList[6], description='Current wildfire :', style={'description_width': '125px'}, layout=widgets.Layout(width='400px'))
353
+
354
+ def on_selector_change(change):
355
+ if change['name'] == 'value':
356
+ selected_fire.value = change['new']
357
+ self.customize_ee_data(selected_fire.value, today)
358
+
359
+ selector.observe(on_selector_change, names='value')
360
+ self.add_widget(selector, position="topleft")
361
+
362
+
363
+ def add_dwnldButton(self):
364
+ button = widgets.Button(description='Export to Drive',icon='cloud-arrow-down')
365
+
366
+ #def on_button_click(change, file):
367
+ # if change['name'] == 'value':
368
+ # selected_days.value = change['new']
369
+ # self.download_ee_image(file, "trial_file.tif", scale=30)
370
+ def on_button_click(b):
371
+ # Get the currently selected fire and elapsed days
372
+ fire = selected_fire.value
373
+ elapDays = today
374
+
375
+ # Customize the EE data and download the image
376
+ file = self.customize_ee_data(fire, elapDays)
377
+ #self.download_ee_image(file, f"{fire}_NBR_{elapDays}days.tif", scale=30)
378
+
379
+ button.observe(on_button_click)
380
+ self.add_widget(button, position="topleft")
381
+
382
+
383
+
384
+ @solara.component
385
+ def Page():
386
+
387
+ with solara.Column(align="center"):
388
+ markdown = """
389
+ ## Current 2024 wildfires over 10,000 acres"""
390
+ solara.Markdown(markdown)
391
+
392
+ # Isolation is required to prevent the map from overlapping navigation (when screen width < 960px)
393
+ with solara.Column(style={"isolation": "isolate"}):
394
+ map_widget = Map.element(
395
+ center=[39, -120.5],
396
+ zoom=8,
397
+ height="600px",
398
+ toolbar_ctrl=False
399
+ )
pages/historical_fires.py ADDED
@@ -0,0 +1,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ee
2
+ import geemap
3
+ import solara
4
+ import ipywidgets as widgets
5
+ #from NBR_calculations import GcalcNBR, VcalcNBR, LcalcNBR, ScalcNBR, GcalcCCsingle
6
+ import requests
7
+
8
+ # Bit-masking
9
+ BitMask_0 = 1 << 0
10
+ BitMask_1 = 1 << 1
11
+ BitMask_2 = 1 << 2
12
+ BitMask_3 = 1 << 3
13
+ BitMask_4 = 1 << 4
14
+ BitMask_5 = 1 << 5
15
+ BitMask_6 = 1 << 6
16
+ BitMask_7 = 1 << 7
17
+ BitMask_8 = 1 << 8
18
+ BitMask_9 = 1 << 9
19
+
20
+ def GcalcCCsingle (goesImg):
21
+
22
+ fireDQF = goesImg.select('DQF').int()
23
+ CMI_QF3 = goesImg.select('DQF_C03').int()
24
+ CMI_QF6 = goesImg.select('DQF_C06').int()
25
+
26
+ #Right now, cloud mask is excluding clouds and water; active fire, bad data and fire free are unmasked. NBR mask exlcudes fire
27
+ F_Mask = fireDQF.eq(0)
28
+ C_Mask = (fireDQF.lt(2).Or(fireDQF.gt(2))).rename('C_Mask')
29
+ #.And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('C_Mask')
30
+ QF_Mask = (fireDQF.eq(1).Or(fireDQF.gt(3)))\
31
+ .And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('QFmask')
32
+
33
+ GOESmasked = goesImg.select(['CMI_C03','CMI_C06']).updateMask(QF_Mask)
34
+ NBRmasked = GOESmasked.normalizedDifference(['CMI_C03', 'CMI_C06']).toFloat().rename('NBR')
35
+ cloudMasked = goesImg.select('CMI_C03').updateMask(C_Mask).toFloat().rename('CC')
36
+ fireMasked = goesImg.select('CMI_C03').updateMask(F_Mask).toFloat().rename('FC')
37
+
38
+ return goesImg.addBands([NBRmasked,cloudMasked, fireMasked,QF_Mask,C_Mask])
39
+
40
+ '''Parameter Array Name Value Bit(s) = Value
41
+ Sun Glint QF1 Surface Reflectance None 6-7 = 00
42
+ Low Sun Mask QF1 Surface Reflectance High 5 = 0
43
+ Day/Night QF1 Surface Reflectance Day 4 =0
44
+ Cloud Detection QF1 Surface Reflectance Confident Clear 2-3 = 00 or Problably Clear 2-3 = 01
45
+ Cloud Mask Quality QF1 Surface Reflectance High or Medium 0-1 = 10 or 11
46
+ Snow/Ice QF2 Surface Reflectance No Snow or Ice 5 = 0
47
+ Cloud Shadow QF2 Surface Reflectance No Cloud Shadow 3 = 0
48
+ LandWater QF2 Surface Reflectance Land, Snow, Arctic, Antarctic or Greenland, Desert 0-2 = 011, 100, 101, 110, 111
49
+ Thin Cirrus Flag QF7 Surface Reflectance No Thin Cirrus 4 = 0
50
+ Aerosol Quantity QF7 Surface Reflectance Climatology, Low or Medium 2-3 = 00, 01 or 10
51
+ Adjacent to Cloud QF7 Surface Reflectance Not Adjacent to Cloud 1 = 0'''
52
+
53
+ def VcalcNBR (VIIRSimg):
54
+
55
+ QF1 = VIIRSimg.select('QF1').int()
56
+ QF2 = VIIRSimg.select('QF2').int()
57
+ QF7 = VIIRSimg.select('QF7').int()
58
+
59
+ QF_Mask = (QF1.bitwiseAnd(BitMask_3).eq(0)).And\
60
+ ((QF2.bitwiseAnd(BitMask_2).eq(4)).Or((QF2.bitwiseAnd(BitMask_1).eq(0)))).And\
61
+ (QF2.bitwiseAnd(BitMask_5).eq(0)).rename('QFmask');
62
+
63
+ VIIRSm = VIIRSimg.select(['I2','M11']).updateMask(QF_Mask);
64
+ NBR = VIIRSm.normalizedDifference(['I2','M11']).toFloat().rename('NBR')
65
+ return VIIRSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
66
+
67
+ ''' Bit 1: Dilated Cloud
68
+ Bit 2: Cirrus (high confidence)
69
+ Bit 3: Cloud
70
+ Bit 4: Cloud Shadow
71
+ Bit 5: Snow
72
+ Bit 6: Clear (0: Cloud or Dilated Cloud bits are set, 1: Cloud and Dilated Cloud bits are not set)
73
+ Bit 7: Water
74
+ Bits 8-9: Cloud Confidence (0: None, 1: Low, 2: Medium, 3: High)
75
+ Bits 10-11: Cloud Shadow Confidence (0: None, 1: Low, 2: Medium, 3: High)
76
+ Bits 12-13: Snow/Ice Confidence (0: None, 1: Low, 2: Medium, 3: High)
77
+ Bits 14-15: Cirrus Confidence (0: None, 1: Low, 2: Medium, 3: High)'''
78
+
79
+ def LcalcNBR (LSimg):
80
+ QApixel = LSimg.select('QA_PIXEL').int()
81
+ QF_Mask =(QApixel.bitwiseAnd(BitMask_3).eq(0)).And\
82
+ (QApixel.bitwiseAnd(BitMask_5).eq(0)).And\
83
+ (QApixel.bitwiseAnd(BitMask_7).eq(0)).rename('QFmask');
84
+
85
+ LSmasked = LSimg.select(['SR_B5','SR_B7']).updateMask(QF_Mask);
86
+ NBR = LSmasked.normalizedDifference(['SR_B5','SR_B7']).toFloat().rename('NBR')
87
+ return LSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
88
+
89
+ ''' 1 Saturated or defective
90
+ 2 Dark Area Pixels
91
+ 3 Cloud Shadows
92
+ 4 Vegetation
93
+ 5 Bare Soils
94
+ 6 Water
95
+ 7 Clouds Low Probability / Unclassified
96
+ 8 Clouds Medium Probability
97
+ 9 Clouds High Probability
98
+ 10 Cirrus
99
+ 11 Snow / Ice'''
100
+
101
+ def ScalcNBR (sentImg):
102
+ SCL = sentImg.select('SCL');
103
+ QF_Mask =(SCL.neq(6)).And\
104
+ (SCL.neq(8)).And\
105
+ (SCL.neq(9)).And\
106
+ (SCL.neq(11))\
107
+ .rename('QFmask');
108
+ sentMasked = sentImg.select(['B8A','B12']).updateMask(QF_Mask); #B8 is another option- broadband NIR
109
+ NBR = sentMasked.normalizedDifference(['B8A','B12']).toFloat().rename('NBR')
110
+ return sentImg.addBands(NBR).addBands(QF_Mask).addBands(SCL)#.set('avgNBR', avgNBR)
111
+
112
+
113
+ fireList = ["North Complex", "Dixie", "Cameron Peak", "August Complex", "South Fork"]
114
+ selected_fire = solara.reactive(fireList[4])
115
+ selected_days = solara.reactive(25) #30
116
+ dNBRvisParams = {'min': 0.0,'max': 0.8, 'palette': ['green', 'yellow','orange','red']}
117
+
118
+
119
+ class Map(geemap.Map):
120
+ def __init__(self, **kwargs):
121
+ super().__init__(**kwargs)
122
+ self.add_basemap('OpenStreetMap')
123
+ self.customize_ee_data(selected_fire.value, selected_days.value)
124
+ self.add_selector()
125
+ self.add_intSlider()
126
+ self.add_dwnldButton()
127
+ self.add("layer_manager")
128
+ self.remove("draw_control")
129
+
130
+
131
+ def customize_ee_data(self, fire, elapDays):
132
+ elapDayNum = ee.Number(elapDays)
133
+ elapDay_plusOne = elapDayNum.add(ee.Number(1))
134
+
135
+ north_startDate = ee.Date('2020-08-16')
136
+ dixie_startDate = ee.Date('2021-07-13')
137
+ cam_startDate = ee.Date('2020-08-13')
138
+ aug_startDate = ee.Date('2020-08-15')
139
+ sfork_startDate = ee.Date('2024-05-25')
140
+
141
+ north_complex_bb = ee.Geometry.BBox(-121.616097, 39.426723, -120.668526, 40.030845)
142
+ dixie_bb = ee.Geometry.BBox(-121.680467, 39.759303, -120.065477, 40.873387)
143
+ cam_peak_bb = ee.Geometry.BBox(-106.014784, 40.377907, -105.116651, 40.822094)
144
+ aug_complex_bb = ee.Geometry.BBox(-123.668726, 39.337654, -122.355860, 40.498304)
145
+ sfork_bb = ee.Geometry.BBox(-106.192, 33.1, -105.065, 33.782)
146
+
147
+ def calc_nbr(pre_start, pre_stop, post_start, post_stop, bbox, goes):
148
+ def MergeBands (eachImage):
149
+ oneImage = ee.Image.cat(eachImage.get('CMI'), eachImage.get('FDC'))
150
+ return oneImage
151
+ displacementImg18 = ee.Image.load('projects/ee-losos/assets/G18-F-meter-offset_GEE')
152
+ y_dif = displacementImg18.select([1])
153
+ x_dif = displacementImg18.select([0]).multiply(-1)
154
+ displacement18 = ee.Image([x_dif, y_dif])
155
+
156
+ displacementImg16 = ee.Image.load('projects/ee-losos/assets/G16-F-meter-offset_GEE')
157
+ y_dif = displacementImg16.select([1])
158
+ x_dif = displacementImg16.select([0]).multiply(-1)
159
+ displacement16 = ee.Image([x_dif, y_dif]);
160
+
161
+ preCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
162
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
163
+ preFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
164
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
165
+ postCMIcol = ee.ImageCollection(f"NOAA/GOES/{goes}/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
166
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
167
+ postFDCcol = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(post_start, post_stop))\
168
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
169
+
170
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
171
+ primary = preCMIcol,
172
+ secondary = preFDCcol,
173
+ condition = ee.Filter.maxDifference(
174
+ difference = 10, #milliseconds
175
+ leftField = 'system:time_start',
176
+ rightField = 'system:time_start',))
177
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
178
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
179
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
180
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
181
+
182
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
183
+ primary = postCMIcol,
184
+ secondary = postFDCcol,
185
+ condition = ee.Filter.maxDifference(
186
+ difference = 10, #milliseconds
187
+ leftField = 'system:time_start',
188
+ rightField = 'system:time_start',))
189
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
190
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
191
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
192
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
193
+
194
+ dNBR_goes17 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
195
+
196
+
197
+ #GOES-16
198
+ preCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(pre_start, pre_stop))\
199
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
200
+ preFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_start, pre_stop))\
201
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
202
+
203
+ prejoinedGOES = ee.Join.inner('CMI','FDC').apply(
204
+ primary = preCMIcol,
205
+ secondary = preFDCcol,
206
+ condition = ee.Filter.maxDifference(
207
+ difference = 10, #milliseconds
208
+ leftField = 'system:time_start',
209
+ rightField = 'system:time_start',))
210
+ preMiddayGOEScol = ee.ImageCollection(prejoinedGOES.map(lambda object: MergeBands(object)))
211
+ preMiddayGOEScol = preMiddayGOEScol.map(GcalcCCsingle)
212
+ pre_meanNBR = preMiddayGOEScol.select(['NBR']).mean()
213
+ pre_meanNBR = pre_meanNBR.multiply(1.18).subtract(0.12)
214
+
215
+
216
+ postCMIcol = ee.ImageCollection("NOAA/GOES/16/MCMIPF").filter(ee.Filter.date(post_start, post_stop))\
217
+ .filter(ee.Filter.calendarRange(17, 21, 'hour'))#10-2pm PCT, 11am-3pm MST
218
+ postFDCcol = ee.ImageCollection("NOAA/GOES/16/FDCF").filter(ee.Filter.date(post_start, post_stop))\
219
+ .filter(ee.Filter.calendarRange(17, 21, 'hour')) #10-2pm PCT, 11am-3pm MST
220
+
221
+ postjoinedGOES = ee.Join.inner('CMI','FDC').apply(
222
+ primary = postCMIcol,
223
+ secondary = postFDCcol,
224
+ condition = ee.Filter.maxDifference(
225
+ difference = 10, #milliseconds
226
+ leftField = 'system:time_start',
227
+ rightField = 'system:time_start',))
228
+ postMiddayGOEScol = ee.ImageCollection(postjoinedGOES.map(lambda object: MergeBands(object)))
229
+ postMiddayGOEScol = postMiddayGOEScol.map(GcalcCCsingle)
230
+ post_meanNBR = postMiddayGOEScol.select(['NBR']).mean()
231
+ post_meanNBR = post_meanNBR.multiply(1.18).subtract(0.12)
232
+
233
+ dNBR_goes16 = pre_meanNBR.subtract(post_meanNBR).select('NBR')
234
+
235
+ dNBRclip_goes17= dNBR_goes17.clip(bbox)
236
+ dNBRclip_goes16= dNBR_goes16.clip(bbox)
237
+ dNBRdisp_goes17 = dNBRclip_goes17.displace(displacement18, 'bicubic')
238
+ dNBRdisp_goes16 = dNBRclip_goes16.displace(displacement16, 'bicubic')
239
+ dNBRgoes_compos = ee.ImageCollection([dNBRdisp_goes17,dNBRdisp_goes16]).mean()
240
+
241
+ #ACTIVE fire
242
+ activeFire18 = ee.ImageCollection(f"NOAA/GOES/{goes}/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
243
+ activeFire16 = ee.ImageCollection(f"NOAA/GOES/16/FDCF").filter(ee.Filter.date(pre_stop, post_stop))
244
+ sumFRP18 = activeFire18.select('Power').sum().rename('sumFRP')
245
+ sumFRP16 = activeFire16.select('Power').sum().rename('sumFRP')
246
+ maskNoFire18 = sumFRP18.gt(200).displace(displacement18, 'bicubic')
247
+ maskNoFire16 = sumFRP16.gt(200).displace(displacement16, 'bicubic')
248
+ maskNoFire = ee.ImageCollection([maskNoFire18,maskNoFire16]).sum().gt(0)
249
+
250
+ '''
251
+ activeSNPP = ee.ImageCollection("NASA/LANCE/SNPP_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
252
+ activeNOAA20 = ee.ImageCollection("NASA/LANCE/NOAA20_VIIRS/C2").filter(ee.Filter.date(pre_stop, post_stop))
253
+ sumFRP_SNPP = activeSNPP.select('confidence').max().rename('sumFRP')
254
+ sumFRP_NOAA20 = activeNOAA20.select('confidence').max().rename('sumFRP')
255
+ #maskNoFire = ee.ImageCollection([sumFRP_SNPP,sumFRP_NOAA20]).sum().gt(0)
256
+ maskNoFire = sumFRP_SNPP.gt(0)
257
+ '''
258
+
259
+ #VIIRS
260
+ preVIIRSimg = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(pre_start, pre_stop)).mean()
261
+ #postVIIRSimgCol = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(post_start, post_stop))
262
+ postVIIRSimgCol = ee.ImageCollection("NASA/VIIRS/002/VNP09GA").filter(ee.Filter.date(post_start, post_stop)) #TO FIX ON JUNE 18 sfork_startDate.advance(24, 'day'), sfork_startDate.advance(25,'day')
263
+
264
+ #Landsat
265
+ prelandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
266
+ postlandsat8col = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
267
+ prelandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
268
+ postlandsat9col = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2").filterDate(post_start, post_stop).filterBounds(bbox)
269
+ prelandsatcol = prelandsat8col.merge(prelandsat9col)
270
+ postlandsatcol = postlandsat8col.merge(postlandsat9col)
271
+
272
+ #Sentinel
273
+ presentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(pre_start.advance(-10, 'day'), pre_stop).filterBounds(bbox)
274
+ postsentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(post_start, post_stop).filterBounds(bbox) #TO FIX on JULY 5: sfork_startDate.advance(32, 'day'), sfork_startDate.advance(33,'day')
275
+ olderPostSentCol = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED").filterDate(sfork_startDate.advance(37, 'day'), sfork_startDate.advance(38,'day')).filterBounds(bbox)
276
+ #SAR
277
+ #SARimg = ee.Image('projects/ovcrge-ssec-burn-scar-map-c116/assets/burned_20200907_20200919_test')
278
+ #SARmask = SARimg.eq(1)
279
+ if postVIIRSimgCol.size().getInfo() > 0:
280
+ postVIIRSimg = postVIIRSimgCol.mean()
281
+ preVIIRSimg = VcalcNBR(preVIIRSimg)
282
+ postVIIRSimg = VcalcNBR(postVIIRSimg)
283
+ dNBR_viirs = preVIIRSimg.subtract(postVIIRSimg).select('NBR')
284
+ dNBRclip_viirs = dNBR_viirs.clip(bbox)
285
+ else:
286
+ dNBR_composite = dNBRgoes_compos
287
+ if postsentCol.size().getInfo() > 0:
288
+ presentMean = presentCol.mean()
289
+ postsentMean = postsentCol.mean()
290
+ postsent2Mean = olderPostSentCol.mean()
291
+ presentImg = ScalcNBR(presentMean)
292
+ postsentImg = ScalcNBR(postsentMean)
293
+ postsentImg2 = ScalcNBR(postsent2Mean)
294
+ postSentCombo = ee.ImageCollection([postsentImg,postsentImg2]).mosaic()
295
+ dnbr_sent = presentImg.subtract(postSentCombo).multiply(1.3).add(0.05).select('NBR')
296
+ dNBRclip_sent = dnbr_sent.clip(bbox)
297
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_viirs,dNBRclip_sent]).mosaic()
298
+ elif postlandsatcol.size().getInfo() > 0:
299
+ print(postlandsatcol.size().getInfo())
300
+ prelandsat = prelandsatcol.mean()
301
+ prelandsatImg = LcalcNBR(prelandsat)
302
+ postlandsat = postlandsatcol.mean()
303
+ postlandsatImg = LcalcNBR(postlandsat)
304
+ dNBR_landsat = prelandsatImg.subtract(postlandsatImg).multiply(3.23).add(0.01).select('NBR')
305
+ dNBRclip_ls = dNBR_landsat.clip(bbox)
306
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_viirs,dNBRclip_ls]).mosaic()
307
+ else:
308
+ dNBR_composite = ee.ImageCollection([dNBRgoes_compos,dNBRclip_viirs]).mosaic()
309
+
310
+ masked_compos = dNBR_composite.updateMask(maskNoFire) #(SARmask)
311
+ #doubleMasked_compos = masked_compos.updateMask(maskNoFire)
312
+ doubleMasked_compos = masked_compos.mask(masked_compos.mask()).float()
313
+ downloadArgs = {'name': 'VIIRS_burnMap',
314
+ 'crs': 'EPSG:4326',
315
+ 'scale': 60,
316
+ 'region': bbox}
317
+ url = doubleMasked_compos.getDownloadURL(downloadArgs)
318
+
319
+ print(url)
320
+ noDataVal = -9999
321
+ unmaskedImage = doubleMasked_compos.unmask(noDataVal, False)
322
+
323
+ task = ee.batch.Export.image.toDrive(**{
324
+ 'image': unmaskedImage,
325
+ 'description': "Composite_burnMap6",
326
+ 'folder': "Earth Engine Outputs",
327
+ 'fileNamePrefix': "Composite_burnMap_noData_VIIRS_June18_espg3857_60m",
328
+ 'region': bbox,
329
+ 'crs': 'EPSG:3857',
330
+ 'scale': 60,})
331
+ #task.start()
332
+ return masked_compos
333
+
334
+
335
+ self.clear_specific_layers()
336
+
337
+ if fire == "North Complex":
338
+ north_complex = calc_nbr(north_startDate.advance(-7, 'day'), north_startDate, north_startDate.advance(elapDayNum, 'day'), north_startDate.advance(elapDay_plusOne,'day'), north_complex_bb, 17)
339
+ self.addLayer(north_complex, dNBRvisParams, 'North Complex GOES NBR', True)
340
+ self.centerObject(north_complex_bb, 9)
341
+ file = north_complex
342
+ elif fire == "Dixie":
343
+ dixie = calc_nbr(dixie_startDate.advance(-7, 'day'), dixie_startDate, dixie_startDate.advance(elapDayNum, 'day'), dixie_startDate.advance(elapDay_plusOne,'day'), dixie_bb, 17)
344
+ self.addLayer(dixie, dNBRvisParams, 'Dixie Complex GOES NBR', True)
345
+ self.centerObject(dixie_bb, 9)
346
+ file = dixie
347
+ elif fire == "Cameron Peak":
348
+ cam_peak = calc_nbr(cam_startDate.advance(-7, 'day'), cam_startDate, cam_startDate.advance(elapDayNum, 'day'), cam_startDate.advance(elapDay_plusOne,'day'), cam_peak_bb, 17)
349
+ self.addLayer(cam_peak, dNBRvisParams, 'Cameron Peak GOES NBR', True)
350
+ self.centerObject(cam_peak_bb, 9)
351
+ file = cam_peak
352
+ elif fire == "August Complex":
353
+ aug_complex = calc_nbr(aug_startDate.advance(-7, 'day'), aug_startDate, aug_startDate.advance(elapDayNum, 'day'), aug_startDate.advance(elapDay_plusOne,'day'), aug_complex_bb, 17)
354
+ self.addLayer(aug_complex, dNBRvisParams, 'August Complex GOES NBR', True)
355
+ self.centerObject(aug_complex_bb, 9)
356
+ file = aug_complex
357
+ elif fire == "South Fork":
358
+ sfork = calc_nbr(sfork_startDate.advance(-7, 'day'), sfork_startDate, sfork_startDate.advance(elapDayNum, 'day'), sfork_startDate.advance(elapDay_plusOne,'day'), sfork_bb, 18)
359
+ self.addLayer(sfork, dNBRvisParams, 'South Fork GOES NBR', True)
360
+ self.centerObject(sfork_bb, 9)
361
+ file = sfork
362
+
363
+ def clear_specific_layers(self):
364
+ layers_to_keep = ['OpenStreetMap']
365
+ layers = list(self.layers)
366
+ for layer in layers:
367
+ if layer.name not in layers_to_keep:
368
+ self.remove_layer(layer)
369
+
370
+
371
+ def add_selector(self):
372
+ selector = widgets.Dropdown(options=fireList, value="South Fork", description='Wildfire Case Study:', style={'description_width': '125px'}, layout=widgets.Layout(width='400px'))
373
+
374
+ def on_selector_change(change):
375
+ if change['name'] == 'value':
376
+ selected_fire.value = change['new']
377
+ self.customize_ee_data(selected_fire.value, selected_days.value)
378
+
379
+ selector.observe(on_selector_change, names='value')
380
+ self.add_widget(selector, position="topleft")
381
+
382
+ def add_intSlider(self):
383
+ slider = widgets.IntSlider(value=selected_days.value,min=1,max=40,step=1,description='Elapsed days:',style={'description_width': '125px'}, layout=widgets.Layout(width='400px'))
384
+
385
+ def on_slider_change(change):
386
+ if change['name'] == 'value':
387
+ selected_days.value = change['new']
388
+ self.customize_ee_data(selected_fire.value, selected_days.value)
389
+
390
+ slider.observe(on_slider_change, names='value')
391
+ self.add_widget(slider, position="topleft")
392
+
393
+ def add_dwnldButton(self):
394
+ button = widgets.Button(description='Download',icon='cloud-arrow-down')
395
+
396
+ #def on_button_click(change, file):
397
+ # if change['name'] == 'value':
398
+ # selected_days.value = change['new']
399
+ # self.download_ee_image(file, "trial_file.tif", scale=30)
400
+ def on_button_click(b):
401
+ # Get the currently selected fire and elapsed days
402
+ fire = selected_fire.value
403
+ elapDays = selected_days.value
404
+
405
+ # Customize the EE data and download the image
406
+ file = self.customize_ee_data(fire, elapDays)
407
+ #self.download_ee_image(file, f"{fire}_NBR_{elapDays}days.tif", scale=30)
408
+
409
+ button.observe(on_button_click)
410
+ self.add_widget(button, position="topleft")
411
+
412
+
413
+
414
+ @solara.component
415
+ def Page():
416
+
417
+ with solara.Column(align="center"):
418
+ markdown = """
419
+ ## Historical Western US wildfires from 2020-2021 """
420
+ solara.Markdown(markdown)
421
+
422
+ # Isolation is required to prevent the map from overlapping navigation (when screen width < 960px)
423
+ with solara.Column(style={"isolation": "isolate"}):
424
+ map_widget = Map.element(
425
+ center=[39, -120.5],
426
+ zoom=8,
427
+ height="600px",
428
+ toolbar_ctrl=False
429
+ )
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ geemap
2
+ solara== 1.33.0
3
+ geopandas
4
+ pydantic< 2.0
5
+ ipyevents
6
+ ipywidgets
utils/NBR_calculations.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ee
2
+
3
+ ''' 0 Good quality fire
4
+ 1 Good quality fire-free land
5
+ 2 Invalid due to opaque cloud
6
+ 3 Invalid due to surface type or sunglint or LZA threshold exceeded or off earth or missing input data
7
+ 4 Invalid due to bad input data
8
+ 5 Invalid due to algorithm failure'''
9
+ # Bit-masking
10
+ BitMask_0 = 1 << 0
11
+ BitMask_1 = 1 << 1
12
+ BitMask_2 = 1 << 2
13
+ BitMask_3 = 1 << 3
14
+ BitMask_4 = 1 << 4
15
+ BitMask_5 = 1 << 5
16
+ BitMask_6 = 1 << 6
17
+ BitMask_7 = 1 << 7
18
+ BitMask_8 = 1 << 8
19
+ BitMask_9 = 1 << 9
20
+
21
+ def GcalcNBR (goesImg, aoi):
22
+ #day = ee.Date(eachImg.get('system:time_start')).get('day','America/Los_Angeles')
23
+ fireMode = goesImg.select('fireMode')
24
+ fireMin = goesImg.select('fireMin')
25
+
26
+ CMI_QF3 = goesImg.select('DQF_C03').int()
27
+ CMI_QF6 = goesImg.select('DQF_C06').int()
28
+
29
+ # To include active fire pixels - fireMin.lt(2)\ for next line
30
+ QF_Mask = (fireMin.eq(1)\
31
+ .Or(fireMin.gt(3)))\
32
+ .And(CMI_QF3.lt(2))\
33
+ .And(CMI_QF6.lt(2))\
34
+ .rename('QFmask');
35
+ GOESm = goesImg.select(['CMI_C03','CMI_C06']).updateMask(QF_Mask)
36
+ NBR = GOESm.normalizedDifference(['CMI_C03', 'CMI_C06']).toFloat().rename('NBR')
37
+
38
+ return goesImg.addBands([NBR,QF_Mask])
39
+
40
+ def GcalcCCsingle (goesImg):
41
+
42
+ fireDQF = goesImg.select('DQF').int()
43
+ CMI_QF3 = goesImg.select('DQF_C03').int()
44
+ CMI_QF6 = goesImg.select('DQF_C06').int()
45
+
46
+ #Right now, cloud mask is excluding clouds and water; active fire, bad data and fire free are unmasked. NBR mask exlcudes fire
47
+ F_Mask = fireDQF.eq(0)
48
+ C_Mask = (fireDQF.lt(2).Or(fireDQF.gt(2))).rename('C_Mask')
49
+ #.And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('C_Mask')
50
+ QF_Mask = (fireDQF.eq(1).Or(fireDQF.gt(3)))\
51
+ .And(CMI_QF3.lt(2)).And(CMI_QF6.lt(2)).rename('QFmask')
52
+
53
+ GOESmasked = goesImg.select(['CMI_C03','CMI_C06']).updateMask(QF_Mask)
54
+ NBRmasked = GOESmasked.normalizedDifference(['CMI_C03', 'CMI_C06']).toFloat().rename('NBR')
55
+ cloudMasked = goesImg.select('CMI_C03').updateMask(C_Mask).toFloat().rename('CC')
56
+ fireMasked = goesImg.select('CMI_C03').updateMask(F_Mask).toFloat().rename('FC')
57
+
58
+ return goesImg.addBands([NBRmasked,cloudMasked, fireMasked,QF_Mask,C_Mask])
59
+
60
+ '''Parameter Array Name Value Bit(s) = Value
61
+ Sun Glint QF1 Surface Reflectance None 6-7 = 00
62
+ Low Sun Mask QF1 Surface Reflectance High 5 = 0
63
+ Day/Night QF1 Surface Reflectance Day 4 =0
64
+ Cloud Detection QF1 Surface Reflectance Confident Clear 2-3 = 00 or Problably Clear 2-3 = 01
65
+ Cloud Mask Quality QF1 Surface Reflectance High or Medium 0-1 = 10 or 11
66
+ Snow/Ice QF2 Surface Reflectance No Snow or Ice 5 = 0
67
+ Cloud Shadow QF2 Surface Reflectance No Cloud Shadow 3 = 0
68
+ LandWater QF2 Surface Reflectance Land, Snow, Arctic, Antarctic or Greenland, Desert 0-2 = 011, 100, 101, 110, 111
69
+ Thin Cirrus Flag QF7 Surface Reflectance No Thin Cirrus 4 = 0
70
+ Aerosol Quantity QF7 Surface Reflectance Climatology, Low or Medium 2-3 = 00, 01 or 10
71
+ Adjacent to Cloud QF7 Surface Reflectance Not Adjacent to Cloud 1 = 0'''
72
+
73
+ def VcalcNBR (VIIRSimg):
74
+
75
+ QF1 = VIIRSimg.select('QF1').int()
76
+ QF2 = VIIRSimg.select('QF2').int()
77
+ QF7 = VIIRSimg.select('QF7').int()
78
+
79
+ QF_Mask = (QF1.bitwiseAnd(BitMask_3).eq(0)).And\
80
+ ((QF2.bitwiseAnd(BitMask_2).eq(4)).Or((QF2.bitwiseAnd(BitMask_1).eq(0)))).And\
81
+ (QF2.bitwiseAnd(BitMask_5).eq(0)).rename('QFmask');
82
+
83
+ VIIRSm = VIIRSimg.select(['I2','M11']).updateMask(QF_Mask);
84
+ NBR = VIIRSm.normalizedDifference(['I2','M11']).toFloat().rename('NBR')
85
+ return VIIRSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
86
+
87
+ ''' Bit 1: Dilated Cloud
88
+ Bit 2: Cirrus (high confidence)
89
+ Bit 3: Cloud
90
+ Bit 4: Cloud Shadow
91
+ Bit 5: Snow
92
+ Bit 6: Clear (0: Cloud or Dilated Cloud bits are set, 1: Cloud and Dilated Cloud bits are not set)
93
+ Bit 7: Water
94
+ Bits 8-9: Cloud Confidence (0: None, 1: Low, 2: Medium, 3: High)
95
+ Bits 10-11: Cloud Shadow Confidence (0: None, 1: Low, 2: Medium, 3: High)
96
+ Bits 12-13: Snow/Ice Confidence (0: None, 1: Low, 2: Medium, 3: High)
97
+ Bits 14-15: Cirrus Confidence (0: None, 1: Low, 2: Medium, 3: High)'''
98
+
99
+ def LcalcNBR (LSimg):
100
+ QApixel = LSimg.select('QA_PIXEL').int()
101
+ QF_Mask =(QApixel.bitwiseAnd(BitMask_3).eq(0)).And\
102
+ (QApixel.bitwiseAnd(BitMask_5).eq(0)).And\
103
+ (QApixel.bitwiseAnd(BitMask_7).eq(0)).rename('QFmask');
104
+
105
+ LSmasked = LSimg.select(['SR_B5','SR_B7']).updateMask(QF_Mask);
106
+ NBR = LSmasked.normalizedDifference(['SR_B5','SR_B7']).toFloat().rename('NBR')
107
+ return LSimg.addBands(NBR).addBands(QF_Mask)#.set('avgNBR', avgNBR)
108
+
109
+ ''' 1 Saturated or defective
110
+ 2 Dark Area Pixels
111
+ 3 Cloud Shadows
112
+ 4 Vegetation
113
+ 5 Bare Soils
114
+ 6 Water
115
+ 7 Clouds Low Probability / Unclassified
116
+ 8 Clouds Medium Probability
117
+ 9 Clouds High Probability
118
+ 10 Cirrus
119
+ 11 Snow / Ice'''
120
+
121
+ def ScalcNBR (sentImg):
122
+ SCL = sentImg.select('SCL');
123
+ QF_Mask =(SCL.neq(6)).And\
124
+ (SCL.neq(8)).And\
125
+ (SCL.neq(9)).And\
126
+ (SCL.neq(11))\
127
+ .rename('QFmask');
128
+ sentMasked = sentImg.select(['B8A','B12']).updateMask(QF_Mask); #B8 is another option- broadband NIR
129
+ NBR = sentMasked.normalizedDifference(['B8A','B12']).toFloat().rename('NBR')
130
+ return sentImg.addBands(NBR).addBands(QF_Mask).addBands(SCL)#.set('avgNBR', avgNBR)
utils/__init__.py ADDED
File without changes