Learn to crosswalk LCMS datasets to different levels#
Currently, all LCMS deliverables are delivered at the highest level (largest number of classes)
This notebook facilitates crosswalking of LCMS deliverables to different levels
Lower levels provide greater accuracy, while higher levels provide greater thematic detail
Use this notebook to find the level that suits your data needs and tolerance for map error
Copyright 2025 Ian Housman
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
http://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.
#Boiler plate
#Import modules
try:
from geeViz.geeView import *
except:
!python -m pip install geeViz
from geeViz.geeView import *
import geeViz.examples.lcmsLevelLookup as ll
import pandas as pd
import numpy as np
from IPython.display import Markdown
print('Done')
Initializing GEE
Successfully initialized
Done
First, we’ll take a look at the various levels for LCMS data#
This is a standard way of crosswalking LCMS data to an appropriate level of thematic detail for your needs
You can also crosswalk LCMS data in many other ways by combining different sets of Change, Land Cover, and Land Use classes in various manners
# Bring in the lookup dictionary and convert it to a Pandas dataframe for easy viewing
products = list(ll.all_lookup.keys())
for product in products:
product_title = product.replace('_',' ')
product_lookup = ll.all_lookup[product]
available_levels = ll.product_levels[product]
highest_level = max(available_levels)
highest_level = [n for n in product_lookup.keys() if len(n.split(".")) == highest_level]
table = [highest_level]
for level in available_levels[1:]:
table.append(['.'.join(l.split('.')[:level]) for l in highest_level])
table = np.transpose(table)
out_table = [[product_lookup[v][2] for v in r] for r in table]
df = pd.DataFrame(out_table)
blankIndex=[''] * len(df)
df.index=blankIndex
df.columns = [f'{product_title} Level {l}' for l in available_levels]
display(Markdown(f'<h1>{product_title} Levels</h1>'))
display(df)
Land Cover Levels
Land Cover Level 4 | Land Cover Level 3 | Land Cover Level 2 | Land Cover Level 1 | |
---|---|---|---|---|
Tree | Tree | Tree Vegetated | Vegetated | |
Tall Shrub & Tree Mix (SEAK Only) | Tree | Tree Vegetated | Vegetated | |
Shrub & Tree Mix | Tree | Tree Vegetated | Vegetated | |
Grass/Forb/Herb & Tree Mix | Tree | Tree Vegetated | Vegetated | |
Barren & Tree Mix | Tree | Tree Vegetated | Vegetated | |
Tall Shrub (SEAK Only) | Shrub | Non-Tree Vegetated | Vegetated | |
Shrub | Shrub | Non-Tree Vegetated | Vegetated | |
Grass/Forb/Herb & Shrub Mix | Shrub | Non-Tree Vegetated | Vegetated | |
Barren & Shrub Mix | Shrub | Non-Tree Vegetated | Vegetated | |
Grass/Forb/Herb | Grass/Forb/Herb | Non-Tree Vegetated | Vegetated | |
Barren & Grass/Forb/Herb Mix | Grass/Forb/Herb | Non-Tree Vegetated | Vegetated | |
Barren or Impervious | Barren or Impervious | Non-Vegetated | Non-Vegetated | |
Snow or Ice | Snow or Ice | Non-Vegetated | Non-Vegetated | |
Water | Water | Non-Vegetated | Non-Vegetated | |
Non-Processing Area Mask | Non-Processing Area Mask | Non-Processing Area Mask | Non-Processing Area Mask |
Change Levels
Change Level 3 | Change Level 2 | Change Level 1 | |
---|---|---|---|
Stable | Stable | Stable | |
Gain | Gain | Stable | |
Slow Loss | Loss | Loss | |
Fast Loss | Loss | Loss | |
Non-Processing Area Mask | Non-Processing Area Mask | Non-Processing Area Mask |
Land Use Levels
Land Use Level 3 | Land Use Level 2 | Land Use Level 1 | |
---|---|---|---|
Agriculture | Agriculture | Anthropogenic | |
Developed | Developed | Anthropogenic | |
Forest | Forest | Non-Anthropogenic | |
Non-Forest Wetland | Other | Non-Anthropogenic | |
Other | Other | Non-Anthropogenic | |
Rangeland or Pasture | Rangeland or Pasture | Non-Anthropogenic | |
Non-Processing Area Mask | Non-Processing Area Mask | Non-Processing Area Mask |
Learn how to crosswalk and symbolize LCMS products at a specific level#
You need to crosswalk (remap) values and provide the relevant symbology to render the maps properly
The code below will show different products and levels and their respective crosswalk (remap) class numbers and symbology properties
for product in ll.product_levels.keys():
product_title = product.replace('_',' ')
for level in ll.product_levels[product]:
remap_dict = ll.getLevelNRemap(level, bandName=product)
print('Product:',product_title, 'Level:',level, remap_dict)
Product: Change Level: 3 {'remap_from': [1, 2, 3, 4, 5], 'remap_to': [1, 2, 3, 4, 5], 'viz_dict': {'Change_class_names': ['Stable', 'Gain', 'Slow Loss', 'Fast Loss', 'Non-Processing Area Mask'], 'Change_class_palette': ['3D4551', '00A398', 'F39268', 'D54309', '1B1716'], 'Change_class_values': [1, 4, 2, 3, 5]}}
Product: Change Level: 2 {'remap_from': [1, 2, 3, 4, 5], 'remap_to': [1, 3, 3, 2, 4], 'viz_dict': {'Change_class_names': ['Stable', 'Gain', 'Loss', 'Non-Processing Area Mask'], 'Change_class_palette': ['3D4551', '00A398', 'D54309', '1B1716'], 'Change_class_values': [1, 2, 3, 4]}}
Product: Change Level: 1 {'remap_from': [1, 2, 3, 4, 5], 'remap_to': [1, 2, 2, 1, 3], 'viz_dict': {'Change_class_names': ['Stable', 'Loss', 'Non-Processing Area Mask'], 'Change_class_palette': ['3D4551', 'D54309', '1B1716'], 'Change_class_values': [1, 2, 3]}}
Product: Land Cover Level: 4 {'remap_from': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 'remap_to': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 'viz_dict': {'Land_Cover_class_names': ['Tree', 'Tall Shrub & Tree Mix (SEAK Only)', 'Shrub & Tree Mix', 'Grass/Forb/Herb & Tree Mix', 'Barren & Tree Mix', 'Tall Shrub (SEAK Only)', 'Shrub', 'Grass/Forb/Herb & Shrub Mix', 'Barren & Shrub Mix', 'Grass/Forb/Herb', 'Barren & Grass/Forb/Herb Mix', 'Barren or Impervious', 'Snow or Ice', 'Water', 'Non-Processing Area Mask'], 'Land_Cover_class_palette': ['004E2B', '009344', '61BB46', 'E5E98A', '8B8560', 'CAFD4B', 'F89A1C', '8FA55F', 'BEBB8E', 'FFFF00', 'DDB925', 'D4D4D3', 'E4F5FD', '00B6F0', '1B1716'], 'Land_Cover_class_values': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]}}
Product: Land Cover Level: 3 {'remap_from': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 'remap_to': [1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 5, 6, 7], 'viz_dict': {'Land_Cover_class_names': ['Tree', 'Shrub', 'Grass/Forb/Herb', 'Barren or Impervious', 'Snow or Ice', 'Water', 'Non-Processing Area Mask'], 'Land_Cover_class_palette': ['004E2B', 'F89A1C', 'E5E98A', 'D4D4D3', 'E4F5FD', '00B6F0', '1B1716'], 'Land_Cover_class_values': [1, 2, 3, 4, 5, 6, 7]}}
Product: Land Cover Level: 2 {'remap_from': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 'remap_to': [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4], 'viz_dict': {'Land_Cover_class_names': ['Tree Vegetated', 'Non-Tree Vegetated', 'Non-Vegetated', 'Non-Processing Area Mask'], 'Land_Cover_class_palette': ['004E2B', '8DA463', 'D4D4D3', '1B1716'], 'Land_Cover_class_values': [1, 2, 3, 4]}}
Product: Land Cover Level: 1 {'remap_from': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 'remap_to': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3], 'viz_dict': {'Land_Cover_class_names': ['Vegetated', 'Non-Vegetated', 'Non-Processing Area Mask'], 'Land_Cover_class_palette': ['61BB46', '58646E', '1B1716'], 'Land_Cover_class_values': [1, 2, 3]}}
Product: Land Use Level: 3 {'remap_from': [1, 2, 3, 4, 5, 6, 7], 'remap_to': [1, 2, 3, 4, 5, 6, 7], 'viz_dict': {'Land_Use_class_names': ['Agriculture', 'Developed', 'Forest', 'Non-Forest Wetland', 'Other', 'Rangeland or Pasture', 'Non-Processing Area Mask'], 'Land_Use_class_palette': ['FBFF97', 'E6558B', '004E2B', '36C5B2', 'D4D4D3', 'A6976A', '1B1716'], 'Land_Use_class_values': [1, 2, 3, 4, 5, 6, 7]}}
Product: Land Use Level: 2 {'remap_from': [1, 2, 3, 4, 5, 6, 7], 'remap_to': [1, 2, 3, 4, 4, 5, 6], 'viz_dict': {'Land_Use_class_names': ['Agriculture', 'Developed', 'Forest', 'Other', 'Rangeland or Pasture', 'Non-Processing Area Mask'], 'Land_Use_class_palette': ['FBFF97', 'E6558B', '004E2B', 'D4D4D3', 'A6976A', '1B1716'], 'Land_Use_class_values': [1, 2, 3, 4, 5, 6]}}
Product: Land Use Level: 1 {'remap_from': [1, 2, 3, 4, 5, 6, 7], 'remap_to': [1, 1, 2, 2, 2, 2, 3], 'viz_dict': {'Land_Use_class_names': ['Anthropogenic', 'Non-Anthropogenic', 'Non-Processing Area Mask'], 'Land_Use_class_palette': ['FF9EAB', '004E2B', '1B1716'], 'Land_Use_class_values': [1, 2, 3]}}
Crosswalk and visualize all LCMS products and levels#
This will apply the crosswalk (remap) and update the symbology for all products and levels
A map viewer will then open to visualize the resulting layers
Map.clearMap()
lcms = ee.ImageCollection("USFS/GTAC/LCMS/v2023-9")
for product in ll.product_levels.keys():
product_title = product.replace('_',' ')
lc = lcms.select([product])
isFirst = True
levels = ll.product_levels[product]
for level in levels:
remap_dict = ll.getLevelNRemap(level, bandName=product)
lcT = lc.map(lambda img: img.remap(remap_dict["remap_from"], remap_dict["remap_to"]).rename([product]).set(remap_dict["viz_dict"])) # Crosswalk and set symbology
Map.addLayer(lcT, {"autoViz": True, "canAreaChart": True, "includeClassValues": False}, f"{product_title} Level {level}", isFirst) # Visualize output
isFirst = False
Map.setCenter(-111.83856, 40.73678, 11)
Map.turnOnAutoAreaCharting()
Map.view()
Adding layer: Change Level 3
Adding layer: Change Level 2
Adding layer: Change Level 1
Adding layer: Land Cover Level 4
Adding layer: Land Cover Level 3
Adding layer: Land Cover Level 2
Adding layer: Land Cover Level 1
Adding layer: Land Use Level 3
Adding layer: Land Use Level 2
Adding layer: Land Use Level 1
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=nlcd-tcc&accessToken=ya29.a0AXeO80Sy2em_qCGusz2iK4kIa3wvnw82EX4bUNPviytsDQREIIoDihdOlqDe-OMcAacZIMXYcQ4NNapO9xab-DC3WtdpA6L9ZJ0G3d9DPaaURRXv_w3F-02gyuuF2R1NT0Bz6BU2_Q4fK7jXwfqw974UmHqypqGvoMgEMhDmQFkaCgYKAbASARESFQHGX2MiAqaBnb76Je5CoKcHQTAV-w0178&accessTokenCreationTime=1737669512448