LCMS Viewer Intro Notebook#

  • Based on https://github.com/google/earthengine-community/blob/master/datasets/scripts/LCMS_Visualization.js Copyright 2024 The Google Earth Engine Community Authors

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

  • Example script for visualizing LCMS change summaries, land cover, and land use.

  • A more in-depth visualization of LCMS products is available at: https://apps.fs.usda.gov/lcms-viewer/index.html

  • Contact sm.fs.lcms@usda.gov with any questions or specific data requests.

github github

#Boiler plate
#Import modules
import os,sys
sys.path.append(os.getcwd())


try:
    from  geeViz.geeView import *
except:
    !python -m pip install geeViz
    from  geeViz.geeView import *


print('Done')
Done

First, we’ll take a look at the foundation of the geeViz.geeView module - the LCMS Data Explorer#

  • This viewer framework serves as the foundation for this module

#This notebook breaks down various pieces of LCMS data visualization available in this interactive viewer
IFrame(src='https://apps.fs.usda.gov/lcms-viewer/', width='100%', height='500px')

Create your own LCMS Viewer#

  • You can create your own instance of the LCMS Data Explorer using geeViz.geeView

#Clear any layers added to Map object
#If map is not cleared, layers are simply appended to the existing list of layers if layers have been added previously
Map.clearMap()

#############################################################################
### Define visualization parameters ###
#############################################################################
startYear = 1985;
endYear = 2022;
lossYearPalette = ['ffffe5', 'fff7bc', 'fee391', 'fec44f', 'fe9929',
                   'ec7014', 'cc4c02']
gainYearPalette = ['c5ee93', '00a398']
durationPalette = ['BD1600', 'E2F400','0C2780']

lossYearViz = {'min': startYear, 'max': endYear, 'palette': lossYearPalette};
gainYearViz = {'min': startYear, 'max': endYear, 'palette': gainYearPalette};
durationViz = {'min': 1, 'max': 5, 'palette': durationPalette};

#############################################################################
### Define functions ###
#############################################################################

#Convert given code to year that number was present in the image.
def getMostRecentChange(c, code):
	def wrapper(img):
		yr = ee.Date(img.get('system:time_start')).get('year')
		return ee.Image(yr).int16().rename(['year']).updateMask(img.eq(code)).copyProperties(img,['system:time_start'])
	return c.map(wrapper)
#############################################################################
### Bring in LCMS annual outputs ###
#############################################################################

lcms = ee.ImageCollection('USFS/GTAC/LCMS/v2022-8')
bandNames =  lcms.first().bandNames().getInfo()
print('Available study areas:', lcms.aggregate_histogram('study_area').keys().getInfo())
print('Available LCMS products',bandNames);
print('Learn more about visualization of LCMS products here', 'https://apps.fs.usda.gov/lcms-viewer/')

#Filter out study area
# lcms = lcms.filter(ee.Filter.eq('study_area','CONUS'))

#Set up time periods to compare land cover and land use
earlySpan = [startYear, startYear+4]
lateSpan = [endYear-4, endYear]
c:\Users\ihousman\AppData\Local\Programs\Python\Python311\Lib\site-packages\ee\deprecation.py:202: DeprecationWarning: 

Attention required for USFS/GTAC/LCMS/v2022-8! You are using a deprecated asset.
To ensure continued functionality, please update it.
Learn more: https://developers.google.com/earth-engine/datasets/catalog/USFS_GTAC_LCMS_v2022-8

  warnings.warn(warning, category=DeprecationWarning)
Available study areas: ['CONUS', 'PRUSVI', 'SEAK']
Available LCMS products ['Change', 'Land_Cover', 'Land_Use', 'Change_Raw_Probability_Slow_Loss', 'Change_Raw_Probability_Fast_Loss', 'Change_Raw_Probability_Gain', 'Land_Cover_Raw_Probability_Trees', 'Land_Cover_Raw_Probability_Tall-Shrubs-and-Trees-Mix', 'Land_Cover_Raw_Probability_Shrubs-and-Trees-Mix', 'Land_Cover_Raw_Probability_Grass-Forb-Herb-and-Trees-Mix', 'Land_Cover_Raw_Probability_Barren-and-Trees-Mix', 'Land_Cover_Raw_Probability_Tall-Shrubs', 'Land_Cover_Raw_Probability_Shrubs', 'Land_Cover_Raw_Probability_Grass-Forb-Herb-and-Shrubs-Mix', 'Land_Cover_Raw_Probability_Barren-and-Shrubs-Mix', 'Land_Cover_Raw_Probability_Grass-Forb-Herb', 'Land_Cover_Raw_Probability_Barren-and-Grass-Forb-Herb-Mix', 'Land_Cover_Raw_Probability_Barren-or-Impervious', 'Land_Cover_Raw_Probability_Snow-or-Ice', 'Land_Cover_Raw_Probability_Water', 'Land_Use_Raw_Probability_Agriculture', 'Land_Use_Raw_Probability_Developed', 'Land_Use_Raw_Probability_Forest', 'Land_Use_Raw_Probability_Non-Forest-Wetland', 'Land_Use_Raw_Probability_Other', 'Land_Use_Raw_Probability_Rangeland-or-Pasture', 'QA_Bits']
Learn more about visualization of LCMS products here https://apps.fs.usda.gov/lcms-viewer/

  • Raw LCMS outputs are available for more customized analysis

  • Double click anywhere within CONUS to plot the time series of raw LCMS outputs

