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.

github github

#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