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
|