#############################################################################
### Add full raw model outputs ###
#############################################################################
Map.clearMap()
#Separate products
raw_change = lcms.select(['Change_Raw_.*'])
raw_land_cover = lcms.select(['Land_Cover_Raw_.*'])
raw_land_use = lcms.select(['Land_Use_Raw_.*'])

#Shorten names
raw_change_bns = [bn for bn in bandNames if bn.find('Change_Raw_') > -1]
raw_land_cover_bns = [bn for bn in bandNames if bn.find('Land_Cover_Raw_') > -1]
raw_land_use_bns = [bn for bn in bandNames if bn.find('Land_Use_Raw_') > -1]

raw_change_bns_short = [i.split('_Probability_')[-1] for i in raw_change_bns]
raw_land_cover_bns_short = [i.split('_Probability_')[-1] for i in raw_land_cover_bns]
raw_land_use_bns_short = [i.split('_Probability_')[-1] for i in raw_land_use_bns]

raw_change = raw_change.select(raw_change_bns,raw_change_bns_short)
raw_land_cover = raw_land_cover.select(raw_land_cover_bns,raw_land_cover_bns_short)
raw_land_use = raw_land_use.select(raw_land_use_bns,raw_land_use_bns_short)


#Add to map
Map.addLayer(raw_land_use,{'reducer':ee.Reducer.max(),'min':0,'max':30,'opacity':1,'addToLegend':False},'Raw LCMS Land Use Model Probability',True) 
Map.addLayer(raw_land_cover,{'reducer':ee.Reducer.max(),'min':0,'max':30,'opacity':1,'addToLegend':False},'Raw LCMS Land Cover Model Probability',True) 
Map.addLayer(raw_change,{'reducer':ee.Reducer.max(),'min':0,'max':30,'bands':'Fast_Loss,Gain,Slow_Loss','opacity':1,'addToLegend':False},'Raw LCMS Change Model Probability',True) 


Map.centerObject(lcms.first())
Map.turnOnInspector()
Map.setQueryDateFormat('YYYY')
Map.view()
Adding layer: Raw LCMS Land Use Model Probability
Adding layer: Raw LCMS Land Cover Model Probability
Adding layer: Raw LCMS Change Model Probability
Setting default query date format to: YYYY
Starting webmap
Using default refresh token for geeView
Local web server at: http://localhost:8001/geeView/ already serving.
cwd a:\GEE\gee_py_modules_package\geeViz\examples
geeView URL: http://localhost:8001/geeView/?projectID=lcms-292214&accessToken=ya29.a0AcM612y-wMlkXgO_OFkZr_Lacz34yFleugdxgOurv_dsLeXSWXK-EChFophmj9oxlcyQW3N2YTzC_TzY8SxmdqXpklggko8X1EFWM8FEm8gQ0y9bcyFJd1zbM1jUOJbTTzV44mJ7oDhGeJA0m95KLMvtcErfKf0OkT1pgNBcVVsaCgYKAZgSARESFQHGX2Mik1zFDPF9TgvOZ--wV79HUw0178
Map.clearMap()
#############################################################################
### Visualize Land Use change ###
#############################################################################
lu = lcms.select(['Land_Use'])
earlyLu = lu.filter(ee.Filter.calendarRange(earlySpan[0], earlySpan[1], 'year'))
lateLu = lu.filter(ee.Filter.calendarRange(lateSpan[0], lateSpan[1], 'year'))
Map.addLayer(earlyLu, {'reducer':ee.Reducer.mode(),'autoViz':True}, 'Early Land Use Mode ({}-{})'.format(earlySpan[0],earlySpan[1]), True)
Map.addLayer(lateLu, {'reducer':ee.Reducer.mode(),'autoViz':True,'opacity':0}, 'Recent Land Use Mode ({}-{})'.format(lateSpan[0],lateSpan[1]), True);



#############################################################################
### Visualize Land Cover change ###
#############################################################################
lc = lcms.select(['Land_Cover'])
earlyLc = lc.filter(ee.Filter.calendarRange(earlySpan[0], earlySpan[1], 'year'))
lateLc = lc.filter(ee.Filter.calendarRange(lateSpan[0], lateSpan[1], 'year'))
Map.addLayer(earlyLc, {'reducer':ee.Reducer.mode(),'autoViz':True}, 'Early Land Cover Mode ({}-{})'.format(earlySpan[0],earlySpan[1]), True);
Map.addLayer(lateLc, {'reducer':ee.Reducer.mode(),'autoViz':True,'opacity':0}, 'Recent Land Cover Mode ({}-{})'.format(lateSpan[0],lateSpan[1]), True);


