{"id":13724663,"url":"https://github.com/forestobservatory/cfo-api","last_synced_at":"2025-05-07T18:33:25.952Z","repository":{"id":53786428,"uuid":"274784327","full_name":"forestobservatory/cfo-api","owner":"forestobservatory","description":"Python wrappers for accessing Forest Observatory data via the Salo API","archived":false,"fork":false,"pushed_at":"2021-03-14T00:37:05.000Z","size":1410,"stargazers_count":14,"open_issues_count":5,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-21T11:07:30.575Z","etag":null,"topics":["api","california","ecology","python-wrappers","wildfire"],"latest_commit_sha":null,"homepage":"https://www.forestobservatory.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/forestobservatory.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-24T22:49:53.000Z","updated_at":"2025-01-01T06:17:27.000Z","dependencies_parsed_at":"2022-09-24T19:51:52.674Z","dependency_job_id":null,"html_url":"https://github.com/forestobservatory/cfo-api","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forestobservatory%2Fcfo-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forestobservatory%2Fcfo-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forestobservatory%2Fcfo-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forestobservatory%2Fcfo-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forestobservatory","download_url":"https://codeload.github.com/forestobservatory/cfo-api/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250430580,"owners_count":21429323,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","california","ecology","python-wrappers","wildfire"],"created_at":"2024-08-03T01:02:01.366Z","updated_at":"2025-05-07T18:33:25.895Z","avatar_url":"https://github.com/forestobservatory.png","language":"Python","readme":"\u003cimg src=\"https://raw.githubusercontent.com/forestobservatory/cfo-api/master/img/cfo-logo.png\" alt=\"California Forest Observatory\" style=\"display: block;margin:auto;\"/\u003e\n\n# Introduction\n\nThe [California Forest Observatory][cfo-web] (CFO) is a data-driven forest monitoring system that maps the drivers of wildfire behavior across the state—including vegetation fuels, weather, topography \u0026 infrastructure—from space.\n\nThe `cfo` library was designed to provide easy access to CFO datasets. Each dataset has a unique `asset_id`, and the `search` and `fetch` workflows were designed to query and download these assets. \n\n- You can search for asset IDs by geography, data type, and time of year\n  - `forest.search(geography=\"SantaCruzCounty\", metric=\"CanopyHeight\", year=2020)`\n- You can download the data to your loacal machine\n  - `forest.download(asset_id, output_file)`\n- If you don't want the file, you can just fetch the download URL\n  - `forest.fetch(asset_id, dl=True)` \n- Or a WMS URL for web mapping \n  - `forest.fetch(asset_id, wms=True)`\n\nYou can find support for the CFO API at the [community forum][cfo-forum].\n\n## License\n\nCFO data are available for free for non-commercial use per the [API terms][api-terms]. You must have a CFO account, which you can create by visiting [the web map][cfo-web], clicking the menu in the top right corner and selecting \"Create an account.\" Please keep track of the e-mail address and password you used to create your Forest Observatory account, as you'll need them to authenticate API access.\n\nThe software provided here, the `cfo` python API wrapper, is provided with an MIT license. Please do not confuse the license terms for the wrapper with the [terms of use][api-terms] for the API.\n\n## Table of contents\n\n- [Installation](#installation)\n- [Authentication](#authentication)\n- [Searching for data](#searching)\n- [Downloading data](#downloads)\n- [Serving map tiles](#map-tiles)\n- [Contact](#contact)\n\n# Installation\n\nThis library can be installed via `pip` directly from Github.\n\n```bash\npip install cfo\n```\n\nIf you don't have `pip` you could also clone the repository locally and install using python's `setuptools`\n\n```bash\ngit clone https://github.com/forestobservatory/cfo-api.git\ncd cfo-api\npython setup.py install\n```\n\nOnce installed, you should be able to load the `cfo` module in python. Instatiate the `api` class to begin working with the Forest Observatory API.\n\n```python\nimport cfo\nforest = cfo.api()\n```\n\n\u003cimg src=\"https://raw.githubusercontent.com/forestobservatory/cfo-api/master/img/cfo-height.png\" alt=\"Canopy Height\" style=\"display:block;margin:auto;width:100%;\"/\u003e\n\n# Authentication\n\nA Forest Observatory account is required to use the API (sign up free at [forestobservatory.com][cfo-web]).\n\nThere are two authentication methods: entering your CFO account's email/password at runtime or setting environment variables.\n\n### Passing your credentials at runtime\n\nUsing any API call (`forest.search()`, `forest.fetch()`, `forest.download()`) will prompt you to enter the following authentication information:\n\n```python\n\u003e\u003e\u003e CFO E-mail: slug@forest.net\n\u003e\u003e\u003e CFO Password: **********\n```\n\nYou can also authenticate directly with `forest.authenticate()`.\n\nThis retrieves an authentication token from the API, which is stored as a temp file for future access (this does not store your e-mail/password). The API reads this stored token, which means you won't have to pass your email/password during each session.\n\n### Setting environment variables \n\nYou can forego runtime credential entry by setting environment variables. This is the lowest friction, least secure approach. You'll set the following variables in your `.bashrc` profile or elsewhere.\n\n```bash\nexport CFO_EMAIL=slug@forest.net\nexport CFO_PASS=ari0limax\n```\n\n### Restoring a botched authentication\n\nThe temp file that stores your authentication credentials can sometimes get donked up. To re-authenticate, use the following command to pass your credentials and overwrite the temporary token data.\n\n```python\nforest.authenticate(ignore_temp=True)\n```\n\n\u003cimg src=\"https://raw.githubusercontent.com/forestobservatory/cfo-api/master/img/cfo-fire.jpg\" alt=\"Spotting during the Rough Fire\" style=\"display:block;margin:auto;width:100%;\"/\u003e\n\n# Searching\n\nCFO data are organized by `asset_id`. These IDs contain information on the spatial extent of the data, the category and name of the data, the time of collection, and the spatial resolution. Asset IDs follow this naming format:\n\n```python\nasset_id = {geography}-{category}-{metric}-{year}-{timeOfYear}-{resolution}\n```\n\nSome examples:\n\n- A statewide vegetation fuels dateset that's rendered in the Layers tab: `California-Vegetation-CanopyHeight-2020-Summer-00010m`.\n- A statewide weather dataset queried in the Trends tab: `California-Weather-WindSpeed-2020-0601-03000m`.\n- A county-level dataset accessed in the Download tab: `Marin-Vegetation-SurfaceFuels-2020-Spring-00010m`.\n\nThe `forest.search()` function queries the API and returns the assets that match the search terms.\n\n```python\n\u003e\u003e\u003e import cfo\n\u003e\u003e\u003e forest = cfo.api()\n\u003e\u003e\u003e forest.search(geography=\"MendocinoCounty\", metric=\"CanopyCover\")\n2020-09-07 13:53:47,028 INFO cfo.utils [authenticate] Loaded cfo token\n['MendocinoCounty-Vegetation-CanopyCover-2020-Fall-00010m']\n```\n\nThe default behavior of this function is to return the asset IDs as a list.\n\nYou could instead return the API JSON data, including asset ID, the spatial extent (`bbox`) of the data, the catalog its stored in, etc. by setting `just_assets=False`.\n\n```python\n\u003e\u003e\u003e forest.search(geography=\"MendocinoCounty\", metric=\"CanopyCover\", just_assets=False)\n[{'asset_id': 'MendocinoCounty-Vegetation-CanopyCover-2020-Fall-00010m',\n'attribute_dict': {},\n'bbox': [-124.022978699284, -122.814767867036, 38.7548320538975, 40.0060478879686],\n'catalog': 'cfo',\n'description': 'CanopyCover',\n'expiration_utc_datetime': '',\n'utc_datetime': '2020-07-09 09:52:42.292286+00:00'}]\n```\n\nAnd to examine the full response from the `requests` library, use `forest.search(raw=True)`.\n\nBut with over 17,000 published assets it's not easy to know just what to search by. So we wrote some functions to simplify your searches.\n\n### Convenience functions\n\nBased on the asset ID naming convention above, we've provided some `list` functions as a guide to what's available.\n\n- Geography - CFO datasets have been clipped to different spatial extents: statewide, by county, by municipality, by watershed.\n  - `forest.list_geographies()` - returns the different geographic extents. Use `forest.list_geographies(by=\"County\")` to narrow return just the unique counties.\n  - `forest.list_geography_types()` - returns the categories of geographical clipping available.\n- Category - we currently provide three categories of data.\n  - `forest.list_categories()` - returns [`Vegetation`, `Weather`, `Wildfire`]\n- Metric - each category of data contains a list of different available data types\n  - `forest.list_metrics()` - returns the unique metrics for each category. \n  - Run `forest.list_metrics(category=\"Weather\")` to return only weather-specific metrics.\n\nUse these as keywords when searching for data (e.g. `id_list = forest.search(geography=\"FresnoCounty\", category=\"Vegetation\")`).\n\nYou can also use wildcards:\n\n```python\n\u003e\u003e\u003e forest.search(geography='Plumas*', metric='CanopyHeight')\n['PlumasCounty-Vegetation-CanopyHeight-2020-Fall-00010m',\n'PlumasEurekaMunicipality-Vegetation-CanopyHeight-2020-Fall-00010m',\n'PlumasLakeMunicipality-Vegetation-CanopyHeight-2020-Fall-00010m']\n```\n\n### A note on availabile datasets\n\nEven though we have a range of geographic extents, resolutions, and metrics, it is **not** the case that we provide all permutations of extent/resolution/metric. For example, we clip all `Vegetation` data to the county level, but we do not clip any `Weather` data that fine. All weather data are only available at the state level. \n\nThis means you don't really need to specify the geographic extent if you search for weather data. You'll get pretty far with `wind_ids = forest.search(metric=\"WindSpeed\")`.\n\n\u003cimg src=\"https://raw.githubusercontent.com/forestobservatory/cfo-api/master/img/cfo-understory.png\" alt=\"Redwood understory\" style=\"display:block;margin:auto;width:100%;\"/\u003e\n\n# Downloads\n\nOnce you've generated a list of asset IDs, you can then download the files to your local machine. The `forest.download()` function requires an asset_id string so you'll have to iterate over search results, which are often returned as lists.\n\nHere's how to search for and download all data from Mendocino County.\n\n```python\nimport cfo\nforest = cfo.api()\nasset_ids = forest.search(geography=\"MendocinoCounty\")\nfor asset in asset_ids:\n    forest.download(asset)\n```\n\nWhich generates the following output as it downloads each file.\n\n```\n2020-09-07 16:19:24,542 INFO cfo.utils [download] Beginning download for: MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m\n2020-09-07 16:19:28,853 INFO cfo.utils [download] Successfully downloaded MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m to file: /home/slug/MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m.tif\n2020-09-07 16:19:29,359 INFO cfo.utils [download] Beginning download for: MendocinoCounty-Vegetation-CanopyBaseHeight-2020-Fall-00010m\n2020-09-07 16:19:32,321 INFO cfo.utils [download] Successfully downloaded MendocinoCounty-Vegetation-CanopyBaseHeight-2020-Fall-00010m to file: /home/slug/MendocinoCounty-Vegetation-CanopyBaseHeight-2020-Fall-00010m.tif\n...\n```\n\nThis function uses the `fetch()` command under the hood to retrieve a URL for where the file is hosted on google cloud storage. It then performs a `GET` call to download the file locally. \n\nThe function will download the file to your current working directory if you don't specify an output file path. You can set a custom output path with `forest.download(asset_id, path)`. This may be tricky if you're downloading multiple datasets, but you could parse the asset_id to generate useful names for output files.\n\n```python\nasset_ids = forest.search(geography=\"MendocinoCounty\")\nfor asset in asset_ids:\n    geo, category, metric, year, timeOfYear, res = asset.split(\"-\")\n    output_path = f\"/external/downloads/CFO-{metric}-{year}.tif\"\n    forest.download(asset, output_path)\n```\n\nWhich generates the following output:\n\n```\n2020-09-07 23:10:02,312 INFO cfo.utils [download] Beginning download for: MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m\n2020-09-07 23:10:25,163 INFO cfo.utils [download] Successfully downloaded MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m to file: /external/downloads/CFO-CanopyHeight-2020.tif\n2020-09-07 23:10:25,596 INFO cfo.utils [download] Beginning download for: MendocinoCounty-Vegetation-CanopyBaseHeight-2020-Fall-00010m\n2020-09-07 23:10:47,965 INFO cfo.utils [download] Successfully downloaded MendocinoCounty-Vegetation-CanopyBaseHeight-2020-Fall-00010m to file: /external/downloads/CFO-CanopyBaseHeight-2020.tif\n...\n```\n\u003cimg src=\"img/cfo-terrain.jpg\" alt=\"Mono Lake\" style=\"display:block;margin:auto;width:100%;\"/\u003e\n\n# Map tiles\n\nThe `fetch` function also returns URLs for displaying CFO data in web mapping applications as WMS tile layers.\n\n```python\nforest.fetch(\"MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m\", wms=True)\n'https://maps.salo.ai/geoserver/cfo/wms?layers=cfo:MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m\u0026format=\"image/png\"\u0026styles=vegetation\u0026p0=0.0\u0026p2=1.44\u0026p25=18.0\u0026p30=21.599999999999998\u0026p50=36.0\u0026p60=43.199999999999996\u0026p75=54.0\u0026p90=64.8\u0026p98=70.56\u0026p100=72.0'\n\n```\n\nWMS URLs don't always easily plug and play with different rendering services, but they should work with a little nudging. Here's how to use the above URL to visualize these data in a jupyter notebook with `ipyleaflet`.\n\n```python\nfrom ipyleaflet import Map, WMSLayer, LayersControl, basemaps\nwms = WMSLayer(\n    url='https://maps.salo.ai/geoserver/cfo/wms?p0=0.0\u0026p2=1.44\u0026p25=18.0\u0026p30=21.599999999999998\u0026p50=36.0\u0026p60=43.199999999999996\u0026p75=54.0\u0026p90=64.8\u0026p98=70.56\u0026p100=72.0',\n    layers=\"cfo:MendocinoCounty-Vegetation-CanopyHeight-2020-Fall-00010m\",\n    name=\"Mendocino Canopy Height\",\n    styles=\"vegetation\",\n    format=\"image/png8\",\n    transparent=True,\n    attribution=\"Forest Observatory © \u003ca href=https://salo.ai'\u003eSalo Sciences\u003c/a\u003e\",\n)\nm = Map(basemap=basemaps.Stamen.Terrain, center=(39.39,-123.33), zoom=10)\nm.add_layer(wms)\ncontrol = LayersControl(position='topright')\nm.add_control(control)\nm\n```\n\nThis code, executed in `jupyter-lab`, should look something like this.\n\n\u003cimg src=\"https://raw.githubusercontent.com/forestobservatory/cfo-api/master/img/ipyleaflet-example.jpg\" alt=\"CFO WMS example\" style=\"display:block;margin:auto;width:100%;\"/\u003e\n\nThe URL has a lot of useful information. Here's a quick breakdown of what's encoded in the string returned from `fetch`. \n\n- The base URL (`https://maps.salo.ai/geoserver/cfo/wms`) is our map server address.\n- Each component following the `?` is a parameter passed to the map server.\n- `layers` specifies which asset to show (and is defined based on `{catalog}:{asset_id}` naming).\n- `format` defines the image format the data are rendered in (use `image/png8` for best performance).\n- `styles` defines the color palette (which you can retrieve with `forest.list_styles()`).\n- the long list of `p0, p2, p25, ..., p100` are parameters we use to render custom raster styles on the fly. These numbers are based on the min/max raster values of a dataset and can be altered on the fly to dynamically scale the data.\n\n# Contact\n\nIssue tracking isn't set up for this repository yet. Please visit the [Forest Observatory Community Forum][cfo-forum] for technical support. To get in touch directly or to inquire about commercial API access, contact [tech@forestobservatory.com](mailto:tech@forestobservatory.com).\n\nThe California Forest Observatory API is developed and maintained by [Salo Sciences][salo-web].\n\n\n[api-terms]: https://forestobservatory.com/api.html\n[cfo-web]: https://forestobservatory.com\n[cfo-forum]: https://groups.google.com/a/forestobservatory.com/g/community\n[salo-web]: https://salo.ai\n","funding_links":[],"categories":["Biosphere"],"sub_categories":["Forest Observation and Management"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforestobservatory%2Fcfo-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforestobservatory%2Fcfo-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforestobservatory%2Fcfo-api/lists"}