LCOV - code coverage report
Current view: top level - src/src - LANDFIRE.jl (source / functions) Coverage Total Hit
Test: on branch main Lines: 68.2 % 107 73
Test Date: 2026-09-13 00:06:12 Functions: - 0 0

            Line data    Source code
       1              : module LANDFIRE
       2              : 
       3              : using ..WildfireData
       4              : using Downloads
       5              : using HTTP
       6              : 
       7              : 
       8              : #-----------------------------------------------------------------------------# Data Directory
       9            2 : dir() = WildfireData.dir("LANDFIRE")
      10              : 
      11              : #-----------------------------------------------------------------------------# Constants
      12              : 
      13              : const DOWNLOAD_BASE = "https://landfire.gov/data-downloads"
      14              : const WCS_BASE = "https://edcintl.cr.usgs.gov/geoserver/landfire_wcs"
      15              : const WMS_BASE = "https://edcintl.cr.usgs.gov/geoserver/landfire"
      16              : 
      17              : # Available regions
      18              : const REGIONS = Dict{Symbol, NamedTuple{(:name, :code, :wcs_code, :wms_code), Tuple{String, String, String, String}}}(
      19              :     :conus => (name="Continental US", code="US", wcs_code="us", wms_code="us"),
      20              :     :alaska => (name="Alaska", code="AK", wcs_code="ak", wms_code="ak"),
      21              :     :hawaii => (name="Hawaii", code="HI", wcs_code="hi", wms_code="hi"),
      22              : )
      23              : 
      24              : # Available versions with their numeric codes
      25              : const VERSIONS = Dict{Symbol, NamedTuple{(:year, :code), Tuple{Int, String}}}(
      26              :     :LF2024 => (year=2024, code="250"),
      27              :     :LF2023 => (year=2023, code="240"),
      28              :     :LF2022 => (year=2022, code="230"),
      29              :     :LF2020 => (year=2020, code="220"),
      30              :     :LF2016 => (year=2016, code="200"),
      31              :     :LF2014 => (year=2014, code="140"),
      32              :     :LF2012 => (year=2012, code="130"),
      33              :     :LF2010 => (year=2010, code="120"),
      34              :     :LF2008 => (year=2008, code="110"),
      35              :     :LF2001 => (year=2001, code="105"),
      36              : )
      37              : 
      38              : # Product categories and their products
      39              : const PRODUCTS = Dict{Symbol, NamedTuple{(:name, :category, :description), Tuple{String, Symbol, String}}}(
      40              :     # Fuel Products
      41              :     :FBFM13 => (
      42              :         name = "13 Anderson Fire Behavior Fuel Models",
      43              :         category = :fuel,
      44              :         description = "Fire behavior fuel models based on Anderson's 13 fuel model classification"
      45              :     ),
      46              :     :FBFM40 => (
      47              :         name = "40 Scott and Burgan Fire Behavior Fuel Models",
      48              :         category = :fuel,
      49              :         description = "Fire behavior fuel models based on Scott and Burgan's 40 fuel model classification"
      50              :     ),
      51              :     :CFFDRS => (
      52              :         name = "Canadian Forest Fire Danger Rating System",
      53              :         category = :fuel,
      54              :         description = "Fuel types mapped to the Canadian Forest Fire Danger Rating System"
      55              :     ),
      56              :     :CBD => (
      57              :         name = "Canopy Bulk Density",
      58              :         category = :fuel,
      59              :         description = "Mass of available canopy fuel per unit canopy volume (kg/m³)"
      60              :     ),
      61              :     :CBH => (
      62              :         name = "Canopy Base Height",
      63              :         category = :fuel,
      64              :         description = "Height from ground to the base of the canopy (m)"
      65              :     ),
      66              :     :CC => (
      67              :         name = "Canopy Cover",
      68              :         category = :fuel,
      69              :         description = "Percent cover of the tree canopy"
      70              :     ),
      71              :     :CH => (
      72              :         name = "Canopy Height",
      73              :         category = :fuel,
      74              :         description = "Average height of the top of the canopy (m)"
      75              :     ),
      76              :     :FVC => (
      77              :         name = "Fuel Vegetation Cover",
      78              :         category = :fuel,
      79              :         description = "Percent cover of fuel vegetation"
      80              :     ),
      81              :     :FVH => (
      82              :         name = "Fuel Vegetation Height",
      83              :         category = :fuel,
      84              :         description = "Average height of fuel vegetation"
      85              :     ),
      86              :     :FVT => (
      87              :         name = "Fuel Vegetation Type",
      88              :         category = :fuel,
      89              :         description = "Classification of fuel vegetation types"
      90              :     ),
      91              : 
      92              :     # Vegetation Products
      93              :     :BPS => (
      94              :         name = "Biophysical Settings",
      95              :         category = :vegetation,
      96              :         description = "Potential natural vegetation that may have been dominant prior to Euro-American settlement"
      97              :     ),
      98              :     :EVC => (
      99              :         name = "Existing Vegetation Cover",
     100              :         category = :vegetation,
     101              :         description = "Vertically projected percent cover of the existing vegetation"
     102              :     ),
     103              :     :EVH => (
     104              :         name = "Existing Vegetation Height",
     105              :         category = :vegetation,
     106              :         description = "Average height of the dominant vegetation"
     107              :     ),
     108              :     :EVT => (
     109              :         name = "Existing Vegetation Type",
     110              :         category = :vegetation,
     111              :         description = "Classification of existing vegetation types"
     112              :     ),
     113              :     :SCLASS => (
     114              :         name = "Succession Class",
     115              :         category = :vegetation,
     116              :         description = "Current vegetation conditions relative to reference conditions"
     117              :     ),
     118              :     :VCC => (
     119              :         name = "Vegetation Condition Class",
     120              :         category = :vegetation,
     121              :         description = "Departure of current vegetation from historical reference conditions"
     122              :     ),
     123              :     :VDEP => (
     124              :         name = "Vegetation Departure",
     125              :         category = :vegetation,
     126              :         description = "Degree to which current vegetation has departed from simulated historical reference"
     127              :     ),
     128              : 
     129              :     # Disturbance Products
     130              :     :Dist => (
     131              :         name = "Annual Disturbance",
     132              :         category = :disturbance,
     133              :         description = "Annual disturbance events including fire, insects, disease, and other factors"
     134              :     ),
     135              :     :HDist => (
     136              :         name = "Historical Disturbance",
     137              :         category = :disturbance,
     138              :         description = "Cumulative disturbance from 1999 to present"
     139              :     ),
     140              : 
     141              :     # Topographic Products
     142              :     :Elev => (
     143              :         name = "Elevation",
     144              :         category = :topographic,
     145              :         description = "Elevation above sea level (m)"
     146              :     ),
     147              :     :Slp => (
     148              :         name = "Slope",
     149              :         category = :topographic,
     150              :         description = "Slope steepness (degrees)"
     151              :     ),
     152              :     :Asp => (
     153              :         name = "Aspect",
     154              :         category = :topographic,
     155              :         description = "Slope direction (degrees from north)"
     156              :     ),
     157              : 
     158              :     # Fire Regime Products
     159              :     :FRG => (
     160              :         name = "Fire Regime Group",
     161              :         category = :fire_regime,
     162              :         description = "Groupings of fire frequency and severity"
     163              :     ),
     164              :     :MFRI => (
     165              :         name = "Mean Fire Return Interval",
     166              :         category = :fire_regime,
     167              :         description = "Average period between fires under historical conditions"
     168              :     ),
     169              :     :PLS => (
     170              :         name = "Percent Low Severity",
     171              :         category = :fire_regime,
     172              :         description = "Percent of fires that were low severity under historical conditions"
     173              :     ),
     174              :     :PMS => (
     175              :         name = "Percent Mixed Severity",
     176              :         category = :fire_regime,
     177              :         description = "Percent of fires that were mixed severity under historical conditions"
     178              :     ),
     179              :     :PRS => (
     180              :         name = "Percent Replacement Severity",
     181              :         category = :fire_regime,
     182              :         description = "Percent of fires that were stand-replacing under historical conditions"
     183              :     ),
     184              : )
     185              : 
     186              : #-----------------------------------------------------------------------------# Info Functions
     187              : 
     188              : """
     189              :     info()
     190              : 
     191              : Print information about LANDFIRE data products and access methods.
     192              : """
     193            1 : function info()
     194            1 :     println("LANDFIRE: Landscape Fire and Resource Management Planning Tools")
     195            1 :     println("=" ^ 65)
     196            1 :     println("A joint USDI/USDA Forest Service program providing geospatial data")
     197            1 :     println("for wildland fire and natural resource management.")
     198            1 :     println()
     199            1 :     println("Data Categories:")
     200            1 :     println("  - Fuel: Fire behavior fuel models, canopy characteristics")
     201            1 :     println("  - Vegetation: Existing and potential vegetation classifications")
     202            1 :     println("  - Disturbance: Annual and historical disturbance events")
     203            1 :     println("  - Topographic: Elevation, slope, aspect")
     204            1 :     println("  - Fire Regime: Historical fire frequency and severity")
     205            1 :     println()
     206            1 :     println("Access Methods:")
     207            1 :     println("  - Full extent downloads (GeoTIFF rasters)")
     208            1 :     println("  - WCS/WMS web services for streaming access")
     209            1 :     println()
     210            1 :     println("Regions: CONUS, Alaska, Hawaii")
     211            1 :     println("Versions: 2001-2024")
     212            1 :     println()
     213            1 :     println("Use `LANDFIRE.products()` to list available products")
     214            1 :     println("Use `LANDFIRE.download_product()` to download data")
     215            1 :     return nothing
     216              : end
     217              : 
     218              : """
     219              :     products(; category=nothing)
     220              : 
     221              : List available LANDFIRE products. Optionally filter by category.
     222              : 
     223              : # Categories
     224              : - `:fuel` - Fire behavior fuel models and canopy characteristics
     225              : - `:vegetation` - Existing and potential vegetation
     226              : - `:disturbance` - Annual and historical disturbances
     227              : - `:topographic` - Elevation, slope, aspect
     228              : - `:fire_regime` - Historical fire patterns
     229              : 
     230              : # Example
     231              : ```julia
     232              : LANDFIRE.products()  # all products
     233              : LANDFIRE.products(category=:fuel)  # only fuel products
     234              : ```
     235              : """
     236           14 : function products(; category::Union{Symbol, Nothing}=nothing)
     237           14 :     if isnothing(category)
     238            1 :         return PRODUCTS
     239              :     else
     240           13 :         return Dict(k => v for (k, v) in PRODUCTS if v.category == category)
     241              :     end
     242              : end
     243              : 
     244              : """
     245              :     versions()
     246              : 
     247              : List available LANDFIRE versions.
     248              : 
     249              : # Example
     250              : ```julia
     251              : LANDFIRE.versions()
     252              : ```
     253              : """
     254            1 : versions() = VERSIONS
     255              : 
     256              : """
     257              :     regions()
     258              : 
     259              : List available LANDFIRE regions.
     260              : 
     261              : # Example
     262              : ```julia
     263              : LANDFIRE.regions()
     264              : ```
     265              : """
     266            1 : regions() = REGIONS
     267              : 
     268              : #-----------------------------------------------------------------------------# WCS/WMS URLs
     269              : 
     270              : """
     271              :     wcs_url(region::Symbol, version::Symbol)
     272              : 
     273              : Get the WCS (Web Coverage Service) URL for a region and version.
     274              : 
     275              : WCS provides pixel-level data access for analysis without downloading.
     276              : 
     277              : # Arguments
     278              : - `region::Symbol`: `:conus`, `:alaska`, or `:hawaii`
     279              : - `version::Symbol`: e.g., `:LF2024`, `:LF2023`, `:LF2020`
     280              : 
     281              : # Example
     282              : ```julia
     283              : LANDFIRE.wcs_url(:conus, :LF2024)
     284              : ```
     285              : """
     286            5 : function wcs_url(region::Symbol, version::Symbol)
     287            6 :     haskey(REGIONS, region) || error("Unknown region: $region. Options: $(keys(REGIONS))")
     288            5 :     haskey(VERSIONS, version) || error("Unknown version: $version. Options: $(keys(VERSIONS))")
     289              : 
     290            3 :     r = REGIONS[region]
     291            3 :     v = VERSIONS[version]
     292            3 :     return "$WCS_BASE/$(r.wcs_code)_$(v.code)/wcs"
     293              : end
     294              : 
     295              : """
     296              :     wms_url(region::Symbol, version::Symbol)
     297              : 
     298              : Get the WMS (Web Map Service) URL for a region and version.
     299              : 
     300              : WMS provides map image access for viewing in GIS applications.
     301              : 
     302              : # Arguments
     303              : - `region::Symbol`: `:conus`, `:alaska`, or `:hawaii`
     304              : - `version::Symbol`: e.g., `:LF2024`, `:LF2023`, `:LF2020`
     305              : 
     306              : # Example
     307              : ```julia
     308              : LANDFIRE.wms_url(:conus, :LF2024)
     309              : # Add ?service=WMS&request=GetCapabilities for capabilities document
     310              : ```
     311              : """
     312            3 : function wms_url(region::Symbol, version::Symbol)
     313            3 :     haskey(REGIONS, region) || error("Unknown region: $region. Options: $(keys(REGIONS))")
     314            3 :     haskey(VERSIONS, version) || error("Unknown version: $version. Options: $(keys(VERSIONS))")
     315              : 
     316            3 :     r = REGIONS[region]
     317            3 :     v = VERSIONS[version]
     318            3 :     return "$WMS_BASE/$(r.wms_code)_$(v.code)/ows"
     319              : end
     320              : 
     321              : """
     322              :     wms_capabilities_url(region::Symbol, version::Symbol)
     323              : 
     324              : Get the WMS GetCapabilities URL for a region and version.
     325              : 
     326              : # Example
     327              : ```julia
     328              : LANDFIRE.wms_capabilities_url(:conus, :LF2024)
     329              : ```
     330              : """
     331            1 : function wms_capabilities_url(region::Symbol, version::Symbol)
     332            1 :     return wms_url(region, version) * "?service=WMS&request=GetCapabilities"
     333              : end
     334              : 
     335              : """
     336              :     wcs_capabilities_url(region::Symbol, version::Symbol)
     337              : 
     338              : Get the WCS GetCapabilities URL for a region and version.
     339              : 
     340              : # Example
     341              : ```julia
     342              : LANDFIRE.wcs_capabilities_url(:conus, :LF2024)
     343              : ```
     344              : """
     345            1 : function wcs_capabilities_url(region::Symbol, version::Symbol)
     346            1 :     return wcs_url(region, version) * "?request=GetCapabilities&service=WCS"
     347              : end
     348              : 
     349              : #-----------------------------------------------------------------------------# Download Functions
     350              : 
     351              : """
     352              :     download_url(product::Symbol, region::Symbol, version::Symbol)
     353              : 
     354              : Get the download URL for a specific product, region, and version.
     355              : 
     356              : Note: Not all product/region/version combinations are available.
     357              : 
     358              : # Example
     359              : ```julia
     360              : LANDFIRE.download_url(:FBFM40, :conus, :LF2024)
     361              : ```
     362              : """
     363            4 : function download_url(product::Symbol, region::Symbol, version::Symbol)
     364            5 :     haskey(PRODUCTS, product) || error("Unknown product: $product. Use `LANDFIRE.products()` to list.")
     365            3 :     haskey(REGIONS, region) || error("Unknown region: $region. Options: $(keys(REGIONS))")
     366            3 :     haskey(VERSIONS, version) || error("Unknown version: $version. Options: $(keys(VERSIONS))")
     367              : 
     368            3 :     r = REGIONS[region]
     369            3 :     v = VERSIONS[version]
     370            3 :     p = PRODUCTS[product]
     371              : 
     372              :     # Build the download path based on product category and naming conventions
     373            3 :     region_code = r.code
     374            3 :     version_code = v.code
     375            3 :     year = v.year
     376              : 
     377              :     # Different products have different URL patterns
     378            3 :     if p.category == :topographic
     379            0 :         dir_suffix = "Topo_$(year)"
     380            0 :         filename = "LF$(year)_$(product)_$(version_code)_$(region_code).zip"
     381            3 :     elseif p.category == :disturbance
     382            1 :         if product == :HDist
     383              :             # Historical disturbance has special naming
     384            1 :             if region == :conus
     385            1 :                 return "$DOWNLOAD_BASE/AnnualDist/USAnnualDisturbance_1999_present.zip"
     386            0 :             elseif region == :alaska
     387            0 :                 return "$DOWNLOAD_BASE/AnnualDist/AKAnnualDisturbance_1999_present.zip"
     388              :             else
     389            0 :                 return "$DOWNLOAD_BASE/AnnualDist/HIAnnualDisturbance_2011_present.zip"
     390              :             end
     391              :         else
     392            0 :             dir_suffix = "Disturbance"
     393            0 :             filename = "LF$(year)_$(product)_$(version_code)_$(region_code).zip"
     394              :         end
     395              :     else
     396            2 :         dir_suffix = version_code
     397            2 :         filename = "LF$(year)_$(product)_$(version_code)_$(region_code == "US" ? "CONUS" : region_code).zip"
     398              :     end
     399              : 
     400            2 :     return "$DOWNLOAD_BASE/$(region_code)_$(dir_suffix)/$filename"
     401              : end
     402              : 
     403              : """
     404              :     download_product(product::Symbol, region::Symbol, version::Symbol; force=false, verbose=true)
     405              : 
     406              : Download a LANDFIRE product for a specific region and version.
     407              : 
     408              : Note: Files can be very large (hundreds of MB to several GB). Not all combinations are available.
     409              : 
     410              : # Arguments
     411              : - `product::Symbol`: Product key (see `LANDFIRE.products()`)
     412              : - `region::Symbol`: `:conus`, `:alaska`, or `:hawaii`
     413              : - `version::Symbol`: e.g., `:LF2024`, `:LF2023`, `:LF2020`
     414              : - `force::Bool`: Re-download even if file exists
     415              : - `verbose::Bool`: Print progress information
     416              : 
     417              : # Returns
     418              : The path to the downloaded file.
     419              : 
     420              : # Example
     421              : ```julia
     422              : # Download FBFM40 fuel model for CONUS (warning: ~3 GB)
     423              : path = LANDFIRE.download_product(:FBFM40, :conus, :LF2024)
     424              : 
     425              : # Download aspect data for Alaska
     426              : path = LANDFIRE.download_product(:Asp, :alaska, :LF2020)
     427              : ```
     428              : """
     429            0 : function download_product(product::Symbol, region::Symbol, version::Symbol;
     430              :                           force::Bool=false, verbose::Bool=true)
     431            0 :     url = download_url(product, region, version)
     432              : 
     433            0 :     mkpath(dir())
     434            0 :     filename = basename(url)
     435            0 :     filepath = joinpath(dir(), filename)
     436              : 
     437            0 :     if isfile(filepath) && !force
     438            0 :         verbose && println("File already exists: $filepath")
     439            0 :         verbose && println("Use `force=true` to re-download.")
     440            0 :         return filepath
     441              :     end
     442              : 
     443            0 :     p = PRODUCTS[product]
     444            0 :     r = REGIONS[region]
     445            0 :     v = VERSIONS[version]
     446              : 
     447            0 :     verbose && println("Downloading: $(p.name)")
     448            0 :     verbose && println("Region: $(r.name)")
     449            0 :     verbose && println("Version: LF $(v.year)")
     450            0 :     verbose && println("URL: $url")
     451              :     # Check if URL is valid first and get file size
     452            0 :     response = HTTP.head(url; status_exception=false, connect_timeout=60, readtimeout=60)
     453            0 :     if response.status != 200
     454            0 :         error("Download not available for this product/region/version combination. HTTP status: $(response.status)")
     455              :     end
     456              : 
     457            0 :     cl = tryparse(Int, HTTP.header(response, "Content-Length", ""))
     458            0 :     if verbose && !isnothing(cl)
     459            0 :         size_mb = round(cl / 1024^2, digits=1)
     460            0 :         println("File size: ~$(size_mb) MB")
     461              :     end
     462              : 
     463            0 :     Downloads.download(url, filepath)
     464              : 
     465            0 :     verbose && println("Saved to: $filepath")
     466            0 :     return filepath
     467              : end
     468              : 
     469              : """
     470              :     list_downloads()
     471              : 
     472              : List all previously downloaded LANDFIRE files in the local data directory.
     473              : 
     474              : # Example
     475              : ```julia
     476              : LANDFIRE.list_downloads()
     477              : ```
     478              : """
     479            1 : function list_downloads()
     480            1 :     d = dir()
     481            1 :     if !isdir(d)
     482            1 :         return String[]
     483              :     end
     484            0 :     return readdir(d)
     485              : end
     486              : 
     487              : #-----------------------------------------------------------------------------# Convenience Functions
     488              : 
     489              : """
     490              :     fuel_products()
     491              : 
     492              : List all fuel-related products.
     493              : """
     494            1 : fuel_products() = products(category=:fuel)
     495              : 
     496              : """
     497              :     vegetation_products()
     498              : 
     499              : List all vegetation-related products.
     500              : """
     501            1 : vegetation_products() = products(category=:vegetation)
     502              : 
     503              : """
     504              :     topographic_products()
     505              : 
     506              : List all topographic products.
     507              : """
     508            1 : topographic_products() = products(category=:topographic)
     509              : 
     510              : """
     511              :     fire_regime_products()
     512              : 
     513              : List all fire regime products.
     514              : """
     515            1 : fire_regime_products() = products(category=:fire_regime)
     516              : 
     517              : """
     518              :     disturbance_products()
     519              : 
     520              : List all disturbance products.
     521              : """
     522            1 : disturbance_products() = products(category=:disturbance)
     523              : 
     524              : end # module
        

Generated by: LCOV version 2.0-1