Map.turnOnInspector()
Map.setQueryDateFormat('YYYY')
Map.view()
Adding layer: Early Land Use Mode (1985-1989)
Adding layer: Recent Land Use Mode (2018-2022)
Adding layer: Early Land Cover Mode (1985-1989)
Adding layer: Recent Land Cover Mode (2018-2022)
Setting default query date format to: YYYY
Starting webmap
Using default refresh token for geeView
Local web server at: http://localhost:8001/geeView/ already serving.
cwd a:\GEE\gee_py_modules_package\geeViz\examples
geeView URL: http://localhost:8001/geeView/?projectID=lcms-292214&accessToken=ya29.a0AcM612zDgRjgEmDi_yYVNIvaVY2azV8cVYE6ETwGhWAsQQ1CzIwHreik7o0TClPAyf9Okm6xURUPlDUyxkodC15EDFza8rYZUEk61FhReaxwzrdMOUL5_HU6aO0OOvHpQxlWjqmBS9rcU8_iLAB27qv9vvosyhFo8fyHpjDDcXgaCgYKAb8SARESFQHGX2MionRzqxWQQ-3VjFP8htnWOA0178
Map.clearMap()
#############################################################################
### Visualize Change products ###
#############################################################################

#Select the change band. Land_Cover and Land_Use are also available.
change = lcms.select(['Change'])

#Convert to year collection for a given code.
slowLossYears = getMostRecentChange(change, 2)
fastLossYears = getMostRecentChange(change, 3)
gainYears = getMostRecentChange(change, 4)

#Find the most recent year.
mostRecentSlowLossYear = slowLossYears.max()
mostRecentFastLossYear = fastLossYears.max()
mostRecentGainYear = gainYears.max()

#Find the duration.
slowLossDuration = slowLossYears.count()
fastLossDuration = fastLossYears.count()
gainDuration = gainYears.count()

#Add year summaries to the map.
Map.addLayer(mostRecentSlowLossYear, lossYearViz, 'Most Recent Slow Loss Year', True)
Map.addLayer(mostRecentFastLossYear, lossYearViz, 'Most Recent Fast Loss Year', True)
Map.addLayer(mostRecentGainYear, gainYearViz, 'Most Recent Gain Year', True)

#Add durations to the map.
Map.addLayer(slowLossDuration,durationViz, 'Slow Loss Duration', False);
Map.addLayer(fastLossDuration,durationViz, 'Fast Loss Duration', False);
Map.addLayer(gainDuration,durationViz, 'Gain Duration', False);


Map.turnOnInspector()
Map.view()
Adding layer: Most Recent Slow Loss Year
Adding layer: Most Recent Fast Loss Year
Adding layer: Most Recent Gain Year
Adding layer: Slow Loss Duration
Adding layer: Fast Loss Duration
Adding layer: Gain Duration
Starting webmap
Using default refresh token for geeView
Local web server at: http://localhost:8001/geeView/ already serving.
cwd a:\GEE\gee_py_modules_package\geeViz\examples
geeView URL: http://localhost:8001/geeView/?projectID=lcms-292214&accessToken=ya29.a0AcM612xoftpqheIuHbi9bcWDlmIZ5O9-c9ZwgSBhcAeoIFQL5OuPmgdeReJmy6bCQ9byq4mgyDibRB-EAyImkDoZ4WhcailRq4XoMqD21af80vhmzxv4mBKOlmOhs1ShH5yfgXR-C36i97o5a6gejqxD8Ft2nCxob-x7w9j7y3AaCgYKAXQSARESFQHGX2Midz2BZdheheSq91hgy_2OMQ0178

Time lapses enable quick visualization of multi-temporal imageCollections#

# Since any collection can be viewed as a time lapse, let's bring in the change outputs as a timelapse
# These can take time to load since each frame is an individual tile map service
Map.clearMap()
justChange = change.map(lambda img:img.updateMask(ee.Image(img.gt(1).And(img.lt(5))).copyProperties(lcms.first())))
Map.addTimeLapse(justChange,{'autoViz':True},'LCMS Change Time Lapse',False)

Map.setQueryDateFormat('YYYY')
Map.turnOnInspector()
Map.view()
Adding layer: LCMS Change Time Lapse
Setting default query date format to: YYYY
Starting webmap
Using default refresh token for geeView
Local web server at: http://localhost:8001/geeView/ already serving.
cwd a:\GEE\gee_py_modules_package\geeViz\examples
geeView URL: http://localhost:8001/geeView/?projectID=lcms-292214&accessToken=ya29.a0AcM612wkLZ5nkrRefktkInWr4bG1xoFijDRm4zKqEkOSY5SlJkshjY8c8xPH0opFivgsSvpi-zs-EMn4cghA5Q9vvm7dszHkaSY2xuMWT94ACWv_8wy0D-nhVOi29Oo5fQ5wmHrYHpnRzBiH6SF3RyS-9rr7i3VwptGM3yc1aokaCgYKAUQSARESFQHGX2MiEKkp7i2adXQeQErg-zELAw0178