diff --git a/.github/PULL_REQUEST_TEMPLATE/new-api-endpoint.md b/.github/PULL_REQUEST_TEMPLATE/new-api-endpoint.md index feafbb8b..8de1fae3 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new-api-endpoint.md +++ b/.github/PULL_REQUEST_TEMPLATE/new-api-endpoint.md @@ -1,4 +1,4 @@ -# New API Endpoint(s): +# New API Endpoint(s): Closes # ### Checklist @@ -12,4 +12,3 @@ Closes # * **Tests** - [ ] Add sample response data - [ ] Add unit tests for new endpoint(s) - diff --git a/.github/pre-commit.yml b/.github/pre-commit.yml new file mode 100644 index 00000000..dc7679c8 --- /dev/null +++ b/.github/pre-commit.yml @@ -0,0 +1,23 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + - repo: https://github.com/timothycrosley/isort + rev: 5.6.4 + hooks: + - id: isort + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + - repo: git://github.com/luismayta/pre-commit-mypy + rev: 0.1.1 + hooks: + - id: mypy diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..a12ee3a4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,93 @@ +name: Build + +on: + push: + branches: [master, dev] + tags: ['v*'] + pull_request: + branches: [master, dev] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-18.04 + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + # Cache packages per python version, and reuse until setup.py changes + - name: Cache pip packages + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + restore-keys: ${{ runner.os }}-pip-${{ matrix.python-version }} + + - name: Install dependencies + run: pip install ".[dev]" + - name: Run unit tests + run: pytest + + analyze: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.8' + - name: Cache pip packages + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-3.8-${{ hashFiles('setup.py') }} + restore-keys: ${{ runner.os }}-pip-3.8 + + - name: Install dependencies + run: pip install ".[dev]" + - name: Run style checks + run: | + black --check --diff . + isort --check --diff . + - name: Run linter + run: flake8 . + - name: Run type checks + run: mypy . + - name: Generate code coverage report + run: pytest --cov --cov-report=term --cov-report=html + - name: Send code coverage report to Coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: coveralls --service=github + - name: Test Sphinx documentation build + run: make -C docs all + - name: Test package build + run: | + python setup.py sdist bdist_wheel + twine check dist/* + + # Deploy pre-release builds from dev branch, and stable builds on tags only + release: + needs: [test, analyze] + if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/dev' + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install dependencies + run: pip install -U ".[build]" + - name: Build wheel + run: python setup.py sdist bdist_wheel + - name: Deploy to pypi + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + run: twine upload dist/* diff --git a/.gitignore b/.gitignore index b245282f..f2ab11f0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,17 +6,19 @@ # Packages *.egg *.egg-info -dist -build -eggs -parts +.installed.cfg +.mypy_cache/ bin -var -sdist +build develop-eggs -.installed.cfg +dist +eggs lib lib64 +parts +sdist +var +venv/ # Installer logs pip-log.txt @@ -48,8 +50,9 @@ docs/modules/ # Mac OS X .DS_Store -.idea -.mypy_cache -venv/ - +# Editors +.~* +.idea/ +.ipynb_checkpoints/ swagger.json +dev_*.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 19c38841..00000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Config file for automatic testing at travis-ci.org - -sudo: required -language: python -dist: xenial - -matrix: - include: - # Note: Python 3.5 will reach EOL in September 2020 - - python: "3.5" - - python: "3.6" - - python: "3.7" - # Run a separate job for combined code quality checks & build tests - - python: "3.8" - env: TOXENV=coverage,mypy,style,docs,dist-test - # Only this job will contain an encrypted PYPI_TOKEN for deployment - - python: "3.8" - env: - - secure: QFRSBbtAPfFxO2GONfI2KCrq7gdSI6zj4wI5QEunjz8xVjvb60TD2Q4ULmoMPBqgFkqV6NZr6bB49MpNcpfw9aeBkUqjYmWWkLfZZsFWvJeRnkPV4EcfmMKJ9pph9QUYQ1BUcqWPZbPYk0VQdDxMVwDKETW2nzVNgRFaJm4O9kM6qV462/3dN53Q0NNTBfSju80nV8YQycP7onEQQILFityDOThKqR3FctDiwrfMo3ALWSfghkyvCgvrvpf6uAWncM1hwDwga+1Luo3vDG6reDE+M3AitFwo5XsPXy8hER8BwuUh8MRrr/HbakH66e83vvBX593DoGZr7UMqwEg+Xzpa2yUvSszW2U9ksh9BaYPbZG3nzsJJlTg61k6Sh7OU4AXglsDBPVX5aoLVz6XSAL97p+Ec85yo8dzpxzBc31ow8rI6JS9yycRnHXGATKqTIQ7Z3QiVaNKrIJVKhMdqvng1zb8RFR49MbPFT5QQuxOJLe/S1qkzOjclp2scZV17TPa6hkfTuDz01+rPXKh3US0WbR689TtNCBB/UwuoX5vnFqcvmqi0sZtl0G4eqXPvik4r7JnuX3d81LIGzF6e45FyO8sSvicZxCu8KmQQ7xI0hXjP6+vfEOnnz4xpAKmHmf2xTIStpuaCm7xqdt3M070P/A4kC0xBGcbcNCq1xU4= - - python: "3.9" - -# Install dependencies, run tests, and run coveralls after 'coverage' job -install: pip install ".[build]" -script: tox -after_success: if [[ $TOXENV == *"coverage"* ]]; then coveralls; fi - -# Note: These two release types could be combined, but requires a long, ugly custom condition -# See: https://docs.travis-ci.com/user/deployment#conditional-releases-with-on -deploy: - # Stable releases: only git tags on master branch - - provider: pypi - user: __token__ - password: $PYPI_TOKEN - distributions: sdist bdist_wheel - skip_cleanup: true # Don't delete build artifacts - skip_existing: true # Don't overwrite an existing package with the same name - on: - tags: true - condition: $PYPI_TOKEN # Only run in the job containing an encrypted PYPI_TOKEN - # Pre-releases: dev branch only; pre-release version number is set in __init__.py - - provider: pypi - user: __token__ - password: $PYPI_TOKEN - distributions: sdist bdist_wheel - skip_cleanup: true - skip_existing: true - on: - branch: dev - condition: $PYPI_TOKEN diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..dc2f05dd --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,77 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at nicolas@niconoe.eu. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5b54513..6059f19f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,17 +3,26 @@ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. +## Installation +To set up for local development: +```bash +$ git clone https://github.com/niconoe/pyinaturalist.git +$ cd pyinaturalist +$ pip install -Ue ".[dev]" +$ # Optional but recommended: +$ pre-commit install --config .github/pre-commit.yml +``` + ## Contribution Guidelines ### Documentation - We use [Sphinx](http://www.sphinx-doc.org/en/master/), and the references page is automatically generated thanks to `sphinx.ext.autodoc` and `sphinx_autodoc_typehints` extensions. All functions / methods / classes should have proper docstrings. To build the docs locally: ```bash -$ tox -e docs +$ make -C docs html ``` To preview: @@ -24,26 +33,45 @@ $ open docs/_build/index.html $ xdg-open docs/_build/index.html ``` -[Hosted documentation](https://pyinaturalist.readthedocs.io/) is automatically updated when code -gets pushed to the `master` branch. +Documentation is automatically built by ReadTheDocs whenever code is merged into either `master` or `dev`: +* [Stable release (master branch)](https://pyinaturalist.readthedocs.io/en/stable/) +* [Development pre-release (dev branch)](https://pyinaturalist.readthedocs.io/en/latest/) For any new or changed behavior, add a brief high-level summary to `HISTORY.md`. This isn't needed for internal changes (tests, other docs, refactoring, etc.). ### Tests - We use the [pytest](https://docs.pytest.org/en/latest/) framework for unit testing. - Just run the `pytest` command to run locally. -We also use [tox](https://tox.readthedocs.io/en/latest/) to test multiple python versions, as well as test coverage, style, and type checks: - -Use the `tox` command to run locally. This is also run by Travis CI on all pull requests. +We also use [tox](https://tox.readthedocs.io/en/latest/) to test multiple python versions. +Use the `tox` command to run locally. This is also run by GitHub Actions on all pull requests. ### Type Annotations +All parameters and return values should have type annotations, which will be checked by `mypy` and +will show up in the documentation. -All functions / methods should have parameters and return value type annotations. -Those type annotations are checked by MyPy (`tox -e mypy`) and will appear in the documentation. +### Formatting, Linting, Type Checking, Etc. +Code checking and formatting tools used include: +* [black](https://github.com/psf/black) +* [isort](https://pycqa.github.io/isort/) +* [flake8](https://flake8.pycqa.org/en/latest/) +* [mypy](https://mypy.readthedocs.io/en/stable/getting_started.html) + +All of these will be run by GitHub Actions on pull requests. + +#### Pre-Commit Hooks +Optionally, there is included config to easily set these up to run as a +[pre-commit hook](https://github.com/pre-commit/pre-commit): +```bash +pre-commit install --config .github/pre-commit.yml +``` + +This can save you some time in that it will show you errors immediately rather than waiting for CI +jobs to complete. You can disable these hooks at any time with: +```bash +pre-commit uninstall +``` ### Pull Requests Here are some general guidelines for submitting a pull request: @@ -56,7 +84,7 @@ Here are some general guidelines for submitting a pull request: ### Releases For maintainers: -Releases are based on git tags. Travis CI will build and deploy packages to PyPi on tagged commits +Releases are based on git tags. GitHub Actions will build and deploy packages to PyPi on tagged commits on the `master` branch. Release steps: - Update the version in `pyinaturalist/__init__.py` @@ -93,7 +121,7 @@ official pyinaturalist docs, in docstrings, or even on the web in blog posts, articles, and such. ### Submit Feedback -The best way to send feedback is to file an issue at https://github.com/niconoe/pyinaturalist/issues. +The best way to send feedback is to [create an issue](https://github.com/niconoe/pyinaturalist/issues/new/choose) If you are proposing a feature: diff --git a/HISTORY.md b/HISTORY.md index 85cc5de8..0bbf8668 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,44 @@ # History -## 0.11.0 (2020-11-04) +## 0.12 (2021-02-02) +[See all Issues & PRs](https://github.com/niconoe/pyinaturalist/milestone/3?closed=1) + +### New Endpoints +* Added new function for **Observation Histogram** endpoint: `get_observation_histogram()` +* Added new function for **Observers** endpoint: `get_observation_observers()` +* Added new function for **Identifiers** endpoint: `get_observation_identifiers()` +* Added new function for **Controlled Terms** endpoints: `get_controlled_terms()` + * Wraps both `GET /controlled_terms` and `/controlled_terms/for_taxon` endpoints + +### Modified Endpoints +* Added conversion from date/time strings to timezone-aware python `datetime` objects. + This applies to the following functions: + * `node_api.get_observation()` + * `node_api.get_observations()` + * `node_api.get_all_observation()` + * `node_api.get_projects()` + * `node_api.get_projects_by_id()` + * `node_api.get_taxa()` + * `node_api.get_taxa_by_id()` + * `rest_api.get_observation()` + * `rest_api.get_observation_fields()` + * `rest_api.get_all_observation_fields()` +* Added conversion for an additional `location` field in observation responses + +### Authentication +* Added support for providing credentials via environment variables +* Added integration with system keyring for credentials storage +* Added documentation & examples for authentication options + +### Other Changes +* Added a `Dockerfile` and `docker-compose.yml` for a Jupyter notebook containing pyinaturalist + other relevant packages +* Added some more detailed usage examples under `examples/` +* Improved performance for large paginated queries +* Fixed bug that dropped request parameter values of `0` as if they were `None` +* Dropped support for python 3.5 +* Removed request parameters that were deprecated in 0.11 + +## 0.11 (2020-11-04) [See all Issues & PRs](https://github.com/niconoe/pyinaturalist/milestone/2?closed=1) ### New Endpoints @@ -44,7 +82,7 @@ * Added testing & support for python 3.9 * Added parameter validation for multiple-choice request parameters -## 0.10.0 (2020-06-16) +## 0.10 (2020-06-16) [See all Issues & PRs](https://github.com/niconoe/pyinaturalist/milestone/1?closed=1) ### New Endpoints @@ -61,9 +99,9 @@ ## 0.9.1 (2020-05-26) -* Bugfix: proper support for boolean and integer list parameters (see https://github.com/niconoe/pyinaturalist/issues/17). +* Bugfix: proper support for boolean and integer list parameters ([Issue #17](https://github.com/niconoe/pyinaturalist/issues/17)) -## 0.9.0 (2020-05-06) +## 0.9 (2020-05-06) ### New Endpoints * Added new functions for Node API **Taxa** endpoints: @@ -72,38 +110,38 @@ * `node_api.get_taxa_autocomplete()` * `node_api.get_taxa_by_id()` -## 0.8.0 (2019-07-11) +## 0.8 (2019-07-11) * All functions now take an optional `user-agent `_ parameter in order to identify yourself to iNaturalist. If not set, `Pyinaturalist/` will be used. -## 0.7.0 (2019-05-08) +## 0.7 (2019-05-08) * `rest_api.delete_observation()` now raises `ObservationNotFound` if the observation doesn't exist * minor dependencies update for security reasons -## 0.6.0 (2018-11-15) +## 0.6 (2018-11-15) * New function: `rest_api.delete_observation()` -## 0.5.0 (2018-11-05) +## 0.5 (2018-11-05) * New function: `node_api.get_observation()` -## 0.4.0 (2018-11-05) +## 0.4 (2018-11-05) * `create_observation()` now raises exceptions in case of errors. -## 0.3.0 (2018-11-05) +## 0.3 (2018-11-05) * `update_observation()` now raises exceptions in case of errors. -## 0.2.0 (2018-10-31) +## 0.2 (2018-10-31) * Better infrastructure (type annotations, documentation, ...) * Dropped support for Python 2. * New function: `update_observation()` * `rest_api.AuthenticationError` is now `exceptions.AuthenticationError` -## 0.1.0 (2018-10-10) +## 0.1 (2018-10-10) * First release on PyPI. diff --git a/MANIFEST.in b/MANIFEST.in index f7d4f773..178c9228 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,7 @@ -include AUTHORS.rst -include CONTRIBUTING.rst -include HISTORY.rst +include AUTHORS.md +include CONTRIBUTING.md +include HISTORY.md include LICENSE -include README.rst +include README.md + +recursive-exclude test * diff --git a/README.md b/README.md index e821c435..5ae77a96 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # pyinaturalist - -[![Build Status](https://img.shields.io/travis/niconoe/pyinaturalist)](https://travis-ci.org/github/niconoe/pyinaturalist) +[![Build status](https://github.com/niconoe/pyinaturalist/workflows/Build/badge.svg)](https://github.com/niconoe/pyinaturalist/actions) [![Documentation Status (stable)](https://img.shields.io/readthedocs/pyinaturalist/stable?label=docs%20%28master%29)](https://pyinaturalist.readthedocs.io/en/stable/) [![Documentation Status (latest)](https://img.shields.io/readthedocs/pyinaturalist/latest?label=docs%20%28dev%29)](https://pyinaturalist.readthedocs.io/en/latest/) [![Coverage Status](https://coveralls.io/repos/github/niconoe/pyinaturalist/badge.svg?branch=master)](https://coveralls.io/github/niconoe/pyinaturalist?branch=master) @@ -9,83 +8,116 @@ [![PyPI - Python Versions](https://img.shields.io/pypi/pyversions/pyinaturalist)](https://pypi.org/project/pyinaturalist) [![PyPI - Format](https://img.shields.io/pypi/format/pyinaturalist?color=blue)](https://pypi.org/project/pyinaturalist) -Python client for the [iNaturalist APIs](https://www.inaturalist.org/pages/api+reference). -See full documentation at https://pyinaturalist.readthedocs.io. +Pyinaturalist is an iNaturalist API client for python. +See full project documentation at https://pyinaturalist.readthedocs.io. + +* [Summary](#pyinaturalist) + + [Features](#features) + + [Installation](#installation) + + [Development Status](#development-status) +* [Usage Examples](#usage-examples) + + [Observations](#observations) + + [Species](#species) +* [Feedback](#feedback) + +## Features + +[iNaturalist](https://www.inaturalist.org) is a rich source of biodiversity data, and offers an +extensive API to interact with nearly every aspect of its platform. + +If you want to explore or analyze these data using python, then pyinaturalist can help. +It adds a number of python-specific conveniences, including: + +* **Requests:** Simplified usage with python types and data structures rather than raw request parameter strings +* **Responses:** Type conversions from JSON primitives to things you would expect in python +* **Typing:** Complete parameter definitions with type annotations, which significantly enhances + usability within an IDE, Jupyter notebook, or any other environment with type checking & autocompletion +* **Messages:** Improved error handling, which means less time spent figuring out why an API call failed +* **Docs:** Thorough documentation with example requests and responses +* **Security:** Keyring integration for secure credential storage +* **Testing:** A dry-run mode to preview your requests before potentially modifying data + +Many of the most relevant API endpoints are implemented, including: +* **Searching for:** + * controlled terms + * observations + * observation fields + * observation species counts + * places + * projects + * species +* **Text search autocompletion for:** + * places + * species +* **Creating and updating:** + * observations + * observation fields + * observation photos + +See [Endpoints](https://pyinaturalist.readthedocs.io/en/latest/endpoints.html) for a complete list of +implemented endpoints, and see [Reference](https://pyinaturalist.readthedocs.io/en/latest/reference.html) +to skip straight to the API docs. ## Installation Install the latest stable version with pip: - ```bash $ pip install pyinaturalist ``` -Or, if you would like to use the latest development (non-stable) version: - +Or, if you would like to use the latest development (pre-release) version: ```bash $ pip install --pre pyinaturalist ``` -To set up for local development (preferably in a new virtualenv): - +To install with minimal dependencies (which disables some optional features): ```bash -$ git clone https://github.com/niconoe/pyinaturalist.git -$ cd pyinaturalist -$ pip install -Ue ".[dev]" +$ pip install --no-deps pyinaturalist +$ pip install python-dateutil requests ``` -## Development Status - -Pyinaturalist is under active development. Currently, a handful of the most relevant API endpoints -are implemented, including: - -* Searching, creating, and updating observations and observation fields -* Searching for places, projects, species, and species counts -* Text search autocompletion for species and places - -See below for some examples, -see [Endpoints](https://pyinaturalist.readthedocs.io/en/latest/endpoints.html) for a complete list of implemented endpoints, and -see [Issues](https://github.com/niconoe/pyinaturalist/issues) for planned & proposed features. +See [Contributing](https://pyinaturalist.readthedocs.io/en/latest/contributing.html) for details on +setup for local development. -More endpoints will continue to be added as they are needed. -Please **create an issue** if there is an endpoint you would like to have added, and **PRs are welcome!** - -**Note:** +## Development Status +Pyinaturalist is under active development. More endpoints and features will continue to be added as +they are needed or requested. -The two iNaturalist APIs expose a combined total of 103 endpoints\*. Some of these are generally -useful and could potentially be added to pyinaturalist, but many others are primarily for -internal use by the iNaturalist web application and mobile apps, and are unlikely to be added -unless there are specific use cases for them. +* See [History](https://github.com/niconoe/pyinaturalist/blob/dev/HISTORY.md) for details on past and current releases +* See [Issues](https://github.com/niconoe/pyinaturalist/issues) for planned & proposed features +* [Create an issue](https://github.com/niconoe/pyinaturalist/issues/new/choose) if there is an endpoint + or feature you would like to have added +* **PRs are welcome!** -\*As of 2020-10-01: 37 in REST API, 65 in Node API, and 1 undocumented +## Usage Examples +Following are usage examples for some of the most commonly used basic features. -## Examples +Also see the **examples/** folder for some more detailed examples. ### Observations #### Search observations - +There are [numerous fields you can search on](https://pyinaturalist.readthedocs.io/en/latest/modules/pyinaturalist.node_api.html#pyinaturalist.node_api.get_observations). +An obvious search to start with would be getting all of your own observations: ```python from pyinaturalist.node_api import get_all_observations obs = get_all_observations(user_id='my_username') ``` -See [available parameters](https://api.inaturalist.org/v1/docs/#!/Observations/get_observations/) - #### Get an access token - For authenticated API calls (creating/updating/deleting data), you first need to obtain an access token. This requires creating an [iNaturalist app](https://www.inaturalist.org/oauth/applications/new). - ```python -from pyinaturalist.rest_api import get_access_token +from pyinaturalist.auth import get_access_token token = get_access_token( - username='', - password='', - app_id='', - app_secret='', + username='my_username', + password='my_password', + app_id='my_app_id', + app_secret='my_app_secret', ) ``` +See [Authentication](https://pyinaturalist.readthedocs.io/en/latest/general_usage.html#authentication) +for additional authentication options, including environment variables and keyring services. #### Create a new observation ```python @@ -102,9 +134,7 @@ response = create_observation( longitude=4.360216, positional_accuracy=50, # meters, # sets vespawatch_id (an observation field whose ID is 9613) to the value '100'. - observation_field_values_attributes=[ - {'observation_field_id': 9613,'value': 100}, - ], + observation_fields={9613: 100}, access_token=token, ) new_observation_id = response[0]['id'] @@ -114,18 +144,18 @@ new_observation_id = response[0]['id'] ```python from pyinaturalist.rest_api import add_photo_to_observation -r = add_photo_to_observation( +add_photo_to_observation( new_observation_id, access_token=token, photo='/Users/nicolasnoe/vespa.jpg', ) ``` -#### Update an existing observation of yours +#### Update an existing observation ```python from pyinaturalist.rest_api import update_observation -r = update_observation( +update_observation( 17932425, access_token=token, description='updated description !', @@ -135,42 +165,45 @@ r = update_observation( #### Get a list of all (globally available) observation fields ```python from pyinaturalist.rest_api import get_all_observation_fields -r = get_all_observation_fields(search_query="DNA") +response = get_all_observation_fields(search_query="DNA") ``` -#### Set an observation field value on an existing observation +#### Set an observation field on an existing observation +[Observation Fields](https://www.inaturalist.org/pages/extra_fields_nz) are a way to add extra information +to your observations. They are similar to tags, but with a typed value. ```python -from pyinaturalist.rest_api import put_observation_field_values +from pyinaturalist.rest_api import get_observation_fields, put_observation_field_values + +# First find an observation field by name, if the ID is unknown +response = get_observation_fields('vespawatch_id') +observation_field_id = response[0]['id'] put_observation_field_values( observation_id=7345179, - observation_field_id=9613, + observation_field_id=observation_field_id, value=250, access_token=token, ) ``` #### Get observation data in alternative formats -A separate endpoint can provide other data formats, including Darwin Core, KML, and CSV: - +A [separate endpoint](https://pyinaturalist.readthedocs.io/en/latest/modules/pyinaturalist.rest_api.html#pyinaturalist.rest_api.get_observations) +can provide other data formats, including Darwin Core, KML, and CSV: ```python from pyinaturalist.rest_api import get_observations obs = get_observations(user_id='niconoe', response_format='dwc') ``` -See [available parameters and formats](https://www.inaturalist.org/pages/api+reference#get-observations) - #### Get observation species counts -There is an additional endpoint to get counts of observations by species. -On the iNaturalist web UI, this information can be found on the 'Species' tab of search results. +You can also get counts of observations by species. On the iNaturalist web UI, +this information can be found on the 'Species' tab of search results. For example, to get the counts of all your own research-grade observations: - ```python from pyinaturalist.node_api import get_observation_species_counts obs_counts = get_observation_species_counts(user_id='my_username', quality_grade='research') ``` -### Taxonomy +### Species #### Search species and other taxa Let's say you partially remember either a genus or family name that started with **'vespi'**-something: @@ -192,7 +225,7 @@ from the results above: {343248: "Polistinae", 84738: "Vespinae", 119344: "Eumeninae", 121511: "Masarinae", ...} ``` -### Get a species by ID +#### Get a species by ID Let's find out more about this 'Polistinae' genus. We could search for it by name or by ID, but since we already know the ID from the previous search, let's use that: @@ -260,3 +293,15 @@ info. For example: >>> first_result["matched_term"] "Zygocactus truncatus" # An older synonym for Schlumbergera ``` + +### ...And much more! +Check out the [Reference](https://pyinaturalist.readthedocs.io/en/latest/reference.html) section to +see what else you can do with pyinaturalist. + +## Feedback +If you have any problems, suggestions, or questions about pyinaturalist, please let us know! +Just [create an issue here](https://github.com/niconoe/pyinaturalist/issues/new/choose). + +**Note:** pyinaturalist is not directly affiliated with iNaturalist.org or the +California Academy of Sciences. If you have non-python-specific questions about iNaturalist, the +[iNaturalist Community Forum](https://forum.inaturalist.org/) is going to be your best resource. diff --git a/docs/conf.py b/docs/conf.py index 3574e2fd..c4a848e4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,48 +5,48 @@ DOCS_DIR = abspath(dirname(__file__)) PROJECT_DIR = dirname(DOCS_DIR) -PACKAGE_DIR = join(PROJECT_DIR, "pyinaturalist") +PACKAGE_DIR = join(PROJECT_DIR, 'pyinaturalist') # Source paths and symlink paths for static content to include -SRC_IMAGE_DIR = join(DOCS_DIR, "images") -SYMLINK_IMAGE_DIR = join(DOCS_DIR, "docs", "images") -SRC_DATA_DIR = join(PROJECT_DIR, "test", "sample_data") -SYMLINK_DATA_DIR = join(DOCS_DIR, "sample_data") +SRC_IMAGE_DIR = join(DOCS_DIR, 'images') +SYMLINK_IMAGE_DIR = join(DOCS_DIR, 'docs', 'images') +SRC_DATA_DIR = join(PROJECT_DIR, 'test', 'sample_data') +SYMLINK_DATA_DIR = join(DOCS_DIR, 'sample_data') # Add project path so we can import our package sys.path.insert(0, PROJECT_DIR) -from pyinaturalist import __version__ +from pyinaturalist import __version__ # noqa # General information about the project. -project = "pyinaturalist" -copyright = "2020, Nicolas Noé" -needs_sphinx = "3.0" -master_doc = "index" -source_suffix = [".rst", ".md"] +project = 'pyinaturalist' +copyright = '2020, Nicolas Noé' +needs_sphinx = '3.0' +master_doc = 'index' +source_suffix = ['.rst', '.md'] version = release = __version__ -html_static_path = ["_static"] -templates_path = ["_templates"] +html_static_path = ['_static'] +templates_path = ['_templates'] # Exclude the generated pyinaturalist.rst, which will just contain top-level __init__ info # and add an extra level to the toctree -exclude_patterns = ["_build", "modules/pyinaturalist.rst"] +exclude_patterns = ['_build', 'modules/pyinaturalist.rst'] # Sphinx extension modules extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.napoleon", - "sphinx.ext.viewcode", - "sphinx_autodoc_typehints", - "sphinx_automodapi.automodapi", - "sphinx_automodapi.smart_resolver", - "sphinxcontrib.apidoc", - "m2r2", + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinx_autodoc_typehints', + 'sphinx_automodapi.automodapi', + 'sphinx_automodapi.smart_resolver', + 'sphinxcontrib.apidoc', + 'm2r2', ] # Enable automatic links to other projects' Sphinx docs intersphinx_mapping = { - "requests": ("https://requests.readthedocs.io/en/master/", None), + 'requests': ('https://requests.readthedocs.io/en/master/', None), } # Enable Google-style docstrings @@ -63,21 +63,21 @@ # Use apidoc to auto-generate rst sources # Added here instead of instead of in Makefile so it will be used by ReadTheDocs apidoc_module_dir = PACKAGE_DIR -apidoc_output_dir = "modules" -apidoc_excluded_paths = ["api_docs.py"] +apidoc_output_dir = 'modules' +apidoc_excluded_paths = ['api_docs.py'] apidoc_module_first = True apidoc_separate_modules = True apidoc_toc_file = False # Move type hint info to function description instead of signature; -# since we have some really long signatures, the default (`autodoc_typehints = "signature"`) +# since we have some really long signatures, the default (`autodoc_typehints = 'signature'`) # becomes unreadable because all params + types get crammed into a single line. -autodoc_typehints = "description" +autodoc_typehints = 'description' set_type_checking_flag = True # HTML theme settings -pygments_style = "sphinx" -html_theme = "sphinx_rtd_theme" +pygments_style = 'sphinx' +html_theme = 'sphinx_rtd_theme' # html_theme_options = {} # Favicon & sidebar logo @@ -97,9 +97,9 @@ def setup(app): * https://docs.readthedocs.io/en/stable/builds.html * https://github.com/sphinx-contrib/apidoc """ - app.connect("builder-inited", make_symlinks) - app.connect("builder-inited", patch_automodapi) - app.add_css_file("collapsible_container.css") + app.connect('builder-inited', make_symlinks) + app.connect('builder-inited', patch_automodapi) + app.add_css_file('collapsible_container.css') def make_symlinks(app): diff --git a/docs/endpoints.rst b/docs/endpoints.rst index 9bd70e72..7ab0b014 100644 --- a/docs/endpoints.rst +++ b/docs/endpoints.rst @@ -1,3 +1,5 @@ +.. _endpoints: + Implemented Endpoints ===================== @@ -24,5 +26,14 @@ All iNaturalist endpoints ---------------------------------------- .. Writing the table in markdown because markdown table syntax is much more sane than rst +.. note:: + + The two iNaturalist APIs expose a combined total of 103 endpoints\*. Some of these are generally + useful and could potentially be added to pyinaturalist, but many others are primarily for + internal use by the iNaturalist web application and mobile apps, and are unlikely to be added + unless there are specific use cases for them. + + \*As of 2020-10-01: 37 in REST API, 65 in Node API, and 1 undocumented + .. mdinclude:: endpoints_table.md :start-line: 1 diff --git a/docs/endpoints_table.md b/docs/endpoints_table.md index 8e7e7403..225f8ef2 100644 --- a/docs/endpoints_table.md +++ b/docs/endpoints_table.md @@ -14,8 +14,8 @@ DELETE | /votes/unvote/annotation/{id} | POST | /comments | DELETE | /comments/{id} | PUT | /comments/{id} | -GET | /controlled_terms | -GET | /controlled_terms/for_taxon | +GET | /controlled_terms | yes +GET | /controlled_terms/for_taxon | yes POST | /flags | DELETE | /flags/{id} | PUT | /flags/{id} | @@ -55,12 +55,12 @@ GET | /observations/{id}/taxon_summary | POST | /subscriptions/observation/{id}/subscribe | POST | /votes/vote/observation/{id} | DELETE | /votes/unvote/observation/{id} | -GET | /observations | +GET | /observations | yes POST | /observations | GET | /observations/deleted | -GET | /observations/histogram | -GET | /observations/identifiers | -GET | /observations/observers | +GET | /observations/histogram | yes +GET | /observations/identifiers | yes +GET | /observations/observers | yes GET | /observations/popular_field_values | GET | /observations/species_counts | yes GET | /observations/updates | @@ -110,7 +110,7 @@ GET | /grid/{zoom}/{x}/{y}.grid.json | GET | /heatmap/{zoom}/{x}/{y}.grid.json | GET | /points/{zoom}/{x}/{y}.grid.json | POST | /photos | - + ### Rails-Based API For full documentation, see: https://www.inaturalist.org/pages/api+reference diff --git a/docs/general_usage.rst b/docs/general_usage.rst index f309215a..fd343de9 100644 --- a/docs/general_usage.rst +++ b/docs/general_usage.rst @@ -2,14 +2,15 @@ General Usage ============= Following is some general usage information that applies to most or all pyinaturalist functions. +.. _auth: + Authentication -------------- For any endpoints that create, update, or delete data, you will need to authenticate using credentials for an -`iNaturalist Application `_, +`iNaturalist Application `_. -See `iNaturalist documentation `_ -for more details on authentication, -and see :py:func:`.get_access_token` for pyinaturalist usage info. +See `iNaturalist documentation `_ +for more details on authentication, and see :py:func:`.get_access_token` for pyinaturalist usage info and examples. .. note:: @@ -17,6 +18,111 @@ and see :py:func:`.get_access_token` for pyinaturalist usage info. private data visible only to your user (for example, obscured or private coordinates), you will need to use an access token. +In addition to :py:func:`.get_access_token` arguments, there are some other options for +providing credentials: + +Environment Variables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You may provide credentials via environment variables instead of arguments. The +environment variable names are the keyword arguments in uppercase, prefixed with ``INAT_``: + +* ``INAT_USERNAME`` +* ``INAT_PASSWORD`` +* ``INAT_APP_ID`` +* ``INAT_APP_SECRET`` + +**Examples:** + +.. admonition:: Set environment variables in python: + :class: toggle + + >>> import os + >>> os.environ['INAT_USERNAME'] = 'my_username' + >>> os.environ['INAT_PASSWORD'] = 'my_password' + >>> os.environ['INAT_APP_ID'] = '33f27dc63bdf27f4ca6cd95dd9dcd5df' + >>> os.environ['INAT_APP_SECRET'] = 'bbce628be722bfe2abd5fc566ba83de4' + +.. admonition:: Set environment variables in a POSIX shell (bash, etc.): + :class: toggle + + .. code-block:: bash + + export INAT_USERNAME="my_username" + export INAT_PASSWORD="my_password" + export INAT_APP_ID="33f27dc63bdf27f4ca6cd95dd9dcd5df" + export INAT_APP_SECRET="bbce628be722bfe2abd5fc566ba83de4" + +.. admonition:: Set environment variables in a Windows shell: + :class: toggle + + .. code-block:: bat + + set INAT_USERNAME="my_username" + set INAT_PASSWORD="my_password" + set INAT_APP_ID="33f27dc63bdf27f4ca6cd95dd9dcd5df" + set INAT_APP_SECRET="bbce628be722bfe2abd5fc566ba83de4" + +.. admonition:: Set environment variables in PowerShell: + :class: toggle + + .. code-block:: powershell + + $Env:INAT_USERNAME="my_username" + $Env:INAT_PASSWORD="my_password" + $Env:INAT_APP_ID="33f27dc63bdf27f4ca6cd95dd9dcd5df" + $Env:INAT_APP_SECRET="bbce628be722bfe2abd5fc566ba83de4" + +Note that in any shell, these environment variables will only be set for your current shell +session. I.e., you can't set them in one terminal and then access them in another. + +Keyring Integration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To handle your credentials more securely, you can store them in your system keyring. +You could manually store and retrieve them with a utility like +`secret-tool `_ +and place them in environment variables as described above, but there is a much simpler option. + +Direct keyring integration is provided via `python keyring `_. Most common keyring bakcends are supported, including: + +* macOS `Keychain + `_ +* Freedesktop `Secret Service + `_ +* KDE `KWallet `_ +* `Windows Credential Locker + `_ + +To store your credentials in the keyring, run :py:func:`.set_keyring_credentials`: + + >>> from pyinaturalist.auth import set_keyring_credentials + >>> set_keyring_credentials( + >>> username='my_username', + >>> password='my_password', + >>> app_id='33f27dc63bdf27f4ca6cd95dd9dcd5df', + >>> app_secret='bbce628be722bfe2abd5fc566ba83de4', + >>> ) + +Afterward, you can call :py:func:`.get_access_token` without any arguments, and your credentials +will be retrieved from the keyring. You do not need to run :py:func:`.set_keyring_credentials` +again unless you change your iNaturalist password. + +Password Manager Integration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Keyring integration can be taken a step further by managing your keyring with a password +manager. This has the advantage of keeping your credentials in one place that can be synced +across multiple machines. `KeePassXC `_ offers this feature for +macOS and Linux systems. See this guide for setup info: +`KeepassXC and secret service, a small walk-through +`_. + +.. figure:: images/password_manager_keying.png + :alt: map to buried treasure + + Credentials storage with keyring + KeePassXC + Dry-run mode ------------ @@ -25,7 +131,7 @@ useful to temporarily mock out HTTP requests, especially requests that add, modi real data. Pyinaturalist has some settings to make this easier. Dry-run all requests -^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To enable dry-run mode, set the ``DRY_RUN_ENABLED`` variable. When set, requests will not be sent but will be logged instead: @@ -53,7 +159,7 @@ environment variable instead (case-insensitive): $ python my_script.py Dry-run only write requests -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you would like to run ``GET`` requests but mock out any requests that modify data (``POST``, ``PUT``, ``DELETE``, etc.), you can use the ``DRY_RUN_WRITE_ONLY`` variable instead: @@ -100,3 +206,8 @@ In the rare cases where you want to use multiple user agents in your script, you All functions that communicate with the API accept the `user_agent` optional parameter. If you don't configure the user agent, `Pyinaturalist/` will be used. + +API Recommended Practices +------------------------- +See `API Recommended Practices `_ +on iNaturalist for more general usage information and notes. diff --git a/docs/images/password_manager_keying.png b/docs/images/password_manager_keying.png new file mode 100644 index 00000000..b9bdb387 Binary files /dev/null and b/docs/images/password_manager_keying.png differ diff --git a/docs/index.rst b/docs/index.rst index f6dd396c..40809b5a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ :end-line: 11 .. mdinclude:: ../README.md - :start-line: 13 + :start-line: 12 Contents @@ -20,12 +20,3 @@ Contents contributing authors history - -Feedback -======== - -If you have any suggestions or questions about **pyinaturalist** feel free to email me -at nicolas@niconoe.eu. - -If you encounter any errors or problems with **pyinaturalist**, please let me know! -Open an Issue at the GitHub http://github.com/niconoe/pyinaturalist main repository. diff --git a/docs/reference.rst b/docs/reference.rst index 716fb06d..c5fd4066 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -13,6 +13,7 @@ iNaturalist actually provides two APIs: fewer features. Pyinaturalist provides functions to use both of those APIs. +Also see :ref:`endpoints` for an overview of which iNaturalist endpoints are currently implemented. .. Note: Source files for module docs are generated by sphinx-apidoc .. toctree:: diff --git a/examples/Data Visualizations - Matplotlib.ipynb b/examples/Data Visualizations - Matplotlib.ipynb new file mode 100644 index 00000000..60cd8c15 --- /dev/null +++ b/examples/Data Visualizations - Matplotlib.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Matplotlib examples\n", + "Here is an example of making a simple seasonality chart using [matplotlib](https://matplotlib.org/).\n", + "This shows observations counts by month for monarch butterflies observed in the state of Iowa." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from pyinaturalist.node_api import get_all_observations" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "590\n" + ] + } + ], + "source": [ + "# Then, get all observations in that place of a given species\n", + "observations = get_all_observations(\n", + " taxon_name='Danaus plexippus',\n", + " photos=True,\n", + " geo=True,\n", + " geoprivacy='open',\n", + " place_id=24,\n", + " per_page=200,\n", + ")\n", + "print(len(observations))\n", + "\n", + "# Flatten nested JSON values\n", + "df = pd.json_normalize(observations)\n", + "df['observed_month'] = df['observed_on'].apply(lambda x: x.month)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAEHCAYAAABV4gY/AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAAnCElEQVR4nO3deXSc1Znn8e9TktlDYoJwHCAxnWMIS3Y3k3T2gCGEDKa7T7rJdNLunvTQMyedTnqZDHSYJN1ZhizNHkgMGMwOccA4hjg2xvuKvGBblm3JsmTLkrVa+1LbM3/U6yAbyZJVVXpr+X3O0amqW29VPZatn6/ue997zd0REZHCEgm7ABERyTyFu4hIAVK4i4gUIIW7iEgBUriLiBSg0rALADj33HN92rRpYZchIpJXNm/e3OruZcM9lxPhPm3aNMrLy8MuQ0Qkr5hZ3UjPaVhGRKQAKdxFRAqQwl1EpACNGu5mNtfMms1s53Ht3zCzPWZWYWY/HdJ+q5lVB89dm42iRUTkxMZyQvVR4D7gsaMNZvZZYBbwfncfNLPzgvbLgJuAy4F3Aq+Y2cXunsh04SIiMrJRe+7uvgpoP675fwG3u/tgcExz0D4LeMbdB919P1ANXJnBekVEZAzGO+Z+MfBJM9toZivN7I+D9vOBg0OOqw/a3sTMbjazcjMrb2lpGWcZIiIynPGGeykwGfgo8L+B58zMABvm2GHXFHb3Oe4+w91nlJUNOwdfRETGabwXMdUDz3tqMfhNZpYEzg3aLxxy3AVAQ3olihSOtp5BfrWqhkXbG7hs6tlcd8VUPvve8zjnzFPCLk0KzHjDfQHwOWCFmV0MnAK0AguBp8zsDlInVKcDmzJQp0he6+yLMWf1PuauqWUglsAMGjoGeKWyGQMuP/9svvj+d3L1pefxnrKzSP0iLDJ+o4a7mT0NfAY418zqge8Bc4G5wfTIKDA76MVXmNlzwC4gDnxdM2WkmHUPxJi7ppaH1tTQPRCnJGJMKjGiiTdGKyMGFYe62Hmoi9t/t5spZ5/KdVdM5ZrLp/DH085hUokuR5GTZ7mwzd6MGTNca8tIIekdjDNvfS2/WllDZ3+MkgiURIxo/MQ/bwZEIpBIph6fPqmET19SxnVXvIPPXHwebz1jUvaLl7xhZpvdfcZwz+XEwmEihWIgluCJDXXcv2If7b1RSiLGqaXGYNxJJEfvSDlvBDtALJFk8c7DLN55GDP4wAVv5fr3vZOrL5vCReeemb0/iOQ99dxFMmAwnuDZ1w5y36vVNHcPUhoxSiLGYDw5+ovHqMQg6W9MP3vn207jC++bysxLp/CRd0+mVMM3RedEPXeFu0gaYokk8zfXc8+yKho7B7IS6sMxUmP1R4fuzzylhM9cch7Xve8dfOriMs4+TcM3xUDDMiIZFk8kWbCtgXuWVXGgvY/SiHFqaYTBeJL4GIZf0uW8EewAg/EkL+1o5KUdjUQMPvSuyVz/vqlcfekU3vX2M7Jej+Qe9dxFTkIy6Sza0chdS/dS09rLpIgRmYCe+skoiUAy+cbwzYWTT+f6909l5mVT+OCFkymJaJplodCwjEia3J3fVxzmzqV72dPUw6QSw8yI5lCoD+dojh/9ZeKsU0u5+tLzuPbyd/DJi8s461T98p7PNCwjMk7uzvI9zfznkr1UNHQxqcQ45Q/z1MPvGI3m+BGigViCBdsaWLCtgZKIMePdk7n+/VO56tIpnP+208MpUrJCPXeRYbg7a6pb+c8le9l2sCPVU4djLj7Kd6URO+b8wLS3n5Eap79sCh+44G1ENHyT8zQsI3ISNtS0cceSvWyqbWdSSSrgYgUU6sM5fvjm7NNKuebyd3DNZVP4xPRzOeMU/ZKfizQsIzIGm+uOcMfSPaytbmNSiTEpYgUf6kcdP3zTF00wf3M98zfXUxoxvnn1dL7xuenhFCfjonCXorejvpM7lu5h+Z4WSiNGaRGF+kiGDtck3bljyV7+7hMXcbp68HlDf1NStHYf7uKOJXtZsqvpD6E+EXPU883Rb8mvN9fz1x+bFmotMnYKdyk61c093PXKXl7a3khJEOqJpOfB3Jdw/bpc4Z5PFO5SNOraern7lSoWbDuEmRGx1JCDOutjU9nYhbtrrfk8oXCXgld/pI97l1Uzf3M9kFqXxRXqJy2edLYc6OAj754cdikyBgp3KViHOwf4xfJqnt50gKQ7Rzf5HWVJdTmBx9bXKtzzhMJdCk5L9yAPrNjHExvqiCWTRCw1jzvHVwrICyv3tIRdgozRqAtAm9lcM2sOttQ7/rl/NTM3s3OHtN1qZtVmtsfMrs10wSIjOdIb5f/9rpJP/vRVHlm7n0QyGZwsVbBnSkd/jI6+aNhlyBiMZXX/R4HPH99oZhcCM4EDQ9ouA24CLg9ec7+ZlWSkUpETeHJjHZ/4yav8amUNsbhTWmIkvPCvLA3DUxvrwi5BxmDUcHf3VUD7ME/dCXybY1dPmgU84+6D7r4fqAauzEShIiPZfbiL775YwUA8ySmlRsJdoZ5FC7Y2hF2CjMG49uUysxuAQ+7++nFPnQ8cHPK4Pmgb7j1uNrNyMytvadE4noxPMunc9sJODMB91A2oJX3VLT3kwppUcmInHe5mdgbwHeC7wz09TNuw/wrcfY67z3D3GWVlZSdbhggA8zfXU153BLNjdyaS7Ek6rNqrDlmuG0/P/T3ARcDrZlYLXABsMbN3kOqpXzjk2AsA/Q4nWXGkN8qPf1eptWBC8MQGjbvnupMOd3ff4e7nufs0d59GKtA/7O6HgYXATWZ2qpldBEwHNmW0YpHA7b/bTWdfjMi4BhclHetq2sIuQUYxlqmQTwPrgUvMrN7MvjbSse5eATwH7AIWA19390SmihU5qry2nWfLDxKJoHH2EPQOJmjqGgi7DDmBscyW+bK7T3X3Se5+gbs/fNzz09y9dcjjH7n7e9z9Enf/XTaKluIWSyT5zgs7mFRiJDV/PTTz1tWGXYKcgH6hlbzzyNr97Gnqwcy0kmOIFm3X6bRcpnCXvHKoo587lu6lNGJEddlpqA6295PQ6ms5S+EueeXfF1YQSzjauzl8Dize2Rh2GTIChbvkjWWVTSzZ1YQBUU19zAlPbTww+kESCoW75IX+aILvvljBpBJthZdLNtcdCbsEGYHCXfLCPa9WcaijHxv2ImgJy0A8SW1rb9hlyDAU7pLzqpq6mbOqhpKIEU3oJGqumbt2f9glyDAU7pLT3J3bFqQWBjMtVpWTllQcDrsEGYbCXXLa81sOsXF/O2baHi9XHe4aZCAWD7sMOY7CXXJWR1+UH760i0klWhgs12mN99yjcJec9ZPFe+joj2lOex54tvzg6AfJhFK4S07aXHeEpzcdIGIwqPGYnLfzUGfYJchxFO6Sc+KJJLctSC0M5pockxdiCWdHfUfYZcgQCnfJOY+uq6WysRszQ9meP+at0wYeuUThLjmlsVMLg+WrV3c3hV2CDKFwl5zyg0W7GIwldRI1D7X3xegZiIVdhgQU7pIzlu9p5uUdhzHTwmD56qlNmjWTK8ayzd5cM2s2s51D2n5mZrvNbLuZvWBmbxvy3K1mVm1me8zs2izVLQVmIJbge1oYLO+9sLU+7BIkMJae+6PA549rWwpc4e7vB/YCtwKY2WXATcDlwWvuN7OSjFUrBesXy6s50N5HxDQek8/2Hu7BtUxEThjLHqqrgPbj2pa4+9HrjTcAFwT3ZwHPuPugu+8HqoErM1ivFKDq5h4eWLGPkogxqJOoeS3hzoaatrDLEDIz5v7fgaMbYZ8PDB10qw/aRIbl7vzfBakRP+2IWhgeW68pkbkgrXA3s+8AceDJo03DHDbsT6yZ3Wxm5WZW3tLSkk4Zksde3NbA+po2DFCnvTCsqW4NuwQhjXA3s9nAF4G/8jcG2eqBC4ccdgEw7IpC7j7H3We4+4yysrLxliF5rLM/xg8WBQuD6SRqwegeiNPaPRh2GUVvXOFuZp8H/g9wg7v3DXlqIXCTmZ1qZhcB04FN6Zcphejnv99De29UJ1EL0OMbNDQTtrFMhXwaWA9cYmb1ZvY14D7gLcBSM9tmZr8EcPcK4DlgF7AY+Lq7J7JWveStbQc7eGJDHZEIOolagBa+riWAw1Y62gHu/uVhmh8+wfE/An6UTlFS2BJJ57YXdlBaYiQ0HFOQatt6SSadiC41Do2uUJUJ9/j6WnY2dAGgbC9M7vBKpdaaCZPCXSZUU9cAP1uyh9KIdlcqdE9uPBB2CUVN4S4T6geLdtEfTaBzqIVv035dzBQmhbtMmNVVLSza3kjE1GsvBv2xJAeP9IZdRtFSuMuEGIgluG3BTi0MVmQeXaspkWFRuMuEeGDFPuratDBYsVm883DYJRQthbtk3f7WXu5fUa2FwYpQQ0c/0bgudQmDwl2yyt357os7cdfCYMXIgd++3hh2GUVJ4S5ZtWh7I6urUgtJqdNenJ55TVMiw6Bwl6zpGojxH7/dpZOoRe71g51hl1CUFO6SNXcs2UtLzyCRYVeClmIRTSTZ09gVdhlFR+EuWbHzUCePra+lJAKDCY3HFLtH19WGXULRUbhLxiWSznde2EFJxNB2mgKwVOvMTDiFu2TcUxvreL0+Nc6qoXYBaO2J0h+Nj36gZIzCXTKquXuAn/5eC4PJmz372sHRD5KMUbhLRv34pUp6B+NE9C9LjvObLYfCLqGo6EdQMmZtdSsLtjVgBtG4eu1yrMrGLlwnYSaMwl0yYjD+xsJgmhwjw4knnS0HjoRdRtEYyx6qc82s2cx2Dmk7x8yWmllVcDt5yHO3mlm1me0xs2uzVbjkljkra9jf2quFweSE5q2tDbuEojGWnvujwOePa7sFWObu04FlwWPM7DLgJuDy4DX3m1lJxqqVnFTX1st9y6tTc9q1xoCcwMqqlrBLKBqjhru7rwLaj2ueBcwL7s8DbhzS/oy7D7r7fqAauDIzpUouSi0MVkE86Rrjk1F19sfp6IuGXUZRGO/P4xR3bwQIbs8L2s8Hhs53qg/a3sTMbjazcjMrb2nR/+b56nc7D7NybwsGxNRplzF4YoM28JgIme5sDTfgOuzpcXef4+4z3H1GWVlZhsuQidAzGOfff1uhhcHkpCzYpimRE2G84d5kZlMBgtvmoL0euHDIcRcADeMvT3LZnUv30tSlhcHk5NS09JJUZyDrxhvuC4HZwf3ZwItD2m8ys1PN7CJgOrApvRIlF1U0dPLI2v2p3ZU091FOQtJhlU6sZt1YpkI+DawHLjGzejP7GnA7MNPMqoCZwWPcvQJ4DtgFLAa+7u7aY6vAJJPObQt2UhIxtDKYjMfj6zXunm2lox3g7l8e4amrRjj+R8CP0ilKctszrx1k64GO1AVLynYZhw01bWGXUPA0e01OSmvPID9ZvFsLg0laeqMJGjv6wy6joCnc5aT8+OVKugdiWhhM0jZvfW3YJRQ0/YjKmG2oaeP5LYcwtDCYpO+l7Y1hl1DQFO4yJtF48g8Lg2kWm2RC/ZF+4ppplTUKdxmTB1fXUN3cQ8Rs+KvSRE6SAy/vVO89WxTuMqqD7X3cu6yK0ohpYTDJqGc2aXembFG4ywm5O99fWEEs6Wg1X8m0zXVa3z1bFO5yQkt2NbFsd3NqYTBNfZQMG4wnqWnpCbuMgqRwlxH1Dsb53kItDCbZ9Yg28MgKhbuM6O5lVRzuHNDuSpJVS3YdDruEgqRwl2FVN3fz8Or9lJhOokp2NXUNMhCLh11GwVG4y7DuXlYdrM6v4RjJvue3aI33TFO4y5tUN/ew6PUGcNfCYDIhnivXlMhMU7jLm9z3ahWRiC5WkolT0dAVdgkFR+Eux6hp6WFh0GvXBBmZKLGEs72+I+wyCorCXY5x36vVmLbNkxDMW1cbdgkFReEuf1Db2htsXqyxdpl4r+5uHv0gGbO0wt3M/snMKsxsp5k9bWanmdk5ZrbUzKqC28mZKlay677l1WGXIEXsSF+M7v5Y2GUUjHGHu5mdD/wjMMPdrwBKgJuAW4Bl7j4dWBY8lhxX19bLC8Fa7eq1S1ie3Hgg7BIKRrrDMqXA6WZWCpwBNACzgHnB8/OAG9P8DJkAv1hejeMabZdQpYYFJRPGHe7ufgj4OXAAaAQ63X0JMMXdG4NjGoHzhnu9md1sZuVmVt7S0jLeMiQDDrb38Zug164NliRMVU09uOsfYSakMywzmVQv/SLgncCZZvaVsb7e3ee4+wx3n1FWVjbeMiQD7l9RjbuW9JXwJdxZt68t7DIKQjrDMlcD+929xd1jwPPAnwBNZjYVILjVKfAcVn+kj+fK6wHQEjKSCx7TxtkZkU64HwA+amZnmJkBVwGVwEJgdnDMbODF9EqUbLp/xT7cnYh67ZIj1lWr554JpeN9obtvNLP5wBYgDmwF5gBnAc+Z2ddI/QfwpUwUKpnX0NHPc6+l1vRQr11yRfdgnJbuAcreclrYpeS1tGbLuPv33P297n6Fu3/V3Qfdvc3dr3L36cFte6aKlcx6YMU+ku6UqNcuOeax9XVhl5D3dIVqkWrs7OeZ11JzimPqtUuO+e3rDWGXkPcU7kXqlyv2EU86JZoiIzmorr2PhFauS4vCvQg1dQ3w9KaDRICYfoAkB7nDUm2/lxaFexH65cp9xBJJIpoiIznsKS1FkBaFe5Fp7hrgqY0HiFhqDW2RXPVareZipEPhXmR+taqGaDxJiXrtkuP6Y0kOtPWGXUbeUrgXkZbuQZ7cUIcZRNVrlzzwyNrasEvIWwr3IvLg6hoG1WuXPPL7Cp1UHS+Fe5Fo7RnksfW1RMw01i55o6FzgMFYIuwy8pLCvUg8uLqGgViSiP7GJc8s1AVN46If9SLQ3hvlsXV1lKjXLnno2WD9Izk5Cvci8NDqGvpjCfXaJS9tr+8Mu4S8pB/3AnekN8qj62rVa5e8FU0kqWzsCruMvKNwL3APr9lPXzRBSUnYlYiM36PrasMuIe8o3AtYZ1+MR9bup8Qgqs1RJY+9sqsp7BLyjsK9gD28dj+90QQlWrBd8lxbb5TegXjYZeQVhXuB6uxXr10Ky7PlWkjsZKQV7mb2NjObb2a7zazSzD5mZueY2VIzqwpuJ2eqWBm7R9bup3sgTql67VIgfrPlUNgl5JV0e+53A4vd/b3AB0htkH0LsMzdpwPLgscygboGYsxds5+SCAyq1y4FYvfhbtz173msxh3uZnY28CngYQB3j7p7BzALmBccNg+4Mb0S5WTNW1tL10CcUk1slwKSSDrlWgZ4zNL56f8joAV4xMy2mtlDZnYmMMXdGwGC2/OGe7GZ3Wxm5WZW3tLSkkYZMlT3QIwH19RQEjEG49ocVQrLvHXaOHus0gn3UuDDwAPu/iGgl5MYgnH3Oe4+w91nlJWVpVGGDPXY+jq6+uOUaqhdCtCqanUExyqdcK8H6t19Y/B4PqmwbzKzqQDBbXN6JcpY9QzGmbMq6LXralQpQF39cdp7B8MuIy+MO9zd/TBw0MwuCZquAnYBC4HZQdts4MW0KpQxe3x9HZ39MUpN3XYpXE9s0JTIsUj3jNs3gCfNbDvwQeDHwO3ATDOrAmYGjyXLegfjzFm1L+i1a6xdCteL2zQlcixK03mxu28DZgzz1FXpvK+cvCc21HGkL8YpJRESaEhGCtf+1l6SSSeiHcVOSHPlCkB/NMGvgrH2qHrtUuCSDsv36FTeaBTuBeDJjXW090Y11i5F48kNmhI5GoV7nuuPJvjlyn2UaqxdisiG/bqYaTQK9zz31KYDtPZEKdH4oxSRvmiCho7+sMvIaQr3PDYQS/DLFUGvXVejSpF5dG1t2CXkNIV7Hntm0wFaegbVa5ei9PLOxrBLyGkK9zw1EEtwv3rtUsQOHeknrvNMI1K456nnyg/S3K1euxQvB17aod77SBTueWgwrl67CMDTG7UUwUgU7nno1+X1HO4cUK9dit7Wgx1hl5CzFO55JhpPcv+KavXaRYDBeJKq5u6wy8hJCvc8M39zPQ0d6rWLHKUpkcNTuOeRWCLJL5ar1y4y1NJdTWGXkJMU7nnk+S31HOroV69dZIjm7kH6o/Gwy8g5Cvc8EUskue9V9dpFhjN/s9Z4P57CPU+8sPUQB4+o1y4ynPmbD4ZdQs5RuOeBuHrtIidU0dCFuzapGSrtcDezEjPbamaLgsfnmNlSM6sKbienX2Zxe3FbAwfa+7TzjMgI4knndc15P0Ymeu7fBCqHPL4FWObu04FlwWMZp3giyb2vVjGpxIiq1y4yokfX1YZdQk5JK9zN7ALgeuChIc2zgHnB/XnAjel8RrH77fYGatv6MO2yJHJCK/e2hF1CTkm3534X8G1gaJdyirs3AgS35w33QjO72czKzay8pUV/KcNJJJ17l1Wr1y4yBkf6YnT2x8IuI2eMO9zN7ItAs7tvHs/r3X2Ou89w9xllZWXjLaOgLdreQE1rr3rtImP01EbtrXpUOj33jwM3mFkt8AzwOTN7Amgys6kAwa22KR8H9dpFTt4LWzXf/ahxh7u73+ruF7j7NOAm4FV3/wqwEJgdHDYbeDHtKovQyzsaqW7pQX12kbGrbu7RlMhANua53w7MNLMqYGbwWE5CMulvzJBJ6B+qyFglHdZUt4ZdRk4ozcSbuPsKYEVwvw24KhPvW6wWVxxmb1MPp5So3y5ysh5fX8cnp+s8nq5QzTHJpHP3MvXaRcZr3T713EHhnnOW7DrMnsPdGmsXGaeewQTNXQNhlxE6hXsOcVevXSQTvjbvNeKJ4p5lpnDPIUt3NVHZqF67SLp2HOriL+esL+qAV7jnCPXaRTJrc10Hf/Gr4g14hXuOWFbZTEVDl3rtIhm05UAHXyrSgFe45wB35x712kWyYuuBDr70y/XEiizgFe45YMWeFrYf6lSvXSRLth7s4EsPrCuqgFe4h8zdueuVveq1i2TZtvpO/ryIAl7hHrKVe1t4vb4z7DJEisL2+k7+7P7iCHiFe4iGzpCJqdcuMiF2HEoFfKGvtqpwD9Ga6la2HugA5brIhNpxqJM/vX9tQQe8wj0k7s7drwS99qTSXWSiVTR0FXTAK9xDsm5fG+V1R9RrFwlRRUMXN/5iDYPxRNilZJzCPSR/GGtXr10kVLsau7nxvrUFF/AK9xCs39fGpv3t6rWL5IjKw93MKrCAV7iH4O5le9VrF8kxuw93c8O9axmIFUbAjzvczexCM1tuZpVmVmFm3wzazzGzpWZWFdxOzly5+W9jTRsbatrRNo8iuWdPUzc33LemIAI+nZ57HPgXd78U+CjwdTO7DLgFWObu04FlwWMJ3PNqFZMiRly9dpGctLeph/96b/4H/LjD3d0b3X1LcL8bqATOB2YB84LD5gE3plljwSivbWdtdZuG2kVyXFVz/gd8RsbczWwa8CFgIzDF3Rsh9R8AcF4mPiOfuTu/rzjMt57dRql67SJ5oaq5h+vvWZ23AZ92uJvZWcBvgG+5e9dJvO5mMys3s/KWlpZ0y8hZlY1d/LcHN/L3j2+mqWuAiGntR5F8sa+lN28DPq1wN7NJpIL9SXd/PmhuMrOpwfNTgebhXuvuc9x9hrvPKCsrS6eMnNTaM8itz+/g+ntW81pdO5MiqfVjokWwYJFIIdnX0ssX7l5NfzS/Aj6d2TIGPAxUuvsdQ55aCMwO7s8GXhx/efknGk/y4KoaPvOzFTzz2gEMSCZd0x5F8lhNay9fuCe/Aj6dnvvHga8CnzOzbcHXF4DbgZlmVgXMDB4XPHdn6a4mZt65kh+9XMlALMGkiJFwUK6L5L/9rb1cd/eqvAn40vG+0N3XwIibB1013vfNR7sPd/HDRZWsqW5lUolxammEwQJdjEikmNW29XHd3at4+Zuf5IxTxh2fE0JXqKahrWeQ2xbs4At3r2ZDTRulwbi6gl2kcNW29XHdXavpi8bDLuWEFO7jEI0neWh1DZ/5+Qqe3JAaV3d3TXEUKRJ17X18/q5VOR3wCveT4O4sq2zi2rtW8cOXKumLJphUmhpX10ZKIsXlQHs/1965it7B3Ax4hfsY7W3q5q/nbuJr88qpP9LHqaUREkknGleqixSrg0f6ufau3Ax4hfsojvRG+e6LO7nurtWs36dxdRE5Vn0Q8D05FvAK9xHEEknmrtnPp3+2nMfX1wGucXURGVb9kdQQTS4FvMJ9GMt3N3Ptnav4j0W7NK4uImNyqKOfa+9cmTMBr3Aforq5m9lzN/G3j77GwWBcPa5xdREZo0MdA1xzx0q6B2Jhl6JwB+joi/L9hRVce+dq1la3alxdRMatoXOAa+5cFXrAF3W4xxJJ5q2r5dM/W8G8dbVgGlcXkfQ1dg4w885VdIUY8EUb7iv3tnDd3av53sIKegfjnFIaIZHUuLqIZMbhzgGuuSO8gC+6cN/X0sPfPrKJ2XM3UdfWyynBuLqGYEQk0w53DTDzjpV09k98wBdNuHf2xfiP3+7imjtXsbrqjXH1qEJdRLKoqWswlIAv+HCPJ5I8vr6WT/98OY+s3Y/mq4vIRGvuDgK+b+ICvqDDfXVVC1+4ZzX/98UKegY0ri4i4WnuHuTqCQz4ggz3mpYe/m7ea3z14U3sb9W4uojkhpaeQa66YwVHegez/lkFFe6d/TF+uCg1rr5iT4vG1UUk57T2RJl556qsB3xBhHs8keSJDXV85mfLeWjNftwdNK4uIjmqtSfK1VkO+KyFu5l93sz2mFm1md2Src9ZW93K9feu4bYFO+keiHNqsA6MVgwQkVzW1hPl6jtW0d6TnYDPSribWQnwC+A64DLgy2Z2WaY/Z01VK3/10EZqWnqGjKsr1UUkP7T1Rpl1/9qsvHe2eu5XAtXuXuPuUeAZYFamP+RP3vN2rrviHSQ0ri4ieaqtO5qV983W9t3nAweHPK4H/svQA8zsZuDm4GGPme0ZzwdFTj97spWeMi1y+tl50WVP9ndZvtQK+VWvas2efKo3n2oFSPQeidsPj+wc58vfPdIT2Qp3G6btmG+2u88B5mTkw8zK410tMzLxXtmWT7VCftWrWrMnn+rNp1ohVa+7Z7zebA3L1AMXDnl8AdCQpc8SEZHjZCvcXwOmm9lFZnYKcBOwMEufJSIix8nKsIy7x83sH4DfAyXAXHevyMZnBTIyvDNB8qlWyK96VWv25FO9+VQrZKlec8+b8w4iIjJGBXGFqoiIHEvhLiJSgPI+3M2sxMy2mtmisGsZjZnVmtkOM9tmZuVh13MiZvY2M5tvZrvNrNLMPhZ2TSMxs0uC7+nRry4z+1bYdY3EzP7JzCrMbKeZPW1mp4Vd00jM7JtBnRW5+D01s7lm1mxmO4e0nWNmS82sKridHGaNQ41Q75eC72/SzDI2JTLvwx34JlAZdhEn4bPu/sFszGvNsLuBxe7+XuAD5PD32N33BN/TDwIfAfqAF8Ktanhmdj7wj8AMd7+C1ISDm8KtanhmdgXwP0hdcf4B4ItmNj3cqt7kUeDzx7XdAixz9+nAsuBxrniUN9e7E/gzYFUmPyivw93MLgCuBx4Ku5ZCYmZnA58CHgZw96i7d4Ra1NhdBexz97qwCzmBUuB0MysFziB3rwG5FNjg7n3uHgdWAn8ack3HcPdVQPtxzbOAecH9ecCNE1nTiQxXr7tXuvu4rtA/kbwOd+Au4NtAviws48ASM9scLL+Qq/4IaAEeCYa8HjKzM8MuaoxuAp4Ou4iRuPsh4OfAAaAR6HT3JeFWNaKdwKfM7O1mdgbwBY69ODFXTXH3RoDg9ryQ6wlF3oa7mX0RaHb3zWHXchI+7u4fJrVa5tfN7FNhFzSCUuDDwAPu/iGgl9z61XZYwQVzNwC/DruWkQTjv7OAi4B3Amea2VfCrWp47l4J/ARYCiwGXgfioRYlY5a34Q58HLjBzGpJrTr5OTN7ItySTszdG4LbZlJjwleGW9GI6oF6d98YPJ5PKuxz3XXAFndvCruQE7ga2O/uLe4eA54H/iTkmkbk7g+7+4fd/VOkhhOqwq5pDJrMbCpAcNsccj2hyNtwd/db3f0Cd59G6lfxV909J3tAAGZ2ppm95eh94BpSv/bmHHc/DBw0s0uCpquAXSGWNFZfJoeHZAIHgI+a2RlmZqS+tzl7strMzgtu30XqpF+uf38htdTJ7OD+bODFEGsJTbZWhZQ3mwK8kPp5phR4yt0Xh1vSCX0DeDIY6qgB/jbkek4oGBOeCfx92LWciLtvNLP5wBZSQxxbye3L5X9jZm8HYsDX3f1I2AUNZWZPA58BzjWzeuB7wO3Ac2b2NVL/mX4pvAqPNUK97cC9QBnwkpltc/dr0/4sLT8gIlJ48nZYRkRERqZwFxEpQAp3EZECpHAXESlACncRkQKkcBcRKUAKd8k5ZjZt6JKoucLMVmRySdaT+Nx/G3I/J783knsU7lIUghUY89W/jX6IyLEU7hI6M/vnYEOInUM2hCg1s3lmtj3YNOSM4NjbzWxX0P7zoK3MzH5jZq8FXx8P2r9vZnPMbAnwmJltNLPLh3zuCjP7SLA0xNzgtVvNbFbw/Olm9kzwWc8Cp4/y5+gxs58Eq36+YmZXBp9RY2Y3BMecZmaPWGrTlq1m9tmg/W/M7HkzWxxsMvHTo39eUssDbzOzJ4OPKjGzB4MNHpaY2QnrkiLl7vrSV2hfpDbX2AGcCZwFVAAfIrU88seDY+YC/wqcA+zhjSur3xbcPgV8Irj/LqAyuP99YDNwevD4n4B/D+5PBfYG938MfOXoewJ7g3r+GZgbtL+f1HIBM07wZ3HguuD+C8ASYBKpjS62Be3/AjwS3H8vqcvjTwP+htQyD28NHtcBFwbH9Qz5jGlBHR8MHj93tHZ96Wvol3ruErZPAC+4e6+795BaJfGTwEF3Xxsc80RwXBcwADxkZn9GasclSK20eJ+ZbSO1aNTZRxdpAxa6e39w/zneWGfkL3hjaeBrgFuC168gFa7vIrVhyRMA7r4d2D7KnyVKamlcSP2HtdJTKz/uIBXKR/+8jwfvuZtUiF8cPLfM3TvdfYDUQm3vHuFz9rv7tuD+5iHvLfIH+TwOKYXBRmg/ftEjd/e4mV1JaiXFm4B/AD5HanjxY0NCPPXGqUXaeoe8wSEzazOz9wN/yRuLjBnw537cbjjB609m8aWYux89PgkMBp+bHDLmP9Kfl6PHBxKM/PN5/HEalpE3Uc9dwrYKuDFYAvdMUtu4rQbeZW9syv1lYI2ZnQW81d1fBr4FfDB4fgmpoAfAzI62D+cZUrt3vdXddwRtvwe+ESzBi5l9aEhtfxW0XUFqaCZdQ9/zYlK/IYy2xVrMzCZl4LOliCjcJVTuvoXUpsGbgI2k9sM9QmqN89lmtp3UWPsDwFuARUHbSlJj6BBsOB2c+NwF/M8TfOR8Ur3+54a0/YDU2Pj2YJrhD4L2B4Czgs/7dlBjuu4ndUJ0B/As8DfuPjjKa+YEtT05ynEif6Alf0VECpB67iIiBUgnVEVOkpltBE49rvmrQ8bwRUKnYRkRkQKkYRkRkQKkcBcRKUAKdxGRAqRwFxEpQP8f+R8DUnEkn6IAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Plot observation counts by month\n", + "df['observed_month'].groupby(df['observed_month']).count().plot(kind='area')\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/Data Visualizations - Regional Activity Report.ipynb b/examples/Data Visualizations - Regional Activity Report.ipynb new file mode 100644 index 00000000..1487d5f7 --- /dev/null +++ b/examples/Data Visualizations - Regional Activity Report.ipynb @@ -0,0 +1,834 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "attached-priority", + "metadata": {}, + "source": [ + "# Regional activity time series visualizations\n", + "This example shows how to create visualizations of iNaturalist activity over time in a given region.\n", + "See https://www.inaturalist.org/places to find place IDs.\n", + "\n", + "Visualization are made using [Altair](https://altair-viz.github.io), with the following metrics:\n", + "* Number of observations\n", + "* Number of taxa observed\n", + "* Number of observers\n", + "* Number of identifiers" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "prostate-overall", + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import datetime\n", + "from time import sleep\n", + "\n", + "from dateutil.relativedelta import relativedelta\n", + "from IPython.display import Image\n", + "from typing import Any, BinaryIO, Dict, Iterable, List, Optional, Tuple\n", + "\n", + "import altair as alt\n", + "import pandas as pd\n", + "\n", + "from pyinaturalist.node_api import (\n", + " get_observations,\n", + " get_observation_histogram,\n", + " get_observation_species_counts,\n", + " get_observation_observers,\n", + " get_observation_identifiers,\n", + ")\n", + "from pyinaturalist.request_params import ICONIC_TAXA, get_interval_ranges\n", + "\n", + "# Adjustable values\n", + "PLACE_ID = 6\n", + "PLACE_NAME = 'Alaska'\n", + "YEAR = 2020\n", + "\n", + "THROTTLING_DELAY = 1.0 # Time to wait in between subsequent requests" + ] + }, + { + "cell_type": "markdown", + "id": "angry-longer", + "metadata": {}, + "source": [ + "### Show observation counts by year" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "joint-interference", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "observations_by_year = get_observation_histogram(\n", + " place_id=PLACE_ID,\n", + " interval='year',\n", + " d1='2008-01-01',\n", + " d2=f'{YEAR}-12-31',\n", + " verifiable=True,\n", + ")\n", + "observations_by_year = pd.DataFrame([\n", + " {'date': k, 'observations': v}\n", + " for k, v in observations_by_year.items()\n", + "])\n", + "alt.Chart(observations_by_year).mark_bar().encode(x='year(date):T', y='observations:Q')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "complex-yacht", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Including the rendered image so the chart will display outside Jupyter, e.g. on GitHub's notebook viewer\n", + "Image('images/observations_by_year.png')" + ] + }, + { + "cell_type": "markdown", + "id": "invisible-needle", + "metadata": {}, + "source": [ + "### Show observation counts by month" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "dietary-tours", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "observations_by_month = get_observation_histogram(\n", + " place_id=PLACE_ID,\n", + " interval='month',\n", + " d1='2020-01-02',\n", + " d2='2020-12-31',\n", + " verifiable=True,\n", + ")\n", + "observations_by_month = pd.DataFrame([\n", + " {'metric': 'Observations', 'date': k, 'count': v}\n", + " for k, v in observations_by_month.items()\n", + "])\n", + "alt.Chart(observations_by_month).mark_bar().encode(x='month(date):T', y='count:Q')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "clinical-madagascar", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('images/observations_by_month.png')" + ] + }, + { + "cell_type": "markdown", + "id": "genetic-camping", + "metadata": {}, + "source": [ + "### Making histograms for custom metrics\n", + "The API does not have a histogram endpoint for taxa observed, observers, or identifiers,\n", + "so we first need to determine our date ranges of interest, and then run one search per date range.\n", + "\n", + "Here are a couple helper functions to make this easier:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "duplicate-attribute", + "metadata": {}, + "outputs": [], + "source": [ + "def count_date_range_results(function, start_date, end_date):\n", + " \"\"\"Get the count of results for the given date range and search function\"\"\"\n", + " # Running this search with per_page=0 will (quickly) return only a count of results, not complete results\n", + " response = function(\n", + " place_id=PLACE_ID,\n", + " d1=start_date,\n", + " d2=end_date,\n", + " verifiable=True,\n", + " per_page=0,\n", + " )\n", + " print(f'Total results for {start_date.strftime(\"%b\")}: {response[\"total_results\"]}')\n", + " return response['total_results']\n", + " if start_date.month != 12:\n", + " sleep(THROTTLING_DELAY)\n", + "\n", + "\n", + "def get_monthly_counts(function, label):\n", + " \"\"\"Get the count of results per month for the given search function\"\"\"\n", + " month_ranges = get_interval_ranges(datetime(YEAR, 1, 1), datetime(YEAR, 12, 31), 'monthly')\n", + " counts_by_month = {\n", + " start_date: count_date_range_results(function, start_date, end_date)\n", + " for (start_date, end_date) in month_ranges\n", + " }\n", + " return pd.DataFrame([{'metric': label, 'date': k, 'count': v} for k, v in counts_by_month.items()])" + ] + }, + { + "cell_type": "markdown", + "id": "induced-stone", + "metadata": {}, + "source": [ + "### Show unique taxa observed per month" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "exempt-victor", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total results for Jan: 183\n", + "Total results for Feb: 176\n", + "Total results for Mar: 318\n", + "Total results for Apr: 790\n", + "Total results for May: 1333\n", + "Total results for Jun: 1482\n", + "Total results for Jul: 1686\n", + "Total results for Aug: 1569\n", + "Total results for Sep: 1249\n", + "Total results for Oct: 638\n", + "Total results for Nov: 408\n", + "Total results for Dec: 550\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "taxa_by_month = get_monthly_counts(get_observation_species_counts, 'Taxa')\n", + "alt.Chart(taxa_by_month).mark_bar().encode(x='month(date):T', y='count:Q')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "talented-andrews", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('images/taxa_by_month.png')" + ] + }, + { + "cell_type": "markdown", + "id": "brief-daniel", + "metadata": {}, + "source": [ + "### Show number of observers per month" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "generous-candy", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total results for Jan: 36\n", + "Total results for Feb: 41\n", + "Total results for Mar: 71\n", + "Total results for Apr: 140\n", + "Total results for May: 361\n", + "Total results for Jun: 456\n", + "Total results for Jul: 529\n", + "Total results for Aug: 562\n", + "Total results for Sep: 404\n", + "Total results for Oct: 173\n", + "Total results for Nov: 85\n", + "Total results for Dec: 50\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "observers_by_month = get_monthly_counts(get_observation_observers, 'Observers')\n", + "alt.Chart(observers_by_month).mark_bar().encode(x='month(date):T', y='count:Q')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "certified-monaco", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('images/observers_by_month.png')" + ] + }, + { + "cell_type": "markdown", + "id": "earlier-warren", + "metadata": {}, + "source": [ + "### Show number of identifiers per month" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "parliamentary-edward", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total results for Jan: 134\n", + "Total results for Feb: 149\n", + "Total results for Mar: 184\n", + "Total results for Apr: 349\n", + "Total results for May: 618\n", + "Total results for Jun: 585\n", + "Total results for Jul: 661\n", + "Total results for Aug: 615\n", + "Total results for Sep: 490\n", + "Total results for Oct: 314\n", + "Total results for Nov: 216\n", + "Total results for Dec: 206\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "identifiers_by_month = get_monthly_counts(get_observation_identifiers, 'Identifiers')\n", + "alt.Chart(identifiers_by_month).mark_bar().encode(x='month(date):T', y='count:Q')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "international-israel", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('images/identifiers_by_month.png')" + ] + }, + { + "cell_type": "markdown", + "id": "another-ambassador", + "metadata": {}, + "source": [ + "### Combine all four metrics into one chart" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "serious-literacy", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "combined_results = observations_by_month.append([taxa_by_month, observers_by_month, identifiers_by_month])\n", + "\n", + "\n", + "alt.Chart(\n", + " combined_results,\n", + " title=f'iNaturalist activity in {PLACE_NAME} ({YEAR})',\n", + " width=750,\n", + " height=500,\n", + ").mark_line().encode(\n", + " alt.X('month(date):T', axis=alt.Axis(title=\"Month\")),\n", + " alt.Y('count:Q', axis=alt.Axis(title=\"Count\")),\n", + " color='metric',\n", + " strokeDash='metric',\n", + ").configure_axis(\n", + " labelFontSize=15,\n", + " titleFontSize=20,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "portuguese-dining", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('images/combined_activity_stats.png')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Data Visualizations - Regional Observation Stats.ipynb b/examples/Data Visualizations - Regional Observation Stats.ipynb new file mode 100644 index 00000000..13c638a3 --- /dev/null +++ b/examples/Data Visualizations - Regional Observation Stats.ipynb @@ -0,0 +1,434 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "separate-stylus", + "metadata": {}, + "source": [ + "# Regional observation stats\n", + "\n", + "This example shows how to get some general statistics on all observations in a given region.\n", + "See https://www.inaturalist.org/places to find place IDs." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "thirty-banking", + "metadata": {}, + "outputs": [], + "source": [ + "from time import sleep\n", + "\n", + "import altair as alt\n", + "import pandas as pd\n", + "from IPython.display import Image\n", + "from pyinaturalist.node_api import (\n", + " get_observations,\n", + " get_observation_species_counts,\n", + " get_observation_observers,\n", + " get_observation_identifiers,\n", + ")\n", + "from pyinaturalist.request_params import ICONIC_TAXA\n", + "\n", + "\n", + "# Adjustable values\n", + "PLACE_ID = 6\n", + "PLACE_NAME = 'Alaska'" + ] + }, + { + "cell_type": "markdown", + "id": "touched-fusion", + "metadata": {}, + "source": [ + "### General stats\n", + "Total observations, unique taxa, identifiers, and observers" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "indirect-authority", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total observations: 160759\n", + "Total taxa observed: 5869\n", + "Total identifiers: 4993\n", + "Total observers: 5970\n" + ] + } + ], + "source": [ + "total_observations = get_observations(\n", + " place_id=PLACE_ID,\n", + " verifiable=True,\n", + " per_page=0,\n", + ")['total_results']\n", + "print(f'Total observations: {total_observations}')\n", + "\n", + "total_taxa = get_observation_species_counts(\n", + " place_id=PLACE_ID,\n", + " verifiable=True,\n", + " per_page=0,\n", + ")['total_results']\n", + "print(f'Total taxa observed: {total_taxa}')\n", + "\n", + "total_identifiers = get_observation_identifiers(place_id=PLACE_ID, per_page=0)['total_results']\n", + "print(f'Total identifiers: {total_identifiers}')\n", + "\n", + "total_observers = get_observation_observers(place_id=PLACE_ID, per_page=0)['total_results']\n", + "print(f'Total observers: {total_observers}')" + ] + }, + { + "cell_type": "markdown", + "id": "outside-airline", + "metadata": {}, + "source": [ + "### Stats by iconic taxon\n", + "Show a breakdown of observations and taxa observed for each of the iconic taxa (major species groups), using their corresponding icons on iNaturalist.\n", + "Here are a couple helper functions to make this easier:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "preceding-divide", + "metadata": {}, + "outputs": [], + "source": [ + "THROTTLING_DELAY = 1.0 # Time to wait in between subsequent requests\n", + "TAXON_IMAGE_URL = 'https://raw.githubusercontent.com/inaturalist/inaturalist/main/app/assets/images/iconic_taxa/{taxon}-75px.png'\n", + "iconic_taxa = list(ICONIC_TAXA.values())\n", + "iconic_taxa.remove('Unknown')\n", + "\n", + "\n", + "# Run one search for each iconic taxon\n", + "def get_iconic_taxa_counts(function):\n", + " iconic_taxa_counts = {}\n", + " for taxon_name in iconic_taxa:\n", + " total_taxon_observations = function(\n", + " place_id=PLACE_ID,\n", + " iconic_taxa=taxon_name,\n", + " verifiable=True,\n", + " per_page=0,\n", + " )['total_results']\n", + "\n", + " iconic_taxa_counts[taxon_name] = total_taxon_observations\n", + " print(f'Total results for {taxon_name}: {total_taxon_observations}')\n", + " if taxon_name != iconic_taxa[-1]:\n", + " sleep(THROTTLING_DELAY)\n", + " return iconic_taxa_counts\n", + "\n", + "\n", + "def get_iconic_icon(taxon_name):\n", + " return TAXON_IMAGE_URL.format(taxon=taxon_name.lower())" + ] + }, + { + "cell_type": "markdown", + "id": "medium-joining", + "metadata": {}, + "source": [ + "#### Observations" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "interim-republic", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total results for Animalia: 9423\n", + "Total results for Aves: 28191\n", + "Total results for Amphibia: 380\n", + "Total results for Reptilia: 4\n", + "Total results for Mammalia: 9812\n", + "Total results for Actinopterygii: 2214\n", + "Total results for Mollusca: 5808\n", + "Total results for Arachnida: 1882\n", + "Total results for Insecta: 17780\n", + "Total results for Plantae: 63226\n", + "Total results for Fungi: 20492\n", + "Total results for Chromista: 951\n", + "Total results for Protozoa: 479\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total_observations_by_iconic_taxon = get_iconic_taxa_counts(get_observations)\n", + "\n", + "# Create a chart, sorted by number of observations, using the appropriate iNaturalist icons\n", + "observations_df = pd.DataFrame([\n", + " {'iconic taxon': k, 'observations': v, 'img': get_iconic_icon(k)}\n", + " for k, v in total_observations_by_iconic_taxon.items()\n", + "])\n", + "alt.Chart(\n", + " observations_df,\n", + " title=f'Verifiable observations in {PLACE_NAME} by iconic taxon',\n", + " width=750,\n", + " height=500,\n", + ").mark_image().encode(\n", + " x=alt.X('iconic taxon:N', sort='-y'),\n", + " y='observations:Q',\n", + " url='img'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "historical-friendship", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Including the rendered image so the chart will display outside Jupyter, e.g. on GitHub's notebook viewer\n", + "Image('images/total_observations_by_iconic_taxon.png')" + ] + }, + { + "cell_type": "markdown", + "id": "unlikely-mongolia", + "metadata": {}, + "source": [ + "#### Taxa" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "hybrid-investor", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total results for Animalia: 550\n", + "Total results for Aves: 346\n", + "Total results for Amphibia: 7\n", + "Total results for Reptilia: 2\n", + "Total results for Mammalia: 80\n", + "Total results for Actinopterygii: 143\n", + "Total results for Mollusca: 295\n", + "Total results for Arachnida: 132\n", + "Total results for Insecta: 1195\n", + "Total results for Plantae: 1791\n", + "Total results for Fungi: 1196\n", + "Total results for Chromista: 96\n", + "Total results for Protozoa: 26\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total_taxa_by_iconic_taxon = get_iconic_taxa_counts(get_observation_species_counts)\n", + "\n", + "# Create a chart, sorted by number of observations, using the appropriate iNaturalist icons\n", + "taxa_df = pd.DataFrame([\n", + " {'iconic taxon': k, 'unique taxa': v, 'img': get_iconic_icon(k)}\n", + " for k, v in total_taxa_by_iconic_taxon.items()\n", + "])\n", + "alt.Chart(\n", + " taxa_df,\n", + " title=f'Unique taxa observed in {PLACE_NAME} by iconic taxon',\n", + " width=750,\n", + " height=500,\n", + ").mark_image().encode(\n", + " x=alt.X('iconic taxon:N', sort='-y'),\n", + " y='unique taxa:Q',\n", + " url='img'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "architectural-temperature", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Image('images/total_taxa_by_iconic_taxon.png')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/Data Visualizations - Seaborn.ipynb b/examples/Data Visualizations - Seaborn.ipynb new file mode 100644 index 00000000..907246b1 --- /dev/null +++ b/examples/Data Visualizations - Seaborn.ipynb @@ -0,0 +1,564 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Seaborn examples\n", + "Here are some examples of visualizations that can be created using [Seaborn](seaborn.pydata.org)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from datetime import datetime\n", + "from dateutil import tz\n", + "from os.path import exists\n", + "from pprint import pprint\n", + "\n", + "import seaborn as sns\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib as mpl\n", + "from matplotlib import dates\n", + "from matplotlib import pyplot as plt\n", + "\n", + "from pyinaturalist.node_api import get_observations\n", + "\n", + "BASIC_OBS_COLUMNS = [\n", + " 'id', 'observed_on', 'location', 'uri', 'taxon.id',\n", + " 'taxon.name', 'taxon.rank', 'taxon.preferred_common_name', 'user.login',\n", + "]\n", + "DATASET_FILENAME = 'midwest_monarchs.json'\n", + "PLOT_COLOR = '#fa7b23'\n", + "MIDWEST_STATE_IDS = [3, 20, 24, 25, 28, 32, 35, 38] # place_ids of 8 states in the Midwest US\n", + "\n", + "sns.set_theme(style=\"darkgrid\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def date_to_mpl_day_of_year(dt):\n", + " \"\"\"Get a matplotlib-compatible date number, ignoring the year (to represent day of year)\"\"\"\n", + " try:\n", + " return dates.date2num(dt.replace(year=datetime.now().year))\n", + " except ValueError:\n", + " return None\n", + "\n", + "def date_to_mpl_time(dt):\n", + " \"\"\"Get a matplotlib-compatible date number, ignoring the date (to represent time of day)\"\"\"\n", + " try:\n", + " return date_to_num(dt) % 1\n", + " except ValueError:\n", + " return None\n", + "\n", + "def to_local_tz(dt):\n", + " \"\"\"Convert a datetime object to the local time zone\"\"\"\n", + " try:\n", + " return dt.astimezone(tz.tzlocal())\n", + " except (TypeError, ValueError):\n", + " return None\n", + " \n", + "def get_xlim():\n", + " \"\"\"Get limits of x axis for first and last days of the year\"\"\"\n", + " now = datetime.now()\n", + " xmin = dates.date2num(datetime(now.year, 1, 1))\n", + " xmax = dates.date2num(datetime(now.year, 12, 31))\n", + " return xmin, xmax\n", + "\n", + "def get_colormap(color):\n", + " \"\"\"Make a colormap (gradient) based on the given color; copied from seaborn.axisgrid\"\"\"\n", + " color_rgb = mpl.colors.colorConverter.to_rgb(color)\n", + " colors = [sns.set_hls_values(color_rgb, l=l) for l in np.linspace(1, 0, 12)]\n", + " return sns.blend_palette(colors, as_cmap=True)\n", + "\n", + "def pdir(obj, sort_types=False, non_callables=False):\n", + " attrs = {attr: type(getattr(obj, attr)).__name__ for attr in dir(obj)}\n", + " if sort_types:\n", + " attrs = {k: v for k, v in sorted(attrs.items(), key=lambda x: x[1])}\n", + " if non_callables:\n", + " attrs = {k: v for k, v in attrs.items() if v not in ['function', 'method', 'method-wrapper', 'builtin_function_or_method']}\n", + " pprint(attrs, sort_dicts=not sort_types)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get all observations for a given place and species" + ] + }, + { + "cell_type": "code", + "execution_count": 478, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{24: 'Iowa',\n", + " 1911: 'Iowa',\n", + " 2840: 'Iowa',\n", + " 8680: 'Iowa City',\n", + " 119385: 'Iowa Wetland Management District',\n", + " 125537: 'Terry Trueblood Wetland Exploration Trail',\n", + " 136739: 'Eastern Iowa and Minnesota Drift Plains (US EPA Level IV Ecoregion)',\n", + " 137891: 'Pammel State Park, Winterset, Iowa',\n", + " 151098: 'Mount Vernon, Iowa walking path',\n", + " 161392: 'Upper Iowa River Wildlife Management Areas'}\n" + ] + } + ], + "source": [ + "# Optional: search for a place ID by name\n", + "response = get_places_autocomplete(q='iowa')\n", + "pprint({p['id']: p['name'] for p in response['results']})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Reload from previously loaded results, if available\n", + "#if exists(DATASET_FILENAME):\n", + "# with open(DATASET_FILENAME) as f:\n", + "# observations = json.load(f)\n", + "#else:\n", + "observations = get_all_observations(\n", + " taxon_name='Danaus plexippus',\n", + " photos=True,\n", + " geo=True,\n", + " geoprivacy='open',\n", + " place_id=MIDWEST_STATE_IDS,\n", + " per_page=200,\n", + ")\n", + "# Save results for future usage\n", + "with open(DATASET_FILENAME, 'w') as f:\n", + " json.dump(observations, f, indent=4, sort_keys=True, default=str)\n", + " \n", + "print(f'Total observations: {len(observations)}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data cleanup" + ] + }, + { + "cell_type": "code", + "execution_count": 453, + "metadata": {}, + "outputs": [], + "source": [ + "# Flatten nested JSON values\n", + "df = pd.json_normalize(observations)\n", + "\n", + "# Normalize timezones\n", + "df['observed_on'] = df['observed_on'].dropna().apply(to_local_tz)\n", + "\n", + "# Add some extra date/time columns that matplotlib can more easily handle\n", + "df['observed_time_mp'] = df['observed_on'].apply(date_to_mpl_time)\n", + "df['observed_on_mp'] = df['observed_on'].apply(date_to_mpl_day_of_year)" + ] + }, + { + "cell_type": "code", + "execution_count": 397, + "metadata": {}, + "outputs": [], + "source": [ + "# Optional: narrow down to just a few columns of interest\n", + "#pprint(list(sorted(df.columns)))\n", + "#df = df[OBS_COLUMNS]\n", + "\n", + "# Optional: Hacky way of setting limits by adding outliers\n", + "# JointGrid + hexbin doesn't make it easy to do this the 'right' way without distorting the plot\n", + "#df2 = pd.DataFrame([\n", + "# {'observed_on': datetime(2020, 1, 1, 0, 0, 0, tzinfo=tz.tzlocal()), 'quality_grade': 'research'},\n", + "# {'observed_on': datetime(2020, 12, 31, 23, 59, 59, tzinfo=tz.tzlocal()), 'quality_grade': 'research'},\n", + "#])\n", + "#df = df.append(df2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic seasonality plot: observation counts by month & quality grade" + ] + }, + { + "cell_type": "code", + "execution_count": 455, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
observed_monthquality_gradecounts
01.0casual3
13.0casual1
24.0casual4
34.0research29
45.0casual2
55.0research309
66.0casual20
76.0research1479
87.0casual21
97.0needs_id2
107.0research3325
118.0casual27
128.0needs_id3
138.0research3772
149.0casual34
159.0needs_id6
169.0research3023
1710.0casual6
1810.0research652
1911.0casual5
2011.0research23
2112.0research6
\n", + "
" + ], + "text/plain": [ + " observed_month quality_grade counts\n", + "0 1.0 casual 3\n", + "1 3.0 casual 1\n", + "2 4.0 casual 4\n", + "3 4.0 research 29\n", + "4 5.0 casual 2\n", + "5 5.0 research 309\n", + "6 6.0 casual 20\n", + "7 6.0 research 1479\n", + "8 7.0 casual 21\n", + "9 7.0 needs_id 2\n", + "10 7.0 research 3325\n", + "11 8.0 casual 27\n", + "12 8.0 needs_id 3\n", + "13 8.0 research 3772\n", + "14 9.0 casual 34\n", + "15 9.0 needs_id 6\n", + "16 9.0 research 3023\n", + "17 10.0 casual 6\n", + "18 10.0 research 652\n", + "19 11.0 casual 5\n", + "20 11.0 research 23\n", + "21 12.0 research 6" + ] + }, + "execution_count": 455, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Preview: Show counts by month observed X quality grade\n", + "df['observed_month'] = df['observed_on'].apply(lambda x: x.month)\n", + "df[['observed_month', 'quality_grade']].groupby(['observed_month', 'quality_grade']).size().reset_index(name='counts')" + ] + }, + { + "cell_type": "code", + "execution_count": 456, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 456, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plot the same data on a simple histogram\n", + "sns.histplot(data=df, x='observed_month', hue='quality_grade', bins=12, discrete=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More detailed seasonality plot: observation counts by month observed & time of day\n", + "\n", + "This plot uses a joint hexbin plot with marginal distributions.\n", + "\n", + "It attempts to answer the question \"When is the best time to see monarch butterfies in the Midwest US?\"" + ] + }, + { + "cell_type": "code", + "execution_count": 471, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 471, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtIAAALUCAYAAAAmM9W3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAEAAElEQVR4nOy9eZwkdX3///xUVR9z7szsPexyCogHsLIIBlkUiIicRjGHmkSNEW/NYfyq0cQjCcb8iF/F4JVEv/EblW8URYkQj4hGRBAUEJBrWXZ39pq7p6evqs/790d19/bMdPd0V9fszrDv5yOG2a7qV78/dXzq9Xl/3lVlRERQFEVRFEVRFKUtnMMdgKIoiqIoiqKsRNRIK4qiKIqiKEoE1EgriqIoiqIoSgTUSCuKoiiKoihKBNRIK4qiKIqiKEoE1EgriqIoiqIoSgTUSCvLik984hP82Z/92eEOo23+6I/+iK9//euH5LcuueQS7rjjjkPyW1F4/PHHufLKK9myZQtf/OIXD3c4sfC1r32N3/3d3215/fPPP5+f/OQnSxhR+4yOjvLKV76SLVu28Hd/93dzzrWRkRG2bNlCEASx/V7cmoejb/jmN7/Ja1/72obLX/3qV3PDDTccwogURVluqJFWDilf+9rXuOyyyzjttNM455xz+MAHPsD09PThDqst6l3QP/e5z/HSl7409t9697vfzbXXXjvns29/+9ucddZZsf9WXHzuc5/juc99Lvfccw+///u/f7jDWXHU2+dxGPOvfOUrDA4Ocvfdd/Pud797zrLh4WHuueceXNft6Dfi0rzjjjvYtm1bbLHM593vfjcnn3wy3/ve9+Z8/pGPfISTTz6Zr33tawBcfvnl/PM///OSxdEOi5n2Rtus9nvT09P8r//1vzjnnHPYsmULF110EZ/5zGeWLGZFORJQI60cMv75n/+Zj33sY/z5n/85d911F1/5ylcYGRnhNa95DcVi8ZDF4fv+IfutI5GRkRFOPPHEwx1G28SZjV1OiAjWWkZGRjjhhBMwxhzukJYFxx57LDfeeGP1377v853vfIejjz768AW1xPzt3/4ts7Oz3Hzzzfz85z/nU5/61FO6vYpyKFAjrRwSZmZm+MQnPsH73vc+tm3bRiKRYNOmTfzjP/4jIyMjfPOb36yuWywWecc73sGWLVt46UtfykMPPVRd9pnPfIZzzz23mk25/fbbAbDW8pnPfIYLL7yQs846i7e//e1MTk4CsGvXLk4++WRuuOEGXvCCF/AHf/AHvO51r+Pf/u3f5sR4+eWXc+uttwLw4Q9/mPPOO4/nPOc5/NZv/RZ33XUXALfddhuf/vSn+c///E+2bNnC5ZdfDszN+lhr+dSnPsULX/hCnve85/Gud72LTCYzJ5avf/3rvOAFL+Css87in/7pn+pus6985SvcdNNNfP7zn2fLli1cffXVwNzs5Cc+8Qne9ra38Wd/9mds2bKFyy67jO3bt/PpT3+a5z3veZx33nn8+Mc/rmpmMhne85738PznP59zzz2Xa6+9tmogd+zYwate9SrOOOMMzjrrLN7xjnc03J/f+973uOSSS9i6dSuvfvWreeyxxwD4/d//fe644w4++MEPsmXLFrZv377gu69+9au59tpr+Z3f+Z1quyYmJvjTP/1TnvOc5/Cyl72MXbt2Vde/++67ednLXsYZZ5zBy172Mu6+++45Wv/4j/9Y1Xrta1/L+Ph4dfnb3vY2zjnnHM444wxe+cpX8sgjj1SXvfvd7+YDH/gAr3/96zn99NO544472LNnD295y1s4++yzOeuss/jgBz84J/ZrrrmGM888k/PPP58f/vCHDbcPwH333cdLXvISzjzzTP7X//pfFAoFoH6ZyMknn8yOHTvq7vM///M/Z2RkhKuvvpotW7bw2c9+FoBf/OIX/M7v/A5bt27l8ssvn1PuU7uNTzvtNN71rndx4403VnXnZ7crx2VlkBnHcTJfc7F9VWF2dpbXv/717N+/ny1btrBlyxb27dsHQKlU4l3vehdbtmzhkksu4b777qt+b9++fbz1rW/l7LPP5vzzz1+0rOj888/n7rvvZmpqCoAf/ehHnHzyyaxZs6a6zvx99T//8z+8+MUv5owzzuCDH/wgtS8GfuELX8j9998PwDe+8Q1OPvlkHn30UQBuuOEG3vSmNwHN+6pCocCf/dmfcdZZZ7F161Ze9rKXMTo6yrXXXstdd91VPa/mH5etct9993HZZZexatUqHMfhhBNO4MUvfnEkLUVRQtRIK4eEu+++m0KhwIte9KI5n/f09LBt27Y5F/bvfe97vPjFL+ZnP/sZl156KW9605solUo8/vjjfOlLX+L//b//xz333MPnP/95jjrqKAC++MUv8t3vfpd/+7d/40c/+hGrVq1acLG58847ufnmm/n85z/PZZddxre+9a3qskcffZSRkRFe8IIXAPDsZz+bG2+8sRrD29/+dgqFAtu2beMNb3gDF198Mffcc8+cAUCFr33ta3z961+vxjQ7O7sglp///Od85zvf4Qtf+ALXXXdd1YjW8tu//dtcdtllvO51r+Oee+7h+uuvr7ttf/CDH3DFFVdw5513csopp/C6170Oay233XYbb37zm3n/+99fXfcv/uIv8DyPW2+9lRtvvJH/+Z//qQ4APv7xj3POOedw5513ctttt/GqV72q7u9t376dP/3TP+U973kPt99+O9u2bePqq6+mWCzyxS9+ka1bt/L+97+fe+65h+OOO66uxs0338xHP/pRbrvtNp588kl+53d+h5e97GX87Gc/44QTTuC6664DYHJykje84Q28+tWv5o477uA1r3kNb3jDG5iYmKhqfetb3+Jv//Zvuf322ymVSnOm4rdt28Ytt9zC7bffzjOe8YwFJTnf+ta3uPrqq7n77rs5/fTTecMb3sDw8DDf//73ue2223jJS15SXffee+/luOOO46c//Sl/9Ed/xHvf+945Rmo+FUP8X//1X2zfvp1PfepTDdetUG+f//3f/z3Dw8Ncf/313HPPPbz+9a9n3759vOENb+CNb3wjP/vZz/iLv/gL3va2t80xpt/4xjf40Ic+xN13383f/u3fztH9jd/4jaZxxHGc1KPZvqrQ3d3NZz/7WdatW8c999zDPffcw/r16wH4/ve/zyWXXMJdd93F+eefz4c+9CEgNKdvfOMbOfnkk7ntttv4whe+wBe+8AV+9KMfNYwlmUxy/vnn8+1vfxuAG2+8kSuvvLLh+uPj47z1rW/lHe94Bz/96U85+uij5wzqzjzzTH72s58BcNddd7F58+bqv++8806e+9znAs37qq9//evMzMzw3//939xxxx389V//Nel0mne+851zzqvac7odTjvtNK699lr+4z/+gyeeeCKShqIoc1EjrRwSJiYmGBwcxPO8BcvWrl07xxg985nP5MUvfjGJRKJa9vHLX/4S13UpFos89thjlEolNm3aVJ2W/MpXvsI73/lONmzYQDKZ5C1veQu33HLLnDKOt771rXR3d5NOp7nwwgt56KGH2L17NxCant/8zd8kmUwCcMUVV1Tjfe1rX0uxWKybXa3HTTfdxB/+4R+yefNmenp6+JM/+RNuvvnmObG85S1vIZ1O8/SnP52nP/3pc7Lu7bJ161bOPfdcPM/jxS9+MRMTE/zxH/8xiUSCl7zkJezevZvp6WlGR0e57bbbeM973kN3dzerV6/mD//wD6tGwvM8RkZG2L9/P6lUiq1bt9b9vZtvvpnzzjuPc845h0Qiwete9zry+Tz33HNPyzH/1m/9FkcffTR9fX1s27aNzZs38xu/8RvVNjzwwAMA/Pd//zfHHHMMV155JZ7ncemll3L88cfzgx/8YI7WcccdRzqd5sUvfjEPPvhgddnLX/5yent7SSaTvPWtb+Whhx6qzg4AXHDBBZxxxhk4jsOvf/1r9u/fz7ve9S66u7sXbIPh4WFe8YpX4LouL33pSzlw4ACjo6MN2/jKV76SjRs3MjAwwBvf+Mbqdo6Db3zjG2zbto3zzjsPx3E455xzeNaznjUnS/7Sl76UE088Ec/zSCQSLWvHdZzUo9m+aoUzzjiD8847D9d1ueKKK6rnzX333cf4+DhvectbSCaTbN68mVe84hXcfPPNTfWuuOIKvvGNb5DJZLjzzju58MILG65722238bSnPa3aN/3BH/zBnOz1fCP9hje8gTvvvBMIjfSZZ54JNO+rPM9jcnKSHTt24Louz3rWs+jt7W1rGzXjL//yL7nsssv40pe+xCWXXMJv/uZvLjqzoihKcxa6GkVZAgYHB5mYmKheLGo5cOAAg4OD1X9v2LCh+rfjOKxfv579+/ezdetW3vOe9/CJT3yCRx99lOc///m8+93vZv369YyMjPDmN78Zx3HmfHdsbKyubm9vL+eddx7f/va3+eM//mO+/e1vV7NbENZz33DDDezfvx9jDDMzM3PMfjP2799fzZQDHHXUUfi+PyeW2gtwV1cXs7OzLWnXY/Xq1dW/0+k0g4OD1Ru80uk0EE6X79+/H9/3ef7zn19d31rLxo0bAfjzP/9zPv7xj/Pyl7+cVatW8ZrXvIaXv/zldds3PDxc/bfjOGzcuLE6/d4Kte1PpVJz/p1Op6vbY/5vQWhoa39r7dq11b9rt2UQBFx77bV85zvfYXx8vHpsTExM0NfXB1BtO8CePXsYHh6uO9ibH3NXVxdA0/1Wqz08PMz+/fsbrtsuIyMjfOc735kzoPB9f85NqLW/3652HMdJPRrtq1aZf5wUCgV832f37t3VPqJCEASLmvytW7cyPj7Opz71KV7wghdUz5d67N+/f04fYoyZs42f+9zn8tGPfpQDBw5greXiiy/mk5/8JLt27SKTyXDKKacANO2rrrjiCvbu3cuf/MmfMD09zeWXX8473/nOlgZCruvWvf+jVCpVj+l0Os3VV1/N1VdfzczMDJ/5zGd4xzvewQ9+8AMGBgYW/Q1FURaiRlo5JGzZsoVkMsmtt946Z7p8dnaW2267jT/5kz+pfrZ3797q39Za9u3bx7p16wC47LLLuOyyy5iZmeH9738/H/vYx/j7v/97NmzYwN/8zd9wxhlnLPjtSr3t/JusLr30Uj75yU9y5plnks/nqybkrrvu4rOf/Sz/+q//yoknnojjOJx55pnVafzFbtZat25dNdMN4YXT8zxWr149p22tEOeNYZUM2E9/+tOGMwMf/vCHgXAbvOY1r+HMM8/kmGOOmbPeunXrePjhh6v/FhH27NlTnX6Pk3Xr1jEyMjLnsz179nDuuecu+t2bbrqJ733ve/zLv/wLmzZtIpPJzNmP89m4cSN79uypO9iLwp49e6p/j4yMVI/hrq4u8vl8ddmBAwfmfK+Vfb5x40auuOKK6v6qR9RjJ67jpBPajX3jxo1s2rSpeo9DO1x++eVcd911i9ZUr127ds75WznuKxxzzDGk02n+z//5P2zdupXe3l7WrFnDV7/61eqsB9C0r4Jwtuotb3kLu3bt4o//+I857rjjuOqqqxZtx/DwMBMTE2SzWXp6eqoxjoyMLBiMQphMeMMb3sCnP/1pdu3apUZaUSKipR3KIaGvr483v/nNfPjDH+a2226jVCqxa9cu3v72t7NhwwauuOKK6rq/+tWvuPXWW/F9ny984Qskk0lOO+00Hn/8cW6//XaKxSLJZJJUKlXNvP7u7/4u//iP/1g1sOPj43z3u99tGtN5553HyMgI//t//29e8pKXVC902WwW13UZGhrC930++clPMjMzU/3e6tWr2b17N9baurqXXnopX/jCF9i5cyfZbJZrr72Wiy++OJI5W7169Zwb7zph3bp1nHPOOfzd3/0dMzMzWGt58sknq9PR//mf/1k1CqtWrcIYMydrVuHiiy/mhz/84Zw612QyyZYtW2KJs5bzzjuPJ554gptuugnf97n55pt59NFHq7XszchmsySTSQYHB8nlcvx//9//13T9U089lbVr1/IP//APzM7OUigU+PnPfx459v/7f/8ve/fuZXJykk9/+tPVAeTTn/50HnnkER588EEKhQKf+MQn5nyv3j5fs2YNO3furP778ssv5wc/+AE/+tGPCIKAQqHAHXfc0fZArR5xHSedsHr1aiYnJ+eU4TTj1FNPpbe3l8985jPk83mCIODhhx/m3nvvXfS7r371q/mXf/mXaulFI8477zweeeSRat/0xS9+cUFpz3Of+1z+7d/+rao1/9/QvK/66U9/yq9//WuCIKC3txfP86p93PxjYD7Dw8OcdtppfOxjHyObzVIsFvnc5z6H53mcfvrpAFx33XXce++9FItFCoUCX/ziF+nv7294L4OiKIujRlo5ZLz+9a/nne98Jx/96Ec544wzeMUrXsHGjRv513/912ptMoR1qzfffDNnnnkm3/jGN/jEJz5BIpGgWCzyD//wD5x11lk8//nPZ3x8nHe+851A+LSI888/n9e+9rVs2bKFV7ziFYteRJPJJL/5m7/JT37yEy699NLq589//vPZtm0bF110Eeeffz6pVGrOFG7lLvezzjqr7rOjX/ayl3H55Zfzqle9igsuuIBkMslf/uVfRtpmL3/5y3n00UfZunVr9a7/TvjoRz9KqVSqPk3ibW97WzUjet9993HVVVexZcsW3vjGN/Le976XzZs3L9A4/vjj+fu//3s+9KEPcfbZZ/ODH/yA66+/fs4+jIvBwUGuv/56/uVf/oWzzjqLz33uc1x//fUMDQ0t+t0rr7yS4eFhzj33XC655JKqmWiE67pcf/317Nixgxe+8IVs27aN//zP/4wc+6WXXsprX/taLrzwQjZv3swb3/hGAI477jje/OY384d/+Ie86EUvWpCZrLfP//iP/5h/+qd/YuvWrXz+859n48aNfOpTn5rzdJbPf/7zDQd37RLHcdIJJ5xwApdccgkXXnghW7duXbRsyHVd/umf/omHHnqICy64gLPPPpv3ve99cwbAjRgYGOB5z3veolnwoaEhPv7xj1f7oB07dvCc5zxnzjpnnnkm2Wx2jpGu/Tc076tGR0d529vexhlnnMFLXvISnvvc51afDPT7v//73HLLLZx55pkNZyKuvfZaxsbGeNGLXsS5557LT3/6Uz7zmc+QSqWAMNP/nve8h7PPPptzzz2Xn/zkJ3z605+uZrAVRWkfI81u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Eom0+27RokX64osv1rFYTO/YsUOPGTPGPl+vv/66bm1t1Vpr/cknn+ijjz5aBwKBDm0/+uij+vzzz9fHHXecDoVCOfmltdaxWMw+NwnWr1+vx4wZo3fs2KFjsZi++OKL9aJFi7TWWjc1NemVK1fqFStW6MmTJ3do08lzl4l58+bpG264QWut9Zo1a/R//dd/ab/fr7XW+rPPPtNffPGFvvbaazs85t7ysampyf57y5Yt+sgjj7TbSWc+JvvqVBsOBAL2dRKLxfTll1+u//jHP2qtM9d7NmTjZ7p9wuGwfc1rrfUdd9yh58yZo7XW2u/36//6r//Sa9as0VprfcMNN+h58+Zl5VOh7SMTGzZs0P/5z3+01ta5vPLKK/V9992ntdb6yy+/1CeccILesWOH1lrr+fPn6xtvvNH+bUNDgx49erTevHmz1lpr0zT1qlWrHPUvUz9y++236zvvvFNrbZ37M888U7/44osd2rn++uv1lVdeqYPBoNbaum7HjBlj9/3pePPNN9P2EVpr/f777+stW7Zora12Mm7cOP32229rrbX+61//qqdNm2b3nXPmzNE333xzOxsnn3yyPvnkk/Wzzz6b8p1T7WXnzp36zTfftD/feeed+vrrr+9w39tvv13feuut7fxL9mXZsmX66KOP1u+//3638E/Yd5GIdB48/PDDTJ06lUmTJnH22WfzySef2NtGjBjBggULmDp1KqeccgrLli3r0MbSpUuZNGkSPp8Pn8/HpEmT7Ajb6NGjqaysBGC//fZjwIABbNmyJWc/Fy1axLRp05gyZQoXXnghX331lb2tubmZK664gokTJ3LhhRd2GKEAePnllznnnHMA+P73v4/X6+Wjjz4C4PDDD+eAAw7IypfJkyfz7LPPArBhwwYCgQAHH3ywvf1f//oXZ599NpMmTaK2tpYXX3zR3nbBBRfw61//mh/96EdMnz69ne2lS5cybdo0DMOgT58+jBs3jpdffhmAMWPGUFxcDFh1o7VuF81KsHDhQqZPn87BBx/MihUr7O/nzZvHlVdeyaWXXsqECRO44ooraG5utrddc801TJ8+nbq6OpqamlJsLlu2jHHjxtGnTx8Mw2DatGl2PZeXlzNq1ChKSkq65Nx9+OGH1NTUpNiaOHFih1Gcl156ya7nAw88kEMPPZTXX38dgIMPPphvfetbGEb67mJv+FheXm7/3drailLKjp5l42MybSNtyZ/Hjh3Lb3/7W84++2zGjh2bNsLt8/koKioCIBqNEgwG7eNnqvdcyORnOjwej33Nx2IxWltbbb9ef/11Dj30UA488EAAzjnnHF566aWsfEnXPmbOnJlyjpI/NzQ08KMf/ciOJF922WUdns8hQ4bwne98BwDDMDj88MPZvHkzAJ9//jkjR46kT58+AJx00kk8//zz9m+3b9+O2+2mV69eACilGDlypL39gw8+4IILLmDKlClMmTKFV199FbCijKNHj2bu3LmceeaZ1NbW8s4773RY9kz9iFKKlpYWTNMkHA4TiUQYOHBgOxubNm3ipZde4pZbbsHr9QLWdXvZZZdx33332fv97//+L7W1tUycOJFzzjkH0zSZPXs2q1evpq6ursPZxO9973v2McvLyxk+fDibNm2yt4fDYUKhEKZp0tLSwn777ddhOS+//HLmzZuXMrOSYN26dfzoRz+itraWyZMn2/3D/PnzU2ZBd+7cyejRo2ltbU35fa9evRg9erT9+YgjjrDrOJlwOMzzzz/P1KlTO/QxwWmnncY555zDQw89ZP8uUZd1dXVce+21tLS0ANb97vrrr7fP6+zZs9vZc9o/Yd9BhHQeTJo0iYULF7J48WKuvPJKbr755pTtZWVlLFy4kLvuuovbb7+9Qxv19fUMGjTI/lxVVUV9fX27/d566y2ampo49NBDM/o0Y8YMe9rp73//O++88w4vvfQSjz32GM8++yyXXHIJN9xwg73/v//9b66++mqee+45jjnmmA6na3fu3InW2r6BJfzMR9SPHj2azz77jN27d7No0SImTZqUsv073/kOjz/+OIsXL+YPf/gDc+fOTZmO/vzzz3nooYd48MEH29nu6Fx25OPixYvZf//9O7yJfPrpp+zevZtjjz2WKVOmsHDhwpTt//73v/nVr37Fiy++SFlZGffff7+97Z133uH222/n+eeftwdA6XwbNGhQh/WciXzP3eGHH05JSQkrV660/TQMg6OOOqrdMTZv3szgwYPtz7nW897wEeCJJ56gurqayZMnc9ttt9G7d++sfcyFYDDIk08+yZ/+9Cfuuece+4bcloaGBurq6hg9ejSlpaWcddZZgDP1Xih1dXUcd9xxrFu3jp/97GcF+dVZ+0jH7bffzujRo3nxxRe58cYb7XrORDAYZOHChYwdOxaAQw45hI8//pgNGzagteaFF16gtbXVFrKHHHIIhx9+OD/4wQ+YMWMGjzzyCDt37gSgqamJm2++mXvuuYdnn32WBQsWcNNNN9kD3l27djFixAieeeYZbrzxRv7f//t/HYrIZNr2I9OnT2fNmjWccMIJ9r9HH310u999/vnn7L///rbgT3DEEUfw6aefAlbwY8WKFTzxxBM899xzPPDAAxiGwU033cTw4cNZsmQJ9957b0b/Vq9ezfvvv8+xxx4LYKchnHDCCRx//PGsWbOGiy++uMPfHnrooRx66KE88cQT7bZdc8011NTU8Pzzz3P33Xdz7bXX0tjYyOTJk1m6dKmdzvLCCy8wduzYjAEC0zR54okn7DpOZsWKFQwcOJDvfve7GcsJ1gDiyy+/BOD3v/895eXlPPPMMyxZsoQBAwbY94s5c+ZQUlLCkiVLeO6557j88ssz2nXKP2HfQIR0Hnz88cecd9551NTU8Ktf/SolIg1wxhlnAFYHuXXrVkKhUF7H+fLLL7nuuuu455578Pl8Gfe99957WbJkCUuWLGHMmDGsWLGCTz/9lGnTplFXV8c999yTIoyOPvpohg0bBsC0adN488038/IxW5RSnH766bz44ossXbqUCRMmpGxvbGxkxowZ1NTUcMkll7B7927WrFljb6+trcXtdud9/JUrV/Lb3/6We+65p8PtzzzzDHV1dSilOO200/jggw9SovQ/+MEP6NevHwBnnnlmyvk68cQTUwYbTlPIubvgggt4/PHHAXjsscc477zzerSP5557Li+//DJPPvkkDzzwgC2YnCbRhocMGUJFRUXaQcXAgQNZsmQJb7zxBpFIhL/+9a9d4k8+JPwaNmxYh8IoFzprH+l466237Mjd4MGDOe644zLuH41Gufrqqzn22GM55ZRTADjooIOYNWsWV199NWeddZYtRBP9gWEY3H///Tz66KOMHj2a1157jYkTJ7Jr1y7ee+89Nm7cyKWXXkpdXR2XXnopSinWrVsHWNH7iRMnAnDMMcfg8/lSZu7a0lE/8vLLLzNixAj+8Y9/8Prrr/POO+/YM2LJaK07PV9/+9vfOPfccykrKwPIeaC4detWpk+fzk033WRHqP/zn/+wevVqXn/9df7xj39wyCGHcOedd6a1cdVVV/G73/0uZfDo9/v55JNP7Lr81re+xciRI3n//fcZNGgQw4cP57XXXgOswUBn0drbbruNkpISzj///HbbFi5cmHW0N/mcrlixgueee84OKK1YsYL169cD1nm95JJL7JmZzvprp/wT9g3yVyb7KKZpcuWVV/LnP/+Z7373uzQ0NHDiiSem7JOYtnO5XIB1c0h8l6Cqqipl2qi+vp6qqir789q1a/nJT37Crbfeyve///2c/dRaM3XqVK688sqs9lVKtfs+0Yk3NjbaHU99fX3aacHOmDJlCtOmTeOYY45pd4O45ZZbGDt2LPfddx9KKcaPH58yAMkU3Uicy8MPP9z2MTnq9t5773Httddy//3324OHZBJTdV6v136wMxKJsGjRIi677LJ2+7c9X6WlpZ36lmDz5s0p9Zwt+Z676upqfv3rX7Nq1SreeuuttA+iDho0iE2bNqXUc/I0Z3fwMZkRI0YwYMAAVq5cyfjx43PyE6y2mfxQVdvBbnJ7dblcKQ9SdkRJSQlnnHEGzz//PBMmTHCs3jvzszM8Hg+TJ0/mxhtv5NJLL6Wqqoq33norJ786ax+F+pggFotxzTXXUFlZyS9/+cuUbRMmTLAHZx9++CEDBw60xWaCgw8+mIMPPpjzzjuPM844g5UrV1JUVMSIESN47LHH2h1v48aN7b5L1xdC+n7kz3/+M3PmzMEwDMrLyxk7dixvvfUW1dXV7fxbv349u3btSolKv//++4wYMSLzycmCHTt2cNFFF/HjH//YHgiCJWyPPfZYOzVq4sSJKbOTbRk2bBgnnXQSf/jDHzo9ZuJcTZ48mcWLFzN06FCam5sz3rPmzp3LunXrWLBgQbtUrIaGBt5++23uuuuuTo8N8NFHH/Htb38bsOru5ptv7nSw1hlO+ifsG0hEOg+i0ah980lE0nKlurqaxYsXEwwGCQaDLF68mNNPPx2wckwvueQSZs2axUknnZSX/bFjx7JkyRI7khaLxfj444/t7e+++y5r164FrFU80omm6upq/vKXvwDWtHswGOw0zSQdQ4cO5eqrr+4wz7m5uZnBgwejlOKNN96wI0bZUF1dzdNPP41pmjQ2NrJ8+XJbXH344YdcffXV3HvvvWmn4pYvX86wYcN4/fXXWbFiBStWrODhhx+2c34BXn31VfsJ7kWLFmUtMsePH8/y5ctpbGzENE2efvppu55zId9z5/F4mDp1Kj/96U+pra218zzbUl1dba8KsHbtWj766CPGjBnTrXxcvXq1/feGDRv45JNP+Na3vpWTjwn2339/O9f/X//6F9u3b8/ZxoYNG+w0gHA4zCuvvGLnhTtV7/n4WV9fb0cTTdNk2bJltl9jxozho48+stv+X/7yl0796qx9JPu4devWFKF+zDHHsGjRItuvdDNfpmkyc+ZMXC4Xd9xxRzsxu23bNsAS6ffee29KakJDQwPvvfee/XnLli00NjYyZMgQjjzySNatW5dy3A8//NCOZEYiETvf+p133iEUCnHQQQe18y9TPzJkyBA7XzgcDvOvf/3LFndt90us6pEYbHz++ecsWLDATjU4+eSTeeKJJ/D7/QD2jEtZWZn9XUfs3LmTiy66iPPOO49p06a1O+6bb75JJBIB4LXXXuvQv2SuuOIKHn/8cfs6KisrY+TIkXZdrl69mk8//ZTvfe97gHW9v/322zz88MNMnjw5rd3f/OY3fPzxx8yfP99+viCZRYsWcdJJJ2UViV++fDlPPPEEF110EWDd8x555BF7tRe/32/3GSeffDIPPfSQXe+Jvrwr/RP2HSQinQPRaJTi4mJmzJjBmWeeSVVVVbtodLaMHj2a0047jZqaGrTWTJo0yV5O5+6772bXrl3ce++9dj7cNddck5OwGTVqFFdddRU//elPicViRCIRqqurbRE8atQo5s2bxxdffEGvXr24++67O7Tz85//nGuvvZbFixfj9Xq566677FH673//e/70pz/R2NjIzJkz8Xq9LF26tF2kKJmzzz477XFuvfVWfve73zFixIicIjR1dXV88MEHnHbaaQD87Gc/Y+jQoQDceuutBINBbrrpJnv/u+66K8X+s88+S21tbYrNI488EtM0efvttwE47rjjuOGGG9iwYQMHHXQQM2fOzMq3oUOHMn36dDt39vjjj7enkmOxGCeffDLhcBi/38+JJ57ItGnTuOKKKzq0le+5mzZtGvfddx/nnntuWj8vueQSZs6cyamnnophGMyePduuxxdeeIG77rqLpqYmXnnlFR588EEefvjhDkVsV/p477338uWXX+J2u3G5XPzyl79k+PDhOfmYmB268sormTlzJk8//TRHHXVUygxGtrz77rv8/ve/tx96HDVqlD2IyFTv2VCIn2vWrGHu3LmYponWmkMOOYRZs2YBliCaPXs2//3f/41pmowcOdLelo7O2sdZZ53FjBkzmDhxIgceeKA9MwQwa9YsfvGLX7B06VKGDRvGUUcd1WH/8Prrr/Pcc89x8MEH20tBHnXUUfbzJ9dffz2bN28mEolwxhlncOGFF6acq3nz5rFp0yZ8Ph+maXLVVVfZDy/ef//93H333cyZM4dIJMLQoUNZsGABYD1gtm7dOqZNm0YwGOTXv/51hwIqUz9yww03cPPNN1NbW0ssFmP06NF2vbfllltu4Z577uGMM87A4/Hg9XqZNWuW3fdPmjSJhoYGzj77bFwuF6WlpTz22GOMGDGCgw46iJqaGoYNG9YuT/rBBx9k7dq1PPnkk/aA+MILL2Tq1Kmcd955fPHFF0ycOBG3201VVRW33XZbh/4l2G+//airq0tZRu9//ud/uOmmm3jkkUdwu93cdddd9gxWcXExp5xyCs8++yyvvPJKhza/+OILFixYwIEHHmg/2DxkyJCUpV8XLVqU8XqcMWMGRUVFBAIBhg8fzoMPPsgRRxwBwE9+8hPuu+8+zjzzTJRSKKW4/PLLGT58ONdffz1z5syhpqYGl8vFMccc027Wwwn/hH0TpbNJ3BLYunUrp59+Om+88Uan+crCN4t58+bR2trKdddd93W7khdLlizhxRdf7PBBze7C3vCxp7ThnuJnNgSDQdxuN263m61bt3LmmWfyyCOPdJhitbfZuHEjU6dOTYmgC4Ig5IpEpLPgT3/6E48//jjXXXddj7+xCfsWl1xyCevXr+eBBx74ul1Jy97wsae04Z7iZ7asXbuW6667Dq010WiUyy+/vFuIaEEQBKeQiLQgCIIgCIIg5IE8bCgIgiAIgiAIeSBCWhAEQRAEQRDyQIS0IAiCIAiCIOSBCGlBEIQuZsSIETmtjS4IgiD0DERIC4KwTzF27FgOPfTQdi9lqKurY8SIER2+8S4XLrjgAp5++umCbAiCIAg9AxHSgiDscwwePJgXX3zR/vzZZ5/Zb0QTBEEQhGwRIS0Iwj5HXV0dixcvtj8vXryYSZMm2Z+bm5v5xS9+wbHHHsvJJ5/M/fffj2magPWmv3PPPZe5c+cyatQoxo4dy2uvvQZYrxh+5513mD17NkceeSSzZ8+2bf7zn//ktNNOY9SoUdx6663IyqOCIAg9HxHSgiDscxxxxBH4/X5Wr15NLBZj6dKlKa/wvu2222hubmb58uU8+uijLFmyhIULF9rbP/zwQw466CDefPNNfvzjHzNr1iy01lx99dV8//vf56abbuK9995LeaX0q6++yjPPPMOSJUt46aWX+Pvf/75XyywIgiA4jwhpQRD2SRJR6TfeeINhw4YxcOBAAEzTZOnSpfz85z+nrKyMIUOGcNFFF/Hcc8/Zvx00aBBnnXUWLpeLyZMns23bNrZv357xeJdeeikVFRUMGjSI0aNH8+mnn3Zp+QRBEISuR14RLgjCPkldXR3nn38+GzdupK6uzv5+586dRCIRBg0aZH83aNAgGhoa7M/9+vWz/y4uLgagtbU14/H69++f8puWlpaCyyAIgiB8vUhEWhCEfZLBgwczZMgQXnvtNU477TT7+969e+PxeNi8ebP9XX19vR2xFgRBEIQEIqQFQdhnueOOO/jjH/9ISUmJ/Z1hGFRXV/Ob3/wGv9/Ppk2b+MMf/pCSQ52Jfv36sWHDhq5yWRAEQehGiJAWBGGfZf/99+ewww5r9/2NN95IcXEx48aN44c//CE1NTVMnTo1K5sXXnghy5YtY9SoUdx+++1OuywIgiB0I5SWNZgEQRAEQRAEIWckIi0IgiAIgiAIeSBCWhAEQRAEQRDyQIS0IAiCIAiCIOSBCGlBEARBEARByAMR0oIgCIIgCIKQByKkBUEQBEEQBCEPREgLgiAIgiAIQh6IkBYEQRAEQRCEPBAhLQiCIAiCIAh58P8B0NMp56XORJgAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "grid = sns.JointGrid(data=df, x='observed_on_mp', y='observed_time_mp', height=10, dropna=True)\n", + "grid.ax_marg_x.set_title('Observation times of monarch butterflies in the Midwest US')\n", + "\n", + "# Format X axis labels & ticks\n", + "xaxis = grid.ax_joint.get_xaxis()\n", + "xaxis.label.set_text('Month')\n", + "xaxis.set_major_locator(dates.DayLocator(interval=30))\n", + "xaxis.set_major_formatter(dates.DateFormatter('%b %d'))\n", + "#xaxis.set_minor_locator(dates.DayLocator(interval=7))\n", + "#xaxis.set_minor_formatter(dates.DateFormatter('%d'))\n", + "\n", + "# Format Y axis labels & ticks\n", + "yaxis = grid.ax_joint.get_yaxis()\n", + "yaxis.label.set_text('Time of Day')\n", + "yaxis.set_major_locator(dates.HourLocator(interval=2))\n", + "yaxis.set_major_formatter(dates.DateFormatter('%H:%M'))\n", + "#yaxis.set_minor_locator(dates.HourLocator())\n", + "#yaxis.set_minor_formatter(dates.DateFormatter('%H:%M'))\n", + "\n", + "# Generate a joint plot with marginal plots\n", + "# Using the hexbin plotting function, because hexagons are the bestagons.\n", + "# Also because it looks just a little like butterfly scales.\n", + "grid.plot_joint(plt.hexbin, gridsize=24, cmap=get_colormap(PLOT_COLOR))\n", + "grid.plot_marginals(sns.histplot, color=PLOT_COLOR, kde=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Alternate version with shorter syntax but messier labels" + ] + }, + { + "cell_type": "code", + "execution_count": 475, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 475, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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UTs4ZUdERJrAhERERJrAhERERJrAhERERJrAhERERJrAhERERJrAhERERJrAhERERJrAhERERJqg6YSUmZmJKVOmYMiQIfj2228djx84cACzZs1CamoqUlJSsHfvXgBAWVkZ0tPTkZSUhJSUFDz++OMoLS11PG/KlCmYPn06UlNTkZqaikOHDjn+LT8/H/feey+SkpJw77334rvvvnNbXADw6KOP4s4778SsWbOQlpaGM2fOCO1bNC53x+SOY9WRmBq9+eabzZ7njuOkRlzeOlYd3a+3YvLm56+2thYrVqzA7bffjpSUFPz2t79167EiN1E07Msvv1QKCgqUyZMnK2fPnlUURVHsdruSmJjo+P7MmTPKqFGjFJvNppSVlSmff/654/m///3vlWeffdbxvfN2mpo7d66yY8cORVEUZceOHcrcuXPdFpeiKEpFRYXj+X/729+UWbNmCe1bNC53x+SOY9WRmBRFUU6dOqUsWLBAmTRpkksM7jhOasTlrWPV0f16KyZvfv5Wr16t/O53v1PsdruiKIpy+fJltx4rcg9NnyElJiYiNja22eN6vR6VlZUAgMrKSkRFRUGv18NsNmP8+PGOcaNGjUJBQUG7+ykpKUFeXh6Sk5MBAMnJycjLy3M5u+pMXADQvXt3x7hr165Bp9O1u2+ZuNwZU1vUjslqteKFF17AihUrXOJx13Fyd1zePFYd2a+3YupovC2Rjauqqgo7duzAE0884fjZ9erVq919y8ZFnedz3b51Oh1ef/11PProowgNDUVVVRXeeuutZuPsdjvef/99TJkyxeXxJUuWQFEUjBkzBk899RTCwsJgsVgQHR0Ng8EAADAYDIiKioLFYkF4eLjb4nr++edx5MgRKIqCTZs2AUCb+1YUpVNxdTQmNY9VezG98cYbuPPOO9G3b1+X56l5nDoTlzePVUf264n3VEsxqXmc2ovr0qVLMJvNePPNN5Gbm4uuXbviiSeeQGJiourvK5Kj6TOkltTX1+Ott97CunXrcODAAaxfvx6LFy9GVVWVy7jVq1cjNDQUc+bMcTz23nvv4cMPP8Sf//xnKIqCF154waNx/e53v8Mnn3yCxYsX4+WXX3bbvtWISa1j1VZMx44dw8mTJ5GWluaWfXkqLm8cKzX3q1ZM3vr81dfX49KlSxg6dCj+8pe/YMmSJVi4cCGuXbvmtv2T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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sns.jointplot(data=df, x='observed_on_mp', y='observed_time_mp', bins=24, kind='hist')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/Dockerfile b/examples/Dockerfile new file mode 100644 index 00000000..0c31f9a1 --- /dev/null +++ b/examples/Dockerfile @@ -0,0 +1,25 @@ +FROM jupyter/scipy-notebook +USER root + +# Install some more conda packages useful for data exploration & visualization +RUN conda install --quiet --yes \ + 'altair=4.*' \ + 'altair-saver' \ + 'dash=1.*' \ + 'gdal=3.*' \ + 'geoviews' \ + 'geopandas' \ + 'pip=21.*' \ + 'plotly=4.*' \ + 'python-dateutil' \ + 'python-forge' \ + 'requests=2.25.*' \ + 'xarray' && \ + conda clean --all -f -y && \ + # Install any non-conda pip packages last + pip install pyinaturalist && \ + fix-permissions "${CONDA_DIR}" && \ + fix-permissions "/home/${NB_USER}" + +USER $NB_UID +WORKDIR $HOME diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..ab29b064 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,15 @@ +# Pyinaturalist usage examples + +This folder contains some examples of building data visualizations and other neat things using +iNaturalist data. A Dockerfile and docker-compose config are also included to help spin up a +Jupyter notebook container with all prerequisites installed. + +Example visualizations: + +![](images/total_observations_by_iconic_taxon.png) + +![](images/total_taxa_by_iconic_taxon.png) + +![](images/combined_activity_stats.png) + +![](images/observations_by_date_and_time.png) diff --git a/examples/docker-compose.yml b/examples/docker-compose.yml new file mode 100644 index 00000000..933274d9 --- /dev/null +++ b/examples/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.7' + +services: + jupyter: + build: + context: . + network: host + container_name: jupyter + volumes: + # Mount the current directory as the Jupyter workspace + - .:/home/jovyan/work + # Or mount your own workspace path + # - /home/username/workspace:/home/jovyan/work + restart: unless-stopped + # Set user and group IDs, if needed + # user: 'root' + # environment: + # NB_UID: 1000 + # NB_GID: 1000 diff --git a/examples/images/combined_activity_stats.png b/examples/images/combined_activity_stats.png new file mode 100644 index 00000000..b02118d3 Binary files /dev/null and b/examples/images/combined_activity_stats.png differ diff --git a/examples/images/identifiers_by_month.png b/examples/images/identifiers_by_month.png new file mode 100644 index 00000000..760c323b Binary files /dev/null and b/examples/images/identifiers_by_month.png differ diff --git a/examples/images/observations_by_date_and_time.png b/examples/images/observations_by_date_and_time.png new file mode 100644 index 00000000..c878faee Binary files /dev/null and b/examples/images/observations_by_date_and_time.png differ diff --git a/examples/images/observations_by_month.png b/examples/images/observations_by_month.png new file mode 100644 index 00000000..06905f9e Binary files /dev/null and b/examples/images/observations_by_month.png differ diff --git a/examples/images/observations_by_quality_grade.png b/examples/images/observations_by_quality_grade.png new file mode 100644 index 00000000..f34fd1d1 Binary files /dev/null and b/examples/images/observations_by_quality_grade.png differ diff --git a/examples/images/observations_by_year.png b/examples/images/observations_by_year.png new file mode 100644 index 00000000..55c2c598 Binary files /dev/null and b/examples/images/observations_by_year.png differ diff --git a/examples/images/observers_by_month.png b/examples/images/observers_by_month.png new file mode 100644 index 00000000..22501d11 Binary files /dev/null and b/examples/images/observers_by_month.png differ diff --git a/examples/images/taxa_by_month.png b/examples/images/taxa_by_month.png new file mode 100644 index 00000000..362eac3a Binary files /dev/null and b/examples/images/taxa_by_month.png differ diff --git a/examples/images/total_observations_by_iconic_taxon.png b/examples/images/total_observations_by_iconic_taxon.png new file mode 100755 index 00000000..80475664 Binary files /dev/null and b/examples/images/total_observations_by_iconic_taxon.png differ diff --git a/examples/images/total_taxa_by_iconic_taxon.png b/examples/images/total_taxa_by_iconic_taxon.png new file mode 100755 index 00000000..74018693 Binary files /dev/null and b/examples/images/total_taxa_by_iconic_taxon.png differ diff --git a/examples/observations_to_gpx.py b/examples/observations_to_gpx.py new file mode 100644 index 00000000..9f315884 --- /dev/null +++ b/examples/observations_to_gpx.py @@ -0,0 +1,93 @@ +""" Example to get observation locations + metadata in GPX format. +Extra dependencies: + `pip install gpxpy` +""" +from logging import getLogger + +from gpxpy.gpx import GPX, GPXTrack, GPXTrackPoint, GPXTrackSegment, GPXWaypoint + +from pyinaturalist.constants import JsonResponse, List +from pyinaturalist.node_api import get_all_observations, get_observations # noqa +from pyinaturalist.response_format import convert_observation_timestamps, format_observation + +logger = getLogger(__name__) + + +def observations_to_gpx( + observations: List[JsonResponse], output_file: str = "observations.gpx", track: bool = True +): + """Convert a list of observations to a set of GPX waypoints or a GPX track + + Args: + observations: JSON observations + output_file: File path to write to + track: Create an ordered GXP track; otherwise, create unordered GPX waypoints + """ + gpx = GPX() + logger.info(f"Converting {len(observations)} to GPX points") + points = [observation_to_gpx_point(obs, track=track) for obs in observations] + + if track: + gpx_track = GPXTrack() + gpx.tracks.append(gpx_track) + gpx_segment = GPXTrackSegment() + gpx_track.segments.append(gpx_segment) + gpx_segment.points = points + else: + gpx.waypoints = points + + # Save to file + logger.info(f"Writing GPX data to {output_file}") + with open(output_file, "w") as f: + f.write(gpx.to_xml()) + + +def observation_to_gpx_point(observation: JsonResponse, track: bool = True): + """Convert a single observation to a GPX point + + Args: + observation: JSON observation + track: Indicates that this point is part of an ordered GXP track; + otherwise, assume it is an unordered waypoint + + """ + logger.debug(f'Processing observation {observation["id"]}') + # GeoJSON coordinates are ordered as `longitude, latitude` + long, lat = observation["geojson"]["coordinates"] + + # Get medium-sized photo URL, if available; otherwise just use observation URL + if observation["photos"]: + link = observation["photos"][0]["url"].replace("square", "medium") + else: + link = observation["uri"] + + point_cls = GPXTrackPoint if track else GPXWaypoint + point = point_cls( + latitude=lat, + longitude=long, + time=convert_observation_timestamps(observation), + comment=format_observation(observation), + ) + point.description = observation["description"] + point.link = link + point.link_text = f'Observation {observation["id"]}' + return point + + +if __name__ == "__main__": + # Get first page of search results (for testing) + search_params = { + "project_id": 36883, # ID of the 'Sugarloaf Ridge State Park' project + "created_d1": "2020-01-01", # Get observations from January 2020... + "created_d2": "2020-09-30", # ...through September 2020 (adjust these dates as needed) + "geo": True, # Only get observations with geospatial coordinates + "geoprivacy": "open", # Only get observations with public coordinates (not obscured/private) + } + results = get_observations(**search_params)["results"] + + # Paginate through all search results (may take a long time for a large query) + # results = get_all_observations(**search_params) + + # Convert and write to GPX file + observations_to_gpx(results) + # observations_to_tsp(results) diff --git a/examples/observations_to_tsp.py b/examples/observations_to_tsp.py new file mode 100644 index 00000000..ba35bf0d --- /dev/null +++ b/examples/observations_to_tsp.py @@ -0,0 +1,45 @@ +"""Example to convert observation locations to TSP format. + +This file can then be used to compute the shortest path that hits all locations as a +Traveling Salesperson problem. This only exports a TSP file, which is compatible with many TSP +implementations; it does not solve it. + +Extra dependencies: + `pip install geopy networkx tsplib95` +Example python TSP implementations: + `pip install acopy` + `pip install https://github.com/DiegoVicen/som-tsp` +""" +from geopy.distance import distance +from networkx import Graph +from tsplib95.models import StandardProblem + +from pyinaturalist.constants import JsonResponse, List + + +def get_observation_distance(obs1, obs2): + """Get the distance between the locations of two observations (in meters)""" + long1, lat1 = obs1["geojson"]["coordinates"] + long2, lat2 = obs2["geojson"]["coordinates"] + return distance((lat1, long1), (lat2, long2)).meters + + +def observations_to_tsp( + observations: List[JsonResponse], output_file: str = "observations.tsp" +) -> Graph: + """Convert a list of observations to TSP (traveling salesperson problem) format""" + problem = StandardProblem() + problem.comment = f"{len(observations)} iNaturalist observations" + problem.dimension = len(observations) + problem.edge_weight_format = "GEO" + problem.name = "project_observations" + problem.type = "TSP" + + for i, obs in enumerate(observations): + long, lat = obs["geojson"]["coordinates"] + problem.node_coords[i + 1] = [lat, long] + + with open(output_file, "w") as f: + f.write(problem.render()) + + return problem.get_graph() diff --git a/examples/sample_data/observations.gpx b/examples/sample_data/observations.gpx new file mode 100644 index 00000000..d4ee59be --- /dev/null +++ b/examples/sample_data/observations.gpx @@ -0,0 +1,17563 @@ + + + + + + + [37319069] species: Dryopteris arguta (coastal woodfern) observed by mtnfreak on 2020-01-05 + + Observation 37319069 + + + + + [37319131] species: Heteromeles arbutifolia (Toyon) observed by mtnfreak on 2020-01-05 + + Observation 37319131 + + + + + [37510354] species: Pleurotus ostreatus (Oyster Mushroom) observed by cjs041 on 2020-01-11 + + Observation 37510354 + + + + + [37510573] phylum: Mycetozoa (slime molds) observed by cjs041 on 2020-01-11 + + Observation 37510573 + + + + + [37511404] phylum: Basidiomycota (Basidiomycete Fungi) observed by cjs041 on 2020-01-11 + Growing from redwood cone. + + Observation 37511404 + + + + + [37511510] species: Coprinellus micaceus (mica cap) observed by cjs041 on 2020-01-11 + + Observation 37511510 + + + + + [37511631] order: Agaricales (Common Gilled Mushrooms and Allies) observed by cjs041 on 2020-01-11 + At base of charred redwood. + + Observation 37511631 + + + + + [37511790] species: Trametes betulina (Gilled Polypore) observed by cjs041 on 2020-01-11 + On charred base of what I believe was an alder. + + Observation 37511790 + + + + + [37512435] species: Eutypella quaternata (no common name) observed by cjs041 on 2020-01-11 + On base of charred tree, believed to be alder. Several other jellyish fan-like structures and blobs nearby (last pic). Unsure if those are same species. + + Observation 37512435 + + + + + [37573236] species: Hypholoma fasciculare (Sulphur Tuft) observed by woody54 on 2020-01-13 + + Observation 37573236 + + + + + [37574039] genus: Mycena (Bonnets) observed by woody54 on 2020-01-13 + + Observation 37574039 + + + + + [37575916] order: Agaricales (Common Gilled Mushrooms and Allies) observed by sunflowerguy on 2020-01-12 + + Observation 37575916 + + + + + [37575992] species: Schizophyllum commune (splitgill mushroom) observed by sunflowerguy on 2020-01-12 + + Observation 37575992 + + + + + [37576070] genus: Claytonia (spring beauties) observed by sunflowerguy on 2020-01-12 + + Observation 37576070 + + + + + [37576088] genus: Pluteus (Deer Mushrooms) observed by sunflowerguy on 2020-01-12 + + Observation 37576088 + + + + + [37576099] species: Stereum hirsutum (hairy curtain crust) observed by sunflowerguy on 2020-01-12 + + Observation 37576099 + + + + + [37724161] species: Pellaea andromedifolia (Coffee Fern) observed by enhunn323 on 2020-01-18 + + Observation 37724161 + + + + + [37762767] species: Polypodium californicum (California Polypody) observed by woody54 on 2020-01-19 + + Observation 37762767 + + + + + [37765539] species: Hypholoma capnoides (Smoky-gilled Hypholoma) observed by bugplanet666 on 2020-01-19 + + Observation 37765539 + + + + + [37765579] species: Schizophyllum commune (splitgill mushroom) observed by bugplanet666 on 2020-01-19 + + Observation 37765579 + + + + + [37767953] species: Mycena purpureofusca (purple edge bonnet) observed by sapienshane on 2020-01-19 + + Observation 37767953 + + + + + [37767998] species: Clitocybe fragrans (Fragrant Funnel) observed by sapienshane on 2020-01-19 + Anise odor + + Observation 37767998 + + + + + [37768008] species: Corylus cornuta (beaked hazelnut) observed by sapienshane on 2020-01-19 + + Observation 37768008 + + + + + [37768080] species: Calycanthus occidentalis (California sweetshrub) observed by sapienshane on 2020-01-19 + + Observation 37768080 + + + + + [37768233] genus: Exidia (no common name) observed by sapienshane on 2020-01-19 + + Observation 37768233 + + + + + [37768281] species: Pleurotus pulmonarius (summer oyster mushroom) observed by sapienshane on 2020-01-19 + I think it's alder wood + + Observation 37768281 + + + + + [37768310] family: Mollisiaceae (no common name) observed by sapienshane on 2020-01-19 + + Observation 37768310 + + + + + [37768325] kingdom: Fungi (Fungi Including Lichens) observed by sapienshane on 2020-01-19 + + Observation 37768325 + + + + + [37768359] species: Brachycybe producta (no common name) observed by sapienshane on 2020-01-19 + + Observation 37768359 + + + + + [37768404] order: Agaricales (Common Gilled Mushrooms and Allies) observed by sapienshane on 2020-01-19 + Burned area + + Observation 37768404 + + + + + [37768462] genus: Arrhenia (no common name) observed by sapienshane on 2020-01-19 + + Observation 37768462 + + + + + [37768501] order: Agaricales (Common Gilled Mushrooms and Allies) observed by sapienshane on 2020-01-19 + + Observation 37768501 + + + + + [37768521] order: Agaricales (Common Gilled Mushrooms and Allies) observed by sapienshane on 2020-01-19 + + Observation 37768521 + + + + + [37768549] phylum: Bryophyta (mosses) observed by sapienshane on 2020-01-19 + On loamy soil, sloped trailside, moderately moist area, dappled sunlight, dominant trees: Umbellularia californica and pseudotsuga menziseii + + Observation 37768549 + + + + + [37768581] phylum: Bryophyta (mosses) observed by sapienshane on 2020-01-19 + On loamy soil, sloped trailside, moderately moist area, dappled sunlight, dominant trees: Umbellularia californica and pseudotsuga menziseii + + Observation 37768581 + + + + + [37768608] species: Mycena galericulata (common bonnet) observed by sapienshane on 2020-01-19 + On Doug fir + + Observation 37768608 + + + + + [37768693] kingdom: Fungi (Fungi Including Lichens) observed by sapienshane on 2020-01-19 + On California Bay bark that had burned, charred but remained alive. Assuming it's fungal. + + Observation 37768693 + + + + + [37768753] order: Helotiales (no common name) observed by sapienshane on 2020-01-19 + + Observation 37768753 + + + + + [37769587] species: Chlorogalum pomeridianum (wavy-leafed soap plant) observed by woody54 on 2020-01-19 + + Observation 37769587 + + + + + [37771209] species: Opuntia ficus-indica (Indian fig opuntia) observed by woody54 on 2020-01-19 + + Observation 37771209 + + + + + [37773719] species: Coprinellus micaceus (mica cap) observed by sapienshane on 2020-01-19 + + Observation 37773719 + + + + + [37773746] kingdom: Fungi (Fungi Including Lichens) observed by sapienshane on 2020-01-19 + Bay wood perhaps? Other nearby trees were redwood, Doug fir, oaks + + Observation 37773746 + + + + + [37773777] genus: Pluteus (Deer Mushrooms) observed by sapienshane on 2020-01-19 + + Observation 37773777 + + + + + [37773792] species: Pleurotus pulmonarius (summer oyster mushroom) observed by sapienshane on 2020-01-19 + + Observation 37773792 + + + + + [37773819] species: Lepiota magnispora (yellowfoot dapperling) observed by sapienshane on 2020-01-19 + + Observation 37773819 + + + + + [37773836] species: Pentagramma triangularis (goldback fern) observed by sapienshane on 2020-01-19 + + Observation 37773836 + + + + + [37773843] species: Pentagramma triangularis (goldback fern) observed by sapienshane on 2020-01-19 + + + Observation 37773843 + + + + + [37773853] genus: Polystichum (shield ferns) observed by sapienshane on 2020-01-19 + + Observation 37773853 + + + + + [37773863] species: Pholiota brunnescens (no common name) observed by sapienshane on 2020-01-19 + Burned area + + Observation 37773863 + + + + + [37773871] order: Marchantiales (Complex thallose liverworts) observed by sapienshane on 2020-01-19 + + Observation 37773871 + + + + + [37773878] species: Asterella californica (California asterella) observed by sapienshane on 2020-01-19 + + Observation 37773878 + + + + + [37773883] species: Adiantum jordanii (California Maidenhair Fern) observed by sapienshane on 2020-01-19 + + Observation 37773883 + + + + + [37773885] genus: Lepiota (no common name) observed by sapienshane on 2020-01-19 + + Observation 37773885 + + + + + [37774498] species: Athyrium filix-femina (lady fern) observed by sapienshane on 2020-01-19 + + Observation 37774498 + + + + + [37775426] kingdom: Fungi (Fungi Including Lichens) observed by raisd_bywolves on 2020-01-13 + + Observation 37775426 + + + + + [37775498] kingdom: Fungi (Fungi Including Lichens) observed by raisd_bywolves on 2020-01-13 + + Observation 37775498 + + + + + [37789784] species: Erannis vancouverensis (no common name) observed by woody54 on 2020-01-20 + + Observation 37789784 + + + + + [37792067] species: Trametes versicolor (turkey-tail) observed by woody54 on 2020-01-20 + + Observation 37792067 + + + + + [37792113] species: Dryopteris arguta (coastal woodfern) observed by woody54 on 2020-01-20 + + Observation 37792113 + + + + + [37808465] species: Deconica montana (mountain moss deconica) observed by lukemoore on 2019-12-20 + + Observation 37808465 + + + + + [37826899] species: Leratiomyces ceres (Chip Cherries) observed by mtnfreak on 2020-01-20 + + Observation 37826899 + + + + + [37859975] subspecies: Arctostaphylos manzanita manzanita (no common name) observed by morganstickrod on 2020-01-22 + + Observation 37859975 + + + + + [37860075] subspecies: Arctostaphylos viscida pulchella (no common name) observed by morganstickrod on 2020-01-22 + + Observation 37860075 + + + + + [37860494] species: Battus philenor (Pipevine Swallowtail) observed by woody54 on 2020-01-22 + + Observation 37860494 + + + + + [37861227] species: Teloschistes chrysophthalmus (Golden-eye Lichen) observed by woody54 on 2020-01-22 + + Observation 37861227 + + + + + [37861272] species: Usnea intermedia (Arizona Beard Lichen) observed by woody54 on 2020-01-22 + + Observation 37861272 + + + + + [37898827] species: Mantis religiosa (European Mantis) observed by jrlynx on 2020-01-23 + Found two under rocks in an open field approximately 3.5 centimeters long + + Observation 37898827 + + + + + [37898828] family: Agelenidae (Funnel Weavers) observed by jrlynx on 2020-01-23 + + Observation 37898828 + + + + + [37898829] genus: Peltigera (pelt lichens) observed by jrlynx on 2020-01-23 + + Observation 37898829 + + + + + [37898830] genus: Tremella (no common name) observed by jrlynx on 2020-01-23 + + Observation 37898830 + + + + + [37898832] species: Trametes hirsuta (Hairy Bracket) observed by jrlynx on 2020-01-23 + + Observation 37898832 + + + + + [37898834] class: Magnoliopsida (dicots) observed by jrlynx on 2020-01-23 + + Observation 37898834 + + + + + [37898835] species: Caulorhiza umbonata (Redwood Rooter) observed by jrlynx on 2020-01-23 + + Observation 37898835 + + + + + [37898836] family: Strophariaceae (no common name) observed by jrlynx on 2020-01-23 + + Observation 37898836 + + + + + [37900008] species: Calycina citrina (Yellow Fairy Cups) observed by jrlynx on 2020-01-23 + + Observation 37900008 + + + + + [37900318] species: Hygrocybe flavescens (Golden Waxy Cap) observed by jrlynx on 2020-01-23 + + Observation 37900318 + + + + + [37921419] species: Quercus durata (leather oak) observed by jcd_ca on 2019-12-27 + + + Observation 37921419 + + + + + [37931140] phylum: Mycetozoa (slime molds) observed by woody54 on 2020-01-24 + + Observation 37931140 + + + + + [37935146] species: Exidia nigricans (Warlocks's Butter) observed by kamage on 2020-01-23 + + Observation 37935146 + + + + + [37935614] species: Paeromopus angusticeps (no common name) observed by kamage on 2020-01-23 + In process of shedding - not a millipede pro so correction welcomed - in duff of riparian area of coast redwood transitioning to Doug fir + + Observation 37935614 + + + + + [37935627] species: Trametes versicolor (turkey-tail) observed by kamage on 2020-01-23 + + Observation 37935627 + + + + + [37935721] species: Trametes betulina (Gilled Polypore) observed by kamage on 2020-01-23 + + Observation 37935721 + + + + + [37936374] order: Polyporales (shelf fungi) observed by kamage on 2020-01-23 + + Observation 37936374 + + + + + [37936590] genus: Mycena (Bonnets) observed by kamage on 2020-01-23 + + Observation 37936590 + + + + + [37936814] species: Mycena oregonensis (Western Yellow Mycena) observed by kamage on 2020-01-23 + Not a mushroom expert - feel free to correct + + Observation 37936814 + + + + + [37937042] species: Schizophyllum commune (splitgill mushroom) observed by kamage on 2020-01-23 + + Observation 37937042 + + + + + [37937305] stateofmatter: Life (no common name) observed by kamage on 2020-01-23 + ? + + Observation 37937305 + + + + + [37937392] species: Caulorhiza umbonata (Redwood Rooter) observed by kamage on 2020-01-23 + January under coast redwoods + + Observation 37937392 + + + + + [37937416] species: Caulorhiza umbonata (Redwood Rooter) observed by kamage on 2020-01-23 + January under coast redwoods + + Observation 37937416 + + + + + [37937538] species: Pholiota velaglutinosa (no common name) observed by kamage on 2020-01-23 + + Observation 37937538 + + + + + [37937569] species: Atypoides riversi (California Turret Spider) observed by kamage on 2020-01-23 + + Observation 37937569 + + + + + [37937601] species: Ramalina menziesii (lace lichen) observed by kamage on 2020-01-23 + + Observation 37937601 + + + + + [37937608] species: Pellaea andromedifolia (Coffee Fern) observed by kamage on 2020-01-23 + + Observation 37937608 + + + + + [37937639] species: Adiantum jordanii (California Maidenhair Fern) observed by kamage on 2020-01-23 + + Observation 37937639 + + + + + [37937780] species: Mantis religiosa (European Mantis) observed by kamage on 2020-01-23 + Egg case located under rock - sunny area - wet under rock + + Observation 37937780 + + + + + [37937809] species: Mantis religiosa (European Mantis) observed by kamage on 2020-01-23 + Mantis egg case under rock + + Observation 37937809 + + + + + [37937863] genus: Stereum (no common name) observed by kamage on 2020-01-23 + + Observation 37937863 + + + + + [37937902] family: Agelenidae (Funnel Weavers) observed by kamage on 2020-01-23 + + Observation 37937902 + + + + + [37937919] genus: Usnea (beard lichens) observed by kamage on 2020-01-23 + + Observation 37937919 + + + + + [37937967] genus: Peltigera (pelt lichens) observed by kamage on 2020-01-23 + + Observation 37937967 + + + + + [37940501] genus: Coprinellus (no common name) observed by kamage on 2020-01-23 + + Observation 37940501 + + + + + [37940623] species: Trametes versicolor (turkey-tail) observed by kamage on 2020-01-23 + + Observation 37940623 + + + + + [37940715] class: Lecanoromycetes (Common Lichens) observed by kamage on 2020-01-23 + + Observation 37940715 + + + + + [37941186] species: Lepra amara (bitter wart lichen) observed by kamage on 2020-01-23 + + + Observation 37941186 + + + + + [37941510] genus: Xanthoparmelia (rock shield lichens) observed by kamage on 2020-01-23 + ? Growing on rock - small piece extracted to show underside + + Observation 37941510 + + + + + [37965894] species: Stereum hirsutum (hairy curtain crust) observed by woody54 on 2020-01-25 + + Observation 37965894 + + + + + [37966169] class: Arachnida (Arachnids) observed by conci on 2020-01-25 + + Observation 37966169 + + + + + [37966201] family: Parmeliaceae (shield lichens and allies) observed by woody54 on 2020-01-25 + + Observation 37966201 + + + + + [37990704] order: Araneae (Spiders) observed by susbis on 2020-01-25 + + Observation 37990704 + + + + + [37994813] order: Coleoptera (Beetles) observed by susbis on 2020-01-25 + + Observation 37994813 + + + + + [37994845] kingdom: Fungi (Fungi Including Lichens) observed by susbis on 2020-01-25 + + Observation 37994845 + + + + + [37997099] kingdom: Fungi (Fungi Including Lichens) observed by jrlynx on 2020-01-25 + + Observation 37997099 + + + + + [38001227] kingdom: Fungi (Fungi Including Lichens) observed by psj on 2020-01-26 + + Observation 38001227 + + + + + [38001253] kingdom: Fungi (Fungi Including Lichens) observed by psj on 2020-01-26 + + Observation 38001253 + + + + + [38001277] order: Tremellales (no common name) observed by psj on 2020-01-26 + Witch’s Butter + + Observation 38001277 + + + + + [38001286] order: Agaricales (Common Gilled Mushrooms and Allies) observed by psj on 2020-01-26 + + Observation 38001286 + + + + + [38001304] phylum: Mycetozoa (slime molds) observed by psj on 2020-01-26 + + Observation 38001304 + + + + + [38001342] order: Agaricales (Common Gilled Mushrooms and Allies) observed by psj on 2020-01-26 + + Observation 38001342 + + + + + [38001363] kingdom: Fungi (Fungi Including Lichens) observed by psj on 2020-01-26 + + Observation 38001363 + + + + + [38001418] kingdom: Fungi (Fungi Including Lichens) observed by psj on 2020-01-26 + + Observation 38001418 + + + + + [38001466] species: Alnus rhombifolia (white alder) observed by psj on 2020-01-26 + + + Observation 38001466 + + + + + [38001475] kingdom: Fungi (Fungi Including Lichens) observed by psj on 2020-01-26 + + Observation 38001475 + + + + + [38001494] kingdom: Fungi (Fungi Including Lichens) observed by psj on 2020-01-26 + + Observation 38001494 + + + + + [38001508] kingdom: Fungi (Fungi Including Lichens) observed by psj on 2020-01-26 + + Observation 38001508 + + + + + [38003096] species: Arbutus menziesii (Pacific madrone) observed by susbis on 2020-01-25 + + Observation 38003096 + + + + + [38003189] species: Umbellularia californica (California bay) observed by susbis on 2020-01-25 + + Observation 38003189 + + + + + [38003241] genus: Armillaria (honey mushrooms) observed by susbis on 2020-01-25 + + Observation 38003241 + + + + + [38003288] variety: Pteridium aquilinum pubescens (Hairy brackenfern) observed by susbis on 2020-01-25 + + Observation 38003288 + + + + + [38003356] class: Magnoliopsida (dicots) observed by susbis on 2020-01-25 + + Observation 38003356 + + + + + [38003427] kingdom: Fungi (Fungi Including Lichens) observed by susbis on 2020-01-25 + + Observation 38003427 + + + + + [38003452] species: Calycanthus occidentalis (California sweetshrub) observed by susbis on 2020-01-25 + + Observation 38003452 + + + + + [38005644] genus: Cladonia (Pixie Cup Lichens) observed by kamage on 2020-01-23 + + Observation 38005644 + + + + + [38007975] family: Heptageniidae (Stream Mayflies) observed by dlevitis on 2020-01-26 + + + Observation 38007975 + + + + + [38007977] species: Phloeodes plicatus (no common name) observed by dlevitis on 2020-01-26 + + + Observation 38007977 + + + + + [38007978] species: Arcyria obvelata (Yellow Carnival Candy Slime) observed by dlevitis on 2020-01-26 + + + Observation 38007978 + + + + + [38007979] species: Bjerkandera adusta (Smoky polypore) observed by dlevitis on 2020-01-26 + + + Observation 38007979 + + + + + [38007981] family: Agelenidae (Funnel Weavers) observed by dlevitis on 2020-01-26 + + + Observation 38007981 + + + + + [38007983] genus: Exidia (no common name) observed by dlevitis on 2020-01-26 + + + Observation 38007983 + + + + + [38007984] order: Agaricales (Common Gilled Mushrooms and Allies) observed by dlevitis on 2020-01-26 + + + Observation 38007984 + + + + + [38007985] order: Agaricales (Common Gilled Mushrooms and Allies) observed by dlevitis on 2020-01-26 + + + Observation 38007985 + + + + + [38007986] family: Machilidae (Jumping Bristletails) observed by dlevitis on 2020-01-26 + + + Observation 38007986 + + + + + [38007989] species: Ariolimax buttoni (Button's Banana Slug) observed by dlevitis on 2020-01-26 + + Observation 38007989 + + + + + [38008001] species: Trametes versicolor (turkey-tail) observed by dlevitis on 2020-01-26 + + Observation 38008001 + + + + + [38008008] order: Agaricales (Common Gilled Mushrooms and Allies) observed by dlevitis on 2020-01-26 + + Observation 38008008 + + + + + [38008018] genus: Pleurotus (Oyster Mushrooms) observed by dlevitis on 2020-01-26 + + Observation 38008018 + + + + + [38008066] order: Polyporales (shelf fungi) observed by dlevitis on 2020-01-26 + + Observation 38008066 + + + + + [38008092] species: Trametes betulina (Gilled Polypore) observed by dlevitis on 2020-01-26 + + Observation 38008092 + + + + + [38008113] species: Pluteus cervinus (deer mushroom) observed by dlevitis on 2020-01-26 + + Observation 38008113 + + + + + [38008122] order: Agaricales (Common Gilled Mushrooms and Allies) observed by dlevitis on 2020-01-26 + + Observation 38008122 + + + + + [38018673] species: Bolbitius titubans (yellow fieldcap) observed by mylan on 2020-01-26 + + Observation 38018673 + + + + + [38026803] species: Mischocyttarus flavitarsis (Western Paper Wasp) observed by woody54 on 2020-01-26 + + Observation 38026803 + + + + + [38051768] species: Ganoderma applanatum (artist's bracket) observed by woody54 on 2020-01-27 + + Observation 38051768 + + + + + [38064441] genus: Stereum (no common name) observed by gretchenparadis on 2020-01-27 + + Observation 38064441 + + + + + [38124804] genus: Usnea (beard lichens) observed by zuzufarm on 2020-01-27 + + Observation 38124804 + + + + + [38137453] species: Trametes versicolor (turkey-tail) observed by woody54 on 2020-01-30 + + Observation 38137453 + + + + + [38137487] species: Stereum hirsutum (hairy curtain crust) observed by woody54 on 2020-01-30 + + Observation 38137487 + + + + + [38162200] species: Ensatina eschscholtzii (Ensatina) observed by woody54 on 2020-01-31 + + Observation 38162200 + + + + + [38193515] class: Agaricomycetes (no common name) observed by paulahiked on 2020-02-01 + + Observation 38193515 + + + + + [38193531] phylum: Basidiomycota (Basidiomycete Fungi) observed by paulahiked on 2020-02-01 + + Observation 38193531 + + + + + [38195152] order: Polypodiales (no common name) observed by mreddick on 2020-02-01 + + Observation 38195152 + + + + + [38270491] species: Marrubium vulgare (white horehound) observed by cloudya on 2019-05-17 + + Observation 38270491 + + + + + [38270587] species: Clarkia concinna (Red Ribbons) observed by cloudya on 2019-05-17 + + Observation 38270587 + + + + + [38297446] species: Pipilo maculatus (Spotted Towhee) observed by gelarsen on 2013-05-12 + + Observation 38297446 + + + + + [38341342] species: Sanicula crassicaulis (Pacific Sanicle) observed by sjdegen on 2020-02-05 + + Observation 38341342 + + + + + [38371520] genus: Scutellinia (Eyelash cups) observed by woody54 on 2020-02-06 + + Observation 38371520 + + + + + [38376515] species: Usnea intermedia (Arizona Beard Lichen) observed by woody54 on 2020-02-06 + + Observation 38376515 + + + + + [38410519] genus: Mycena (Bonnets) observed by woody54 on 2020-02-01 + + Observation 38410519 + + + + + [38480811] tribe: Arctiini (Tiger Moths) observed by kentcorley on 2020-02-09 + + Observation 38480811 + + + + + [38486733] species: Omphalotus olivascens (western jack-o'-lantern) observed by woody54 on 2020-02-09 + + Observation 38486733 + + + + + [38523477] order: Scolopendromorpha (Tropical Centipedes) observed by woody54 on 2020-02-10 + + Observation 38523477 + + + + + [38562009] species: Elgaria multicarinata (Southern Alligator Lizard) observed by drjohnzoidberg on 2011-05-02 + + + Observation 38562009 + + + + + [38574037] species: Sitta carolinensis (White-breasted Nuthatch) observed by icosahedron on 2020-02-11 + + Observation 38574037 + + + + + [38574094] genus: Arphia (no common name) observed by icosahedron on 2020-02-11 + About an inch long; wings dirty white with a black stripe. + + Observation 38574094 + + + + + [38574155] species: Dasymutilla aureola (Pacific Velvet Ant) observed by icosahedron on 2020-02-11 + + Observation 38574155 + + + + + [38582970] species: Diadophis punctatus (ring-necked snake) observed by drjohnzoidberg on 2011-05-02 + + + Observation 38582970 + + + + + [38584826] species: Quercus berberidifolia (California scrub oak) observed by wendy_h on 2020-02-12 + + Observation 38584826 + + + + + [38584937] species: Quercus agrifolia (coast live oak) observed by wendy_h on 2020-02-12 + + Observation 38584937 + + + + + [38585013] species: Umbellularia californica (California bay) observed by wendy_h on 2020-02-12 + + Observation 38585013 + + + + + [38585046] class: Polypodiopsida (ferns) observed by wendy_h on 2020-02-12 + + Observation 38585046 + + + + + [38585090] genus: Adiantum (maidenhair ferns) observed by wendy_h on 2020-02-12 + + Observation 38585090 + + + + + [38585125] species: Pentagramma triangularis (goldback fern) observed by wendy_h on 2020-02-12 + + Observation 38585125 + + + + + [38585160] species: Quercus chrysolepis (canyon live oak) observed by wendy_h on 2020-02-12 + + Observation 38585160 + + + + + [38585196] genus: Quercus (oaks) observed by wendy_h on 2020-02-12 + + Observation 38585196 + + + + + [38585245] kingdom: Fungi (Fungi Including Lichens) observed by wendy_h on 2020-02-12 + + Observation 38585245 + + + + + [38585285] species: Quercus berberidifolia (California scrub oak) observed by wendy_h on 2020-02-12 + + Observation 38585285 + + + + + [38585347] order: Rosales (roses, elms, figs, and allies) observed by wendy_h on 2020-02-12 + + Observation 38585347 + + + + + [38585387] genus: Ceanothus (no common name) observed by wendy_h on 2020-02-12 + + Observation 38585387 + + + + + [38585431] genus: Arctostaphylos (bearberries and manzanitas) observed by wendy_h on 2020-02-12 + + Observation 38585431 + + + + + [38585500] species: Quercus berberidifolia (California scrub oak) observed by wendy_h on 2020-02-12 + + Observation 38585500 + + + + + [38585532] genus: Euphydryas (no common name) observed by wendy_h on 2020-02-12 + + Observation 38585532 + + + + + [38585542] species: Quercus chrysolepis (canyon live oak) observed by wendy_h on 2020-02-12 + + Observation 38585542 + + + + + [38593999] species: Umbellularia californica (California bay) observed by woody54 on 2020-02-12 + + Observation 38593999 + + + + + [38622470] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by drjohnzoidberg on 2007-04-28 + + Observation 38622470 + + + + + [39036749] species: Umbellularia californica (California bay) observed by lukemoore on 2020-02-21 + + Observation 39036749 + + + + + [39036814] species: Pleurotus ostreatus (Oyster Mushroom) observed by lukemoore on 2020-02-21 + + Observation 39036814 + + + + + [39036841] species: Adiantum jordanii (California Maidenhair Fern) observed by lukemoore on 2020-02-21 + + Observation 39036841 + + + + + [39036864] species: Aesculus californica (California buckeye) observed by lukemoore on 2020-02-21 + + Observation 39036864 + + + + + [39036872] species: Arbutus menziesii (Pacific madrone) observed by lukemoore on 2020-02-21 + + Observation 39036872 + + + + + [39036937] species: Lonicera hispidula (Pink Honeysuckle) observed by lukemoore on 2020-02-21 + + Observation 39036937 + + + + + [39036951] species: Dryocosmus asymmetricus (Split Twig Gall Wasp) observed by lukemoore on 2020-02-21 + Might be some other parasitoid other than a gall wasp. + + Observation 39036951 + + + + + [39174839] species: Aristolochia californica (California Dutchman's Pipe) observed by woody54 on 2020-02-24 + + Observation 39174839 + + + + + [39190194] species: Delphinium nudicaule (Red larkspur) observed by woody54 on 2020-02-24 + + Observation 39190194 + + + + + [39194316] species: Ceanothus cuneatus (Buckbrush) observed by gretchenparadis on 2020-02-24 + + Observation 39194316 + + + + + [39210223] genus: Ranunculus (buttercups) observed by woody54 on 2020-02-24 + + Observation 39210223 + + + + + [39210240] genus: Dryopteris (wood ferns) observed by woody54 on 2020-02-24 + + Observation 39210240 + + + + + [39269941] species: Lomatium dasycarpum (woollyfruit desertparsley) observed by woody54 on 2020-02-26 + + Observation 39269941 + + + + + [39270035] species: Lupinus albifrons (Silver Lupine) observed by woody54 on 2020-02-26 + + Observation 39270035 + + + + + [39301146] species: Pellaea andromedifolia (Coffee Fern) observed by woody54 on 2020-02-26 + + Observation 39301146 + + + + + [39301197] species: Dudleya cymosa (Canyon Live-forever) observed by woody54 on 2020-02-26 + + Observation 39301197 + + + + + [39301294] species: Achillea millefolium (common yarrow) observed by woody54 on 2020-02-26 + + Observation 39301294 + + + + + [39306822] species: Woodwardia fimbriata (giant chain fern) observed by woody54 on 2020-02-26 + + Observation 39306822 + + + + + [39330031] species: Solanum umbelliferum (bluewitch nightshade) observed by woody54 on 2020-02-28 + + Observation 39330031 + + + + + [39330091] species: Fritillaria affinis (checker lily) observed by woody54 on 2020-02-28 + + Observation 39330091 + + + + + [39330121] species: Artemisia douglasiana (California mugwort) observed by woody54 on 2020-02-28 + + Observation 39330121 + + + + + [39362282] species: Adenostoma fasciculatum (chamise) observed by woody54 on 2020-02-28 + + Observation 39362282 + + + + + [39433181] species: Ceanothus cuneatus (Buckbrush) observed by merrikbush on 2020-02-29 + + Observation 39433181 + + + + + [39433406] epifamily: Anthophila (Bees) observed by merrikbush on 2020-02-29 + I'm not very good with bees and would help ID'ing this species, which I found on a ceanothus. + + Observation 39433406 + + + + + [39433433] species: Lathyrus vestitus (Pacific pea) observed by merrikbush on 2020-02-29 + + Observation 39433433 + + + + + [39433580] species: Fritillaria affinis (checker lily) observed by merrikbush on 2020-02-29 + + Observation 39433580 + + + + + [39433602] species: Pedicularis densiflora (Warrior's Plume) observed by merrikbush on 2020-02-29 + + Observation 39433602 + + + + + [39433649] species: Euphydryas chalcedona (Variable Checkerspot) observed by merrikbush on 2020-02-29 + + Observation 39433649 + + + + + [39433667] species: Hypochaeris radicata (Common Cat's-ear) observed by merrikbush on 2020-02-29 + + Observation 39433667 + + + + + [39451921] species: Fritillaria affinis (checker lily) observed by c23bg on 2020-03-01 + + Observation 39451921 + + + + + [39462944] species: Heracleum maximum (common cowparsnip) observed by woody54 on 2020-03-02 + + Observation 39462944 + + + + + [39516454] species: Eriophyllum lanatum (common woolly sunflower) observed by woody54 on 2020-03-03 + + Observation 39516454 + + + + + [39582599] genus: Solanum (nightshades) observed by cazjohn on 2020-03-05 + + Observation 39582599 + + + + + [39596441] class: Diplopoda (Millipedes) observed by woody54 on 2020-03-05 + + Observation 39596441 + + + + + [39631818] species: Lathyrus vestitus (Pacific pea) observed by gretchenparadis on 2020-03-06 + + Observation 39631818 + + + + + [39637230] species: Alnus rhombifolia (white alder) observed by woody54 on 2020-03-06 + + Observation 39637230 + + + + + [39686938] species: Pellaea andromedifolia (Coffee Fern) observed by gretchenparadis on 2020-03-07 + + Observation 39686938 + + + + + [39686996] species: Polystichum munitum (western sword fern) observed by gretchenparadis on 2020-03-07 + + Observation 39686996 + + + + + [39767179] species: Acer macrophyllum (bigleaf maple) observed by woody54 on 2020-03-09 + + Observation 39767179 + + + + + [39767902] species: Marah fabacea (California manroot) observed by woody54 on 2020-03-09 + + Observation 39767902 + + + + + [39829183] species: Primula hendersonii (Henderson's shooting star) observed by woody54 on 2020-03-10 + + Observation 39829183 + + + + + [39836957] species: Adelinia grande (Pacific hound's tongue) observed by woody54 on 2020-03-10 + + Observation 39836957 + + + + + [39850556] species: Coenonympha tullia (Common Ringlet) observed by anniexchang on 2019-07-26 + + Observation 39850556 + + + + + [39850557] subspecies: Odocoileus hemionus columbianus (Columbian Black-tailed Deer) observed by anniexchang on 2019-07-26 + + Observation 39850557 + + + + + [39850558] species: Sceloporus occidentalis (Western Fence Lizard) observed by anniexchang on 2019-07-26 + + Observation 39850558 + + + + + [39850560] species: Calycanthus occidentalis (California sweetshrub) observed by anniexchang on 2019-07-26 + + Observation 39850560 + + + + + [39850561] suborder: Zygoptera (Damselflies) observed by anniexchang on 2019-07-26 + + Observation 39850561 + + + + + [39850562] order: Passeriformes (Perching Birds) observed by anniexchang on 2019-07-26 + + Observation 39850562 + + + + + [39868692] genus: Nemophila (baby blue eyes) observed by woody54 on 2020-03-10 + + Observation 39868692 + + + + + [39883711] phylum: Bryophyta (mosses) observed by woody54 on 2020-03-10 + + Observation 39883711 + + + + + [39901804] species: Glaucopsyche lygdamus (Silvery Blue) observed by nelruzam on 2020-03-12 + + + Observation 39901804 + + + + + [39907291] species: Euphydryas chalcedona (Variable Checkerspot) observed by laurels on 2020-03-08 + + Observation 39907291 + + + + + [39907310] species: Fritillaria affinis (checker lily) observed by laurels on 2020-03-08 + + Observation 39907310 + + + + + [39909954] species: Iris douglasiana (Douglas iris) observed by woody54 on 2020-03-12 + + Observation 39909954 + + + + + [39910532] species: Buteo jamaicensis (Red-tailed Hawk) observed by woody54 on 2020-03-12 + + Observation 39910532 + + + + + [39970206] genus: Nemophila (baby blue eyes) observed by cadeepbluesea on 2020-03-10 + + Observation 39970206 + + + + + [40046377] species: Adelinia grande (Pacific hound's tongue) observed by enhunn323 on 2020-03-15 + + Observation 40046377 + + + + + [40046667] species: Primula hendersonii (Henderson's shooting star) observed by enhunn323 on 2020-03-15 + + Observation 40046667 + + + + + [40095768] species: Urtica dioica (stinging nettle) observed by woody54 on 2020-03-16 + + Observation 40095768 + + + + + [40111122] species: Thermopsis californica (California goldenbanner) observed by woody54 on 2020-03-16 + + Observation 40111122 + + + + + [40111443] species: Lathyrus vestitus (Pacific pea) observed by woody54 on 2020-03-16 + + Observation 40111443 + + + + + [40111502] genus: Lathyrus (sweet peas and vetchlings) observed by woody54 on 2020-03-16 + + Observation 40111502 + + + + + [40111585] genus: Ceanothus (no common name) observed by woody54 on 2020-03-16 + + Observation 40111585 + + + + + [40113640] species: Clematis lasiantha (Pipestem Clematis) observed by woody54 on 2020-03-16 + + Observation 40113640 + + + + + [40114399] species: Toxicoscordion fremontii (Fremont's Deathcamas) observed by woody54 on 2020-03-16 + + Observation 40114399 + + + + + [40115323] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by woody54 on 2020-03-16 + + Observation 40115323 + + + + + [40115540] genus: Lupinus (lupines) observed by woody54 on 2020-03-16 + + Observation 40115540 + + + + + [40208718] species: Lunaria annua (Annual Honesty) observed by woody54 on 2020-03-18 + + Observation 40208718 + + + + + [40239300] genus: Symphoricarpos (snowberries) observed by woody54 on 2020-03-19 + + Observation 40239300 + + + + + [40242663] species: Pedicularis densiflora (Warrior's Plume) observed by hikenbike on 2020-03-19 + + Observation 40242663 + + + + + [40243091] genus: Solanum (nightshades) observed by hikenbike on 2020-03-19 + + Observation 40243091 + + + + + [40248045] subfamily: Solanoideae (nightshades and allies) observed by rumney007 on 2020-03-19 + + Observation 40248045 + + + + + [40259456] genus: Solanum (nightshades) observed by rumney007 on 2020-03-19 + + Observation 40259456 + + + + + [40259769] species: Umbellularia californica (California bay) observed by rumney007 on 2020-03-19 + + Observation 40259769 + + + + + [40259875] species: Fritillaria affinis (checker lily) observed by rumney007 on 2020-03-19 + + Observation 40259875 + + + + + [40259910] species: Erodium cicutarium (common stork's-bill) observed by rumney007 on 2020-03-19 + + Observation 40259910 + + + + + [40260035] species: Toxicoscordion fremontii (Fremont's Deathcamas) observed by rumney007 on 2020-03-19 + + Observation 40260035 + + + + + [40260115] species: Lathyrus vestitus (Pacific pea) observed by rumney007 on 2020-03-19 + + Observation 40260115 + + + + + [40260213] species: Quercus agrifolia (coast live oak) observed by rumney007 on 2020-03-19 + + Observation 40260213 + + + + + [40273993] tribe: Fabeae (no common name) observed by tomlstedman on 2020-03-19 + + Observation 40273993 + + + + + [40274022] species: Quercus kelloggii (California black oak) observed by tomlstedman on 2020-03-19 + + Observation 40274022 + + + + + [40279747] species: Cardamine californica (milkmaids) observed by woody54 on 2020-03-19 + + Observation 40279747 + + + + + [40279773] species: Pellaea andromedifolia (Coffee Fern) observed by woody54 on 2020-03-19 + + Observation 40279773 + + + + + [40279786] species: Adiantum jordanii (California Maidenhair Fern) observed by woody54 on 2020-03-19 + + Observation 40279786 + + + + + [40279798] species: Ranunculus occidentalis (Western Buttercup) observed by woody54 on 2020-03-19 + + Observation 40279798 + + + + + [40279837] species: Viola ocellata (western heart's ease) observed by woody54 on 2020-03-19 + + Observation 40279837 + + + + + [40279851] species: Pedicularis densiflora (Warrior's Plume) observed by woody54 on 2020-03-19 + + Observation 40279851 + + + + + [40289183] family: Liliaceae (lilies) observed by woody54 on 2020-03-19 + + Observation 40289183 + + + + + [40289267] species: Parmelia sulcata (shield lichen) observed by woody54 on 2020-03-19 + + Observation 40289267 + + + + + [40290898] species: Adiantum jordanii (California Maidenhair Fern) observed by woody54 on 2020-03-19 + + Observation 40290898 + + + + + [40296423] species: Marah fabacea (California manroot) observed by rumney007 on 2020-03-19 + + Observation 40296423 + + + + + [40305604] species: Pellaea andromedifolia (Coffee Fern) observed by mimichan on 2020-03-20 + + Observation 40305604 + + + + + [40305646] species: Lathyrus vestitus (Pacific pea) observed by mtamm on 2020-03-20 + + Observation 40305646 + + + + + [40305654] species: Delphinium nudicaule (Red larkspur) observed by mimichan on 2020-03-20 + + Observation 40305654 + + + + + [40307235] species: Fritillaria affinis (checker lily) observed by mtamm on 2020-03-20 + + Observation 40307235 + + + + + [40307366] species: Toxicoscordion fremontii (Fremont's Deathcamas) observed by mtamm on 2020-03-20 + + Observation 40307366 + + + + + [40307380] species: Lomatium dasycarpum (woollyfruit desertparsley) observed by mtamm on 2020-03-20 + + Observation 40307380 + + + + + [40311693] genus: Canis (Wolves, Jackals, and Allies) observed by sslucas on 2019-12-28 + + Observation 40311693 + + + + + [40312232] genus: Lomatium (Biscuitroots) observed by mimichan on 2020-03-20 + + Observation 40312232 + + + + + [40314739] species: Fritillaria affinis (checker lily) observed by mimichan on 2020-03-20 + + Observation 40314739 + + + + + [40330809] species: Pellaea andromedifolia (Coffee Fern) observed by woody54 on 2020-03-20 + + Observation 40330809 + + + + + [40338096] genus: Promecognathus (no common name) observed by dlevitis on 2020-03-20 + + + Observation 40338096 + + + + + [40338097] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-03-20 + + + Observation 40338097 + + + + + [40338099] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-03-20 + + + Observation 40338099 + + + + + [40338100] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-03-20 + + + Observation 40338100 + + + + + [40338101] family: Carabidae (Ground Beetles) observed by dlevitis on 2020-03-20 + + + Observation 40338101 + + + + + [40338103] species: Dryobates pubescens (Downy Woodpecker) observed by dlevitis on 2020-03-20 + + + Observation 40338103 + + + + + [40338108] species: Poecile rufescens (Chestnut-backed Chickadee) observed by dlevitis on 2020-03-20 + + + Observation 40338108 + + + + + [40339124] subgenus: Sessilium (sessile-flowered trilliums) observed by dlevitis on 2020-03-17 + + Observation 40339124 + + + + + [40339157] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-03-19 + + Observation 40339157 + + + + + [40394007] species: Calystegia purpurata (Pacific False Bindweed) observed by rumney007 on 2020-03-21 + + Observation 40394007 + + + + + [40395489] species: Claytonia perfoliata (miner's lettuce) observed by rumney007 on 2020-03-21 + + Observation 40395489 + + + + + [40395643] species: Lathyrus vestitus (Pacific pea) observed by rumney007 on 2020-03-21 + + Observation 40395643 + + + + + [40395939] species: Ramalina menziesii (lace lichen) observed by rumney007 on 2020-03-21 + + Observation 40395939 + + + + + [40396053] species: Adelinia grande (Pacific hound's tongue) observed by rumney007 on 2020-03-21 + + Observation 40396053 + + + + + [40396595] species: Usnea longissima (Methuselah's Beard Lichen) observed by rumney007 on 2020-03-21 + + Observation 40396595 + + + + + [40403784] species: Oemleria cerasiformis (Osoberry) observed by woody54 on 2020-03-21 + + Observation 40403784 + + + + + [40403943] species: Sceloporus occidentalis (Western Fence Lizard) observed by woody54 on 2020-03-21 + + Observation 40403943 + + + + + [40520460] species: Primula hendersonii (Henderson's shooting star) observed by dmirante on 2020-03-21 + + Observation 40520460 + + + + + [40520525] species: Chlorogalum pomeridianum (wavy-leafed soap plant) observed by dmirante on 2020-03-21 + + Observation 40520525 + + + + + [40520539] species: Polypodium californicum (California Polypody) observed by dmirante on 2020-03-21 + + Observation 40520539 + + + + + [40520618] species: Phoradendron villosum (oak mistletoe) observed by dmirante on 2020-03-21 + + Observation 40520618 + + + + + [40520645] species: Toxicoscordion fremontii (Fremont's Deathcamas) observed by dmirante on 2020-03-21 + + Observation 40520645 + + + + + [40520685] species: Adiantum jordanii (California Maidenhair Fern) observed by dmirante on 2020-03-21 + + Observation 40520685 + + + + + [40525496] species: Lomatium dasycarpum (woollyfruit desertparsley) observed by tonypassantino on 2020-03-18 + + Observation 40525496 + + + + + [40538106] genus: Carex (true sedges) observed by woody54 on 2020-03-22 + + Observation 40538106 + + + + + [40539198] order: Polypodiales (no common name) observed by rumney007 on 2020-03-21 + + Observation 40539198 + + + + + [40558284] family: Bombyliidae (Bee Flies) observed by caenvsci on 2020-03-22 + + Observation 40558284 + + + + + [40558299] genus: Arphia (no common name) observed by caenvsci on 2020-03-22 + + Observation 40558299 + + + + + [40558543] genus: Andrena (no common name) observed by caenvsci on 2020-03-22 + First picture is of a bee that was next to what looks to be a conspecific (second picture) on different flower of same plant. + + Observation 40558543 + + + + + [40559287] species: Vicia villosa (hairy vetch) observed by caenvsci on 2020-03-22 + + Observation 40559287 + + + + + [40559313] species: Gilia tricolor (bird's-eye gilia) observed by caenvsci on 2020-03-22 + + Observation 40559313 + + + + + [40559360] species: Adela trigrapha (Three-striped Longhorn) observed by caenvsci on 2020-03-22 + + Observation 40559360 + + + + + [40559441] subgenus: Melanthaxia (no common name) observed by caenvsci on 2020-03-22 + + Observation 40559441 + + + + + [40559466] family: Thomisidae (Crab Spiders) observed by caenvsci on 2020-03-22 + + Observation 40559466 + + + + + [40559552] species: Lupinus albifrons (Silver Lupine) observed by caenvsci on 2020-03-22 + + Observation 40559552 + + + + + [40559593] genus: Negosiana (no common name) observed by caenvsci on 2020-03-22 + + Observation 40559593 + + + + + [40559686] subspecies: Dichelostemma capitatum capitatum (no common name) observed by caenvsci on 2020-03-22 + + Observation 40559686 + + + + + [40559764] subspecies: Dichelostemma capitatum capitatum (no common name) observed by caenvsci on 2020-03-22 + + Observation 40559764 + + + + + [40559785] species: Claytonia perfoliata (miner's lettuce) observed by caenvsci on 2020-03-22 + + Observation 40559785 + + + + + [40559805] species: Iris fernaldii (Fernald's iris) observed by caenvsci on 2020-03-22 + + Observation 40559805 + + + + + [40559861] species: Neurotrichus gibbsii (American Shrewmole) observed by caenvsci on 2020-03-22 + Dead. On trail. + + Observation 40559861 + + + + + [40559905] species: Rhinotropis californica (California milkwort) observed by caenvsci on 2020-03-22 + + Observation 40559905 + + + + + [40559945] species: Cryptoporus volvatus (Veiled Polypore) observed by caenvsci on 2020-03-22 + + Observation 40559945 + + + + + [40559984] species: Icaricia icarioides (Boisduval's Blue) observed by caenvsci on 2020-03-22 + + Observation 40559984 + + + + + [40560006] subspecies: Dichelostemma capitatum capitatum (no common name) observed by caenvsci on 2020-03-22 + + Observation 40560006 + + + + + [40560037] species: Sceloporus occidentalis (Western Fence Lizard) observed by caenvsci on 2020-03-22 + + Observation 40560037 + + + + + [40560072] subgenus: Melanthaxia (no common name) observed by caenvsci on 2020-03-22 + + Observation 40560072 + + + + + [40560092] subgenus: Melanthaxia (no common name) observed by caenvsci on 2020-03-22 + + Observation 40560092 + + + + + [40560118] subspecies: Dichelostemma capitatum capitatum (no common name) observed by caenvsci on 2020-03-22 + + Observation 40560118 + + + + + [40560164] subspecies: Dichelostemma capitatum capitatum (no common name) observed by caenvsci on 2020-03-22 + + Observation 40560164 + + + + + [40560291] order: Thysanoptera (Thrips) observed by caenvsci on 2020-03-22 + + Observation 40560291 + + + + + [40560320] family: Meloidae (Blister Beetles) observed by caenvsci on 2020-03-22 + + Observation 40560320 + + + + + [40560360] species: Sisyrinchium bellum (western blue-eyed grass) observed by caenvsci on 2020-03-22 + + Observation 40560360 + + + + + [40560379] species: Sceloporus occidentalis (Western Fence Lizard) observed by caenvsci on 2020-03-22 + + Observation 40560379 + + + + + [40560400] genus: Arphia (no common name) observed by caenvsci on 2020-03-22 + + Observation 40560400 + + + + + [40560571] genus: Carex (true sedges) observed by caenvsci on 2020-03-22 + + Observation 40560571 + + + + + [40560633] species: Schizophyllum commune (splitgill mushroom) observed by caenvsci on 2020-03-22 + Splitgill and false turkey tail growing in separate colonies. + + Observation 40560633 + + + + + [40560754] species: Stereum ostrea (false turkey-tail) observed by caenvsci on 2020-03-22 + False turkey tail and splitgill growing in separate colonies. + + Observation 40560754 + + + + + [40560865] species: Schizophyllum commune (splitgill mushroom) observed by caenvsci on 2020-03-22 + Splitgill and false turkey tail colonies beginning to mix at edge. + + Observation 40560865 + + + + + [40560899] species: Stereum ostrea (false turkey-tail) observed by caenvsci on 2020-03-22 + False turkey tail and splitgill colonies beginning to mix at edge. + + Observation 40560899 + + + + + [40560936] class: Mammalia (Mammals) observed by caenvsci on 2020-03-22 + Dead. + +Charred bones. Probably deer. + + Observation 40560936 + + + + + [40560996] family: Araneidae (Orbweavers) observed by caenvsci on 2020-03-22 + + Observation 40560996 + + + + + [40561116] species: Rhinotropis californica (California milkwort) observed by caenvsci on 2020-03-22 + + Observation 40561116 + + + + + [40561159] species: Ceanothus oliganthus (hairy ceanothus) observed by caenvsci on 2020-03-22 + + Observation 40561159 + + + + + [40655261] species: Eriogonum luteolum (wicker buckwheat) observed by lbarlas on 2019-10-13 + + + Observation 40655261 + + + + + [40655845] species: Lathyrus vestitus (Pacific pea) observed by lbarlas on 2020-03-23 + + Observation 40655845 + + + + + [40656627] species: Castilleja foliolosa (Woolly Indian Paintbrush) observed by lbarlas on 2020-03-23 + + Observation 40656627 + + + + + [40657563] species: Solanum xanti (purple nightshade) observed by lbarlas on 2020-03-23 + + + Observation 40657563 + + + + + [40657978] species: Adiantum jordanii (California Maidenhair Fern) observed by lbarlas on 2020-03-23 + + Observation 40657978 + + + + + [40658323] species: Lomatium dasycarpum (woollyfruit desertparsley) observed by lbarlas on 2020-03-23 + + Observation 40658323 + + + + + [40659080] genus: Ribes (currants and gooseberries) observed by lbarlas on 2020-03-23 + + Observation 40659080 + + + + + [40659500] variety: Ceanothus oliganthus sorediatus (Jimbrush) observed by lbarlas on 2020-03-23 + + Observation 40659500 + + + + + [40659769] species: Lupinus albifrons (Silver Lupine) observed by lbarlas on 2020-03-23 + + Observation 40659769 + + + + + [40660306] species: Polypodium californicum (California Polypody) observed by lbarlas on 2020-03-23 + + Observation 40660306 + + + + + [40660513] species: Pellaea andromedifolia (Coffee Fern) observed by lbarlas on 2020-03-23 + + Observation 40660513 + + + + + [40660802] genus: Pellaea (cliffbrakes) observed by lbarlas on 2020-03-23 + + Observation 40660802 + + + + + [40661028] species: Dendromecon rigida (Bush Poppy) observed by lbarlas on 2020-03-23 + + Observation 40661028 + + + + + [40661120] species: Pedicularis densiflora (Warrior's Plume) observed by lbarlas on 2020-03-23 + + Observation 40661120 + + + + + [40661371] species: Pentagramma triangularis (goldback fern) observed by lbarlas on 2020-03-23 + + Observation 40661371 + + + + + [40661649] species: Clematis lasiantha (Pipestem Clematis) observed by lbarlas on 2020-03-23 + + Observation 40661649 + + + + + [40661762] species: Adelinia grande (Pacific hound's tongue) observed by lbarlas on 2020-03-23 + + Observation 40661762 + + + + + [40661854] species: Ranunculus occidentalis (Western Buttercup) observed by lbarlas on 2020-03-23 + + Observation 40661854 + + + + + [40661910] species: Delphinium nudicaule (Red larkspur) observed by lbarlas on 2020-03-23 + + + Observation 40661910 + + + + + [40661998] species: Fritillaria affinis (checker lily) observed by lbarlas on 2020-03-23 + + + Observation 40661998 + + + + + [40662123] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by lbarlas on 2020-03-23 + + + Observation 40662123 + + + + + [40662178] species: Cardamine californica (milkmaids) observed by lbarlas on 2020-03-23 + + Observation 40662178 + + + + + [40662328] species: Ceanothus cuneatus (Buckbrush) observed by lbarlas on 2020-03-23 + + Observation 40662328 + + + + + [40663024] species: Arctostaphylos glandulosa (Eastwood's Manzanita) observed by lbarlas on 2020-03-23 + + Observation 40663024 + + + + + [40672941] genus: Calystegia (false bindweeds) observed by caitlincornwall on 2020-03-21 + McCormick addition/Hood Mtn Regional Park + + Observation 40672941 + + + + + [40677780] unknown taxon observed by woody54 on 2020-03-24 + + Observation 40677780 + + + + [40678121] species: Pedicularis densiflora (Warrior's Plume) observed by woody54 on None + + Observation 40678121 + + + + + [40719388] species: Ceanothus cuneatus (Buckbrush) observed by woody54 on 2020-03-25 + + Observation 40719388 + + + + + [40719453] species: Nemophila heterophylla (White nemophila) observed by woody54 on 2020-03-25 + + Observation 40719453 + + + + + [40759669] species: Pentagramma triangularis (goldback fern) observed by woody54 on 2020-03-25 + + Observation 40759669 + + + + + [40803328] species: Solanum xanti (purple nightshade) observed by lbarlas on 2020-03-23 + + + Observation 40803328 + + + + + [40803560] species: Toxicoscordion fremontii (Fremont's Deathcamas) observed by lbarlas on 2020-03-23 + + Observation 40803560 + + + + + [40862128] species: Dendromecon rigida (Bush Poppy) observed by woody54 on 2020-03-27 + + Observation 40862128 + + + + + [40878541] species: Lupinus albifrons (Silver Lupine) observed by woody54 on 2020-03-27 + + Observation 40878541 + + + + + [40888911] species: Quercus garryana (Oregon oak) observed by woody54 on 2020-03-27 + + Observation 40888911 + + + + + [40904022] species: Glaucopsyche lygdamus (Silvery Blue) observed by jackroney on 2020-03-27 + + Observation 40904022 + + + + + [40936712] species: Amanita vernicoccora (Spring Coccora) observed by woody54 on 2020-03-27 + + Observation 40936712 + + + + + [41046800] species: Sylvilagus bachmani (Brush Rabbit) observed by woody54 on 2020-03-29 + + Observation 41046800 + + + + + [41138480] species: Phidippus johnsoni (Johnson's Jumping Spider) observed by jackroney on 2020-03-31 + + Observation 41138480 + + + + + [41141360] genus: Maianthemum (mayflowers and false Solomon's seals) observed by woody54 on 2020-03-31 + + Observation 41141360 + + + + + [41149969] species: Urtica dioica (stinging nettle) observed by woody54 on 2020-03-31 + + Observation 41149969 + + + + + [41173875] species: Poecile rufescens (Chestnut-backed Chickadee) observed by jackroney on 2020-03-31 + + Observation 41173875 + + + + + [41173914] species: Pedicularis densiflora (Warrior's Plume) observed by jackroney on 2020-03-31 + + Observation 41173914 + + + + + [41173971] species: Habropoda depressa (California Mountain-Digger Bee) observed by jackroney on 2020-03-31 + + Observation 41173971 + + + + + [41174015] species: Dudleya cymosa (Canyon Live-forever) observed by jackroney on 2020-03-27 + + Observation 41174015 + + + + + [41187137] species: Battus philenor (Pipevine Swallowtail) observed by woody54 on 2020-04-01 + + Observation 41187137 + + + + + [41187731] species: Pieris marginalis (Margined White) observed by woody54 on 2020-04-01 + + Observation 41187731 + + + + + [41187850] species: Stellaria media (common chickweed) observed by woody54 on 2020-04-01 + + Observation 41187850 + + + + + [41187979] species: Delphinium nudicaule (Red larkspur) observed by woody54 on 2020-04-01 + + Observation 41187979 + + + + + [41188162] order: Diptera (Flies) observed by woody54 on 2020-04-01 + + Observation 41188162 + + + + + [41199896] species: Fritillaria affinis (checker lily) observed by ten_salamanders on 2020-03-22 + + Observation 41199896 + + + + + [41199898] species: Viola ocellata (western heart's ease) observed by ten_salamanders on 2020-03-22 + + Observation 41199898 + + + + + [41199899] species: Lunaria annua (Annual Honesty) observed by ten_salamanders on 2020-03-22 + + Observation 41199899 + + + + + [41199900] genus: Castilleja (Paintbrushes) observed by ten_salamanders on 2020-03-22 + + Observation 41199900 + + + + + [41199905] genus: Lathyrus (sweet peas and vetchlings) observed by ten_salamanders on 2020-03-22 + + Observation 41199905 + + + + + [41199908] genus: Iris (Irises) observed by ten_salamanders on 2020-03-22 + + Observation 41199908 + + + + + [41199909] genus: Ceanothus (no common name) observed by ten_salamanders on 2020-03-22 + + Observation 41199909 + + + + + [41199913] species: Achillea millefolium (common yarrow) observed by ten_salamanders on 2020-03-22 + + Observation 41199913 + + + + + [41199914] genus: Calystegia (false bindweeds) observed by ten_salamanders on 2020-03-22 + + Observation 41199914 + + + + + [41199915] species: Calochortus amabilis (Diogenes' lantern) observed by ten_salamanders on 2020-03-22 + + Observation 41199915 + + + + + [41199917] species: Primula hendersonii (Henderson's shooting star) observed by ten_salamanders on 2020-03-22 + + Observation 41199917 + + + + + [41199918] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by ten_salamanders on 2020-03-22 + + Observation 41199918 + + + + + [41199921] genus: Erodium (stork's-bills) observed by ten_salamanders on 2020-03-22 + + Observation 41199921 + + + + + [41199922] suborder: Polyphaga (Water, Rove, Scarab, Long-horned, Leaf, and Snout Beetles) observed by ten_salamanders on 2020-03-22 + + Observation 41199922 + + + + + [41199925] class: Magnoliopsida (dicots) observed by ten_salamanders on 2020-03-22 + + Observation 41199925 + + + + + [41199926] species: Lupinus bicolor (Miniature Lupine) observed by ten_salamanders on 2020-03-22 + + Observation 41199926 + + + + + [41199929] species: Adela trigrapha (Three-striped Longhorn) observed by ten_salamanders on 2020-03-22 + + Observation 41199929 + + + + + [41199956] species: Adela trigrapha (Three-striped Longhorn) observed by ten_salamanders on 2020-03-22 + + Observation 41199956 + + + + + [41199957] genus: Nemophila (baby blue eyes) observed by ten_salamanders on 2020-03-22 + + Observation 41199957 + + + + + [41199958] species: Fritillaria affinis (checker lily) observed by ten_salamanders on 2020-03-22 + + Observation 41199958 + + + + + [41200030] order: Diptera (Flies) observed by ten_salamanders on 2020-03-22 + + Observation 41200030 + + + + + [41204065] species: Ensatina eschscholtzii (Ensatina) observed by jackroney on 2020-04-01 + + Observation 41204065 + + + + + [41204109] species: Ensatina eschscholtzii (Ensatina) observed by jackroney on 2020-04-01 + + Observation 41204109 + + + + + [41204186] genus: Tylobolus (no common name) observed by jackroney on 2020-04-01 + + Observation 41204186 + + + + + [41204253] order: Lepidoptera (Butterflies and Moths) observed by jackroney on 2020-04-01 + + Observation 41204253 + + + + + [41204374] subfamily: Tibicininae (no common name) observed by jackroney on 2020-04-01 + + Observation 41204374 + + + + + [41204472] species: Pacifastacus leniusculus (Signal Crayfish) observed by jackroney on 2020-04-01 + + Observation 41204472 + + + + + [41204532] species: Plestiodon skiltonianus (Western Skink) observed by jackroney on 2020-04-01 + + Observation 41204532 + + + + + [41205225] species: Rhizomnium glabrescens (fan moss) observed by jackroney on 2020-04-01 + + Observation 41205225 + + + + + [41205340] tribe: Dendryphantini (no common name) observed by jackroney on 2020-04-01 + + Observation 41205340 + + + + + [41209075] species: Ceanothus jepsonii (musk brush) observed by woody54 on 2020-04-01 + + Observation 41209075 + + + + + [41223734] species: Baccharis pilularis (coyote brush) observed by sslucas on 2019-12-28 + + Observation 41223734 + + + + + [41223752] species: Taeniatherum caput-medusae (medusa head) observed by sslucas on 2019-12-28 + + Observation 41223752 + + + + + [41223764] species: Baccharis pilularis (coyote brush) observed by sslucas on 2019-12-28 + + Observation 41223764 + + + + + [41223795] species: Batrachoseps attenuatus (California Slender Salamander) observed by sslucas on 2019-12-28 + + Observation 41223795 + + + + + [41223822] species: Alnus rhombifolia (white alder) observed by sslucas on 2019-12-28 + + Observation 41223822 + + + + + [41223841] species: Pteridium aquilinum (common bracken) observed by sslucas on 2019-12-28 + + Observation 41223841 + + + + + [41223871] species: Trametes versicolor (turkey-tail) observed by sslucas on 2019-12-28 + + Observation 41223871 + + + + + [41223875] kingdom: Fungi (Fungi Including Lichens) observed by sslucas on 2019-12-28 + + Observation 41223875 + + + + + [41223895] species: Stereum ostrea (false turkey-tail) observed by sslucas on 2019-12-28 + + Observation 41223895 + + + + + [41223932] species: Dryopteris arguta (coastal woodfern) observed by sslucas on 2019-12-28 + + Observation 41223932 + + + + + [41223941] species: Pleurotus ostreatus (Oyster Mushroom) observed by sslucas on 2019-12-28 + + Observation 41223941 + + + + + [41223955] species: Pentagramma triangularis (goldback fern) observed by sslucas on 2019-12-28 + + Observation 41223955 + + + + + [41223969] species: Chlorogalum pomeridianum (wavy-leafed soap plant) observed by sslucas on 2019-12-28 + + Observation 41223969 + + + + + [41223982] genus: Mentha (mints) observed by sslucas on 2019-12-28 + + Observation 41223982 + + + + + [41223989] species: Ramalina menziesii (lace lichen) observed by sslucas on 2019-12-28 + + Observation 41223989 + + + + + [41223999] family: Orthotrichaceae (no common name) observed by sslucas on 2019-12-28 + + Observation 41223999 + + + + + [41224008] genus: Usnea (beard lichens) observed by sslucas on 2019-12-28 + + Observation 41224008 + + + + + [41272465] species: Taricha granulosa (Rough-skinned Newt) observed by jackroney on 2020-04-02 + + Observation 41272465 + + + + + [41275244] genus: Gymnoconia (no common name) observed by woody54 on 2020-04-02 + + Observation 41275244 + + + + + [41283766] subspecies: Thamnophis elegans terrestris (Coast Garter Snake) observed by jackroney on 2020-04-02 + + Observation 41283766 + + + + + [41314583] genus: Ranunculus (buttercups) observed by woody54 on 2020-04-03 + + Observation 41314583 + + + + + [41334967] species: Monardella villosa (Coyote Mint) observed by jackroney on 2020-04-03 + + Observation 41334967 + + + + + [41335162] species: Hypericum concinnum (goldwire) observed by jackroney on 2020-04-03 + + Observation 41335162 + + + + + [41335210] species: Calochortus amabilis (Diogenes' lantern) observed by jackroney on 2020-04-03 + + Observation 41335210 + + + + + [41335274] species: Iris hartwegii (rainbow iris) observed by jackroney on 2020-04-03 + + Observation 41335274 + + + + + [41335381] species: Sanicula crassicaulis (Pacific Sanicle) observed by jackroney on 2020-04-03 + Variegated leaves + + Observation 41335381 + + + + + [41335520] species: Xystocheir dissecta (no common name) observed by jackroney on 2020-04-02 + Found and photographed in 1-2 inches of water, alive and well. Another was in the same depth about a foot away. + + Observation 41335520 + + + + + [41335554] species: Ellychnia californica (California Glowworm) observed by jackroney on 2020-04-02 + + Observation 41335554 + + + + + [41335602] species: Monadenia infumata (Redwood Sideband) observed by jackroney on 2020-04-02 + + Observation 41335602 + + + + + [41336458] order: Ephemeroptera (Mayflies) observed by jackroney on 2020-04-02 + Hundreds of individuals of varying sizes in the surrounding area. + + Observation 41336458 + + + + + [41412634] species: Aristolochia californica (California Dutchman's Pipe) observed by woody54 on 2020-04-04 + + Observation 41412634 + + + + + [41450808] species: Pteridium aquilinum (common bracken) observed by woody54 on 2020-04-04 + + Observation 41450808 + + + + + [41460878] tribe: Melitaeini (Checkerspots) observed by mycorrhizal on 2020-03-03 + + Observation 41460878 + + + + + [41460925] species: Fritillaria affinis (checker lily) observed by mycorrhizal on 2020-03-03 + + Observation 41460925 + + + + + [41460948] species: Frangula californica (coffeeberry) observed by mycorrhizal on 2020-03-03 + + Observation 41460948 + + + + + [41460990] species: Fritillaria affinis (checker lily) observed by mycorrhizal on 2020-03-03 + + Observation 41460990 + + + + + [41461050] class: Liliopsida (monocots) observed by mycorrhizal on 2020-03-03 + + Observation 41461050 + + + + + [41461073] species: Aristolochia californica (California Dutchman's Pipe) observed by mycorrhizal on 2020-03-03 + + Observation 41461073 + + + + + [41461094] subphylum: Angiospermae (flowering plants) observed by mycorrhizal on 2020-03-03 + + Observation 41461094 + + + + + [41461137] species: Acer macrophyllum (bigleaf maple) observed by mycorrhizal on 2020-03-03 + + Observation 41461137 + + + + + [41461159] species: Umbellularia californica (California bay) observed by mycorrhizal on 2020-03-03 + + Observation 41461159 + + + + + [41461236] species: Adelinia grande (Pacific hound's tongue) observed by mycorrhizal on 2020-03-03 + + Observation 41461236 + + + + + [41461267] species: Nemophila menziesii (Menzies' baby blue eyes) observed by mycorrhizal on 2020-03-03 + + Observation 41461267 + + + + + [41547645] species: Umbellularia californica (California bay) observed by cloudya on 2020-03-03 + + Observation 41547645 + + + + + [41547942] species: Nemophila menziesii (Menzies' baby blue eyes) observed by cloudya on 2020-03-03 + + Observation 41547942 + + + + + [41560002] species: Trillium albidum (giant white wakerobin) observed by woody54 on 2020-04-06 + + Observation 41560002 + + + + + [41560725] species: Misumena vatia (Goldenrod Crab Spider) observed by woody54 on 2020-04-06 + + Observation 41560725 + + + + + [41566292] species: Salix lasiolepis (Arroyo Willow) observed by woody54 on 2020-04-06 + + Observation 41566292 + + + + + [41568179] species: Glaucopsyche lygdamus (Silvery Blue) observed by woody54 on 2020-04-06 + + Observation 41568179 + + + + + [41569390] species: Lyophyllum decastes (Fried Chicken Mushroom) observed by woody54 on 2020-04-06 + + Observation 41569390 + + + + + [41660698] genus: Barbarea (wintercresses) observed by woody54 on 2020-04-07 + + Observation 41660698 + + + + + [41713068] species: Iris fernaldii (Fernald's iris) observed by woody54 on 2020-04-08 + + Observation 41713068 + + + + + [41713264] genus: Iris (Irises) observed by woody54 on 2020-04-08 + + Observation 41713264 + + + + + [41714057] genus: Osmorhiza (sweet cicely) observed by woody54 on 2020-04-07 + + Observation 41714057 + + + + + [41722457] species: Ixoreus naevius (Varied Thrush) observed by nelruzam on 2020-04-06 + + + Observation 41722457 + + + + + [41732006] species: Plestiodon skiltonianus (Western Skink) observed by dmosur on 2020-04-06 + Big adult + + + + Observation 41732006 + + + + + [41790078] species: Syntrichia ruralis (star moss) observed by woody54 on 2020-04-09 + + Observation 41790078 + + + + + [41791548] order: Araneae (Spiders) observed by woody54 on 2020-04-09 + + Observation 41791548 + + + + + [41860790] species: Callophrys dumetorum (Lotus Hairstreak) observed by woody54 on 2020-04-10 + + Observation 41860790 + + + + + [41860926] species: Pteridium aquilinum (common bracken) observed by woody54 on 2020-04-10 + + Observation 41860926 + + + + + [41864231] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by woody54 on 2020-04-10 + + Observation 41864231 + + + + + [41864592] species: Cynara cardunculus (Artichoke Thistle) observed by woody54 on 2020-04-10 + + Observation 41864592 + + + + + [41924576] genus: Lotus (bird's-foot-trefoils) observed by caitlincornwall on 2020-04-11 + + Observation 41924576 + + + + + [41935806] species: Euphydryas editha (Edith's Checkerspot) observed by jackroney on 2020-03-27 + + Observation 41935806 + + + + + [41935923] species: Anthocharis sara (Sara Orangetip) observed by jackroney on 2020-04-11 + + Observation 41935923 + + + + + [41970800] species: Batrachoseps attenuatus (California Slender Salamander) observed by woody54 on 2020-04-11 + + Observation 41970800 + + + + + [41971133] species: Scandix pecten-veneris (Shepherd's-needle) observed by woody54 on 2020-04-11 + + Observation 41971133 + + + + + [41973922] species: Maianthemum stellatum (star-flowered lily-of-the-valley) observed by woody54 on 2020-04-11 + + Observation 41973922 + + + + + [42012118] species: Ranunculus occidentalis (Western Buttercup) observed by jackroney on 2020-04-11 + Aberrant coloration—second pic shows normal coloration on flowers of the same plant. + + Observation 42012118 + + + + + [42028147] species: Phidippus johnsoni (Johnson's Jumping Spider) observed by woody54 on 2020-04-12 + + Observation 42028147 + + + + + [42028324] species: Lasthenia californica (California goldfields) observed by woody54 on 2020-04-12 + + Observation 42028324 + + + + + [42034402] species: Coenonympha tullia (Common Ringlet) observed by woody54 on 2020-04-12 + + Observation 42034402 + + + + + [42035501] species: Trillium albidum (giant white wakerobin) observed by woody54 on 2020-04-12 + + Observation 42035501 + + + + + [42046516] species: Rubus ursinus (trailing blackberry) observed by woody54 on 2020-04-12 + + Observation 42046516 + + + + + [42063985] kingdom: Fungi (Fungi Including Lichens) observed by katie877 on 2019-11-10 + + Observation 42063985 + + + + + [42087944] species: Petrorhagia dubia (Hairypink) observed by woody54 on 2020-04-12 + + Observation 42087944 + + + + + [42103778] species: Anaxyrus boreas (Western Toad) observed by patrick-mcbride on 2020-04-09 + + Observation 42103778 + + + + + [42183637] species: Amsinckia menziesii (Common Fiddleneck) observed by woody54 on 2020-04-14 + + Observation 42183637 + + + + + [42183825] species: Sisyrinchium bellum (western blue-eyed grass) observed by woody54 on 2020-04-14 + + Observation 42183825 + + + + + [42184204] species: Rhinotropis californica (California milkwort) observed by cloudya on 2019-05-17 + + Observation 42184204 + + + + + [42184753] species: Ceanothus jepsonii (musk brush) observed by cloudya on 2019-05-17 + + Observation 42184753 + + + + + [42185405] species: Hemizonia congesta (Hayfield Tarweed) observed by cloudya on 2019-05-17 + + Observation 42185405 + + + + + [42185800] species: Madia gracilis (grassy tarweed) observed by cloudya on 2019-05-17 + + Observation 42185800 + + + + + [42187537] genus: Lomatium (Biscuitroots) observed by cloudya on 2019-05-17 + + Observation 42187537 + + + + + [42204727] species: Poa bulbosa (Bulbous Meadow-grass) observed by woody54 on 2020-04-14 + + Observation 42204727 + + + + + [42209177] species: Pteridium aquilinum (common bracken) observed by woody54 on 2020-04-14 + + Observation 42209177 + + + + + [42217636] species: Dichelostemma capitatum (Blue Dicks) observed by woody54 on 2020-04-14 + + Observation 42217636 + + + + + [42261773] species: Lamium purpureum (red deadnettle) observed by woody54 on 2020-04-15 + + Observation 42261773 + + + + + [42278883] species: Erynnis propertius (Propertius Duskywing) observed by woody54 on 2020-04-15 + + Observation 42278883 + + + + + [42283416] species: Myosotis discolor (Changing Forget-me-not) observed by woody54 on 2020-04-15 + + Observation 42283416 + + + + + [42336791] order: Lepidoptera (Butterflies and Moths) observed by woody54 on 2020-04-16 + + Observation 42336791 + + + + + [42337922] species: Collinsia heterophylla (Purple Chinese Houses) observed by invictus13 on 2020-04-16 + + Observation 42337922 + + + + + [42340462] species: Augochlorella pomoniella (Peridot Sweat Bee) observed by woody54 on 2020-04-16 + + Observation 42340462 + + + + + [42340874] species: Bellis perennis (common daisy) observed by woody54 on 2020-04-16 + + Observation 42340874 + + + + + [42363850] species: Nemophila menziesii (Menzies' baby blue eyes) observed by alfresco on 2020-04-14 + + Observation 42363850 + + + + + [42364024] species: Sisyrinchium bellum (western blue-eyed grass) observed by alfresco on 2020-04-14 + + Observation 42364024 + + + + + [42364041] species: Delphinium nudicaule (Red larkspur) observed by alfresco on 2020-04-14 + + Observation 42364041 + + + + + [42364071] species: Iris fernaldii (Fernald's iris) observed by alfresco on 2020-04-14 + + Observation 42364071 + + + + + [42364091] species: Toxicoscordion fremontii (Fremont's Deathcamas) observed by alfresco on 2020-04-14 + + Observation 42364091 + + + + + [42364157] species: Leptosiphon bicolor (true babystars) observed by alfresco on 2020-04-14 + + Observation 42364157 + + + + + [42366256] species: Vicia villosa (hairy vetch) observed by woody54 on 2020-04-16 + + Observation 42366256 + + + + + [42366382] species: Diplacus aurantiacus (orange bush monkeyflower) observed by woody54 on 2020-04-16 + + Observation 42366382 + + + + + [42402343] genus: Usnea (beard lichens) observed by sslucas on 2019-12-28 + + Observation 42402343 + + + + + [42402446] family: Teloschistaceae (no common name) observed by sslucas on 2019-12-28 + + Observation 42402446 + + + + + [42403976] species: Pleurotus ostreatus (Oyster Mushroom) observed by sslucas on 2020-01-13 + + Observation 42403976 + + + + + [42404061] species: Primula hendersonii (Henderson's shooting star) observed by sslucas on 2020-03-20 + + Observation 42404061 + + + + + [42404136] genus: Toxicoscordion (Deathcamas) observed by sslucas on 2020-03-20 + + Observation 42404136 + + + + + [42404285] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by sslucas on 2020-03-20 + + Observation 42404285 + + + + + [42428916] species: Phacelia imbricata (Mountain Phacelia) observed by giantsguy on 2020-04-17 + + Observation 42428916 + + + + + [42429016] species: Erythranthe guttata (seep monkeyflower) observed by giantsguy on 2020-04-17 + + Observation 42429016 + + + + + [42429101] species: Stachys byzantina (woolly hedgenettle) observed by giantsguy on 2020-04-17 + + Observation 42429101 + + + + + [42429127] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by giantsguy on 2020-04-17 + + Observation 42429127 + + + + + [42443486] species: Parmotrema perlatum (Black Stone Flower) observed by woody54 on 2020-04-17 + + Observation 42443486 + + + + + [42476940] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by sslucas on 2020-04-17 + + Observation 42476940 + + + + + [42476972] species: Erodium cicutarium (common stork's-bill) observed by sslucas on 2020-04-17 + + Observation 42476972 + + + + + [42477049] genus: Lupinus (lupines) observed by sslucas on 2020-04-17 + + Observation 42477049 + + + + + [42477118] species: Eschscholzia californica (California poppy) observed by sslucas on 2020-04-17 + + Observation 42477118 + + + + + [42477253] subspecies: Equisetum hyemale affine (western scouringrush) observed by sslucas on 2020-04-17 + + Observation 42477253 + + + + + [42477290] species: Helenium puberulum (Rosilla) observed by sslucas on 2020-04-17 + + Observation 42477290 + + + + + [42477341] species: Stachys byzantina (woolly hedgenettle) observed by sslucas on 2020-04-17 + + Observation 42477341 + + + + + [42477371] species: Erythranthe guttata (seep monkeyflower) observed by sslucas on 2020-04-17 + + Observation 42477371 + + + + + [42477399] species: Lupinus albifrons (Silver Lupine) observed by sslucas on 2020-04-17 + + Observation 42477399 + + + + + [42477449] genus: Phacelia (Scorpionweeds) observed by sslucas on 2020-04-17 + + Observation 42477449 + + + + + [42477516] species: Polypodium californicum (California Polypody) observed by sslucas on 2020-04-17 + + Observation 42477516 + + + + + [42477563] species: Polypodium californicum (California Polypody) observed by sslucas on 2020-04-17 + + Observation 42477563 + + + + + [42524016] species: Artemisia douglasiana (California mugwort) observed by woody54 on 2020-04-18 + + Observation 42524016 + + + + + [42524226] genus: Calystegia (false bindweeds) observed by woody54 on 2020-04-18 + + Observation 42524226 + + + + + [42525189] species: Calandrinia menziesii (redmaids) observed by woody54 on 2020-04-18 + + Observation 42525189 + + + + + [42543092] family: Pompilidae (Spider Wasps) observed by cjs041 on 2019-08-24 + + Observation 42543092 + + + + + [42545096] species: Cordulegaster dorsalis (Pacific Spiketail) observed by cjs041 on 2019-08-24 + + Observation 42545096 + + + + + [42545780] subfamily: Asilinae (no common name) observed by cjs041 on 2019-08-24 + + Observation 42545780 + + + + + [42623437] species: Lomatium utriculatum (Foothill desert-parsley) observed by woody54 on 2020-04-19 + + Observation 42623437 + + + + + [42623634] species: Collinsia sparsiflora (fewflower blue-eyed Mary) observed by woody54 on 2020-04-19 + + Observation 42623634 + + + + + [42633673] genus: Leptosiphon (no common name) observed by woody54 on 2020-04-19 + + Observation 42633673 + + + + + [42646386] species: Hericium erinaceus (lion's-mane mushroom) observed by lbarlas on 2017-01-23 + + + Observation 42646386 + + + + + [42646707] genus: Tremella (no common name) observed by lbarlas on 2017-01-23 + + + Observation 42646707 + + + + + [42647160] genus: Stereum (no common name) observed by lbarlas on 2017-01-23 + + + Observation 42647160 + + + + + [42671292] species: Lathyrus cicera (Red Vetchling) observed by woody54 on 2020-04-19 + + Observation 42671292 + + + + + [42727259] species: Trillium albidum (giant white wakerobin) observed by sunflowerguy on 2020-04-20 + + Observation 42727259 + + + + + [42916204] species: Wyethia glabra (smooth mule-ears) observed by woody54 on 2020-04-22 + + Observation 42916204 + + + + + [42916623] genus: Clarkia (no common name) observed by woody54 on 2020-04-22 + + Observation 42916623 + + + + + [42916692] genus: Phacelia (Scorpionweeds) observed by woody54 on 2020-04-22 + + Observation 42916692 + + + + + [42917413] species: Anthocharis sara (Sara Orangetip) observed by woody54 on 2020-04-22 + + Observation 42917413 + + + + + [42918076] species: Pisum sativum (Common Pea) observed by woody54 on 2020-04-22 + + Observation 42918076 + + + + + [42966458] genus: Euphydryas (no common name) observed by woody54 on 2020-04-22 + + Observation 42966458 + + + + + [42967140] species: Castilleja foliolosa (Woolly Indian Paintbrush) observed by woody54 on 2020-04-22 + + Observation 42967140 + + + + + [43043556] species: Sanicula bipinnatifida (Purple Sanicle) observed by sunflowerguy on 2020-04-20 + + Observation 43043556 + + + + + [43257067] species: Buteo jamaicensis (Red-tailed Hawk) observed by woody54 on 2020-04-23 + + Observation 43257067 + + + + + [43257207] species: Sayornis nigricans (Black Phoebe) observed by woody54 on 2020-04-23 + + Observation 43257207 + + + + + [43263394] species: Umbellularia californica (California bay) observed by nwuaf on 2020-02-01 + + Observation 43263394 + + + + + [43263682] species: Prunus lusitanica (Portuguese laurel) observed by nwuaf on 2020-02-23 + + Observation 43263682 + + + + + [43264037] genus: Arctostaphylos (bearberries and manzanitas) observed by nwuaf on 2020-02-23 + + Observation 43264037 + + + + + [43264735] species: Calycanthus occidentalis (California sweetshrub) observed by nwuaf on 2020-03-01 + + Observation 43264735 + + + + + [43423166] genus: Lupinus (lupines) observed by wa-hayes on 2020-04-18 + + Observation 43423166 + + + + + [43423329] genus: Dichelostemma (Wild hyacinths) observed by wa-hayes on 2020-04-18 + + Observation 43423329 + + + + + [43423534] genus: Vicia (Vetches) observed by wa-hayes on 2020-04-18 + + Observation 43423534 + + + + + [43423717] species: Claytonia perfoliata (miner's lettuce) observed by wa-hayes on 2020-04-18 + two colors of flowers, pink and white. split spp. + + Observation 43423717 + + + + + [43424036] species: Trillium albidum (giant white wakerobin) observed by wa-hayes on 2020-04-18 + + Observation 43424036 + + + + + [43424239] genus: Solanum (nightshades) observed by wa-hayes on 2020-04-18 + + Observation 43424239 + + + + + [43424370] genus: Ribes (currants and gooseberries) observed by wa-hayes on 2020-04-18 + forgetting sp, no internet. soft fuzzy leaves, very fine thorns on main leaf vein (back). fine spines near leaf junctures on older canes. + + Observation 43424370 + + + + + [43424650] species: Matricaria discoidea (pineapple-weed) observed by wa-hayes on 2020-04-18 + + Observation 43424650 + + + + + [43424741] subphylum: Angiospermae (flowering plants) observed by wa-hayes on 2020-04-18 + + Observation 43424741 + + + + + [43424909] species: Sanicula bipinnatifida (Purple Sanicle) observed by wa-hayes on 2020-04-18 + + Observation 43424909 + + + + + [43425052] genus: Ranunculus (buttercups) observed by wa-hayes on 2020-04-18 + all flowers golden except two with white petals(same plant). + + Observation 43425052 + + + + + [43458645] genus: Ceanothus (no common name) observed by woody54 on 2020-04-22 + + Observation 43458645 + + + + + [43528702] species: Rosa californica (California Wild Rose) observed by woody54 on 2020-04-25 + + Observation 43528702 + + + + + [43533338] species: Geranium robertianum (herb Robert) observed by woody54 on 2020-04-25 + + Observation 43533338 + + + + + [43683110] species: Eriophyllum lanatum (common woolly sunflower) observed by sonomanewt on 2020-04-26 + + Observation 43683110 + + + + + [43701226] species: Eriophyllum lanatum (common woolly sunflower) observed by sonomanewt on 2020-04-26 + + Observation 43701226 + + + + + [43731021] species: Papilio zelicaon (Anise Swallowtail) observed by woody54 on 2020-04-26 + + Observation 43731021 + + + + + [43768634] tribe: Polyommatini (Typical Blues) observed by woody54 on 2020-04-26 + + Observation 43768634 + + + + + [43805096] species: Delphinium nudicaule (Red larkspur) observed by ten_salamanders on 2020-04-26 + + Observation 43805096 + + + + + [43805155] species: Calochortus amabilis (Diogenes' lantern) observed by ten_salamanders on 2020-04-26 + + Observation 43805155 + + + + + [43805211] species: Sisyrinchium bellum (western blue-eyed grass) observed by ten_salamanders on 2020-04-26 + + Observation 43805211 + + + + + [43805266] genus: Amsinckia (Fiddlenecks) observed by ten_salamanders on 2020-04-26 + + Observation 43805266 + + + + + [43805301] species: Calochortus amabilis (Diogenes' lantern) observed by ten_salamanders on 2020-04-26 + + Observation 43805301 + + + + + [43805329] genus: Iris (Irises) observed by ten_salamanders on 2020-04-26 + + Observation 43805329 + + + + + [43805415] species: Delphinium nudicaule (Red larkspur) observed by ten_salamanders on 2020-04-26 + + Observation 43805415 + + + + + [43805453] order: Lepidoptera (Butterflies and Moths) observed by ten_salamanders on 2020-04-26 + + Observation 43805453 + + + + + [43805502] genus: Lithophragma (woodland-star) observed by ten_salamanders on 2020-04-26 + + Observation 43805502 + + + + + [43805544] genus: Nemophila (baby blue eyes) observed by ten_salamanders on 2020-04-26 + + Observation 43805544 + + + + + [43823003] species: Calochortus amabilis (Diogenes' lantern) observed by woody54 on 2020-04-26 + + Observation 43823003 + + + + + [43823434] species: Sanicula bipinnatifida (Purple Sanicle) observed by woody54 on 2020-04-26 + + Observation 43823434 + + + + + [43839044] species: Ficus carica (common fig) observed by jackroney on 2020-04-26 + + Observation 43839044 + + + + + [43839242] species: Octogomphus specularis (Grappletail) observed by jackroney on 2020-04-26 + + Observation 43839242 + + + + + [43839412] species: Scolopendra polymorpha (Common Desert Centipede) observed by jackroney on 2020-04-26 + + Observation 43839412 + + + + + [43839632] genus: Neohermes (Gray Fishflies) observed by jackroney on 2020-04-26 + Found under a rock in a dried up streambed. + + Observation 43839632 + + + + + [43839840] species: Diadophis punctatus (ring-necked snake) observed by jackroney on 2020-04-26 + + Observation 43839840 + + + + + [43840095] species: Thamnophis atratus (Aquatic Garter Snake) observed by jackroney on 2020-04-26 + + Observation 43840095 + + + + + [43840184] infraorder: Systellognatha (no common name) observed by jackroney on 2020-04-26 + + Observation 43840184 + + + + + [43840304] class: Chilopoda (Centipedes) observed by jackroney on 2020-04-26 + + Observation 43840304 + + + + + [43840520] species: Cosmopepla conspicillaris (Hedge Nettle Stink Bug) observed by jackroney on 2020-04-26 + + Observation 43840520 + + + + + [43840562] species: Ariolimax buttoni (Button's Banana Slug) observed by jackroney on 2020-04-26 + + Observation 43840562 + + + + + [43917855] species: Lithophragma heterophyllum (hillside woodland star) observed by jackroney on 2020-04-26 + + Observation 43917855 + + + + + [43986116] species: Lilium pardalinum (Leopard Lily) observed by jackroney on 2020-04-26 + + Observation 43986116 + + + + + [43987744] family: Asteraceae (sunflowers, daisies, asters, and allies) observed by woody54 on 2020-04-26 + + Observation 43987744 + + + + + [43988168] species: Acmispon brachycarpus (foothill deervetch) observed by woody54 on 2020-04-26 + + Observation 43988168 + + + + + [44035454] species: Rhinotropis californica (California milkwort) observed by jackroney on 2020-04-26 + + Observation 44035454 + + + + + [44035755] species: Polystichum munitum (western sword fern) observed by jackroney on 2020-04-26 + + Observation 44035755 + + + + + [44036252] species: Amphipyra pyramidoides (American Copper Underwing) observed by jackroney on 2020-04-26 + + Observation 44036252 + + + + + [44052628] species: Ceanothus jepsonii (musk brush) observed by woody54 on 2020-04-27 + + Observation 44052628 + + + + + [44052845] species: Viola ocellata (western heart's ease) observed by woody54 on 2020-04-27 + + Observation 44052845 + + + + + [44220107] species: Melozone crissalis (California Towhee) observed by woody54 on 2020-04-28 + + Observation 44220107 + + + + + [44275550] species: Lathyrus cicera (Red Vetchling) observed by wa-hayes on 2020-04-18 + + Observation 44275550 + + + + + [44275723] genus: Iris (Irises) observed by wa-hayes on 2020-04-18 + + Observation 44275723 + + + + + [44282638] species: Hypochaeris radicata (Common Cat's-ear) observed by woody54 on 2020-04-29 + + Observation 44282638 + + + + + [44308290] species: Callipepla californica (California Quail) observed by woody54 on 2020-04-29 + + Observation 44308290 + + + + + [44385220] species: Hypochaeris radicata (Common Cat's-ear) observed by woody54 on 2020-04-30 + + Observation 44385220 + + + + + [44416284] genus: Aralia (spikenards) observed by sunflowerguy on 2020-04-30 + Medium shrub (3-4' wide x 3-4' high) on edge of active stream along lower Vista Trail. Found during trail maintainence workday. No flowers currently on any of 4-6 plants. + + Observation 44416284 + + + + + [44416426] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by sunflowerguy on 2020-04-30 + + Observation 44416426 + + + + + [44416482] species: Achillea millefolium (common yarrow) observed by sunflowerguy on 2020-04-30 + + Observation 44416482 + + + + + [44420330] species: Argia vivida (Vivid Dancer) observed by woody54 on 2020-04-29 + + Observation 44420330 + + + + + [44552785] species: Erythranthe guttata (seep monkeyflower) observed by sunflowerguy on 2020-04-30 + + Observation 44552785 + + + + + [44553027] species: Calochortus amabilis (Diogenes' lantern) observed by sunflowerguy on 2020-04-30 + + Observation 44553027 + + + + + [44553053] species: Woodwardia fimbriata (giant chain fern) observed by sunflowerguy on 2020-04-30 + + Observation 44553053 + + + + + [44553103] species: Helenium bigelovii (Bigelow's sneezeweed) observed by sunflowerguy on 2020-04-30 + + Observation 44553103 + + + + + [44560987] species: Thermopsis californica (California goldenbanner) observed by woody54 on 2020-05-01 + + Observation 44560987 + + + + + [44593819] genus: Phacelia (Scorpionweeds) observed by woody54 on 2020-05-01 + + Observation 44593819 + + + + + [44604075] species: Eriophyllum lanatum (common woolly sunflower) observed by woody54 on 2020-05-01 + + Observation 44604075 + + + + + [44613289] species: Urtica dioica (stinging nettle) observed by woody54 on 2020-05-02 + + Observation 44613289 + + + + + [44627231] species: Prosartes hookeri (Hooker's fairybells) observed by woody54 on 2020-05-02 + + Observation 44627231 + + + + + [44632380] species: Nasturtium officinale (watercress) observed by woody54 on 2020-05-02 + + Observation 44632380 + + + + + [44640391] genus: Lomatium (Biscuitroots) observed by caitlincornwall on 2020-04-11 + + Observation 44640391 + + + + + [44640423] genus: Trifolium (clovers) observed by caitlincornwall on 2020-04-11 + + Observation 44640423 + + + + + [44640458] order: Rosales (roses, elms, figs, and allies) observed by caitlincornwall on 2020-04-11 + + Observation 44640458 + + + + + [44688461] species: Heracleum maximum (common cowparsnip) observed by woody54 on 2020-05-02 + + Observation 44688461 + + + + + [44753820] species: Anas platyrhynchos (Mallard) observed by woody54 on 2020-05-03 + + Observation 44753820 + + + + + [44761960] species: Lilium pardalinum (Leopard Lily) observed by woody54 on 2020-05-03 + Lilyboyjoy + + Observation 44761960 + + + + + [44785138] species: Ceanothus jepsonii (musk brush) observed by jrlynx on 2020-05-02 + + Observation 44785138 + + + + + [44785139] genus: Calystegia (false bindweeds) observed by jrlynx on 2020-05-02 + + Observation 44785139 + + + + + [44785140] species: Quercus durata (leather oak) observed by jrlynx on 2020-05-02 + + Observation 44785140 + + + + + [44785151] genus: Lathyrus (sweet peas and vetchlings) observed by jrlynx on 2020-05-02 + + Observation 44785151 + + + + + [44785152] species: Vicia villosa (hairy vetch) observed by jrlynx on 2020-05-02 + + Observation 44785152 + + + + + [44785153] species: Dichelostemma congestum (Ookow) observed by jrlynx on 2020-05-02 + + Observation 44785153 + + + + + [44785167] species: Madia gracilis (grassy tarweed) observed by jrlynx on 2020-05-02 + + Observation 44785167 + + + + + [44785169] species: Solanum xanti (purple nightshade) observed by jrlynx on 2020-05-02 + + Observation 44785169 + + + + + [44785172] species: Anisocarpus madioides (woodland madia) observed by jrlynx on 2020-05-02 + + Observation 44785172 + + + + + [44785180] family: Brassicaceae (mustard family) observed by jrlynx on 2020-05-02 + + Observation 44785180 + + + + + [44785181] species: Achillea millefolium (common yarrow) observed by jrlynx on 2020-05-02 + + Observation 44785181 + + + + + [44785182] species: Clematis lasiantha (Pipestem Clematis) observed by jrlynx on 2020-05-02 + + Observation 44785182 + + + + + [44785191] species: Rupertia physodes (forest scurfpea) observed by jrlynx on 2020-05-02 + + Observation 44785191 + + + + + [44785193] variety: Penstemon heterophyllus australis (foothill beardtongue) observed by jrlynx on 2020-05-02 + + Observation 44785193 + + + + + [44785195] species: Helenium puberulum (Rosilla) observed by jrlynx on 2020-05-02 + + Observation 44785195 + + + + + [44785206] species: Coenonympha tullia (Common Ringlet) observed by jrlynx on 2020-05-02 + + Observation 44785206 + + + + + [44785209] species: Dasymutilla aureola (Pacific Velvet Ant) observed by jrlynx on 2020-05-02 + + Observation 44785209 + + + + + [44785210] species: Brachysomida californica (no common name) observed by jrlynx on 2020-05-02 + + Observation 44785210 + + + + + [44785223] species: Leptosiphon parviflorus (variable linanthus) observed by jrlynx on 2020-05-02 + + Observation 44785223 + + + + + [44785224] species: Lathyrus cicera (Red Vetchling) observed by jrlynx on 2020-05-02 + + Observation 44785224 + + + + + [44785229] species: Euphydryas chalcedona (Variable Checkerspot) observed by jrlynx on 2020-05-02 + + Observation 44785229 + + + + + [44785235] species: Sambucus cerulea (blue elder) observed by jrlynx on 2020-05-02 + + Observation 44785235 + + + + + [44785237] species: Trifolium willdenovii (tomcat clover) observed by jrlynx on 2020-05-02 + + Observation 44785237 + + + + + [44785240] species: Collinsia sparsiflora (fewflower blue-eyed Mary) observed by jrlynx on 2020-05-02 + + Observation 44785240 + + + + + [44785253] species: Clarkia purpurea (Winecup Clarkia) observed by jrlynx on 2020-05-02 + + Observation 44785253 + + + + + [44785255] species: Clarkia purpurea (Winecup Clarkia) observed by jrlynx on 2020-05-02 + + Observation 44785255 + + + + + [44785256] species: Collinsia heterophylla (Purple Chinese Houses) observed by jrlynx on 2020-05-02 + + Observation 44785256 + + + + + [44785263] genus: Orgyia (no common name) observed by jrlynx on 2020-05-02 + + Observation 44785263 + + + + + [44785266] species: Achillea millefolium (common yarrow) observed by jrlynx on 2020-05-02 + + Observation 44785266 + + + + + [44785267] species: Pellaea andromedifolia (Coffee Fern) observed by jrlynx on 2020-05-02 + + Observation 44785267 + + + + + [44785283] species: Sanicula crassicaulis (Pacific Sanicle) observed by jrlynx on 2020-05-02 + + Observation 44785283 + + + + + [44785287] species: Micropus californicus (Q-tips) observed by jrlynx on 2020-05-02 + + Observation 44785287 + + + + + [44785288] species: Leptosiphon bicolor (true babystars) observed by jrlynx on 2020-05-02 + + Observation 44785288 + + + + + [44785304] species: Glaucopsyche lygdamus (Silvery Blue) observed by jrlynx on 2020-05-02 + + Observation 44785304 + + + + + [44785308] species: Matricaria discoidea (pineapple-weed) observed by jrlynx on 2020-05-02 + + Observation 44785308 + + + + + [44785309] species: Scandix pecten-veneris (Shepherd's-needle) observed by jrlynx on 2020-05-02 + + Observation 44785309 + + + + + [44785320] species: Gilia tricolor (bird's-eye gilia) observed by jrlynx on 2020-05-02 + + Observation 44785320 + + + + + [44785323] species: Lasthenia californica (California goldfields) observed by jrlynx on 2020-05-02 + + Observation 44785323 + + + + + [44785324] species: Pituophis catenifer (Gopher Snake) observed by jrlynx on 2020-05-02 + + Observation 44785324 + + + + + [44793845] family: Asteraceae (sunflowers, daisies, asters, and allies) observed by jrlynx on 2020-05-02 + + Observation 44793845 + + + + + [44793847] species: Iris macrosiphon (Bowltube Iris) observed by jrlynx on 2020-05-02 + + Observation 44793847 + + + + + [44793848] species: Vicia villosa (hairy vetch) observed by jrlynx on 2020-05-02 + + Observation 44793848 + + + + + [44793860] species: Lupinus bicolor (Miniature Lupine) observed by jrlynx on 2020-05-02 + + Observation 44793860 + + + + + [44793865] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by jrlynx on 2020-05-02 + + Observation 44793865 + + + + + [44793866] species: Calochortus amabilis (Diogenes' lantern) observed by jrlynx on 2020-05-02 + + Observation 44793866 + + + + + [44793879] species: Diplacus aurantiacus (orange bush monkeyflower) observed by jrlynx on 2020-05-02 + + Observation 44793879 + + + + + [44793881] species: Erythranthe guttata (seep monkeyflower) observed by jrlynx on 2020-05-02 + + Observation 44793881 + + + + + [44793886] species: Ranunculus californicus (California buttercup) observed by jrlynx on 2020-05-02 + + Observation 44793886 + + + + + [44793897] species: Nemophila parviflora (small-flowered nemophila) observed by jrlynx on 2020-05-02 + + Observation 44793897 + + + + + [44793898] species: Delphinium nudicaule (Red larkspur) observed by jrlynx on 2020-05-02 + + Observation 44793898 + + + + + [44793900] species: Phacelia imbricata (Mountain Phacelia) observed by jrlynx on 2020-05-02 + + Observation 44793900 + + + + + [44793915] species: Toxicoscordion fremontii (Fremont's Deathcamas) observed by jrlynx on 2020-05-02 + + Observation 44793915 + + + + + [44793916] species: Sisyrinchium bellum (western blue-eyed grass) observed by jrlynx on 2020-05-02 + + Observation 44793916 + + + + + [44793917] species: Thermopsis californica (California goldenbanner) observed by jrlynx on 2020-05-02 + + Observation 44793917 + + + + + [44793927] subspecies: Battus philenor hirsuta (California Pipevine Swallowtail) observed by jrlynx on 2020-05-02 + + Observation 44793927 + + + + + [44793928] species: Claytonia perfoliata (miner's lettuce) observed by jrlynx on 2020-05-02 + + Observation 44793928 + + + + + [44793929] species: Sciurus griseus (Western Gray Squirrel) observed by jrlynx on 2020-05-02 + + Observation 44793929 + + + + + [44793933] species: Wyethia angustifolia (narrowleaf mule-ears) observed by jrlynx on 2020-05-02 + + Observation 44793933 + + + + + [44793934] species: Rubus ursinus (trailing blackberry) observed by jrlynx on 2020-05-02 + + Observation 44793934 + + + + + [44793950] variety: Nemophila menziesii atomaria (white baby blue eyes) observed by jrlynx on 2020-05-02 + + Observation 44793950 + + + + + [44848410] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by pinkhockey75 on 2020-05-02 + + Observation 44848410 + + + + + [44848686] species: Clarkia amoena (farewell-to-spring) observed by pinkhockey75 on 2020-05-02 + + Observation 44848686 + + + + + [44883961] species: Cosmopepla conspicillaris (Hedge Nettle Stink Bug) observed by woody54 on 2020-04-27 + Really not sure of this. Just trying to get into the right genus. + + Observation 44883961 + + + + + [44897038] species: Buteo jamaicensis (Red-tailed Hawk) observed by ten_salamanders on 2020-05-03 + + Observation 44897038 + + + + + [44909719] genus: Iris (Irises) observed by cgnpark on 2020-05-02 + + Observation 44909719 + + + + + [44909810] genus: Leptosiphon (no common name) observed by cgnpark on 2020-05-02 + + Observation 44909810 + + + + + [44922017] subspecies: Battus philenor hirsuta (California Pipevine Swallowtail) observed by jrlynx on 2020-05-04 + + Observation 44922017 + + + + + [44922021] species: Phidippus johnsoni (Johnson's Jumping Spider) observed by jrlynx on 2020-05-04 + + Observation 44922021 + + + + + [44922022] species: Argia vivida (Vivid Dancer) observed by jrlynx on 2020-05-04 + + Observation 44922022 + + + + + [44922025] species: Amanita velosa (springtime amanita) observed by jrlynx on 2020-05-04 + + Observation 44922025 + + + + + [44922031] species: Dascillus davidsoni (no common name) observed by jrlynx on 2020-05-04 + + Observation 44922031 + + + + + [44922032] species: Delphinium nudicaule (Red larkspur) observed by jrlynx on 2020-05-04 + + Observation 44922032 + + + + + [44922036] species: Prosartes hookeri (Hooker's fairybells) observed by jrlynx on 2020-05-04 + + Observation 44922036 + + + + + [44922041] species: Pieris marginalis (Margined White) observed by jrlynx on 2020-05-04 + + Observation 44922041 + + + + + [44922042] species: Viola ocellata (western heart's ease) observed by jrlynx on 2020-05-04 + + Observation 44922042 + + + + + [44922049] species: Lysimachia latifolia (Western Star Flower) observed by jrlynx on 2020-05-04 + + Observation 44922049 + + + + + [44922050] genus: Stachys (Hedgenettles) observed by jrlynx on 2020-05-04 + + Observation 44922050 + + + + + [44922051] species: Iris fernaldii (Fernald's iris) observed by jrlynx on 2020-05-04 + + Observation 44922051 + + + + + [44922052] species: Thermopsis californica (California goldenbanner) observed by jrlynx on 2020-05-04 + + Observation 44922052 + + + + + [44922565] species: Rosa gymnocarpa (Baldhip Rose) observed by jrlynx on 2020-05-04 + + Observation 44922565 + + + + + [44944686] species: Collinsia heterophylla (Purple Chinese Houses) observed by tonypassantino on 2020-05-04 + + Observation 44944686 + + + + + [44975831] species: Calochortus amabilis (Diogenes' lantern) observed by ten_salamanders on 2020-05-03 + + Observation 44975831 + + + + + [44976167] species: Calochortus amabilis (Diogenes' lantern) observed by ten_salamanders on 2020-05-03 + + Observation 44976167 + + + + + [44976204] species: Clarkia gracilis (slender clarkia) observed by ten_salamanders on 2020-05-03 + + Observation 44976204 + + + + + [44976244] species: Fritillaria affinis (checker lily) observed by ten_salamanders on 2020-05-03 + + Observation 44976244 + + + + + [44976254] species: Clarkia gracilis (slender clarkia) observed by ten_salamanders on 2020-05-03 + + Observation 44976254 + + + + + [44976380] genus: Solanum (nightshades) observed by ten_salamanders on 2020-05-03 + + Observation 44976380 + + + + + [44976555] genus: Plagiobothrys (Popcorn Flowers) observed by ten_salamanders on 2020-05-03 + + Observation 44976555 + + + + + [44976585] subfamily: Bombyliinae (no common name) observed by ten_salamanders on 2020-05-03 + + Observation 44976585 + + + + + [44998642] species: Collinsia heterophylla (Purple Chinese Houses) observed by invictus13 on 2020-05-05 + + Observation 44998642 + + + + + [45003645] genus: Penstemon (beardtongues) observed by ten_salamanders on 2020-05-03 + + Observation 45003645 + + + + + [45003721] genus: Penstemon (beardtongues) observed by ten_salamanders on 2020-05-03 + + Observation 45003721 + + + + + [45039242] species: Equisetum telmateia (great horsetail) observed by woody54 on 2020-05-05 + + Observation 45039242 + + + + + [45041905] species: Elgaria multicarinata (Southern Alligator Lizard) observed by alfresco on 2020-05-05 + + Observation 45041905 + + + + + [45041920] species: Wyethia helenioides (Gray Mule-Ears) observed by alfresco on 2020-05-05 + + Observation 45041920 + + + + + [45042117] species: Thamnophis elegans (Western Terrestrial Garter Snake) observed by alfresco on 2020-05-05 + + Observation 45042117 + + + + + [45042209] species: Lathyrus cicera (Red Vetchling) observed by alfresco on 2020-05-05 + + Observation 45042209 + + + + + [45111288] species: Euphydryas chalcedona (Variable Checkerspot) observed by woody54 on 2020-05-06 + + Observation 45111288 + + + + + [45188491] species: Nerophilus californicus (no common name) observed by woody54 on 2020-05-07 + + Observation 45188491 + + + + + [45216787] species: Delphinium patens (Zigzag Larkspur) observed by sunflowerguy on 2020-05-07 + McCormick Addition, Headwaters Trail, Sugarloaf St pk + + Observation 45216787 + + + + + [45216886] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by sunflowerguy on 2020-05-07 + + Observation 45216886 + + + + + [45217072] species: Clarkia amoena (farewell-to-spring) observed by sunflowerguy on 2020-05-07 + + Observation 45217072 + + + + + [45217265] species: Eschscholzia caespitosa (Tufted Poppy) observed by sunflowerguy on 2020-05-07 + + Observation 45217265 + + + + + [45230449] species: Aesculus californica (California buckeye) observed by sunflowerguy on 2020-05-07 + + Observation 45230449 + + + + + [45230515] species: Iris fernaldii (Fernald's iris) observed by sunflowerguy on 2020-05-07 + + Observation 45230515 + + + + + [45270944] species: Sisyrinchium bellum (western blue-eyed grass) observed by conci on 2020-05-02 + + Observation 45270944 + + + + + [45271954] species: Lysimachia latifolia (Western Star Flower) observed by conci on 2020-05-02 + + Observation 45271954 + + + + + [45272158] species: Diplacus aurantiacus (orange bush monkeyflower) observed by conci on 2020-05-05 + + Observation 45272158 + + + + + [45272465] genus: Collinsia (Blue-eyed Marys) observed by conci on 2020-05-05 + + Observation 45272465 + + + + + [45288904] species: Adelpha californica (California Sister) observed by woody54 on 2020-05-08 + + Observation 45288904 + + + + + [45397854] order: Araneae (Spiders) observed by woody54 on 2020-05-08 + + Observation 45397854 + + + + + [45402790] genus: Clarkia (no common name) observed by llwagner on 2020-05-09 + + Observation 45402790 + + + + + [45437351] species: Dichelostemma congestum (Ookow) observed by woody54 on 2020-05-09 + + Observation 45437351 + + + + + [45438287] species: Wyethia angustifolia (narrowleaf mule-ears) observed by woody54 on 2020-05-09 + + Observation 45438287 + + + + + [45439299] species: Battus philenor (Pipevine Swallowtail) observed by woody54 on 2020-05-09 + + Observation 45439299 + + + + + [45489341] genus: Rubus (brambles) observed by woody54 on 2020-05-09 + + Observation 45489341 + + + + + [45540889] subphylum: Angiospermae (flowering plants) observed by caitlincornwall on 2020-05-10 + + Observation 45540889 + + + + + [45556055] species: Chlosyne palla (Northern Checkerspot) observed by woody54 on 2020-05-10 + + Observation 45556055 + + + + + [45556343] genus: Tetragnatha (no common name) observed by woody54 on 2020-05-10 + + Observation 45556343 + + + + + [45558003] species: Nasturtium officinale (watercress) observed by woody54 on 2020-05-10 + + Observation 45558003 + + + + + [45562429] species: Callipepla californica (California Quail) observed by patrick-mcbride on 2020-05-10 + + Observation 45562429 + + + + + [45564793] species: Collinsia heterophylla (Purple Chinese Houses) observed by nelruzam on 2020-05-04 + + + Observation 45564793 + + + + + [45595101] species: Thermopsis californica (California goldenbanner) observed by pinkhockey75 on 2020-05-10 + + Observation 45595101 + + + + + [45630027] species: Hypericum concinnum (goldwire) observed by woody54 on 2020-05-11 + + Observation 45630027 + + + + + [45646940] species: Euphydryas chalcedona (Variable Checkerspot) observed by woody54 on 2020-05-11 + + Observation 45646940 + + + + + [45646987] species: Eriodictyon californicum (California yerba santa) observed by woody54 on 2020-05-11 + + Observation 45646987 + + + + + [45647079] species: Pickeringia montana (chaparral pea) observed by woody54 on 2020-05-11 + + Observation 45647079 + + + + + [45647422] species: Collinsia heterophylla (Purple Chinese Houses) observed by woody54 on 2020-05-11 + + Observation 45647422 + + + + + [45647594] species: Solanum douglasii (greenspot nightshade) observed by woody54 on 2020-05-11 + + Observation 45647594 + + + + + [45647682] species: Lonicera hispidula (Pink Honeysuckle) observed by woody54 on 2020-05-11 + + Observation 45647682 + + + + + [45647739] species: Atypoides riversi (California Turret Spider) observed by woody54 on 2020-05-11 + + Observation 45647739 + + + + + [45651989] species: Clarkia gracilis (slender clarkia) observed by woody54 on 2020-05-11 + + Observation 45651989 + + + + + [45652052] species: Trichodes ornatus (Ornate Checkered Beetle) observed by woody54 on 2020-05-11 + + Observation 45652052 + + + + + [45652203] species: Aquilegia formosa (western columbine) observed by woody54 on 2020-05-11 + + Observation 45652203 + + + + + [45652307] species: Acmispon glaber (deerweed) observed by woody54 on 2020-05-11 + + Observation 45652307 + + + + + [45653696] genus: Phacelia (Scorpionweeds) observed by woody54 on 2020-05-11 + + Observation 45653696 + + + + + [45665308] species: Agoseris retrorsa (spearleaf agoseris) observed by woody54 on 2020-05-11 + + Observation 45665308 + + + + + [45677702] genus: Anisocarpus (no common name) observed by sslucas on 2020-05-11 + + Observation 45677702 + + + + + [45677751] species: Calochortus amabilis (Diogenes' lantern) observed by sslucas on 2020-05-11 + + Observation 45677751 + + + + + [45677769] species: Iris fernaldii (Fernald's iris) observed by sslucas on 2020-05-11 + + Observation 45677769 + + + + + [45677795] genus: Pentagramma (no common name) observed by sslucas on 2020-05-11 + + Observation 45677795 + + + + + [45677815] genus: Polypodium (polypody ferns) observed by sslucas on 2020-05-11 + + Observation 45677815 + + + + + [45677843] species: Fritillaria affinis (checker lily) observed by sslucas on 2020-05-11 + + Observation 45677843 + + + + + [45677859] species: Delphinium nudicaule (Red larkspur) observed by sslucas on 2020-05-11 + + Observation 45677859 + + + + + [45677878] genus: Calystegia (false bindweeds) observed by sslucas on 2020-05-11 + + Observation 45677878 + + + + + [45677903] species: Diplacus aurantiacus (orange bush monkeyflower) observed by sslucas on 2020-05-11 + + Observation 45677903 + + + + + [45677938] genus: Solanum (nightshades) observed by sslucas on 2020-05-11 + + Observation 45677938 + + + + + [45677973] genus: Phacelia (Scorpionweeds) observed by sslucas on 2020-05-11 + + Observation 45677973 + + + + + [45677993] genus: Solanum (nightshades) observed by sslucas on 2020-05-11 + + Observation 45677993 + + + + + [45678023] species: Adenostoma fasciculatum (chamise) observed by sslucas on 2020-05-11 + + Observation 45678023 + + + + + [45678039] genus: Collinsia (Blue-eyed Marys) observed by sslucas on 2020-05-11 + + Observation 45678039 + + + + + [45678056] subphylum: Angiospermae (flowering plants) observed by sslucas on 2020-05-11 + + Observation 45678056 + + + + + [45678109] genus: Hypericum (St. John's worts) observed by sslucas on 2020-05-11 + + Observation 45678109 + + + + + [45678154] genus: Arctostaphylos (bearberries and manzanitas) observed by sslucas on 2020-05-11 + + Observation 45678154 + + + + + [45678173] class: Magnoliopsida (dicots) observed by sslucas on 2020-05-11 + + Observation 45678173 + + + + + [45678210] class: Magnoliopsida (dicots) observed by sslucas on 2020-05-11 + + Observation 45678210 + + + + + [45678267] species: Eriodictyon californicum (California yerba santa) observed by sslucas on 2020-05-11 + + Observation 45678267 + + + + + [45678296] species: Euphydryas chalcedona (Variable Checkerspot) observed by sslucas on 2020-05-11 + + Observation 45678296 + + + + + [45691442] species: Lynx rufus (Bobcat) observed by dlevitis on 2019-02-20 + + Observation 45691442 + + + + + [45695651] species: Calystegia collina (Coast Range false bindweed) observed by woody54 on 2020-05-11 + + Observation 45695651 + + + + + [45794857] species: Castilleja foliolosa (Woolly Indian Paintbrush) observed by woody54 on 2020-05-11 + + Observation 45794857 + + + + + [45795698] species: Marmara arbutiella (Madrone Skin Miner) observed by woody54 on 2020-05-11 + + Observation 45795698 + + + + + [45827458] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by dlevitis on 2020-05-13 + + + Observation 45827458 + + + + + [45827459] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-05-13 + + + Observation 45827459 + + + + + [45827460] species: Calystegia collina (Coast Range false bindweed) observed by dlevitis on 2020-05-13 + + + Observation 45827460 + + + + + [45827465] species: Umbellularia californica (California bay) observed by dlevitis on 2020-05-13 + + + Observation 45827465 + + + + + [45827466] species: Phytophthora ramorum (Sudden oak death) observed by dlevitis on 2020-05-13 + + + Observation 45827466 + + + + + [45827468] genus: Sidalcea (Checkerblooms) observed by dlevitis on 2020-05-13 + + + Observation 45827468 + + + + + [45827478] subfamily: Cichorioideae (chicories, dandelions, and allies) observed by dlevitis on 2020-05-13 + + + Observation 45827478 + + + + + [45827513] genus: Umbilicaria (Rock Tripes) observed by dlevitis on 2020-05-13 + + + Observation 45827513 + + + + + [45827527] subtribe: Microseridinae (no common name) observed by dlevitis on 2020-05-13 + + + Observation 45827527 + + + + + [45827550] subspecies: Serradigitus gertschi striatus (no common name) observed by dlevitis on 2020-05-13 + + + Observation 45827550 + + + + + [45827559] genus: Quercus (oaks) observed by dlevitis on 2020-05-13 + + + Observation 45827559 + + + + + [45827571] family: Acrididae (Short-horned Grasshoppers) observed by dlevitis on 2020-05-13 + + + Observation 45827571 + + + + + [45827579] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-05-13 + + + Observation 45827579 + + + + + [45827593] genus: Umbilicaria (Rock Tripes) observed by dlevitis on 2020-05-13 + + + Observation 45827593 + + + + + [45827620] species: Ramalina menziesii (lace lichen) observed by dlevitis on 2020-05-13 + + + Observation 45827620 + + + + + [45827640] species: Scolopendra polymorpha (Common Desert Centipede) observed by dlevitis on 2020-05-13 + + + Observation 45827640 + + + + + [45827654] tribe: Cichorieae (no common name) observed by dlevitis on 2020-05-13 + + + Observation 45827654 + + + + + [45827659] order: Hymenoptera (Ants, Bees, Wasps, and Sawflies) observed by dlevitis on 2020-05-13 + On the underside of a rock in a meadow + + Observation 45827659 + + + + + [45827673] family: Lycosidae (Wolf Spiders) observed by dlevitis on 2020-05-13 + + + Observation 45827673 + + + + + [45827682] genus: Flavoparmelia (greenshield lichens) observed by dlevitis on 2020-05-13 + Looked much greener in person than in photos + + Observation 45827682 + + + + + [45827695] stateofmatter: Life (no common name) observed by dlevitis on 2020-05-13 + What causes this in Madrone, please? + + Observation 45827695 + + + + + [45827702] genus: Flavoparmelia (greenshield lichens) observed by dlevitis on 2020-05-13 + + + Observation 45827702 + + + + + [45827709] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-05-13 + + + Observation 45827709 + + + + + [45827726] genus: Eriophyllum (Woolly sunflowers) observed by dlevitis on 2020-05-13 + + + Observation 45827726 + + + + + [45827733] species: Scolopendra polymorpha (Common Desert Centipede) observed by dlevitis on 2020-05-13 + + + Observation 45827733 + + + + + [45827748] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-05-13 + What's the white fuzzy stuff on these leaves and buds, please? + + Observation 45827748 + + + + + [45827770] subspecies: Serradigitus gertschi striatus (no common name) observed by dlevitis on 2020-05-13 + + + Observation 45827770 + + + + + [45827793] genus: Ceanothus (no common name) observed by dlevitis on 2020-05-13 + + + Observation 45827793 + + + + + [45827802] species: Phytophthora ramorum (Sudden oak death) observed by dlevitis on 2020-05-13 + + + Observation 45827802 + + + + + [45827809] stateofmatter: Life (no common name) observed by dlevitis on 2020-05-13 + What causes these spots, please? + + Observation 45827809 + + + + + [45827814] class: Mammalia (Mammals) observed by dlevitis on 2020-05-13 + Scat full of fur and bits of bone. Some rodent bones, some fragments of larger bones. About 2cm diameter. + + Observation 45827814 + + + + + [45827822] species: Urocyon cinereoargenteus (Gray Fox) observed by dlevitis on 2020-05-13 + With my boot tip for scale. + + Observation 45827822 + + + + + [45827828] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-05-13 + + + Observation 45827828 + + + + + [45827839] species: Sialia mexicana (Western Bluebird) observed by dlevitis on 2020-05-13 + + + Observation 45827839 + + + + + [45827848] species: Batrachoseps attenuatus (California Slender Salamander) observed by dlevitis on 2020-05-13 + + + Observation 45827848 + + + + + [45827855] species: Uroctonus mordax (Western Forest Scorpion) observed by dlevitis on 2020-05-13 + + + Observation 45827855 + + + + + [45827871] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-05-13 + + + Observation 45827871 + + + + + [45827883] genus: Arctostaphylos (bearberries and manzanitas) observed by dlevitis on 2020-05-13 + + + Observation 45827883 + + + + + [45827905] species: Thamnophis atratus (Aquatic Garter Snake) observed by dlevitis on 2020-05-13 + + + Observation 45827905 + + + + + [45827915] order: Embiidina (Webspinners) observed by dlevitis on 2020-05-13 + I could see them scooting through their tubes when I lifted the rock, but in the rain could not get a picture of one. + + Observation 45827915 + + + + + [45827928] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-05-13 + + + Observation 45827928 + + + + + [45827939] family: Spirobolidae (Spirobolid Millipedes) observed by dlevitis on 2020-05-13 + + + Observation 45827939 + + + + + [45827946] genus: Iris (Irises) observed by dlevitis on 2020-05-13 + + + Observation 45827946 + + + + + [45848751] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-05-13 + + + Observation 45848751 + + + + + [45886291] species: Poecile rufescens (Chestnut-backed Chickadee) observed by woody54 on 2020-05-14 + + Observation 45886291 + + + + + [45888302] species: Ariolimax buttoni (Button's Banana Slug) observed by redwoodriverbear on 2020-05-13 + + Observation 45888302 + + + + + [45921694] species: Euphydryas chalcedona (Variable Checkerspot) observed by sunflowerguy on 2020-05-14 + + Observation 45921694 + + + + + [45924147] species: Adenostoma fasciculatum (chamise) observed by sunflowerguy on 2020-05-14 + + Observation 45924147 + + + + + [45924177] genus: Selaginella (spikemosses) observed by sunflowerguy on 2020-05-14 + + Observation 45924177 + + + + + [45924250] species: Clarkia amoena (farewell-to-spring) observed by sunflowerguy on 2020-05-14 + + Observation 45924250 + + + + + [45924280] species: Diplacus aurantiacus (orange bush monkeyflower) observed by sunflowerguy on 2020-05-14 + + Observation 45924280 + + + + + [45924384] species: Eriophyllum lanatum (common woolly sunflower) observed by sunflowerguy on 2020-05-14 + + Observation 45924384 + + + + + [45924527] species: Silene laciniata (cardinal catchfly) observed by sunflowerguy on 2020-05-14 + + Observation 45924527 + + + + + [45924595] species: Wyethia angustifolia (narrowleaf mule-ears) observed by sunflowerguy on 2020-05-14 + + Observation 45924595 + + + + + [45924650] species: Pentagramma triangularis (goldback fern) observed by sunflowerguy on 2020-05-14 + + Observation 45924650 + + + + + [45924830] species: Calochortus amabilis (Diogenes' lantern) observed by sunflowerguy on 2020-05-14 + + Observation 45924830 + + + + + [45924865] genus: Xanthoparmelia (rock shield lichens) observed by sunflowerguy on 2020-05-14 + + Observation 45924865 + + + + + [45925038] class: Magnoliopsida (dicots) observed by sunflowerguy on 2020-05-14 + + Observation 45925038 + + + + + [45925208] species: Chorizanthe membranacea (pink spineflower) observed by sunflowerguy on 2020-05-14 + + Observation 45925208 + + + + + [45988066] species: Silene laciniata (cardinal catchfly) observed by rumney007 on 2020-05-15 + + Observation 45988066 + + + + + [45994373] species: Iris purdyi (Purdy's Iris) observed by mimichan on 2020-05-15 + + Observation 45994373 + + + + + [45994802] species: Lonicera hispidula (Pink Honeysuckle) observed by mimichan on 2020-05-15 + + Observation 45994802 + + + + + [45994940] species: Wyethia angustifolia (narrowleaf mule-ears) observed by mimichan on 2020-05-15 + + Observation 45994940 + + + + + [46002082] species: Triteleia laxa (Ithuriel's Spear) observed by mimichan on 2020-05-15 + + Observation 46002082 + + + + + [46002833] species: Thermopsis californica (California goldenbanner) observed by mimichan on 2020-05-15 + + Observation 46002833 + + + + + [46002854] species: Delphinium nudicaule (Red larkspur) observed by mimichan on 2020-05-15 + + Observation 46002854 + + + + + [46002897] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by mtamm on 2020-05-15 + + Observation 46002897 + + + + + [46002909] species: Clarkia amoena (farewell-to-spring) observed by mimichan on 2020-05-15 + + Observation 46002909 + + + + + [46011980] species: Polygonia satyrus (Satyr Comma) observed by woody54 on 2020-05-15 + + Observation 46011980 + + + + + [46013829] species: Dudleya cymosa (Canyon Live-forever) observed by mimichan on 2020-05-15 + + Observation 46013829 + + + + + [46013911] species: Helenium puberulum (Rosilla) observed by mimichan on 2020-05-15 + + Observation 46013911 + + + + + [46013946] genus: Penstemon (beardtongues) observed by mimichan on 2020-05-15 + + Observation 46013946 + + + + + [46021065] species: Nymphalis californica (California Tortoiseshell) observed by woody54 on 2020-05-15 + + Observation 46021065 + + + + + [46050969] tribe: Sedeae (no common name) observed by mtamm on 2020-05-15 + + Observation 46050969 + + + + + [46051213] species: Clarkia amoena (farewell-to-spring) observed by mtamm on 2020-05-15 + + Observation 46051213 + + + + + [46051279] species: Erythranthe guttata (seep monkeyflower) observed by mtamm on 2020-05-15 + + Observation 46051279 + + + + + [46051321] species: Triteleia laxa (Ithuriel's Spear) observed by mtamm on 2020-05-15 + + Observation 46051321 + + + + + [46051365] species: Iris fernaldii (Fernald's iris) observed by mtamm on 2020-05-15 + + Observation 46051365 + + + + + [46142896] species: Clarkia amoena (farewell-to-spring) observed by tomlstedman on 2020-05-16 + + Observation 46142896 + + + + + [46159297] species: Clarkia concinna (Red Ribbons) observed by scroopynoopers on 2020-05-16 + + Observation 46159297 + + + + + [46166671] subtribe: Microseridinae (no common name) observed by mylan on 2020-05-16 + + Observation 46166671 + + + + + [46166705] genus: Chorizanthe (spineflowers) observed by mylan on 2020-05-16 + + Observation 46166705 + + + + + [46168630] species: Silene laciniata (cardinal catchfly) observed by mylan on 2020-05-16 + + Observation 46168630 + + + + + [46168680] species: Dasymutilla aureola (Pacific Velvet Ant) observed by mylan on 2020-05-16 + + Observation 46168680 + + + + + [46168696] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by mylan on 2020-05-16 + + Observation 46168696 + + + + + [46170292] species: Clarkia gracilis (slender clarkia) observed by tomlstedman on 2020-05-16 + + Observation 46170292 + + + + + [46170338] species: Coluber constrictor (North American Racer) observed by tomlstedman on 2020-05-16 + + Observation 46170338 + + + + + [46185805] species: Plagiobothrys nothofulvus (Rusty Popcornflower) observed by morganmomsen on 2020-05-16 + + Observation 46185805 + + + + + [46186135] species: Euphydryas chalcedona (Variable Checkerspot) observed by morganmomsen on 2020-05-16 + + Observation 46186135 + + + + + [46192379] species: Collinsia heterophylla (Purple Chinese Houses) observed by scroopynoopers on 2020-05-16 + + Observation 46192379 + + + + + [46192447] species: Erysimum capitatum (western wallflower) observed by scroopynoopers on 2020-05-16 + + Observation 46192447 + + + + + [46262966] genus: Penstemon (beardtongues) observed by morganmomsen on 2020-05-16 + + Observation 46262966 + + + + + [46263380] species: Erythranthe guttata (seep monkeyflower) observed by morganmomsen on 2020-05-16 + + Observation 46263380 + + + + + [46263739] species: Clarkia rubicunda (ruby chalice clarkia) observed by morganmomsen on 2020-05-16 + + Observation 46263739 + + + + + [46264170] species: Iris fernaldii (Fernald's iris) observed by morganmomsen on 2020-05-16 + + Observation 46264170 + + + + + [46264527] genus: Lupinus (lupines) observed by morganmomsen on 2020-05-16 + + Observation 46264527 + + + + + [46293020] species: Sidalcea diploscypha (fringed checkerbloom) observed by woody54 on 2020-05-17 + + Observation 46293020 + + + + + [46295117] species: Euphydryas chalcedona (Variable Checkerspot) observed by rumney007 on 2020-05-17 + + Observation 46295117 + + + + + [46295242] species: Erythranthe guttata (seep monkeyflower) observed by rumney007 on 2020-05-17 + + Observation 46295242 + + + + + [46295362] species: Clematis lasiantha (Pipestem Clematis) observed by rumney007 on 2020-05-17 + + Observation 46295362 + + + + + [46295444] species: Diplacus aurantiacus (orange bush monkeyflower) observed by rumney007 on 2020-05-17 + + Observation 46295444 + + + + + [46308551] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by rumney007 on 2020-05-17 + + Observation 46308551 + + + + + [46309761] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by rumney007 on 2020-05-17 + + Observation 46309761 + + + + + [46314110] species: Coenonympha tullia (Common Ringlet) observed by morganmomsen on 2020-05-16 + + Observation 46314110 + + + + + [46314891] species: Calochortus amabilis (Diogenes' lantern) observed by morganmomsen on 2020-05-16 + + Observation 46314891 + + + + + [46315474] genus: Calystegia (false bindweeds) observed by morganmomsen on 2020-05-16 + + Observation 46315474 + + + + + [46315999] genus: Solanum (nightshades) observed by morganmomsen on 2020-05-16 + + Observation 46315999 + + + + + [46316398] species: Malacosoma constricta (Pacific Tent Caterpillar) observed by morganmomsen on 2020-05-16 + + Observation 46316398 + + + + + [46316666] species: Lysimachia latifolia (Western Star Flower) observed by morganmomsen on 2020-05-16 + + Observation 46316666 + + + + + [46316957] species: Maianthemum racemosum (false Solomon's seal) observed by morganmomsen on 2020-05-16 + + Observation 46316957 + + + + + [46320737] species: Clarkia amoena (farewell-to-spring) observed by woody54 on 2020-05-17 + + Observation 46320737 + + + + + [46518011] family: Gomphidae (Clubtails) observed by sunflowerguy on 2020-05-19 + Sugarloaf park + + Observation 46518011 + + + + + [46532106] kingdom: Fungi (Fungi Including Lichens) observed by kayaker on 2020-01-26 + + Observation 46532106 + + + + + [46546594] species: Carex pendula (Hanging sedge) observed by sunflowerguy on 2020-05-19 + + Observation 46546594 + + + + + [46546681] species: Wyethia angustifolia (narrowleaf mule-ears) observed by sunflowerguy on 2020-05-19 + + Observation 46546681 + + + + + [46546925] genus: Gammarotettix (no common name) observed by sunflowerguy on 2020-05-19 + + Observation 46546925 + + + + + [46555772] family: Syrphidae (Hover Flies) observed by woody54 on 2020-05-19 + + Observation 46555772 + + + + + [46555942] species: Coccinella septempunctata (Seven-spotted Lady Beetle) observed by woody54 on 2020-05-19 + + Observation 46555942 + + + + + [46558436] species: Acmispon glaber (deerweed) observed by sunflowerguy on 2020-05-19 + + Observation 46558436 + + + + + [46634195] species: Toxicodendron diversilobum (Pacific poison oak) observed by wa-hayes on 2020-04-18 + in florescence + + Observation 46634195 + + + + + [46634421] genus: Iris (Irises) observed by wa-hayes on 2020-04-18 + + Observation 46634421 + + + + + [46634655] species: Heteromeles arbutifolia (Toyon) observed by wa-hayes on 2020-04-18 + + Observation 46634655 + + + + + [46634805] species: Delphinium nudicaule (Red larkspur) observed by wa-hayes on 2020-04-18 + + Observation 46634805 + + + + + [46635328] genus: Lupinus (lupines) observed by wa-hayes on 2020-04-18 + lupines? + + Observation 46635328 + + + + + [46640062] species: Toxicoscordion venenosum (Meadow Deathcamas) observed by wa-hayes on 2020-04-18 + Star... check sp + + Observation 46640062 + + + + + [46645663] genus: Clarkia (no common name) observed by woody54 on 2020-05-20 + + Observation 46645663 + + + + + [46645805] species: Gilia capitata (bluehead gilia) observed by woody54 on 2020-05-20 + + Observation 46645805 + + + + + [46647497] genus: Phacelia (Scorpionweeds) observed by woody54 on 2020-05-20 + + Observation 46647497 + + + + + [46647781] order: Coleoptera (Beetles) observed by wa-hayes on 2020-04-18 + + Observation 46647781 + + + + + [46648428] species: Silene coronaria (Rose campion) observed by woody54 on 2020-05-20 + + Observation 46648428 + + + + + [46653542] genus: Delphinium (larkspurs) observed by woody54 on 2020-05-20 + + Observation 46653542 + + + + + [46662367] class: Magnoliopsida (dicots) observed by wa-hayes on 2020-04-18 + fuzzy leaves + + Observation 46662367 + + + + + [46682480] species: Cyperus eragrostis (tall flatsedge) observed by sunflowerguy on 2020-05-19 + + Observation 46682480 + + + + + [46682495] species: Triteleia laxa (Ithuriel's Spear) observed by sunflowerguy on 2020-05-19 + + Observation 46682495 + + + + + [46682661] species: Triteleia laxa (Ithuriel's Spear) observed by woody54 on 2020-05-20 + + Observation 46682661 + + + + + [46683217] species: Rosa rubiginosa (Sweet-brier) observed by woody54 on 2020-05-20 + + Observation 46683217 + + + + + [46683945] species: Tragopogon dubius (yellow salsify) observed by woody54 on 2020-05-20 + + Observation 46683945 + + + + + [46751005] genus: Hypericum (St. John's worts) observed by wtstacy on 2020-05-21 + + Observation 46751005 + + + + + [46755096] species: Solanum xanti (purple nightshade) observed by wtstacy on 2020-05-21 + + Observation 46755096 + + + + + [46759161] species: Fritillaria affinis (checker lily) observed by wa-hayes on 2020-04-18 + Mission lily...lookup sp + + Observation 46759161 + + + + + [46797300] species: Diabrotica undecimpunctata (Spotted Cucumber Beetle) observed by woody54 on 2020-05-20 + + Observation 46797300 + + + + + [46797505] order: Diptera (Flies) observed by woody54 on 2020-05-19 + + Observation 46797505 + + + + + [46803881] species: Eriophyllum lanatum (common woolly sunflower) observed by woody54 on 2020-05-21 + + Observation 46803881 + + + + + [46854846] species: Malacosoma disstria (Forest Tent Caterpillar Moth) observed by woody54 on 2020-05-22 + + Observation 46854846 + + + + + [46888385] species: Lepus californicus (Black-tailed Jackrabbit) observed by dlevitis on 2020-05-22 + + + Observation 46888385 + + + + + [46888386] genus: Bovista (True Puffballs) observed by dlevitis on 2020-05-22 + + + Observation 46888386 + + + + + [46888387] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-05-22 + + + Observation 46888387 + + + + + [46888392] species: Hypochaeris radicata (Common Cat's-ear) observed by dlevitis on 2020-05-22 + + + Observation 46888392 + + + + + [46888393] species: Phytophthora ramorum (Sudden oak death) observed by dlevitis on 2020-05-22 + + + Observation 46888393 + + + + + [46888398] genus: Chalcosyrphus (Leafwalkers) observed by dlevitis on 2020-05-22 + + + Observation 46888398 + + + + + [46888400] genus: Carex (true sedges) observed by dlevitis on 2020-05-22 + + + Observation 46888400 + + + + + [46888414] tribe: Coccinellini (Black-spotted Lady Beetles) observed by dlevitis on 2020-05-22 + + + Observation 46888414 + + + + + [46888415] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-05-22 + + + Observation 46888415 + + + + + [46888425] species: Umbellularia californica (California bay) observed by dlevitis on 2020-05-22 + + + Observation 46888425 + + + + + [46888430] genus: Scirpus (club-rushes and bulrushes) observed by dlevitis on 2020-05-22 + + + Observation 46888430 + + + + + [46888433] species: Nerophilus californicus (no common name) observed by dlevitis on 2020-05-22 + + + Observation 46888433 + + + + + [46888436] species: Anas platyrhynchos (Mallard) observed by dlevitis on 2020-05-22 + + + Observation 46888436 + + + + + [46888441] species: Maianthemum stellatum (star-flowered lily-of-the-valley) observed by dlevitis on 2020-05-22 + + + Observation 46888441 + + + + + [46888442] species: Sanicula crassicaulis (Pacific Sanicle) observed by dlevitis on 2020-05-22 + + + Observation 46888442 + + + + + [46888451] genus: Argia (Dancers) observed by dlevitis on 2020-05-22 + + + Observation 46888451 + + + + + [46888457] genus: Pristoceuthophilus (no common name) observed by dlevitis on 2020-05-22 + + + Observation 46888457 + + + + + [46888466] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-05-22 + + + Observation 46888466 + + + + + [46888472] subfamily: Dryopteridoideae (no common name) observed by dlevitis on 2020-05-22 + + + Observation 46888472 + + + + + [46888480] species: Eupeodes fumipennis (Western Aphideater) observed by dlevitis on 2020-05-22 + + + Observation 46888480 + + + + + [46888500] stateofmatter: Life (no common name) observed by dlevitis on 2020-05-22 + What causes burls in bigleaf maple, please? + + Observation 46888500 + + + + + [46888517] stateofmatter: Life (no common name) observed by dlevitis on 2020-05-22 + What causes this type of spotting and leaf deformity in California Bay, please? + + Observation 46888517 + + + + + [46888547] species: Maianthemum stellatum (star-flowered lily-of-the-valley) observed by dlevitis on 2020-05-22 + + + Observation 46888547 + + + + + [46888579] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-05-22 + + + Observation 46888579 + + + + + [46888590] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-05-22 + + + Observation 46888590 + + + + + [46888608] species: Uroctonus mordax (Western Forest Scorpion) observed by dlevitis on 2020-05-22 + + + Observation 46888608 + + + + + [46888620] family: Cercopidae (Froghoppers) observed by dlevitis on 2020-05-22 + + + Observation 46888620 + + + + + [46888624] superfamily: Tipuloidea (Typical Crane Flies) observed by dlevitis on 2020-05-22 + + + Observation 46888624 + + + + + [46888639] genus: Stachys (Hedgenettles) observed by dlevitis on 2020-05-22 + + + Observation 46888639 + + + + + [46888657] genus: Nephrotoma (Tiger Crane Flies) observed by dlevitis on 2020-05-22 + + + Observation 46888657 + + + + + [46888671] species: Ariolimax buttoni (Button's Banana Slug) observed by dlevitis on 2020-05-22 + + + Observation 46888671 + + + + + [46888690] species: Nemophila heterophylla (White nemophila) observed by dlevitis on 2020-05-22 + + + Observation 46888690 + + + + + [46888734] genus: Scolopocryptops (no common name) observed by dlevitis on 2020-05-22 + + + Observation 46888734 + + + + + [46888756] species: Ariolimax buttoni (Button's Banana Slug) observed by dlevitis on 2020-05-22 + + + Observation 46888756 + + + + + [46888772] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-05-22 + + + Observation 46888772 + + + + + [46888778] family: Lithobiidae (no common name) observed by dlevitis on 2020-05-22 + + + Observation 46888778 + + + + + [46888791] species: Urtica dioica (stinging nettle) observed by dlevitis on 2020-05-22 + + + Observation 46888791 + + + + + [46888803] species: Melozone crissalis (California Towhee) observed by dlevitis on 2020-05-22 + + + Observation 46888803 + + + + + [46888814] subfamily: Cantharinae (no common name) observed by dlevitis on 2020-05-22 + + + Observation 46888814 + + + + + [46888836] species: Prenolepis imparis (Small Honey Ant) observed by dlevitis on 2020-05-22 + + + Observation 46888836 + + + + + [46888855] species: Malacosoma disstria (Forest Tent Caterpillar Moth) observed by dlevitis on 2020-05-22 + + + Observation 46888855 + + + + + [46888868] stateofmatter: Life (no common name) observed by dlevitis on 2020-05-22 + Loss of pigmentation in a splotch of bay leaf, caused by? + + Observation 46888868 + + + + + [46888874] species: Umbellularia californica (California bay) observed by dlevitis on 2020-05-22 + + + Observation 46888874 + + + + + [46888884] species: Eriophyes laevis (Alder Leaf Gall Mite) observed by dlevitis on 2020-05-22 + On Alnus rhombifolia + + Observation 46888884 + + + + + [46888895] species: Nerophilus californicus (no common name) observed by dlevitis on 2020-05-22 + + + Observation 46888895 + + + + + [46889780] species: Dudleya cymosa (Canyon Live-forever) observed by sunflowerguy on 2020-05-22 + Bald Mtn trail- Sugarloaf St pk + + Observation 46889780 + + + + + [46889938] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by sunflowerguy on 2020-05-22 + + Observation 46889938 + + + + + [46908783] species: Malacothrix saxatilis (Cliff Aster) observed by ryderbird on 2020-05-22 + + Observation 46908783 + + + + + [46917726] species: Asclepias cordifolia (heart-leaf milkweed) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46917726 + + + + + [46918049] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by jenopus on 2020-05-22 + + Observation 46918049 + + + + + [46918167] species: Lathyrus cicera (Red Vetchling) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46918167 + + + + + [46918246] species: Vicia gigantea (giant vetch) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46918246 + + + + + [46918302] species: Chlosyne palla (Northern Checkerspot) observed by jenopus on 2020-05-22 + + Observation 46918302 + + + + + [46918349] species: Euphydryas chalcedona (Variable Checkerspot) observed by jenopus on 2020-05-22 + + Observation 46918349 + + + + + [46918421] species: Pseudognaphalium californicum (California cudweed) observed by jenopus on 2020-05-22 + + Observation 46918421 + + + + + [46918763] species: Hypericum concinnum (goldwire) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46918763 + + + + + [46918873] species: Castilleja foliolosa (Woolly Indian Paintbrush) observed by jenopus on 2020-05-22 + + Observation 46918873 + + + + + [46918997] species: Fritillaria affinis (checker lily) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46918997 + + + + + [46919100] species: Icaricia acmon (Acmon Blue) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46919100 + + + + + [46919143] species: Acmispon glaber (deerweed) observed by jenopus on 2020-05-22 + + Observation 46919143 + + + + + [46919287] species: Adenostoma fasciculatum (chamise) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46919287 + + + + + [46919341] species: Lonicera hispidula (Pink Honeysuckle) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46919341 + + + + + [46919437] species: Solanum xanti (purple nightshade) observed by jenopus on 2020-05-22 + Unusual white color? + + Observation 46919437 + + + + + [46919484] species: Solanum xanti (purple nightshade) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46919484 + + + + + [46919552] species: Delphinium nudicaule (Red larkspur) observed by jenopus on 2020-05-22 + Sugarloaf + + Observation 46919552 + + + + + [46919637] subfamily: Asteroideae (no common name) observed by jenopus on 2020-05-22 + + Observation 46919637 + + + + + [46919700] species: Clarkia purpurea (Winecup Clarkia) observed by jenopus on 2020-05-22 + + Observation 46919700 + + + + + [46919773] species: Clarkia purpurea (Winecup Clarkia) observed by jenopus on 2020-05-22 + + Observation 46919773 + + + + + [46972900] genus: Phacelia (Scorpionweeds) observed by sunflowerguy on 2020-05-22 + + Observation 46972900 + + + + + [47045157] species: Iris foetidissima (Stinking iris) observed by woody54 on 2020-05-23 + + Observation 47045157 + + + + + [47045525] genus: Scirpus (club-rushes and bulrushes) observed by woody54 on 2020-05-23 + + Observation 47045525 + + + + + [47074222] species: Pituophis catenifer (Gopher Snake) observed by woody54 on 2020-05-21 + + Observation 47074222 + + + + + [47074230] species: Iris foetidissima (Stinking iris) observed by woody54 on 2020-05-23 + + Observation 47074230 + + + + + [47121791] species: Eriodictyon californicum (California yerba santa) observed by kentcorley on 2020-02-09 + + Observation 47121791 + + + + + [47128132] species: Silene laciniata (cardinal catchfly) observed by giantsguy on 2020-05-18 + + Observation 47128132 + + + + + [47128171] species: Aralia californica (California Spikenard) observed by giantsguy on 2020-05-23 + + Observation 47128171 + + + + + [47186987] species: Plestiodon skiltonianus (Western Skink) observed by marcpauldiaz on 2020-05-24 + + Observation 47186987 + + + + + [47197783] species: Calochortus amabilis (Diogenes' lantern) observed by queeringecology on 2020-05-24 + + Observation 47197783 + + + + + [47197870] species: Prosartes hookeri (Hooker's fairybells) observed by queeringecology on 2020-05-24 + + Observation 47197870 + + + + + [47197955] species: Lysimachia latifolia (Western Star Flower) observed by queeringecology on 2020-05-24 + + Observation 47197955 + + + + + [47198087] species: Delphinium nudicaule (Red larkspur) observed by queeringecology on 2020-05-24 + + Observation 47198087 + + + + + [47198204] species: Amsinckia menziesii (Common Fiddleneck) observed by queeringecology on 2020-05-24 + + Observation 47198204 + + + + + [47198256] species: Dichelostemma congestum (Ookow) observed by queeringecology on 2020-05-24 + + Observation 47198256 + + + + + [47198368] species: Iris hartwegii (rainbow iris) observed by queeringecology on 2020-05-24 + + Observation 47198368 + + + + + [47198508] species: Clarkia purpurea (Winecup Clarkia) observed by queeringecology on 2020-05-24 + + Observation 47198508 + + + + + [47198625] species: Adiantum jordanii (California Maidenhair Fern) observed by queeringecology on 2020-05-24 + + Observation 47198625 + + + + + [47198798] species: Clematis lasiantha (Pipestem Clematis) observed by queeringecology on 2020-05-24 + + Observation 47198798 + + + + + [47198879] species: Anisocarpus madioides (woodland madia) observed by queeringecology on 2020-05-24 + + Observation 47198879 + + + + + [47198957] species: Penstemon laetus (Mountain Blue Penstemon) observed by queeringecology on 2020-05-24 + + Observation 47198957 + + + + + [47204964] species: Clarkia concinna (Red Ribbons) observed by woody54 on 2020-05-24 + + Observation 47204964 + + + + + [47205792] infraorder: Systellognatha (no common name) observed by woody54 on 2020-05-24 + + Observation 47205792 + + + + + [47212361] species: Lepus californicus (Black-tailed Jackrabbit) observed by alfresco on 2020-05-24 + + Observation 47212361 + + + + + [47212387] species: Clarkia amoena (farewell-to-spring) observed by alfresco on 2020-05-24 + + Observation 47212387 + + + + + [47212432] species: Malacosoma disstria (Forest Tent Caterpillar Moth) observed by alfresco on 2020-05-24 + + Observation 47212432 + + + + + [47273621] species: Phacelia imbricata (Mountain Phacelia) observed by queeringecology on 2020-05-24 + + Observation 47273621 + + + + + [47274104] species: Pellaea andromedifolia (Coffee Fern) observed by queeringecology on 2020-05-24 + + Observation 47274104 + + + + + [47274165] species: Maianthemum racemosum (false Solomon's seal) observed by queeringecology on 2020-05-24 + + Observation 47274165 + + + + + [47281514] species: Meleagris gallopavo (Wild Turkey) observed by barbee on 2020-05-22 + + Observation 47281514 + + + + + [47284882] class: Magnoliopsida (dicots) observed by barbee on 2020-05-22 + + Observation 47284882 + + + + + [47289103] species: Euphydryas chalcedona (Variable Checkerspot) observed by crystelhadley on 2020-05-24 + + Observation 47289103 + + + + + [47289910] species: Papilio eurymedon (Pale Swallowtail) observed by crystelhadley on 2020-05-24 + + Observation 47289910 + + + + + [47322268] species: Triteleia laxa (Ithuriel's Spear) observed by enhunn323 on 2020-05-25 + + Observation 47322268 + + + + + [47322380] species: Collinsia heterophylla (Purple Chinese Houses) observed by enhunn323 on 2020-05-25 + + Observation 47322380 + + + + + [47322417] species: Nymphalis californica (California Tortoiseshell) observed by enhunn323 on 2020-05-25 + + Observation 47322417 + + + + + [47322534] genus: Calystegia (false bindweeds) observed by enhunn323 on 2020-05-25 + + Observation 47322534 + + + + + [47322573] species: Clarkia amoena (farewell-to-spring) observed by enhunn323 on 2020-05-25 + + Observation 47322573 + + + + + [47359141] species: Aquilegia formosa (western columbine) observed by tonypassantino on 2020-05-24 + + Observation 47359141 + + + + + [47359746] genus: Penstemon (beardtongues) observed by woody54 on 2020-05-25 + + Observation 47359746 + + + + + [47373819] species: Euphydryas chalcedona (Variable Checkerspot) observed by ravengirl1220 on 2020-05-23 + + Observation 47373819 + + + + + [47375562] family: Hypericaceae (St. John's wort family) observed by ravengirl1220 on 2020-05-23 + + Observation 47375562 + + + + + [47375572] order: Boletales (boletes and allies) observed by ravengirl1220 on 2020-05-23 + + Observation 47375572 + + + + + [47477403] species: Polystichum californicum (California shield fern) observed by jackroney on 2020-05-25 + + Observation 47477403 + + + + + [47477532] species: Cordulegaster dorsalis (Pacific Spiketail) observed by jackroney on 2020-05-25 + + Observation 47477532 + + + + + [47477556] species: Boisea rubrolineata (Western Boxelder Bug) observed by jackroney on 2020-05-25 + + Observation 47477556 + + + + + [47477674] species: Clarkia concinna (Red Ribbons) observed by jackroney on 2020-05-25 + + Observation 47477674 + + + + + [47477745] species: Fritillaria affinis (checker lily) observed by jackroney on 2020-05-25 + + Observation 47477745 + + + + + [47477804] species: Ochlodes agricola (Rural Skipper) observed by jackroney on 2020-05-25 + + Observation 47477804 + + + + + [47477880] species: Cordulegaster dorsalis (Pacific Spiketail) observed by jackroney on 2020-05-25 + + Observation 47477880 + + + + + [47477909] species: Collinsia heterophylla (Purple Chinese Houses) observed by jackroney on 2020-05-25 + + Observation 47477909 + + + + + [47478046] infraorder: Aculeata (Ants, Bees, and Stinging Wasps) observed by jackroney on 2020-05-25 + + Observation 47478046 + + + + + [47478340] genus: Vicia (Vetches) observed by woody54 on 2020-05-26 + + Observation 47478340 + + + + + [47511400] species: Lepus californicus (Black-tailed Jackrabbit) observed by woody54 on 2020-05-27 + + Observation 47511400 + + + + + [47511894] species: Silene laciniata (cardinal catchfly) observed by woody54 on 2020-05-26 + + Observation 47511894 + + + + + [47545961] species: Collinsia heterophylla (Purple Chinese Houses) observed by asharron on 2020-05-22 + + Observation 47545961 + + + + + [47546203] species: Chlosyne palla (Northern Checkerspot) observed by asharron on 2020-05-22 + + Observation 47546203 + + + + + [47648839] species: Monardella villosa (Coyote Mint) observed by invictus13 on 2020-05-28 + + Observation 47648839 + + + + + [47649170] genus: Hypericum (St. John's worts) observed by invictus13 on 2020-05-28 + + Observation 47649170 + + + + + [47649219] species: Clarkia purpurea (Winecup Clarkia) observed by invictus13 on 2020-05-28 + + Observation 47649219 + + + + + [47649633] subspecies: Diadophis punctatus amabilis (Pacific Ringneck Snake) observed by invictus13 on 2020-05-28 + + Observation 47649633 + + + + + [47652427] species: Dudleya cymosa (Canyon Live-forever) observed by tomlstedman on 2020-05-28 + + Observation 47652427 + + + + + [47652948] species: Acmispon glaber (deerweed) observed by tomlstedman on 2020-05-28 + + Observation 47652948 + + + + + [47653076] species: Clarkia amoena (farewell-to-spring) observed by tomlstedman on 2020-05-28 + + Observation 47653076 + + + + + [47679885] species: Erythranthe guttata (seep monkeyflower) observed by clong1 on 2020-05-28 + + Observation 47679885 + + + + + [47679907] genus: Heuchera (alumroots) observed by clong1 on 2020-05-28 + + Observation 47679907 + + + + + [47679965] species: Viola ocellata (western heart's ease) observed by clong1 on 2020-05-28 + + Observation 47679965 + + + + + [47680001] species: Aquilegia formosa (western columbine) observed by clong1 on 2020-05-28 + + Observation 47680001 + + + + + [47680078] species: Rhinotropis californica (California milkwort) observed by clong1 on 2020-05-28 + + Observation 47680078 + + + + + [47680187] genus: Myosotis (Forget-me-nots) observed by clong1 on 2020-05-28 + + Observation 47680187 + + + + + [47680840] species: Collinsia sparsiflora (fewflower blue-eyed Mary) observed by sunflowerguy on 2020-05-28 + + Observation 47680840 + + + + + [47680889] species: Clarkia amoena (farewell-to-spring) observed by sunflowerguy on 2020-05-28 + + Observation 47680889 + + + + + [47680985] species: Delphinium nudicaule (Red larkspur) observed by sunflowerguy on 2020-05-28 + + Observation 47680985 + + + + + [47681017] species: Calochortus luteus (Yellow Mariposa Lily) observed by sunflowerguy on 2020-05-28 + + Observation 47681017 + + + + + [47681083] species: Fritillaria affinis (checker lily) observed by sunflowerguy on 2020-05-28 + + Observation 47681083 + + + + + [47697439] species: Phalaris aquatica (harding grass) observed by sunflowerguy on 2020-05-28 + + Observation 47697439 + + + + + [47697643] species: Eriodictyon californicum (California yerba santa) observed by sunflowerguy on 2020-05-28 + Brushy Peaks Trail + + Observation 47697643 + + + + + [47787312] species: Thamnophis atratus (Aquatic Garter Snake) observed by jackroney on 2020-05-25 + + Observation 47787312 + + + + + [47787394] species: Chlosyne palla (Northern Checkerspot) observed by jackroney on 2020-05-25 + + Observation 47787394 + + + + + [47787636] species: Hippodamia convergens (Convergent Lady Beetle) observed by jackroney on 2020-05-25 + + Observation 47787636 + + + + + [47787735] species: Ochlodes agricola (Rural Skipper) observed by jackroney on 2020-05-25 + + Observation 47787735 + + + + + [47787823] species: Erythranthe guttata (seep monkeyflower) observed by jackroney on 2020-05-25 + + Observation 47787823 + + + + + [47787996] species: Celastrina echo (Echo Azure) observed by jackroney on 2020-05-25 + + Observation 47787996 + + + + + [47788106] species: Octogomphus specularis (Grappletail) observed by jackroney on 2020-05-25 + + Observation 47788106 + + + + + [47788206] species: Argia vivida (Vivid Dancer) observed by jackroney on 2020-05-25 + + Observation 47788206 + + + + + [47788331] suborder: Anisoptera (Dragonflies) observed by jackroney on 2020-05-25 + + Observation 47788331 + + + + + [47788386] genus: Cottus (Freshwater Sculpins) observed by jackroney on 2020-05-25 + + Observation 47788386 + + + + + [47788441] species: Phymatodes aeneus (no common name) observed by jackroney on 2020-05-25 + + Observation 47788441 + + + + + [47788504] infraorder: Systellognatha (no common name) observed by jackroney on 2020-05-25 + + Observation 47788504 + + + + + [47788638] species: Diadophis punctatus (ring-necked snake) observed by jackroney on 2020-05-25 + + Observation 47788638 + + + + + [47803785] species: Triteleia laxa (Ithuriel's Spear) observed by woody54 on 2020-05-29 + + Observation 47803785 + + + + + [47812639] species: Clarkia rubicunda (ruby chalice clarkia) observed by nancyvin on 2020-05-29 + + Observation 47812639 + + + + + [47872571] genus: Collinsia (Blue-eyed Marys) observed by mycologist92 on 2020-05-28 + + Observation 47872571 + + + + + [47885846] subphylum: Angiospermae (flowering plants) observed by cgnpark on 2020-05-02 + + Observation 47885846 + + + + + [47885868] kingdom: Plantae (plants) observed by cgnpark on 2020-05-02 + + Observation 47885868 + + + + + [47885897] species: Trifolium pratense (red clover) observed by cgnpark on 2020-05-02 + + Observation 47885897 + + + + + [47885917] class: Liliopsida (monocots) observed by cgnpark on 2020-05-02 + + Observation 47885917 + + + + + [47885941] genus: Plagiobothrys (Popcorn Flowers) observed by cgnpark on 2020-05-04 + + Observation 47885941 + + + + + [47885968] order: Poales (grasses, sedges, and allies) observed by cgnpark on 2020-05-04 + + Observation 47885968 + + + + + [47885999] family: Asteraceae (sunflowers, daisies, asters, and allies) observed by cgnpark on 2020-05-04 + + Observation 47885999 + + + + + [47886020] class: Magnoliopsida (dicots) observed by cgnpark on 2020-05-04 + + Observation 47886020 + + + + + [47886043] subphylum: Angiospermae (flowering plants) observed by cgnpark on 2020-05-04 + + Observation 47886043 + + + + + [47912434] species: Hypochaeris glabra (Smooth Cat's Ear) observed by enhunn323 on 2020-05-30 + + Observation 47912434 + + + + + [47912453] species: Lonicera utahensis (Utah Honeysuckle) observed by enhunn323 on 2020-05-30 + + Observation 47912453 + + + + + [47912471] species: Coccinella californica (California Lady Beetle) observed by enhunn323 on 2020-05-30 + + Observation 47912471 + + + + + [47925954] species: Triteleia laxa (Ithuriel's Spear) observed by sunflowerguy on 2020-05-29 + + Observation 47925954 + + + + + [47933257] species: Calochortus luteus (Yellow Mariposa Lily) observed by woody54 on 2020-05-30 + + Observation 47933257 + + + + + [47943919] genus: Brodiaea (Brodiaeas) observed by woody54 on 2020-05-30 + + Observation 47943919 + + + + + [47981338] subfamily: Apinae (Apine Bees) observed by woody54 on 2020-05-30 + + Observation 47981338 + + + + + [47984111] subphylum: Angiospermae (flowering plants) observed by susbis on 2020-05-30 + + + Observation 47984111 + + + + + [47992359] species: Diplacus aurantiacus (orange bush monkeyflower) observed by susbis on 2020-05-30 + + Observation 47992359 + + + + + [48009537] subfamily: Myrmicinae (Myrmicine Ants) observed by alfresco on 2020-05-31 + + Observation 48009537 + + + + + [48019344] genus: Eriophyllum (Woolly sunflowers) observed by rokathgw on 2020-05-31 + + Observation 48019344 + + + + + [48027954] species: Silene laciniata (cardinal catchfly) observed by rokathgw on 2020-05-31 + + Observation 48027954 + + + + + [48027972] genus: Calystegia (false bindweeds) observed by rokathgw on 2020-05-31 + + Observation 48027972 + + + + + [48027995] species: Clarkia amoena (farewell-to-spring) observed by rokathgw on 2020-05-31 + + Observation 48027995 + + + + + [48036992] species: Clarkia amoena (farewell-to-spring) observed by kbradshaw04 on 2020-05-31 + + Observation 48036992 + + + + + [48043950] species: Euphydryas chalcedona (Variable Checkerspot) observed by kbradshaw04 on 2020-05-31 + + Observation 48043950 + + + + + [48044936] unknown taxon observed by kbradshaw04 on 2020-05-31 + + Observation 48044936 + + + + + [48050784] species: Castilleja foliolosa (Woolly Indian Paintbrush) observed by dmirante on 2020-05-31 + + Observation 48050784 + + + + + [48050830] species: Lupinus formosus (Summer Lupine) observed by dmirante on 2020-05-31 + + Observation 48050830 + + + + + [48050900] species: Pseudognaphalium californicum (California cudweed) observed by dmirante on 2020-05-31 + + Observation 48050900 + + + + + [48051354] species: Calochortus amabilis (Diogenes' lantern) observed by dmirante on 2020-05-31 + + Observation 48051354 + + + + + [48051581] species: Clarkia gracilis (slender clarkia) observed by dmirante on 2020-05-31 + + Observation 48051581 + + + + + [48057681] species: Aquilegia formosa (western columbine) observed by woody54 on 2020-05-31 + + Observation 48057681 + + + + + [48060571] species: Calochortus luteus (Yellow Mariposa Lily) observed by gretchenparadis on 2020-05-29 + + Observation 48060571 + + + + + [48061556] species: Agaricus californicus (California Agaricus) observed by gretchenparadis on 2020-05-29 + + Observation 48061556 + + + + + [48061772] species: Lonicera hispidula (Pink Honeysuckle) observed by gretchenparadis on 2020-05-29 + + Observation 48061772 + + + + + [48078044] species: Eriophyllum lanatum (common woolly sunflower) observed by direbecca on 2020-05-31 + + Observation 48078044 + + + + + [48078218] species: Eriogonum nudum (Naked Buckwheat) observed by direbecca on 2020-05-31 + + Observation 48078218 + + + + + [48079064] species: Adenostoma fasciculatum (chamise) observed by direbecca on 2020-05-31 + + Observation 48079064 + + + + + [48079291] species: Calochortus luteus (Yellow Mariposa Lily) observed by direbecca on 2020-05-31 + + Observation 48079291 + + + + + [48080503] species: Brodiaea elegans (harvest brodiaea) observed by direbecca on 2020-05-31 + + Observation 48080503 + + + + + [48080643] species: Hypericum concinnum (goldwire) observed by direbecca on 2020-05-31 + + Observation 48080643 + + + + + [48080842] species: Phacelia imbricata (Mountain Phacelia) observed by direbecca on 2020-05-31 + + Observation 48080842 + + + + + [48081118] species: Aesculus californica (California buckeye) observed by direbecca on 2020-05-31 + + Observation 48081118 + + + + + [48081380] species: Lycaena gorgon (Gorgon Copper) observed by crystelhadley on 2020-05-24 + + Observation 48081380 + + + + + [48083979] species: Triteleia laxa (Ithuriel's Spear) observed by rokathgw on 2020-05-31 + + Observation 48083979 + + + + + [48083983] species: Calycanthus occidentalis (California sweetshrub) observed by rokathgw on 2020-05-31 + + Observation 48083983 + + + + + [48148411] species: Phyciodes mylitta (Mylitta Crescent) observed by direbecca on 2020-05-31 + + Observation 48148411 + + + + + [48148419] species: Icaricia icarioides (Boisduval's Blue) observed by direbecca on 2020-05-31 + + Observation 48148419 + + + + + [48148420] species: Euphydryas chalcedona (Variable Checkerspot) observed by direbecca on 2020-05-31 + + Observation 48148420 + + + + + [48148421] species: Thorybes pylades (Northern Cloudywing) observed by direbecca on 2020-05-31 + + Observation 48148421 + + + + + [48148426] species: Erynnis propertius (Propertius Duskywing) observed by direbecca on 2020-05-31 + + Observation 48148426 + + + + + [48148427] species: Sceloporus occidentalis (Western Fence Lizard) observed by direbecca on 2020-05-31 + + Observation 48148427 + + + + + [48148428] species: Erynnis propertius (Propertius Duskywing) observed by direbecca on 2020-05-31 + + Observation 48148428 + + + + + [48148436] species: Bombylius major (Greater Bee Fly) observed by direbecca on 2020-05-31 + + Observation 48148436 + + + + + [48148437] species: Chlosyne palla (Northern Checkerspot) observed by direbecca on 2020-05-31 + + Observation 48148437 + + + + + [48148439] species: Junonia coenia (Common Buckeye) observed by direbecca on 2020-05-31 + + Observation 48148439 + + + + + [48148447] species: Calochortus amabilis (Diogenes' lantern) observed by direbecca on 2020-05-31 + + Observation 48148447 + + + + + [48169796] species: Psaltriparus minimus (Bushtit) observed by dlevitis on 2020-05-31 + + + Observation 48169796 + + + + + [48169798] species: Taricha granulosa (Rough-skinned Newt) observed by dlevitis on 2020-05-31 + + + Observation 48169798 + + + + + [48169799] species: Taricha granulosa (Rough-skinned Newt) observed by dlevitis on 2020-05-31 + + + Observation 48169799 + + + + + [48169808] species: Taricha granulosa (Rough-skinned Newt) observed by dlevitis on 2020-05-31 + + + Observation 48169808 + + + + + [48169809] species: Nerophilus californicus (no common name) observed by dlevitis on 2020-05-31 + + + Observation 48169809 + + + + + [48169810] subspecies: Thamnophis elegans elegans (Mountain Garter Snake) observed by dlevitis on 2020-05-31 + + + Observation 48169810 + + + + + [48169816] species: Claytonia perfoliata (miner's lettuce) observed by dlevitis on 2020-05-31 + + + Observation 48169816 + + + + + [48169818] species: Apis mellifera (Western Honey Bee) observed by dlevitis on 2020-05-31 + + + Observation 48169818 + + + + + [48169822] genus: Ellychnia (Diurnal Fireflies) observed by dlevitis on 2020-05-31 + + + Observation 48169822 + + + + + [48169823] genus: Bombylius (Greater Bee Flies) observed by dlevitis on 2020-05-31 + + + Observation 48169823 + + + + + [48169825] order: Carnivora (Carnivorans) observed by dlevitis on 2020-05-31 + Scat on trail + + Observation 48169825 + + + + + [48169826] kingdom: Animalia (Animals) observed by dlevitis on 2020-05-31 + What makes this trail of marks on stinging nettle leaves? + + Observation 48169826 + + + + + [48169829] order: Diptera (Flies) observed by dlevitis on 2020-05-31 + + + Observation 48169829 + + + + + [48169836] genus: Hippodamia (Oblong Lady Beetles) observed by dlevitis on 2020-05-31 + + + Observation 48169836 + + + + + [48169845] genus: Titiotus (no common name) observed by dlevitis on 2020-05-31 + + + Observation 48169845 + + + + + [48169846] species: Nerophilus californicus (no common name) observed by dlevitis on 2020-05-31 + + + Observation 48169846 + + + + + [48169847] species: Cercyonis sthenele (Great Basin Wood-Nymph) observed by dlevitis on 2020-05-31 + + + Observation 48169847 + + + + + [48169856] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-05-31 + + + Observation 48169856 + + + + + [48169858] species: Boisea rubrolineata (Western Boxelder Bug) observed by dlevitis on 2020-05-31 + + + Observation 48169858 + + + + + [48169860] species: Fuscoporia gilva (Mustard Yellow Polypore) observed by dlevitis on 2020-05-31 + + + Observation 48169860 + + + + + [48169866] species: Sciurus griseus (Western Gray Squirrel) observed by dlevitis on 2020-05-31 + + + Observation 48169866 + + + + + [48169870] species: Sciurus griseus (Western Gray Squirrel) observed by dlevitis on 2020-05-31 + + + Observation 48169870 + + + + + [48169873] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-05-31 + + + Observation 48169873 + + + + + [48169874] species: Melozone crissalis (California Towhee) observed by dlevitis on 2020-05-31 + + + Observation 48169874 + + + + + [48182222] species: Hypericum perforatum (common St. John's-wort) observed by woody54 on 2020-06-01 + + Observation 48182222 + + + + + [48270878] genus: Ochlodes (no common name) observed by woody54 on 2020-06-02 + + Observation 48270878 + + + + + [48309042] species: Euphydryas chalcedona (Variable Checkerspot) observed by charliemcneil on 2020-05-16 + + Observation 48309042 + + + + + [48309043] species: Dudleya cymosa (Canyon Live-forever) observed by charliemcneil on 2020-05-16 + + Observation 48309043 + + + + + [48340732] species: Rupertia physodes (forest scurfpea) observed by woody54 on 2020-06-02 + + Observation 48340732 + + + + + [48379239] species: Euphydryas chalcedona (Variable Checkerspot) observed by erilla on 2020-06-03 + Sugarloaf SP + + Observation 48379239 + + + + + [48379240] species: Euphydryas chalcedona (Variable Checkerspot) observed by erilla on 2020-06-03 + Sugarloaf SP + + Observation 48379240 + + + + + [48379245] species: Euphydryas chalcedona (Variable Checkerspot) observed by erilla on 2020-06-03 + Sugarloaf SP + + Observation 48379245 + + + + + [48379249] species: Limenitis lorquini (Lorquin's Admiral) observed by erilla on 2020-06-03 + Sugarloaf SP + + Observation 48379249 + + + + + [48379250] species: Adelpha californica (California Sister) observed by erilla on 2020-06-03 + Sugarloaf SP + + Observation 48379250 + + + + + [48379252] species: Pituophis catenifer (Gopher Snake) observed by erilla on 2020-06-03 + Sugarloaf SP + + Observation 48379252 + + + + + [48379580] species: Celastrina echo (Echo Azure) observed by erilla on 2020-06-03 + Sugarloaf SP + + Observation 48379580 + + + + + [48400421] genus: Rubus (brambles) observed by woody54 on 2020-06-02 + + Observation 48400421 + + + + + [48448522] species: Cardamine californica (milkmaids) observed by wa-hayes on 2020-04-18 + purplish tint to white flowers + + Observation 48448522 + + + + + [48448585] class: Polypodiopsida (ferns) observed by wa-hayes on 2020-04-18 + + Observation 48448585 + + + + + [48448622] species: Sanicula crassicaulis (Pacific Sanicle) observed by wa-hayes on 2020-04-18 + sanicle? check sp + + Observation 48448622 + + + + + [48448657] family: Liliaceae (lilies) observed by wa-hayes on 2020-04-18 + + Observation 48448657 + + + + + [48448685] class: Magnoliopsida (dicots) observed by wa-hayes on 2020-04-18 + + Observation 48448685 + + + + + [48449177] tribe: Melitaeini (Checkerspots) observed by wa-hayes on 2020-04-18 + + Observation 48449177 + + + + + [48462173] species: Collinsia tinctoria (sticky Chinese houses) observed by woody54 on 2020-06-04 + + Observation 48462173 + + + + + [48616234] genus: Calystegia (false bindweeds) observed by woody54 on 2020-06-05 + + Observation 48616234 + + + + + [48654078] species: Silene laciniata (cardinal catchfly) observed by woody54 on 2020-06-05 + + Observation 48654078 + + + + + [48659811] species: Cathartes aura (Turkey Vulture) observed by woody54 on 2020-05-01 + + Observation 48659811 + + + + + [48717184] species: Calochortus amabilis (Diogenes' lantern) observed by erin223 on 2020-06-06 + + Observation 48717184 + + + + + [48731925] species: Anaxyrus boreas (Western Toad) observed by alfresco on 2020-06-05 + + Observation 48731925 + + + + + [48741638] species: Tachycineta thalassina (Violet-green Swallow) observed by woody54 on 2020-06-06 + + Observation 48741638 + + + + + [48771287] species: Heracleum maximum (common cowparsnip) observed by coffearobusta42 on 2020-05-28 + + Observation 48771287 + + + + + [48771299] tribe: Heliantheae (sunflowers and allies) observed by coffearobusta42 on 2020-06-05 + + Observation 48771299 + + + + + [48792273] species: Aquilegia formosa (western columbine) observed by jackroney on 2020-05-25 + + Observation 48792273 + + + + + [48792295] species: Ellychnia californica (California Glowworm) observed by jackroney on 2020-05-25 + + Observation 48792295 + + + + + [48794669] species: Dudleya cymosa (Canyon Live-forever) observed by jackroney on 2020-05-29 + + Observation 48794669 + + + + + [48794756] species: Acmispon glaber (deerweed) observed by jackroney on 2020-05-29 + + Observation 48794756 + + + + + [48797178] genus: Cirsium (thistles) observed by dlevitis on 2020-06-06 + + + Observation 48797178 + + + + + [48797180] species: Argia vivida (Vivid Dancer) observed by dlevitis on 2020-06-06 + + + Observation 48797180 + + + + + [48797182] class: Insecta (Insects) observed by dlevitis on 2020-06-06 + This bay leaf was bent arond into a tent and held with silk. There was large grained frass inside. + + Observation 48797182 + + + + + [48797191] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-06-06 + + + Observation 48797191 + + + + + [48797193] tribe: Nedubini (no common name) observed by dlevitis on 2020-06-06 + + + Observation 48797193 + + + + + [48797194] species: Diplacus aurantiacus (orange bush monkeyflower) observed by dlevitis on 2020-06-06 + + + Observation 48797194 + + + + + [48797202] species: Schizophyllum commune (splitgill mushroom) observed by dlevitis on 2020-06-06 + + + Observation 48797202 + + + + + [48797205] phylum: Chlorophyta (green algae) observed by dlevitis on 2020-06-06 + growing on a rock face under a recently dried seep + + Observation 48797205 + + + + + [48797212] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-06-06 + Mold growing on hedge nettle leaves + + Observation 48797212 + + + + + [48797213] species: Apis mellifera (Western Honey Bee) observed by dlevitis on 2020-06-06 + + + Observation 48797213 + + + + + [48797220] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-06-06 + growing in the back of a crevice in a rock outcrop + + Observation 48797220 + + + + + [48797222] family: Formicidae (Ants) observed by dlevitis on 2020-06-06 + + + Observation 48797222 + + + + + [48797226] genus: Lathyrus (sweet peas and vetchlings) observed by dlevitis on 2020-06-06 + + + Observation 48797226 + + + + + [48797231] genus: Pentagramma (no common name) observed by dlevitis on 2020-06-06 + + + Observation 48797231 + + + + + [48797233] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-06-06 + + + Observation 48797233 + + + + + [48797243] species: Diplacus aurantiacus (orange bush monkeyflower) observed by dlevitis on 2020-06-06 + + + Observation 48797243 + + + + + [48797248] species: Madia gracilis (grassy tarweed) observed by dlevitis on 2020-06-06 + + + Observation 48797248 + + + + + [48797250] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-06-06 + + + Observation 48797250 + + + + + [48797266] species: Rhinotropis californica (California milkwort) observed by dlevitis on 2020-06-06 + + + Observation 48797266 + + + + + [48797277] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-06-06 + Sheet of fungus growing on underside of burned douglas fir fragment. + + Observation 48797277 + + + + + [48797282] species: Phytophthora ramorum (Sudden oak death) observed by dlevitis on 2020-06-06 + On bay + + Observation 48797282 + + + + + [48797298] kingdom: Plantae (plants) observed by dlevitis on 2020-06-06 + + + Observation 48797298 + + + + + [48797302] family: Poaceae (grasses) observed by dlevitis on 2020-06-06 + + + Observation 48797302 + + + + + [48797308] family: Brassicaceae (mustard family) observed by dlevitis on 2020-06-06 + + + Observation 48797308 + + + + + [48797318] species: Prosartes hookeri (Hooker's fairybells) observed by dlevitis on 2020-06-06 + + + Observation 48797318 + + + + + [48797328] class: Insecta (Insects) observed by dlevitis on 2020-06-06 + I'm guessing it was an insect that chewed this Tanoak leaf. + + Observation 48797328 + + + + + [48797360] species: Notholithocarpus densiflorus (Tanoak) observed by dlevitis on 2020-06-06 + + + Observation 48797360 + + + + + [48797375] family: Poaceae (grasses) observed by dlevitis on 2020-06-06 + + + Observation 48797375 + + + + + [48797398] species: Stachys bullata (California Hedge Nettle) observed by dlevitis on 2020-06-06 + + + Observation 48797398 + + + + + [48797401] species: Dryopteris arguta (coastal woodfern) observed by dlevitis on 2020-06-06 + + + Observation 48797401 + + + + + [48797414] species: Argia vivida (Vivid Dancer) observed by dlevitis on 2020-06-06 + + + Observation 48797414 + + + + + [48797433] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-06-06 + + + Observation 48797433 + + + + + [48797442] genus: Arbutus (madrones) observed by dlevitis on 2020-06-06 + + + Observation 48797442 + + + + + [48797447] class: Insecta (Insects) observed by dlevitis on 2020-06-06 + Who ate this track along the bay stem? + + Observation 48797447 + + + + + [48797455] species: Argia vivida (Vivid Dancer) observed by dlevitis on 2020-06-06 + + + Observation 48797455 + + + + + [48797472] suborder: Brachycera (Brachyceran Flies) observed by dlevitis on 2020-06-06 + + + Observation 48797472 + + + + + [48797493] species: Batrachoseps attenuatus (California Slender Salamander) observed by dlevitis on 2020-06-06 + + + Observation 48797493 + + + + + [48797516] species: Poecile rufescens (Chestnut-backed Chickadee) observed by dlevitis on 2020-06-06 + + + Observation 48797516 + + + + + [48797524] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-06 + A reminder not to leave food out + + Observation 48797524 + + + + + [48797533] species: Sciurus griseus (Western Gray Squirrel) observed by dlevitis on 2020-06-06 + + + Observation 48797533 + + + + + [48797546] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-06-06 + + + Observation 48797546 + + + + + [48797553] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-06 + + + Observation 48797553 + + + + + [48797574] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-06 + + + Observation 48797574 + + + + + [48797584] species: Argia vivida (Vivid Dancer) observed by dlevitis on 2020-06-06 + + + Observation 48797584 + + + + + [48797595] tribe: Satyrini (Alpines, Arctics, Nymphs and Satyrs) observed by dlevitis on 2020-06-06 + + + Observation 48797595 + + + + + [48797613] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-06-06 + + + Observation 48797613 + + + + + [48797628] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-06-06 + + + Observation 48797628 + + + + + [48797645] genus: Sphyrapicus (Sapsuckers) observed by dlevitis on 2020-06-06 + + + Observation 48797645 + + + + + [48800940] species: Pituophis catenifer (Gopher Snake) observed by jackroney on 2020-05-29 + + Observation 48800940 + + + + + [48801474] species: Hypericum perforatum (common St. John's-wort) observed by jackroney on 2020-05-29 + + Observation 48801474 + + + + + [48817951] species: Cirsium occidentale (Cobwebby Thistle) observed by dave-barry on 2020-06-07 + + Observation 48817951 + + + + + [48825243] species: Diplacus aurantiacus (orange bush monkeyflower) observed by kentcorley on 2020-05-31 + + Observation 48825243 + + + + + [48825408] subphylum: Angiospermae (flowering plants) observed by kentcorley on 2020-05-31 + + Observation 48825408 + + + + + [48841874] species: Quercus durata (leather oak) observed by dave-barry on 2020-06-07 + + Observation 48841874 + + + + + [48841970] tribe: Anthophorini (Digger Bees) observed by dave-barry on 2020-06-07 + + Observation 48841970 + + + + + [48842125] species: Thamnophis elegans (Western Terrestrial Garter Snake) observed by dave-barry on 2020-06-07 + + Observation 48842125 + + + + + [48842212] subfamily: Asteroideae (no common name) observed by dave-barry on 2020-06-07 + + Observation 48842212 + + + + + [48842318] class: Magnoliopsida (dicots) observed by dave-barry on 2020-06-07 + + Observation 48842318 + + + + + [48843130] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by dave-barry on 2020-06-07 + + Observation 48843130 + + + + + [48843579] species: Clarkia purpurea (Winecup Clarkia) observed by dave-barry on 2020-06-07 + + Observation 48843579 + + + + + [48843686] suborder: Apocrita (Narrow-waisted Wasps, Ants, and Bees) observed by dave-barry on 2020-06-07 + + Observation 48843686 + + + + + [48843739] species: Pseudognaphalium californicum (California cudweed) observed by dave-barry on 2020-06-07 + + Observation 48843739 + + + + + [48843874] species: Wyethia glabra (smooth mule-ears) observed by dave-barry on 2020-06-07 + + Observation 48843874 + + + + + [48844161] species: Arctostaphylos manzanita (Common Manzanita) observed by dave-barry on 2020-06-07 + + Observation 48844161 + + + + + [48844198] species: Hypericum concinnum (goldwire) observed by dave-barry on 2020-06-07 + + Observation 48844198 + + + + + [48844347] species: Collinsia sparsiflora (fewflower blue-eyed Mary) observed by dave-barry on 2020-06-07 + + Observation 48844347 + + + + + [48844455] species: Aphelocoma californica (California Scrub-Jay) observed by dave-barry on 2020-06-07 + + Observation 48844455 + + + + + [48844531] species: Calochortus amabilis (Diogenes' lantern) observed by dave-barry on 2020-06-07 + + Observation 48844531 + + + + + [48849329] species: Dermacentor occidentalis (Pacific Coast Tick) observed by dave-barry on 2020-06-07 + + Observation 48849329 + + + + + [48860196] species: Ericameria linearifolia (narrowleaf goldenbush) observed by tomlstedman on 2020-06-07 + + Observation 48860196 + + + + + [48860240] species: Pseudognaphalium californicum (California cudweed) observed by tomlstedman on 2020-06-07 + + Observation 48860240 + + + + + [48860284] species: Hypericum concinnum (goldwire) observed by tomlstedman on 2020-06-07 + + Observation 48860284 + + + + + [48860332] species: Cirsium andersonii (Anderson's thistle) observed by tomlstedman on 2020-06-07 + + Observation 48860332 + + + + + [48865885] species: Clarkia amoena (farewell-to-spring) observed by shannondrew on 2020-06-07 + + Observation 48865885 + + + + + [48869409] species: Hemizonia congesta (Hayfield Tarweed) observed by woody54 on 2020-06-07 + + Observation 48869409 + + + + + [48869462] species: Brodiaea elegans (harvest brodiaea) observed by woody54 on 2020-06-04 + + Observation 48869462 + + + + + [48989157] species: Adenostoma fasciculatum (chamise) observed by jackroney on 2020-05-29 + + Observation 48989157 + + + + + [48989227] species: Silene laciniata (cardinal catchfly) observed by jackroney on 2020-05-29 + + Observation 48989227 + + + + + [48989245] species: Euphydryas chalcedona (Variable Checkerspot) observed by jackroney on 2020-05-29 + + Observation 48989245 + + + + + [48989271] genus: Eriophyllum (Woolly sunflowers) observed by jackroney on 2020-05-29 + + Observation 48989271 + + + + + [49028202] species: Diadophis punctatus (ring-necked snake) observed by leopard_16 on 2020-05-25 + + Observation 49028202 + + + + + [49118739] species: Tragopogon porrifolius (purple salsify) observed by jbarcelon on 2020-05-21 + + Observation 49118739 + + + + + [49118749] subphylum: Angiospermae (flowering plants) observed by jbarcelon on 2020-05-21 + Hillside trail + + Observation 49118749 + + + + + [49118762] family: Asteraceae (sunflowers, daisies, asters, and allies) observed by jbarcelon on 2020-05-21 + + Observation 49118762 + + + + + [49118778] species: Dichelostemma congestum (Ookow) observed by jbarcelon on 2020-05-21 + Hillside trail + + Observation 49118778 + + + + + [49118934] species: Diplacus aurantiacus (orange bush monkeyflower) observed by jbarcelon on 2020-05-21 + + Observation 49118934 + + + + + [49119032] species: Sisyrinchium bellum (western blue-eyed grass) observed by jbarcelon on 2020-05-21 + Sugarloaf state park + + Observation 49119032 + + + + + [49144946] species: Icaricia acmon (Acmon Blue) observed by woody54 on 2020-06-10 + + Observation 49144946 + + + + + [49169903] species: Icaricia acmon (Acmon Blue) observed by woody54 on 2020-06-10 + + Observation 49169903 + + + + + [49360921] species: Clarkia amoena (farewell-to-spring) observed by cnt-putt on 2020-06-12 + + Observation 49360921 + + + + + [49361129] genus: Brodiaea (Brodiaeas) observed by cnt-putt on 2020-06-12 + + Observation 49361129 + + + + + [49371559] species: Clarkia amoena (farewell-to-spring) observed by rumney007 on 2020-06-12 + + Observation 49371559 + + + + + [49371980] species: Solanum xanti (purple nightshade) observed by rumney007 on 2020-06-12 + + Observation 49371980 + + + + + [49382686] genus: Brodiaea (Brodiaeas) observed by cnt-putt on 2020-06-12 + + Observation 49382686 + + + + + [49382725] class: Magnoliopsida (dicots) observed by cnt-putt on 2020-06-12 + + Observation 49382725 + + + + + [49382743] order: Polyporales (shelf fungi) observed by cnt-putt on 2020-06-12 + + Observation 49382743 + + + + + [49382761] genus: Campanula (bellflowers) observed by cnt-putt on 2020-06-12 + + Observation 49382761 + + + + + [49382780] class: Magnoliopsida (dicots) observed by cnt-putt on 2020-06-12 + + Observation 49382780 + + + + + [49382810] genus: Clarkia (no common name) observed by cnt-putt on 2020-06-12 + + Observation 49382810 + + + + + [49382819] species: Sisyrinchium bellum (western blue-eyed grass) observed by cnt-putt on 2020-06-12 + + Observation 49382819 + + + + + [49382894] genus: Convolvulus (bindweeds) observed by cnt-putt on 2020-06-12 + + Observation 49382894 + + + + + [49382942] species: Eschscholzia californica (California poppy) observed by cnt-putt on 2020-06-12 + + Observation 49382942 + + + + + [49382972] genus: Lupinus (lupines) observed by cnt-putt on 2020-06-12 + + Observation 49382972 + + + + + [49383005] genus: Lupinus (lupines) observed by cnt-putt on 2020-06-12 + + Observation 49383005 + + + + + [49399604] genus: Verbascum (mulleins) observed by woody54 on 2020-06-12 + + Observation 49399604 + + + + + [49399770] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by woody54 on 2020-06-12 + + Observation 49399770 + + + + + [49401293] genus: Tabanus (True Horse Flies) observed by woody54 on 2020-06-12 + + Observation 49401293 + + + + + [49485208] species: Hemizonia congesta (Hayfield Tarweed) observed by tcmrn6 on 2020-06-13 + + Observation 49485208 + + + + + [49495485] species: Prunus cerasifera (Cherry-plum) observed by jrlynx on 2020-06-12 + + Observation 49495485 + + + + + [49495490] species: Verbascum blattaria (moth mullein) observed by jrlynx on 2020-06-12 + + Observation 49495490 + + + + + [49495492] species: Mentha suaveolens (Apple Mint) observed by jrlynx on 2020-06-12 + + Observation 49495492 + + + + + [49495505] species: Lupinus formosus (Summer Lupine) observed by jrlynx on 2020-06-12 + + Observation 49495505 + + + + + [49495506] species: Scutellaria californica (California skullcap) observed by jrlynx on 2020-06-12 + + Observation 49495506 + + + + + [49495508] species: Madia gracilis (grassy tarweed) observed by jrlynx on 2020-06-12 + + Observation 49495508 + + + + + [49495521] species: Clarkia purpurea (Winecup Clarkia) observed by jrlynx on 2020-06-12 + + Observation 49495521 + + + + + [49495524] species: Pseudognaphalium californicum (California cudweed) observed by jrlynx on 2020-06-12 + + Observation 49495524 + + + + + [49495527] section: Diplacus (bush monkeyflowers) observed by jrlynx on 2020-06-12 + + Observation 49495527 + + + + + [49495542] family: Tenebrionidae (Darkling Beetles) observed by jrlynx on 2020-06-12 + + Observation 49495542 + + + + + [49495548] species: Adenostoma fasciculatum (chamise) observed by jrlynx on 2020-06-12 + + Observation 49495548 + + + + + [49495552] species: Solanum xanti (purple nightshade) observed by jrlynx on 2020-06-12 + + Observation 49495552 + + + + + [49495558] species: Solanum xanti (purple nightshade) observed by jrlynx on 2020-06-12 + + Observation 49495558 + + + + + [49495564] species: Achillea millefolium (common yarrow) observed by jrlynx on 2020-06-12 + + Observation 49495564 + + + + + [49495565] genus: Calystegia (false bindweeds) observed by jrlynx on 2020-06-12 + + Observation 49495565 + + + + + [49495573] species: Eriophyllum lanatum (common woolly sunflower) observed by jrlynx on 2020-06-12 + + Observation 49495573 + + + + + [49495576] species: Castilleja foliolosa (Woolly Indian Paintbrush) observed by jrlynx on 2020-06-12 + + Observation 49495576 + + + + + [49495577] species: Acmispon glaber (deerweed) observed by jrlynx on 2020-06-12 + + Observation 49495577 + + + + + [49495589] genus: Hypericum (St. John's worts) observed by jrlynx on 2020-06-12 + + Observation 49495589 + + + + + [49495593] species: Adenostoma fasciculatum (chamise) observed by jrlynx on 2020-06-12 + + Observation 49495593 + + + + + [49495597] species: Clematis lasiantha (Pipestem Clematis) observed by jrlynx on 2020-06-12 + + Observation 49495597 + + + + + [49495611] species: Pickeringia montana (chaparral pea) observed by jrlynx on 2020-06-12 + + Observation 49495611 + + + + + [49495616] genus: Hypochaeris (Cat's-Ears) observed by jrlynx on 2020-06-12 + + Observation 49495616 + + + + + [49495617] genus: Lomatium (Biscuitroots) observed by jrlynx on 2020-06-12 + + Observation 49495617 + + + + + [49495624] species: Arbutus menziesii (Pacific madrone) observed by jrlynx on 2020-06-12 + + Observation 49495624 + + + + + [49495627] species: Adenostoma fasciculatum (chamise) observed by jrlynx on 2020-06-12 + + Observation 49495627 + + + + + [49495629] species: Apocynum cannabinum (hemp dogbane) observed by jrlynx on 2020-06-12 + + Observation 49495629 + + + + + [49495635] species: Lepechinia calycina (California Pitcher Sage) observed by jrlynx on 2020-06-12 + + Observation 49495635 + + + + + [49495638] species: Eriodictyon californicum (California yerba santa) observed by jrlynx on 2020-06-12 + + Observation 49495638 + + + + + [49495641] species: Trametes hirsuta (Hairy Bracket) observed by jrlynx on 2020-06-12 + + Observation 49495641 + + + + + [49495645] species: Monardella villosa (Coyote Mint) observed by jrlynx on 2020-06-12 + + Observation 49495645 + + + + + [49495646] species: Agoseris grandiflora (bigflower agoseris) observed by jrlynx on 2020-06-12 + + Observation 49495646 + + + + + [49495647] genus: Eriophyllum (Woolly sunflowers) observed by jrlynx on 2020-06-12 + + Observation 49495647 + + + + + [49495656] species: Penstemon speciosus (Royal Penstemon) observed by jrlynx on 2020-06-12 + + Observation 49495656 + + + + + [49495659] species: Wyethia angustifolia (narrowleaf mule-ears) observed by jrlynx on 2020-06-12 + + Observation 49495659 + + + + + [49495660] genus: Osmia (Mason Bees) observed by jrlynx on 2020-06-12 + + Observation 49495660 + + + + + [49495670] species: Sambucus cerulea (blue elder) observed by jrlynx on 2020-06-12 + + Observation 49495670 + + + + + [49495676] species: Phacelia imbricata (Mountain Phacelia) observed by jrlynx on 2020-06-12 + + Observation 49495676 + + + + + [49495677] species: Clarkia amoena (farewell-to-spring) observed by jrlynx on 2020-06-12 + + Observation 49495677 + + + + + [49495680] species: Dudleya cymosa (Canyon Live-forever) observed by jrlynx on 2020-06-12 + + Observation 49495680 + + + + + [49495686] species: Erythranthe guttata (seep monkeyflower) observed by jrlynx on 2020-06-12 + + Observation 49495686 + + + + + [49495688] species: Rosa woodsii (Woods' rose) observed by jrlynx on 2020-06-12 + + Observation 49495688 + + + + + [49495696] species: Lupinus nanus (Sky Lupine) observed by jrlynx on 2020-06-12 + + Observation 49495696 + + + + + [49503593] species: Eriogonum nudum (Naked Buckwheat) observed by redwoodriverbear on 2020-06-13 + + Observation 49503593 + + + + + [49510109] genus: Calochortus (mariposa lilies) observed by redwoodriverbear on 2020-06-13 + + Observation 49510109 + + + + + [49510194] species: Calochortus luteus (Yellow Mariposa Lily) observed by redwoodriverbear on 2020-06-13 + + Observation 49510194 + + + + + [49510350] species: Ochlodes sylvanoides (Woodland Skipper) observed by redwoodriverbear on 2020-06-13 + + Observation 49510350 + + + + + [49510448] subspecies: Silene laciniata californica (North Californian Indian pink) observed by redwoodriverbear on 2020-06-13 + + Observation 49510448 + + + + + [49510530] species: Clarkia amoena (farewell-to-spring) observed by redwoodriverbear on 2020-06-13 + + Observation 49510530 + + + + + [49510589] species: Eriophyllum lanatum (common woolly sunflower) observed by redwoodriverbear on 2020-06-13 + + Observation 49510589 + + + + + [49510644] species: Dudleya cymosa (Canyon Live-forever) observed by redwoodriverbear on 2020-06-13 + + Observation 49510644 + + + + + [49510746] species: Cercyonis pegala (Common Wood-Nymph) observed by redwoodriverbear on 2020-06-13 + + Observation 49510746 + + + + + [49510870] species: Clarkia concinna (Red Ribbons) observed by redwoodriverbear on 2020-06-13 + + Observation 49510870 + + + + + [49511165] species: Aesculus californica (California buckeye) observed by redwoodriverbear on 2020-06-13 + + Observation 49511165 + + + + + [49511320] species: Diplacus aurantiacus (orange bush monkeyflower) observed by redwoodriverbear on 2020-06-13 + + Observation 49511320 + + + + + [49511553] species: Achillea millefolium (common yarrow) observed by redwoodriverbear on 2020-06-13 + + Observation 49511553 + + + + + [49511841] species: Hypericum concinnum (goldwire) observed by redwoodriverbear on 2020-06-13 + + Observation 49511841 + + + + + [49511946] species: Lupinus formosus (Summer Lupine) observed by redwoodriverbear on 2020-06-13 + + Observation 49511946 + + + + + [49516140] genus: Phacelia (Scorpionweeds) observed by redwoodriverbear on 2020-06-13 + + Observation 49516140 + + + + + [49526230] species: Rafinesquia californica (California chicory) observed by woody54 on 2020-06-13 + + Observation 49526230 + + + + + [49543933] species: Rafinesquia californica (California chicory) observed by woody54 on 2020-06-13 + + Observation 49543933 + + + + + [49633946] species: Calycanthus occidentalis (California sweetshrub) observed by dave-barry on 2020-06-14 + + Observation 49633946 + + + + + [49633993] species: Castilleja foliolosa (Woolly Indian Paintbrush) observed by dave-barry on 2020-06-14 + + Observation 49633993 + + + + + [49634070] species: Silene laciniata (cardinal catchfly) observed by dave-barry on 2020-06-14 + + Observation 49634070 + + + + + [49634819] species: Quercus durata (leather oak) observed by dave-barry on 2020-06-14 + + Observation 49634819 + + + + + [49766311] species: Rhinotropis californica (California milkwort) observed by woody54 on 2020-06-15 + + Observation 49766311 + + + + + [49833293] species: Calochortus luteus (Yellow Mariposa Lily) observed by mabrown1953 on 2020-06-15 + Sugarloaf state park + + Observation 49833293 + + + + + [49879344] species: Eriophyllum staechadifolium (seaside woolly sunflower) observed by woody54 on 2020-06-16 + + Observation 49879344 + + + + + [49881380] species: Papilio rutulus (Western Tiger Swallowtail) observed by woody54 on 2020-06-16 + + Observation 49881380 + + + + + [49881904] species: Cirsium occidentale (Cobwebby Thistle) observed by woody54 on 2020-06-16 + + Observation 49881904 + + + + + [49889019] species: Eriogonum latifolium (Seaside Buckwheat) observed by woody54 on 2020-06-16 + + Observation 49889019 + + + + + [49889676] tribe: Madieae (tarweeds and allies) observed by woody54 on 2020-06-16 + + Observation 49889676 + + + + + [49907288] species: Cordulegaster dorsalis (Pacific Spiketail) observed by meghanpearl on 2020-06-16 + + Observation 49907288 + + + + + [49910099] species: Anthemis cotula (Stinking chamomile) observed by woody54 on 2020-06-16 + + Observation 49910099 + + + + + [49915019] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by reptile_galen on 2020-06-16 + + Observation 49915019 + + + + + [49915407] subspecies: Thamnophis elegans elegans (Mountain Garter Snake) observed by reptile_galen on 2020-06-16 + + Observation 49915407 + + + + + [50067042] species: Lilium pardalinum (Leopard Lily) observed by woody54 on 2020-06-18 + + Observation 50067042 + + + + + [50068865] species: Cichorium intybus (chicory) observed by woody54 on 2020-06-18 + + Observation 50068865 + + + + + [50068997] species: Urocyon cinereoargenteus (Gray Fox) observed by woody54 on 2020-06-18 + + Observation 50068997 + + + + + [50444819] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by evebernard on 2020-06-21 + Baby Rattle snake, about 12 inches long + + Observation 50444819 + + + + + [50454737] family: Ichneumonidae (Ichneumonid Wasps) observed by easmeds on 2020-06-01 + + Observation 50454737 + + + + + [50484575] species: Meleagris gallopavo (Wild Turkey) observed by lutzie on 2020-06-21 + + Observation 50484575 + + + + + [50494490] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by woody54 on 2020-06-21 + + Observation 50494490 + + + + + [50575074] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-20 + + + Observation 50575074 + + + + + [50575075] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-06-20 + + + Observation 50575075 + + + + + [50575083] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-06-20 + + + Observation 50575083 + + + + + [50575084] species: Sialia mexicana (Western Bluebird) observed by dlevitis on 2020-06-20 + + + Observation 50575084 + + + + + [50575091] genus: Cercyonis (Wood-Nymphs) observed by dlevitis on 2020-06-20 + + + Observation 50575091 + + + + + [50575096] infraorder: Entelegynae (Entelegyne Spiders) observed by dlevitis on 2020-06-20 + + + Observation 50575096 + + + + + [50575109] family: Miridae (Plant Bugs) observed by dlevitis on 2020-06-20 + + + Observation 50575109 + + + + + [50575120] genus: Fraxinus (ashes) observed by dlevitis on 2020-06-20 + + + Observation 50575120 + + + + + [50575127] species: Alnus rhombifolia (white alder) observed by dlevitis on 2020-06-20 + + + Observation 50575127 + + + + + [50575129] species: Carex nudata (torrent sedge) observed by dlevitis on 2020-06-20 + + + Observation 50575129 + + + + + [50575135] order: Trichoptera (Caddisflies) observed by dlevitis on 2020-06-20 + On underside of rock in creek + + Observation 50575135 + + + + + [50575138] genus: Cercyonis (Wood-Nymphs) observed by dlevitis on 2020-06-20 + + + Observation 50575138 + + + + + [50575139] species: Battus philenor (Pipevine Swallowtail) observed by dlevitis on 2020-06-20 + + + Observation 50575139 + + + + + [50575148] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-20 + + + Observation 50575148 + + + + + [50575149] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50575149 + + + + + [50575156] genus: Brodiaea (Brodiaeas) observed by dlevitis on 2020-06-20 + + + Observation 50575156 + + + + + [50575165] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50575165 + + + + + [50575166] class: Aves (Birds) observed by dlevitis on 2020-06-20 + + + Observation 50575166 + + + + + [50575167] family: Pinaceae (pine family) observed by dlevitis on 2020-06-20 + + + Observation 50575167 + + + + + [50575179] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50575179 + + + + + [50575181] genus: Pteridium (brackens) observed by dlevitis on 2020-06-20 + + + Observation 50575181 + + + + + [50575184] genus: Lupinus (lupines) observed by dlevitis on 2020-06-20 + + + Observation 50575184 + + + + + [50575185] species: Heracleum maximum (common cowparsnip) observed by dlevitis on 2020-06-20 + + + Observation 50575185 + + + + + [50575187] species: Acer macrophyllum (bigleaf maple) observed by dlevitis on 2020-06-20 + + + Observation 50575187 + + + + + [50575196] stateofmatter: Life (no common name) observed by dlevitis on 2020-06-20 + On only this small branch of a young Bigleaf Maple, there is blanching away from the veins. Why? + + Observation 50575196 + + + + + [50575197] species: Aralia californica (California Spikenard) observed by dlevitis on 2020-06-20 + + + Observation 50575197 + + + + + [50575199] species: Vinca major (greater periwinkle) observed by dlevitis on 2020-06-20 + + + Observation 50575199 + + + + + [50575206] species: Pellaea andromedifolia (Coffee Fern) observed by dlevitis on 2020-06-20 + + + Observation 50575206 + + + + + [50575209] genus: Polypodium (polypody ferns) observed by dlevitis on 2020-06-20 + + + Observation 50575209 + + + + + [50575212] species: Meleagris gallopavo (Wild Turkey) observed by dlevitis on 2020-06-20 + + + Observation 50575212 + + + + + [50575219] order: Erysiphales (Powdery Mildews) observed by dlevitis on 2020-06-20 + on Rubus + + Observation 50575219 + + + + + [50575220] species: Eriophyes laevis (Alder Leaf Gall Mite) observed by dlevitis on 2020-06-20 + + + Observation 50575220 + + + + + [50576563] class: Insecta (Insects) observed by dlevitis on 2020-06-20 + leaf mine in Heracleum maximum + + Observation 50576563 + + + + + [50576564] species: Urtica dioica (stinging nettle) observed by dlevitis on 2020-06-20 + + + Observation 50576564 + + + + + [50576565] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-20 + + + Observation 50576565 + + + + + [50576571] species: Aphelocoma californica (California Scrub-Jay) observed by dlevitis on 2020-06-20 + + + Observation 50576571 + + + + + [50576574] species: Sialia mexicana (Western Bluebird) observed by dlevitis on 2020-06-20 + + + Observation 50576574 + + + + + [50576575] genus: Spergularia (Sand-Spurries) observed by dlevitis on 2020-06-20 + + + Observation 50576575 + + + + + [50576580] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-20 + + + Observation 50576580 + + + + + [50576582] subspecies: Odocoileus hemionus columbianus (Columbian Black-tailed Deer) observed by dlevitis on 2020-06-20 + + + Observation 50576582 + + + + + [50576585] species: Sayornis nigricans (Black Phoebe) observed by dlevitis on 2020-06-20 + + + Observation 50576585 + + + + + [50576587] species: Haemorhous mexicanus (House Finch) observed by dlevitis on 2020-06-20 + + + Observation 50576587 + + + + + [50576589] species: Urocyon cinereoargenteus (Gray Fox) observed by dlevitis on 2020-06-20 + Having fed from a nearby cherry-plum tree + + Observation 50576589 + + + + + [50576593] species: Croton setiger (turkey mullein) observed by dlevitis on 2020-06-20 + + + Observation 50576593 + + + + + [50576595] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-06-20 + + + Observation 50576595 + + + + + [50576599] genus: Cercyonis (Wood-Nymphs) observed by dlevitis on 2020-06-20 + + + Observation 50576599 + + + + + [50576600] genus: Lathyrus (sweet peas and vetchlings) observed by dlevitis on 2020-06-20 + + + Observation 50576600 + + + + + [50576605] genus: Verbascum (mulleins) observed by dlevitis on 2020-06-20 + + + Observation 50576605 + + + + + [50576610] genus: Rumex (docks) observed by dlevitis on 2020-06-20 + + + Observation 50576610 + + + + + [50576615] species: Meleagris gallopavo (Wild Turkey) observed by dlevitis on 2020-06-20 + + + Observation 50576615 + + + + + [50576618] genus: Sambucus (elders) observed by dlevitis on 2020-06-20 + + + Observation 50576618 + + + + + [50576621] species: Achillea millefolium (common yarrow) observed by dlevitis on 2020-06-20 + + + Observation 50576621 + + + + + [50576624] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-06-20 + + + Observation 50576624 + + + + + [50578470] species: Spinus psaltria (Lesser Goldfinch) observed by dlevitis on 2020-06-20 + + + Observation 50578470 + + + + + [50578472] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-06-20 + + + Observation 50578472 + + + + + [50578482] species: Spinus psaltria (Lesser Goldfinch) observed by dlevitis on 2020-06-20 + + + Observation 50578482 + + + + + [50578483] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-06-20 + + + Observation 50578483 + + + + + [50578489] species: Trimerotropis fontana (Fontana Grasshopper) observed by dlevitis on 2020-06-20 + + + Observation 50578489 + + + + + [50578497] species: Apis mellifera (Western Honey Bee) observed by dlevitis on 2020-06-20 + + + Observation 50578497 + + + + + [50578501] species: Polistes aurifer (Golden Paper Wasp) observed by dlevitis on 2020-06-20 + + + Observation 50578501 + + + + + [50578508] genus: Ochlodes (no common name) observed by dlevitis on 2020-06-20 + + + Observation 50578508 + + + + + [50578511] genus: Argia (Dancers) observed by dlevitis on 2020-06-20 + + + Observation 50578511 + + + + + [50578517] genus: Chalybion (Blue Mud-dauber Wasps) observed by dlevitis on 2020-06-20 + + + Observation 50578517 + + + + + [50578519] species: Adelpha californica (California Sister) observed by dlevitis on 2020-06-20 + + + Observation 50578519 + + + + + [50578520] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-06-20 + + + Observation 50578520 + + + + + [50578531] species: Adelpha californica (California Sister) observed by dlevitis on 2020-06-20 + + + Observation 50578531 + + + + + [50578532] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-06-20 + + + Observation 50578532 + + + + + [50578538] species: Meleagris gallopavo (Wild Turkey) observed by dlevitis on 2020-06-20 + + + Observation 50578538 + + + + + [50578539] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-06-20 + + + Observation 50578539 + + + + + [50578540] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578540 + + + + + [50578549] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578549 + + + + + [50578550] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578550 + + + + + [50578552] species: Clarkia amoena (farewell-to-spring) observed by dlevitis on 2020-06-20 + + + Observation 50578552 + + + + + [50578558] species: Corvus corax (Common Raven) observed by dlevitis on 2020-06-20 + + + Observation 50578558 + + + + + [50578560] genus: Hypericum (St. John's worts) observed by dlevitis on 2020-06-20 + + + Observation 50578560 + + + + + [50578566] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-06-20 + + + Observation 50578566 + + + + + [50578567] species: Corvus corax (Common Raven) observed by dlevitis on 2020-06-20 + + + Observation 50578567 + + + + + [50578575] species: Alnus rhombifolia (white alder) observed by dlevitis on 2020-06-20 + + + Observation 50578575 + + + + + [50578576] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-06-20 + + + Observation 50578576 + + + + + [50578578] species: Sayornis nigricans (Black Phoebe) observed by dlevitis on 2020-06-20 + + + Observation 50578578 + + + + + [50578582] species: Melozone crissalis (California Towhee) observed by dlevitis on 2020-06-20 + + + Observation 50578582 + + + + + [50578584] genus: Eschscholzia (no common name) observed by dlevitis on 2020-06-20 + + + Observation 50578584 + + + + + [50578585] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-20 + + + Observation 50578585 + + + + + [50578589] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-20 + + + Observation 50578589 + + + + + [50578593] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-06-20 + + + Observation 50578593 + + + + + [50578594] class: Insecta (Insects) observed by dlevitis on 2020-06-20 + leaf miner in common cowparsnip + + Observation 50578594 + + + + + [50578598] genus: Chalybion (Blue Mud-dauber Wasps) observed by dlevitis on 2020-06-20 + + + Observation 50578598 + + + + + [50578601] class: Insecta (Insects) observed by dlevitis on 2020-06-20 + stem borer in common cowparsnip + + Observation 50578601 + + + + + [50578602] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-06-20 + + + Observation 50578602 + + + + + [50578609] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578609 + + + + + [50578613] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578613 + + + + + [50578618] genus: Xylocopa (Large Carpenter Bees) observed by dlevitis on 2020-06-20 + + + Observation 50578618 + + + + + [50578621] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578621 + + + + + [50578623] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578623 + + + + + [50578629] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578629 + + + + + [50578630] species: Patagioenas fasciata (Band-tailed Pigeon) observed by dlevitis on 2020-06-20 + + + Observation 50578630 + + + + + [50578636] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-06-20 + + + Observation 50578636 + + + + + [50578637] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578637 + + + + + [50578638] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-06-20 + + + Observation 50578638 + + + + + [50580295] species: Melozone crissalis (California Towhee) observed by dlevitis on 2020-06-20 + + + Observation 50580295 + + + + + [50580296] species: Zenaida macroura (Mourning Dove) observed by dlevitis on 2020-06-20 + + + Observation 50580296 + + + + + [50580298] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-06-20 + + + Observation 50580298 + + + + + [50580304] species: Aphelocoma californica (California Scrub-Jay) observed by dlevitis on 2020-06-20 + + + Observation 50580304 + + + + + [50580307] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-06-20 + + + Observation 50580307 + + + + + [50580311] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-06-20 + + + Observation 50580311 + + + + + [50580315] species: Megaceryle alcyon (Belted Kingfisher) observed by dlevitis on 2020-06-20 + + + Observation 50580315 + + + + + [50580318] species: Sciurus griseus (Western Gray Squirrel) observed by dlevitis on 2020-06-20 + + + Observation 50580318 + + + + + [50580319] species: Zenaida macroura (Mourning Dove) observed by dlevitis on 2020-06-20 + + + Observation 50580319 + + + + + [50580322] species: Baeolophus inornatus (Oak Titmouse) observed by dlevitis on 2020-06-20 + + + Observation 50580322 + + + + + [50580323] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-06-20 + + + Observation 50580323 + + + + + [50580327] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-06-20 + + + Observation 50580327 + + + + + [50580333] species: Sciurus griseus (Western Gray Squirrel) observed by dlevitis on 2020-06-20 + + + Observation 50580333 + + + + + [50580334] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-06-20 + + + Observation 50580334 + + + + + [50580338] subspecies: Odocoileus hemionus columbianus (Columbian Black-tailed Deer) observed by dlevitis on 2020-06-20 + + + Observation 50580338 + + + + + [50580340] species: Lepus californicus (Black-tailed Jackrabbit) observed by dlevitis on 2020-06-20 + + + Observation 50580340 + + + + + [50580341] order: Lepidoptera (Butterflies and Moths) observed by dlevitis on 2020-06-20 + Found crawling up a dry stem of grass after dusk + + Observation 50580341 + + + + + [50580346] species: Anaxyrus boreas (Western Toad) observed by dlevitis on 2020-06-20 + + + Observation 50580346 + + + + + [50580347] species: Idia lubricalis (Glossy Black Idia Moth) observed by dlevitis on 2020-06-20 + + + Observation 50580347 + + + + + [50580353] genus: Tigrosa (no common name) observed by dlevitis on 2020-06-20 + + + Observation 50580353 + + + + + [50580361] species: Tessellana tessellata (Brown-spotted Bush-cricket) observed by dlevitis on 2020-06-20 + + + Observation 50580361 + + + + + [50580365] subtribe: Pterostichina (no common name) observed by dlevitis on 2020-06-20 + + + Observation 50580365 + + + + + [50580369] genus: Ctenolepisma (no common name) observed by dlevitis on 2020-06-20 + + + Observation 50580369 + + + + + [50580373] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-06-21 + + + Observation 50580373 + + + + + [50580375] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-06-21 + + + Observation 50580375 + + + + + [50580376] species: Tachycineta thalassina (Violet-green Swallow) observed by dlevitis on 2020-06-21 + + + Observation 50580376 + + + + + [50580382] subspecies: Odocoileus hemionus columbianus (Columbian Black-tailed Deer) observed by dlevitis on 2020-06-21 + + + Observation 50580382 + + + + + [50580388] class: Insecta (Insects) observed by dlevitis on 2020-06-21 + in ash + + Observation 50580388 + + + + + [50580389] species: Dryobates pubescens (Downy Woodpecker) observed by dlevitis on 2020-06-21 + + + Observation 50580389 + + + + + [50580392] species: Corvus corax (Common Raven) observed by dlevitis on 2020-06-21 + + + Observation 50580392 + + + + + [50580394] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-06-21 + + + Observation 50580394 + + + + + [50580399] genus: Calliphora (no common name) observed by dlevitis on 2020-06-21 + + + Observation 50580399 + + + + + [50580400] family: Columbidae (Pigeons and Doves) observed by dlevitis on 2020-06-21 + + + Observation 50580400 + + + + + [50580401] species: Adelpha californica (California Sister) observed by dlevitis on 2020-06-21 + + + Observation 50580401 + + + + + [50580409] genus: Asteromyia (no common name) observed by dlevitis on 2020-06-21 + What causes these spots, please? + + Observation 50580409 + + + + + [50580410] species: Eurybia radulina (roughleaf aster) observed by dlevitis on 2020-06-21 + + + Observation 50580410 + + + + + [50636739] genus: Clarkia (no common name) observed by lutzie on 2020-06-21 + + Observation 50636739 + + + + + [50657515] subfamily: Chrysidinae (no common name) observed by dlevitis on 2020-06-20 + @susanna_h thank you for pointing out this was a Cuckoo Wasp + + Observation 50657515 + + + + + [50829404] species: Clarkia amoena (farewell-to-spring) observed by stacatto on 2020-05-17 + + Observation 50829404 + + + + + [50829426] subphylum: Angiospermae (flowering plants) observed by stacatto on 2020-05-17 + + Observation 50829426 + + + + + [50934606] species: Hemizonia congesta (Hayfield Tarweed) observed by mycologist92 on 2020-06-25 + + Observation 50934606 + + + + + [50934631] species: Platanthera transversa (Flat Spurred Piperia) observed by mycologist92 on 2020-06-25 + + Observation 50934631 + + + + + [50937256] species: Epipactis helleborine (Broad-leafed Helleborine) observed by woody54 on 2020-06-25 + + Observation 50937256 + + + + + [50950290] genus: Botryosphaeria (no common name) observed by dlevitis on 2020-06-21 + Botryosphaeria fungus is associated with the leaf galls that Asteromyia midges make on Eurybia. + +See: Stireman III, J.O., Devlin, H., Carr, T.G. and Abbot, P., 2010. Evolutionary diversification of the gall midge genus Asteromyia (Cecidomyiidae) in a multitrophic ecological context. Molecular Phylogenetics and Evolution, 54(1), pp.194-210. + + Observation 50950290 + + + + + [50952542] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by reptile_galen on 2020-06-25 + + Observation 50952542 + + + + + [50952838] subspecies: Coluber constrictor mormon (Western Yellow-bellied Racer) observed by reptile_galen on 2020-06-25 + + Observation 50952838 + + + + + [50957383] species: Silene laciniata (cardinal catchfly) observed by woody54 on 2020-06-25 + + Observation 50957383 + + + + + [51023984] species: Lepus californicus (Black-tailed Jackrabbit) observed by jrlynx on 2020-06-26 + + Observation 51023984 + + + + + [51023985] genus: Zeltnera (no common name) observed by jrlynx on 2020-06-26 + + Observation 51023985 + + + + + [51128870] species: Aralia californica (California Spikenard) observed by jenopus on 2020-06-27 + + Observation 51128870 + + + + + [51129458] species: Madia elegans (Common Madia) observed by jenopus on 2020-06-27 + + Observation 51129458 + + + + + [51158474] genus: Urtica (nettles) observed by mariasky8 on 2020-06-27 + + Observation 51158474 + + + + + [51179934] species: Cirsium occidentale (Cobwebby Thistle) observed by susbis on 2020-06-27 + + Observation 51179934 + + + + + [51185156] genus: Lathyrus (sweet peas and vetchlings) observed by ten_salamanders on 2020-06-27 + + Observation 51185156 + + + + + [51185222] species: Erythranthe cardinalis (scarlet monkeyflower) observed by ten_salamanders on 2020-06-27 + + Observation 51185222 + + + + + [51185270] species: Erythranthe cardinalis (scarlet monkeyflower) observed by ten_salamanders on 2020-06-27 + + Observation 51185270 + + + + + [51185334] species: Erythranthe cardinalis (scarlet monkeyflower) observed by ten_salamanders on 2020-06-27 + + Observation 51185334 + + + + + [51185369] species: Aralia californica (California Spikenard) observed by ten_salamanders on 2020-06-27 + + Observation 51185369 + + + + + [51185408] species: Erythranthe cardinalis (scarlet monkeyflower) observed by ten_salamanders on 2020-06-27 + + Observation 51185408 + + + + + [51185475] species: Monardella villosa (Coyote Mint) observed by ten_salamanders on 2020-06-27 + + Observation 51185475 + + + + + [51185489] species: Silene laciniata (cardinal catchfly) observed by ten_salamanders on 2020-06-27 + + Observation 51185489 + + + + + [51185526] species: Heteromeles arbutifolia (Toyon) observed by ten_salamanders on 2020-06-27 + + Observation 51185526 + + + + + [51185541] species: Eriogonum nudum (Naked Buckwheat) observed by ten_salamanders on 2020-06-27 + + Observation 51185541 + + + + + [51185572] species: Penstemon heterophyllus (Bunchleaf Penstemon) observed by ten_salamanders on 2020-06-27 + + Observation 51185572 + + + + + [51185595] species: Cirsium occidentale (Cobwebby Thistle) observed by ten_salamanders on 2020-06-27 + + Observation 51185595 + + + + + [51185644] genus: Penstemon (beardtongues) observed by ten_salamanders on 2020-06-27 + + Observation 51185644 + + + + + [51185709] genus: Eriogonum (wild buckwheats) observed by ten_salamanders on 2020-06-27 + + Observation 51185709 + + + + + [51185745] genus: Spergularia (Sand-Spurries) observed by ten_salamanders on 2020-06-27 + + Observation 51185745 + + + + + [51191733] species: Diplacus aurantiacus (orange bush monkeyflower) observed by susbis on 2020-06-27 + + Observation 51191733 + + + + + [51312474] species: Hypochaeris radicata (Common Cat's-ear) observed by woody54 on 2020-06-26 + + Observation 51312474 + + + + + [51313657] species: Eristalis tenax (Common Drone Fly) observed by woody54 on 2020-06-28 + + Observation 51313657 + + + + + [51318347] species: Diplacus aurantiacus (orange bush monkeyflower) observed by kgferg on 2020-06-28 + + Observation 51318347 + + + + + [51318354] species: Heteromeles arbutifolia (Toyon) observed by kgferg on 2020-06-28 + Regrowth from 2017 Nuns Fire. + + Observation 51318354 + + + + + [51318368] species: Aesculus californica (California buckeye) observed by kgferg on 2020-06-28 + + Observation 51318368 + + + + + [51318377] species: Calochortus luteus (Yellow Mariposa Lily) observed by kgferg on 2020-06-28 + + Observation 51318377 + + + + + [51318393] species: Cirsium occidentale (Cobwebby Thistle) observed by kgferg on 2020-06-28 + + Observation 51318393 + + + + + [51318439] species: Clarkia amoena (farewell-to-spring) observed by kgferg on 2020-06-28 + + Observation 51318439 + + + + + [51318452] species: Calypte anna (Anna's Hummingbird) observed by kgferg on 2020-06-28 + + Observation 51318452 + + + + + [51318484] species: Sceloporus occidentalis (Western Fence Lizard) observed by kgferg on 2020-06-28 + + Observation 51318484 + + + + + [51364776] species: Sairocarpus breweri (Brewer's Snapdragon) observed by arlenedevitt on 2020-06-28 + Photographed at Sugarloaf State Park, CA USA + + Observation 51364776 + + + + + [51366596] species: Ganoderma oregonense (West Coast Reishi) observed by arlenedevitt on 2020-06-21 + found in oak forest + + Observation 51366596 + + + + + [51366601] species: Ganoderma oregonense (West Coast Reishi) observed by arlenedevitt on 2020-06-21 + found in oak forest + + Observation 51366601 + + + + + [51399633] suborder: Brachycera (Brachyceran Flies) observed by woody54 on 2020-06-28 + + Observation 51399633 + + + + + [51399681] species: Phidippus comatus (Hairy Tufted Jumping Spider) observed by woody54 on 2020-06-29 + + Observation 51399681 + + + + + [51813287] species: Hypericum perforatum (common St. John's-wort) observed by woody54 on 2020-07-03 + + Observation 51813287 + + + + + [51865539] species: Fuligo septica (Dog Vomit Slime Mold) observed by sunflowerguy on 2020-07-03 + + Observation 51865539 + + + + + [51973881] species: Icaricia acmon (Acmon Blue) observed by colinroots on 2020-07-01 + + Observation 51973881 + + + + + [51974123] species: Adelpha californica (California Sister) observed by colinroots on 2020-07-01 + + Observation 51974123 + + + + + [51983949] species: Monardella villosa (Coyote Mint) observed by woody54 on 2020-07-04 + + Observation 51983949 + + + + + [51989415] genus: Clarkia (no common name) observed by wyethialover on 2020-07-03 + + Observation 51989415 + + + + + [51989515] species: Clarkia amoena (farewell-to-spring) observed by wyethialover on 2020-07-03 + + Observation 51989515 + + + + + [51989587] species: Calochortus luteus (Yellow Mariposa Lily) observed by wyethialover on 2020-07-03 + + Observation 51989587 + + + + + [51989603] species: Acmispon americanus (Spanish clover) observed by wyethialover on 2020-07-03 + + Observation 51989603 + + + + + [51989620] species: Silene laciniata (cardinal catchfly) observed by wyethialover on 2020-07-03 + + Observation 51989620 + + + + + [52076959] genus: Theridion (Tangle-web Spiders) observed by deedeemyers on 2020-05-29 + + Observation 52076959 + + + + + [52077082] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by deedeemyers on 2020-06-21 + + Observation 52077082 + + + + + [52077201] genus: Nematocampa (no common name) observed by deedeemyers on 2020-06-03 + + Observation 52077201 + + + + + [52077350] genus: Orgyia (no common name) observed by deedeemyers on 2020-05-28 + + Observation 52077350 + + + + + [52078183] species: Sisyrinchium bellum (western blue-eyed grass) observed by deedeemyers on 2020-05-16 + + Observation 52078183 + + + + + [52092125] species: Holodiscus discolor (Ocean spray) observed by woody54 on 2020-07-05 + + Observation 52092125 + + + + + [52092233] species: Panus conchatus (Lilac oysterling) observed by caenvsci on 2020-07-05 + + Observation 52092233 + + + + + [52092234] species: Panus conchatus (Lilac oysterling) observed by caenvsci on 2020-07-05 + + Observation 52092234 + + + + + [52092236] genus: Neohermes (Gray Fishflies) observed by caenvsci on 2020-07-05 + Burrowed under a rock in damp sandy bed of a dry stream. + + Observation 52092236 + + + + + [52092259] subgenus: Melanthaxia (no common name) observed by caenvsci on 2020-07-05 + + Observation 52092259 + + + + + [52092261] genus: Juga (no common name) observed by caenvsci on 2020-07-05 + + Observation 52092261 + + + + + [52106549] genus: Physa (no common name) observed by ten_salamanders on 2020-07-05 + + Observation 52106549 + + + + + [52106557] superfamily: Astacoidea (Northern Hemisphere Crayfishes) observed by ten_salamanders on 2020-07-05 + Crayfish. Hard to see. Will make into casual observation. + + Observation 52106557 + + + + + [52110004] species: Neriene litigiosa (Sierra Dome Spider) observed by woody54 on 2020-07-05 + + Observation 52110004 + + + + + [52217560] species: Junonia coenia (Common Buckeye) observed by woody54 on 2020-07-06 + + Observation 52217560 + + + + + [52442030] genus: Cibdelis (no common name) observed by woody54 on 2020-07-08 + + Observation 52442030 + + + + + [52506010] species: Aquilegia eximia (Van Houtte's Columbine) observed by woody54 on 2020-07-09 + + Observation 52506010 + + + + + [52617388] species: Erythranthe cardinalis (scarlet monkeyflower) observed by jackroney on 2020-07-10 + + Observation 52617388 + + + + + [52617487] species: Hoita macrostachya (large leatherroot) observed by jackroney on 2020-07-10 + + Observation 52617487 + + + + + [52617616] species: Aquilegia eximia (Van Houtte's Columbine) observed by jackroney on 2020-07-10 + + Observation 52617616 + + + + + [52617636] species: Thamnophis atratus (Aquatic Garter Snake) observed by jackroney on 2020-07-10 + + Observation 52617636 + + + + + [52617826] class: Aves (Birds) observed by jackroney on 2020-07-10 + Found dead in a creek. Wingspan about 12 inches. + + Observation 52617826 + + + + + [52618011] species: Pacifastacus leniusculus (Signal Crayfish) observed by jackroney on 2020-07-10 + + + Observation 52618011 + + + + + [52618047] suborder: Anisoptera (Dragonflies) observed by jackroney on 2020-07-10 + + Observation 52618047 + + + + + [52618446] species: Uroctonus mordax (Western Forest Scorpion) observed by jackroney on 2020-07-10 + + Observation 52618446 + + + + + [52618584] infraclass: Helminthomorpha (Worm-like millipedes) observed by jackroney on 2020-07-10 + + Observation 52618584 + + + + + [52618730] order: Siphonaptera (Fleas) observed by jackroney on 2020-07-10 + + Observation 52618730 + + + + + [52618780] species: Anisocarpus madioides (woodland madia) observed by jackroney on 2020-07-10 + + Observation 52618780 + + + + + [52618850] species: Silene coronaria (Rose campion) observed by jackroney on 2020-07-10 + + Observation 52618850 + + + + + [52618925] genus: Villa (no common name) observed by jackroney on 2020-07-10 + + Observation 52618925 + + + + + [52619029] species: Phyciodes mylitta (Mylitta Crescent) observed by jackroney on 2020-07-10 + + Observation 52619029 + + + + + [52627136] species: Cirsium vulgare (bull thistle) observed by alohajohnny on 2020-04-27 + + Observation 52627136 + + + + + [52627158] species: Erythranthe cardinalis (scarlet monkeyflower) observed by alohajohnny on 2020-07-10 + + Observation 52627158 + + + + + [52703246] order: Embiidina (Webspinners) observed by dlevitis on 2020-07-10 + + + Observation 52703246 + + + + + [52703247] species: Taricha granulosa (Rough-skinned Newt) observed by dlevitis on 2020-07-10 + + + Observation 52703247 + + + + + [52703249] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-10 + + + Observation 52703249 + + + + + [52703261] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-07-10 + + + Observation 52703261 + + + + + [52703264] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-10 + + + Observation 52703264 + + + + + [52703271] species: Poecile rufescens (Chestnut-backed Chickadee) observed by dlevitis on 2020-07-10 + + + Observation 52703271 + + + + + [52703277] genus: Lipoptena (no common name) observed by dlevitis on 2020-07-10 + + + Observation 52703277 + + + + + [52703281] species: Cordulegaster dorsalis (Pacific Spiketail) observed by dlevitis on 2020-07-10 + + + Observation 52703281 + + + + + [52703288] species: Hemizonia congesta (Hayfield Tarweed) observed by dlevitis on 2020-07-10 + + + Observation 52703288 + + + + + [52703293] species: Ursus americanus (American Black Bear) observed by dlevitis on 2020-07-10 + + + Observation 52703293 + + + + + [52703296] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52703296 + + + + + [52704170] species: Centaurea solstitialis (Yellow Starthistle) observed by dlevitis on 2020-07-10 + + + Observation 52704170 + + + + + [52704171] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52704171 + + + + + [52704172] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-10 + + + Observation 52704172 + + + + + [52704210] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52704210 + + + + + [52704212] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52704212 + + + + + [52704215] genus: Euphorbia (spurges) observed by dlevitis on 2020-07-10 + + + Observation 52704215 + + + + + [52704247] species: Urtica dioica (stinging nettle) observed by dlevitis on 2020-07-10 + + + Observation 52704247 + + + + + [52704262] species: Phalaris aquatica (harding grass) observed by dlevitis on 2020-07-10 + + + Observation 52704262 + + + + + [52704270] subgenus: Esula (Euphorbia subg. Esula) observed by dlevitis on 2020-07-10 + + + Observation 52704270 + + + + + [52704279] species: Puma concolor (Mountain Lion) observed by dlevitis on 2020-07-10 + longest piece was about 6 inches + + Observation 52704279 + + + + + [52704288] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52704288 + + + + + [52704311] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-10 + What causes this leaf deformity in toyon leaves? + + Observation 52704311 + + + + + [52704317] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-10 + + + Observation 52704317 + + + + + [52704335] species: Centaurea solstitialis (Yellow Starthistle) observed by dlevitis on 2020-07-10 + + + Observation 52704335 + + + + + [52704357] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-10 + + + Observation 52704357 + + + + + [52704360] genus: Agoseris (mountain dandelion) observed by dlevitis on 2020-07-10 + + + Observation 52704360 + + + + + [52704373] class: Magnoliopsida (dicots) observed by dlevitis on 2020-07-10 + + + Observation 52704373 + + + + + [52704387] genus: Icaricia (no common name) observed by dlevitis on 2020-07-10 + + + Observation 52704387 + + + + + [52704394] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52704394 + + + + + [52704404] species: Eschscholzia californica (California poppy) observed by dlevitis on 2020-07-10 + + + Observation 52704404 + + + + + [52704429] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52704429 + + + + + [52706611] genus: Clarkia (no common name) observed by dlevitis on 2020-07-10 + + + Observation 52706611 + + + + + [52706612] species: Centaurea solstitialis (Yellow Starthistle) observed by dlevitis on 2020-07-10 + + + Observation 52706612 + + + + + [52706614] family: Iridaceae (irises and allies) observed by dlevitis on 2020-07-10 + + + Observation 52706614 + + + + + [52706624] class: Magnoliopsida (dicots) observed by dlevitis on 2020-07-10 + + + Observation 52706624 + + + + + [52706625] species: Tamalia coweni (Manzanita Leafgall Aphid) observed by dlevitis on 2020-07-10 + + + Observation 52706625 + + + + + [52706631] species: Hypericum perforatum (common St. John's-wort) observed by dlevitis on 2020-07-10 + + + Observation 52706631 + + + + + [52706635] genus: Pseudognaphalium (rabbit-tobaccos) observed by dlevitis on 2020-07-10 + + + Observation 52706635 + + + + + [52706638] species: Vitis californica (California wild grape) observed by dlevitis on 2020-07-10 + + + Observation 52706638 + + + + + [52706645] species: Baccharis pilularis (coyote brush) observed by dlevitis on 2020-07-10 + + + Observation 52706645 + + + + + [52706654] species: Aphelocoma californica (California Scrub-Jay) observed by dlevitis on 2020-07-10 + + + Observation 52706654 + + + + + [52706656] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-10 + + + Observation 52706656 + + + + + [52706659] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-10 + What does this to madrone leaves? + + Observation 52706659 + + + + + [52706662] genus: Lolium (Ryegrasses) observed by dlevitis on 2020-07-10 + + + Observation 52706662 + + + + + [52706665] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + + + Observation 52706665 + + + + + [52706667] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-10 + What affecting these bay leaves? + + Observation 52706667 + + + + + [52706676] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52706676 + + + + + [52706679] species: Quercus agrifolia (coast live oak) observed by dlevitis on 2020-07-10 + + + Observation 52706679 + + + + + [52706683] genus: Cirsium (thistles) observed by dlevitis on 2020-07-10 + + + Observation 52706683 + + + + + [52706693] subtribe: Carduinae (Thistles and Burdocks) observed by dlevitis on 2020-07-10 + + + Observation 52706693 + + + + + [52706700] genus: Juncus (rushes) observed by dlevitis on 2020-07-10 + In a disturbed vernal pool + + Observation 52706700 + + + + + [52706712] epifamily: Anthophila (Bees) observed by dlevitis on 2020-07-10 + + + Observation 52706712 + + + + + [52706726] species: Mentha pulegium (Pennyroyal) observed by dlevitis on 2020-07-10 + + + Observation 52706726 + + + + + [52706744] species: Holcus lanatus (Yorkshire fog) observed by dlevitis on 2020-07-10 + + + Observation 52706744 + + + + + [52706756] species: Cirsium vulgare (bull thistle) observed by dlevitis on 2020-07-10 + + + Observation 52706756 + + + + + [52706760] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52706760 + + + + + [52706769] genus: Corylus (hazels) observed by dlevitis on 2020-07-10 + + + Observation 52706769 + + + + + [52706777] genus: Hypochaeris (Cat's-Ears) observed by dlevitis on 2020-07-10 + + + Observation 52706777 + + + + + [52706786] superfamily: Eremiaphiloidea (no common name) observed by dlevitis on 2020-07-10 + + + Observation 52706786 + + + + + [52706794] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-10 + + + Observation 52706794 + + + + + [52706816] species: Achillea millefolium (common yarrow) observed by dlevitis on 2020-07-10 + + + Observation 52706816 + + + + + [52706825] family: Iridaceae (irises and allies) observed by dlevitis on 2020-07-10 + + + Observation 52706825 + + + + + [52709233] species: Perideridia kelloggii (Kellogg's yampah) observed by woody54 on 2020-07-11 + + Observation 52709233 + + + + + [52710126] genus: Sambucus (elders) observed by dlevitis on 2020-07-10 + + + Observation 52710126 + + + + + [52710127] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-10 + lesions on elder leaflets + + Observation 52710127 + + + + + [52710129] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-10 + + + Observation 52710129 + + + + + [52710133] species: Lathyrus vestitus (Pacific pea) observed by dlevitis on 2020-07-10 + + + Observation 52710133 + + + + + [52710136] species: Baccharis pilularis (coyote brush) observed by dlevitis on 2020-07-10 + + + Observation 52710136 + + + + + [52710138] species: Synanthedon resplendens (Sycamore Borer Moth) observed by dlevitis on 2020-07-10 + + + Observation 52710138 + + + + + [52710139] genus: Cyperus (flatsedges) observed by dlevitis on 2020-07-10 + + + Observation 52710139 + + + + + [52710140] species: Aralia californica (California Spikenard) observed by dlevitis on 2020-07-10 + + + Observation 52710140 + + + + + [52710149] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + the vine + + Observation 52710149 + + + + + [52710150] species: Aralia californica (California Spikenard) observed by dlevitis on 2020-07-10 + + + Observation 52710150 + + + + + [52710151] tribe: Astereae (asters and allies) observed by dlevitis on 2020-07-10 + + + Observation 52710151 + + + + + [52710159] species: Cirsium vulgare (bull thistle) observed by dlevitis on 2020-07-10 + + + Observation 52710159 + + + + + [52710162] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + + + Observation 52710162 + + + + + [52710165] order: Diptera (Flies) observed by dlevitis on 2020-07-10 + + + Observation 52710165 + + + + + [52710175] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + + + Observation 52710175 + + + + + [52710176] family: Convolvulaceae (bindweed family) observed by dlevitis on 2020-07-10 + + + Observation 52710176 + + + + + [52710177] species: Calystegia purpurata (Pacific False Bindweed) observed by dlevitis on 2020-07-10 + + + Observation 52710177 + + + + + [52710187] genus: Corylus (hazels) observed by dlevitis on 2020-07-10 + + + Observation 52710187 + + + + + [52710188] genus: Dryopteris (wood ferns) observed by dlevitis on 2020-07-10 + + + Observation 52710188 + + + + + [52710193] species: Acer macrophyllum (bigleaf maple) observed by dlevitis on 2020-07-10 + + + Observation 52710193 + + + + + [52710202] species: Aquarius remigis (Common Water Strider) observed by dlevitis on 2020-07-10 + + + Observation 52710202 + + + + + [52710205] species: Nasturtium officinale (watercress) observed by dlevitis on 2020-07-10 + + + Observation 52710205 + + + + + [52710206] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-10 + + + Observation 52710206 + + + + + [52710214] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-10 + + + Observation 52710214 + + + + + [52710216] genus: Calystegia (false bindweeds) observed by dlevitis on 2020-07-10 + + + Observation 52710216 + + + + + [52710221] species: Woodwardia fimbriata (giant chain fern) observed by dlevitis on 2020-07-10 + + + Observation 52710221 + + + + + [52710228] species: Apis mellifera (Western Honey Bee) observed by dlevitis on 2020-07-10 + + + Observation 52710228 + + + + + [52710229] species: Aralia californica (California Spikenard) observed by dlevitis on 2020-07-10 + + + Observation 52710229 + + + + + [52710237] genus: Mordella (no common name) observed by dlevitis on 2020-07-10 + + + Observation 52710237 + + + + + [52710242] tribe: Alticini (Flea Beetles) observed by dlevitis on 2020-07-10 + + + Observation 52710242 + + + + + [52710244] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-10 + + + Observation 52710244 + + + + + [52710248] species: Ficus carica (common fig) observed by dlevitis on 2020-07-10 + + + Observation 52710248 + + + + + [52710253] species: Aralia californica (California Spikenard) observed by dlevitis on 2020-07-10 + + + Observation 52710253 + + + + + [52710255] species: Acer macrophyllum (bigleaf maple) observed by dlevitis on 2020-07-10 + + + Observation 52710255 + + + + + [52710263] kingdom: Animalia (Animals) observed by dlevitis on 2020-07-10 + Who is eating the maple leaves? + + Observation 52710263 + + + + + [52710264] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-10 + concentric lesions on toyon leaves + + Observation 52710264 + + + + + [52710266] species: Baccharis pilularis (coyote brush) observed by dlevitis on 2020-07-10 + + + Observation 52710266 + + + + + [52710272] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52710272 + + + + + [52710276] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52710276 + + + + + [52710277] species: Liothrips ilex (toyon gall thrips) observed by dlevitis on 2020-07-10 + on Toyon + + Observation 52710277 + + + + + [52710279] species: Cryptoporus volvatus (Veiled Polypore) observed by dlevitis on 2020-07-10 + + + Observation 52710279 + + + + + [52710293] genus: Calystegia (false bindweeds) observed by dlevitis on 2020-07-10 + + + Observation 52710293 + + + + + [52710294] species: Cryptoporus volvatus (Veiled Polypore) observed by dlevitis on 2020-07-10 + growing on fire-killed douglas fir + + Observation 52710294 + + + + + [52710295] species: Artemisia douglasiana (California mugwort) observed by dlevitis on 2020-07-10 + + + Observation 52710295 + + + + + [52710308] genus: Erigeron (Fleabanes and Horseweeds) observed by dlevitis on 2020-07-10 + + + Observation 52710308 + + + + + [52710310] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52710310 + + + + + [52712259] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + + + Observation 52712259 + + + + + [52712260] genus: Cirsium (thistles) observed by dlevitis on 2020-07-10 + + + Observation 52712260 + + + + + [52712261] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712261 + + + + + [52712268] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-10 + + + Observation 52712268 + + + + + [52712269] species: Elymus glaucus (blue wild rye) observed by dlevitis on 2020-07-10 + + + Observation 52712269 + + + + + [52712270] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52712270 + + + + + [52712275] species: Centaurea solstitialis (Yellow Starthistle) observed by dlevitis on 2020-07-10 + + + Observation 52712275 + + + + + [52712276] species: Achillea millefolium (common yarrow) observed by dlevitis on 2020-07-10 + + + Observation 52712276 + + + + + [52712279] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-10 + + + Observation 52712279 + + + + + [52712286] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52712286 + + + + + [52712287] species: Elymus elymoides (squirreltail) observed by dlevitis on 2020-07-10 + + + Observation 52712287 + + + + + [52712293] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + + + Observation 52712293 + + + + + [52712296] species: Quercus berberidifolia (California scrub oak) observed by dlevitis on 2020-07-10 + + + Observation 52712296 + + + + + [52712301] species: Quercus agrifolia (coast live oak) observed by dlevitis on 2020-07-10 + + + Observation 52712301 + + + + + [52712306] genus: Eriogonum (wild buckwheats) observed by dlevitis on 2020-07-10 + + + Observation 52712306 + + + + + [52712308] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712308 + + + + + [52712309] species: Contopus cooperi (Olive-sided Flycatcher) observed by dlevitis on 2020-07-10 + + + Observation 52712309 + + + + + [52712312] species: Diplacus aurantiacus (orange bush monkeyflower) observed by dlevitis on 2020-07-10 + + + Observation 52712312 + + + + + [52712315] species: Empidonax difficilis (Pacific-slope Flycatcher) observed by dlevitis on 2020-07-10 + + + Observation 52712315 + + + + + [52712320] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712320 + + + + + [52712321] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + + Observation 52712321 + + + + + [52712323] species: Cryptoporus volvatus (Veiled Polypore) observed by dlevitis on 2020-07-10 + + + Observation 52712323 + + + + + [52712325] order: Pucciniales (rust fungi) observed by dlevitis on 2020-07-10 + What makes these spots on the leaves of a re-growing madrone? + + Observation 52712325 + + + + + [52712330] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712330 + + + + + [52712332] genus: Convolvulus (bindweeds) observed by dlevitis on 2020-07-10 + + + Observation 52712332 + + + + + [52712339] species: Calystegia occidentalis (Western morning glory) observed by dlevitis on 2020-07-10 + + + Observation 52712339 + + + + + [52712340] species: Lathyrus vestitus (Pacific pea) observed by dlevitis on 2020-07-10 + + + Observation 52712340 + + + + + [52712344] species: Monardella villosa (Coyote Mint) observed by dlevitis on 2020-07-10 + + + Observation 52712344 + + + + + [52712356] class: Insecta (Insects) observed by dlevitis on 2020-07-10 + who has been eating this leaf? + + Observation 52712356 + + + + + [52712357] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712357 + + + + + [52712363] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712363 + + + + + [52712375] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + + + Observation 52712375 + + + + + [52712382] family: Convolvulaceae (bindweed family) observed by dlevitis on 2020-07-10 + + + Observation 52712382 + + + + + [52712383] subfamily: Faboideae (no common name) observed by dlevitis on 2020-07-10 + + + Observation 52712383 + + + + + [52712393] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712393 + + + + + [52712399] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-10 + + + Observation 52712399 + + + + + [52712400] species: Alnus rhombifolia (white alder) observed by dlevitis on 2020-07-10 + + + Observation 52712400 + + + + + [52712407] subfamily: Noctuinae (Cutworms and Dart Moths) observed by dlevitis on 2020-07-10 + on alder + + Observation 52712407 + + + + + [52712410] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-10 + + + Observation 52712410 + + + + + [52712417] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-10 + + + Observation 52712417 + + + + + [52712421] genus: Sonchus (sow thistles) observed by dlevitis on 2020-07-10 + + + Observation 52712421 + + + + + [52712427] species: Aquilegia formosa (western columbine) observed by dlevitis on 2020-07-10 + + + Observation 52712427 + + + + + [52712436] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-10 + + + Observation 52712436 + + + + + [52712447] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-10 + + + Observation 52712447 + + + + + [52712448] species: Calycanthus occidentalis (California sweetshrub) observed by dlevitis on 2020-07-10 + + + Observation 52712448 + + + + + [52718353] family: Convolvulaceae (bindweed family) observed by dlevitis on 2020-07-10 + + Observation 52718353 + + + + + [52718392] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-10 + + Observation 52718392 + + + + + [52718422] species: Aesculus californica (California buckeye) observed by dlevitis on 2020-07-10 + + Observation 52718422 + + + + + [52718462] species: Carex nudata (torrent sedge) observed by dlevitis on 2020-07-10 + + Observation 52718462 + + + + + [52718492] family: Poaceae (grasses) observed by dlevitis on 2020-07-10 + + Observation 52718492 + + + + + [52718505] genus: Clarkia (no common name) observed by dlevitis on 2020-07-10 + + Observation 52718505 + + + + + [52718527] species: Aesculus californica (California buckeye) observed by dlevitis on 2020-07-10 + + Observation 52718527 + + + + + [52718574] class: Magnoliopsida (dicots) observed by dlevitis on 2020-07-10 + + Observation 52718574 + + + + + [52718620] species: Calycanthus occidentalis (California sweetshrub) observed by dlevitis on 2020-07-10 + + Observation 52718620 + + + + + [52718633] species: Acer macrophyllum (bigleaf maple) observed by dlevitis on 2020-07-10 + + Observation 52718633 + + + + + [52718657] species: Aralia californica (California Spikenard) observed by dlevitis on 2020-07-10 + + Observation 52718657 + + + + + [52966686] genus: Verbascum (mulleins) observed by avyfain on 2020-07-13 + + Observation 52966686 + + + + + [52970711] species: Heracleum maximum (common cowparsnip) observed by sunflowerguy on 2020-07-13 + + Observation 52970711 + + + + + [53064490] species: Lepus californicus (Black-tailed Jackrabbit) observed by woody54 on 2020-07-13 + + Observation 53064490 + + + + + [53188708] species: Dermacentor variabilis (American Dog Tick) observed by woody54 on 2020-07-15 + + Observation 53188708 + + + + + [53189419] species: Erythranthe cardinalis (scarlet monkeyflower) observed by woody54 on 2020-07-15 + + Observation 53189419 + + + + + [53225611] subspecies: Elgaria multicarinata multicarinata (California Alligator Lizard) observed by woody54 on 2020-07-15 + + Observation 53225611 + + + + + [53229231] order: Odonata (Dragonflies and Damselflies) observed by dlevitis on 2020-07-10 + The nymph in the lower left. + + Observation 53229231 + + + + + [53263723] genus: Cirsium (thistles) observed by woody54 on 2020-07-15 + + Observation 53263723 + + + + + [53296738] tribe: Madieae (tarweeds and allies) observed by tomlstedman on 2020-07-16 + + Observation 53296738 + + + + + [53318651] genus: Sambucus (elders) observed by stacatto on 2020-06-06 + + Observation 53318651 + + + + + [53435196] infraorder: Tipulomorpha (Crane Flies) observed by dlevitis on 2020-07-07 + Seemed large even for a giant crane fly. I fished it out and it flew right back into the creek. + + Observation 53435196 + + + + + [53435203] tribe: Gerrini (no common name) observed by dlevitis on 2020-07-07 + + + Observation 53435203 + + + + + [53435204] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-07-07 + + + Observation 53435204 + + + + + [53435208] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-07-07 + + + Observation 53435208 + + + + + [53435209] species: Empidonax difficilis (Pacific-slope Flycatcher) observed by dlevitis on 2020-07-07 + + + Observation 53435209 + + + + + [53438612] species: Icaricia acmon (Acmon Blue) observed by dlevitis on 2020-07-17 + + + Observation 53438612 + + + + + [53438613] genus: Erythranthe (no common name) observed by dlevitis on 2020-07-17 + The yellow one. + + Observation 53438613 + + + + + [53438614] species: Cosmopepla conspicillaris (Hedge Nettle Stink Bug) observed by dlevitis on 2020-07-17 + On nettle + + Observation 53438614 + + + + + [53438618] species: Cosmopepla conspicillaris (Hedge Nettle Stink Bug) observed by dlevitis on 2020-07-17 + + + Observation 53438618 + + + + + [53438619] species: Cosmopepla lintneriana (Twice-stabbed Stink Bug) observed by dlevitis on 2020-07-17 + + + Observation 53438619 + + + + + [53438621] species: Alnus rhombifolia (white alder) observed by dlevitis on 2020-07-17 + + + Observation 53438621 + + + + + [53438622] phylum: Chlorophyta (green algae) observed by dlevitis on 2020-07-17 + + + Observation 53438622 + + + + + [53438623] suborder: Anisoptera (Dragonflies) observed by dlevitis on 2020-07-17 + Large bright blue dragonfly patrolling back and forth over a pool in Sonoma Creek. It never held still in the five minutes I was there, just kept following the same route, five to fifteen feet over the water. + + Observation 53438623 + + + + + [53438625] species: Paltothemis lineatipes (Red Rock Skimmer) observed by dlevitis on 2020-07-17 + + + Observation 53438625 + + + + + [53438626] genus: Rubus (brambles) observed by dlevitis on 2020-07-17 + + + Observation 53438626 + + + + + [53438627] species: Sonchus asper (prickly sowthistle) observed by dlevitis on 2020-07-17 + + + Observation 53438627 + + + + + [53438630] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53438630 + + + + + [53438631] species: Cottus asper (Prickly Sculpin) observed by dlevitis on 2020-07-17 + + + Observation 53438631 + + + + + [53438632] species: Apis mellifera (Western Honey Bee) observed by dlevitis on 2020-07-17 + + + Observation 53438632 + + + + + [53438633] species: Cottus asper (Prickly Sculpin) observed by dlevitis on 2020-07-17 + + + Observation 53438633 + + + + + [53439484] species: Empidonax difficilis (Pacific-slope Flycatcher) observed by dlevitis on 2020-07-17 + + + Observation 53439484 + + + + + [53439485] class: Aves (Birds) observed by dlevitis on 2020-07-17 + This slightly larger than robin sized bird only stopped for a moment. I upload this terrible picture only because iNat's bird identifiers seem to be able to ID anything no matter the quality. + + Observation 53439485 + + + + + [53439486] species: Fraxinus latifolia (Oregon Ash) observed by dlevitis on 2020-07-17 + + + Observation 53439486 + + + + + [53439488] species: Poecile rufescens (Chestnut-backed Chickadee) observed by dlevitis on 2020-07-17 + + + Observation 53439488 + + + + + [53439489] genus: Persicaria (knotweeds, smartweeds, and waterpeppers) observed by dlevitis on 2020-07-17 + + + Observation 53439489 + + + + + [53439490] order: Erysiphales (Powdery Mildews) observed by dlevitis on 2020-07-17 + On Persicaria + + Observation 53439490 + + + + + [53439494] species: Rubus armeniacus (Armenian Blackberry) observed by dlevitis on 2020-07-17 + + + Observation 53439494 + + + + + [53439495] species: Oenanthe sarmentosa (water parsley) observed by dlevitis on 2020-07-17 + + + Observation 53439495 + + + + + [53439496] species: Acer macrophyllum (bigleaf maple) observed by dlevitis on 2020-07-17 + + + Observation 53439496 + + + + + [53439498] phylum: Bryophyta (mosses) observed by dlevitis on 2020-07-17 + + + Observation 53439498 + + + + + [53439499] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53439499 + + + + + [53439500] species: Cirsium vulgare (bull thistle) observed by dlevitis on 2020-07-17 + + + Observation 53439500 + + + + + [53439504] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-17 + + + Observation 53439504 + + + + + [53439505] species: Cosmopepla conspicillaris (Hedge Nettle Stink Bug) observed by dlevitis on 2020-07-17 + + + Observation 53439505 + + + + + [53439506] species: Cottus asper (Prickly Sculpin) observed by dlevitis on 2020-07-17 + + + Observation 53439506 + + + + + [53439507] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53439507 + + + + + [53439508] species: Carex nudata (torrent sedge) observed by dlevitis on 2020-07-17 + + + Observation 53439508 + + + + + [53439510] species: Sequoia sempervirens (coast redwood) observed by dlevitis on 2020-07-17 + + + Observation 53439510 + + + + + [53439511] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53439511 + + + + + [53439512] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53439512 + + + + + [53441147] subfamily: Phytomyzinae (no common name) observed by dlevitis on 2020-07-17 + In Erigeron. A guess. + + Observation 53441147 + + + + + [53441148] genus: Erigeron (Fleabanes and Horseweeds) observed by dlevitis on 2020-07-17 + + + Observation 53441148 + + + + + [53441149] species: Artemisia vulgaris (common mugwort) observed by dlevitis on 2020-07-17 + + + Observation 53441149 + + + + + [53441155] species: Urtica dioica (stinging nettle) observed by dlevitis on 2020-07-17 + Stems about 4 feet long + + Observation 53441155 + + + + + [53441156] species: Agromyza pseudoreptans (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53441156 + + + + + [53441157] order: Erysiphales (Powdery Mildews) observed by dlevitis on 2020-07-17 + on stinging nettle + + Observation 53441157 + + + + + [53441161] species: Aquarius remigis (Common Water Strider) observed by dlevitis on 2020-07-17 + + + Observation 53441161 + + + + + [53441164] species: Aralia californica (California Spikenard) observed by dlevitis on 2020-07-17 + + + Observation 53441164 + + + + + [53441165] species: Carex nudata (torrent sedge) observed by dlevitis on 2020-07-17 + + + Observation 53441165 + + + + + [53441170] genus: Geranium (geraniums and cranesbills) observed by dlevitis on 2020-07-17 + + + Observation 53441170 + + + + + [53441173] species: Alnus rhombifolia (white alder) observed by dlevitis on 2020-07-17 + + + Observation 53441173 + + + + + [53441174] species: Calycanthus occidentalis (California sweetshrub) observed by dlevitis on 2020-07-17 + + + Observation 53441174 + + + + + [53441178] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53441178 + + + + + [53441180] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53441180 + + + + + [53441181] species: Sequoia sempervirens (coast redwood) observed by dlevitis on 2020-07-17 + + + Observation 53441181 + + + + + [53441185] phylum: Arthropoda (Arthropods) observed by dlevitis on 2020-07-17 + Tiny leaf galls, I think, on one leaf only of California Spikenard + + Observation 53441185 + + + + + [53441186] species: Vespula pensylvanica (Western Yellowjacket) observed by dlevitis on 2020-07-17 + + + Observation 53441186 + + + + + [53441187] family: Perlodidae (Stripetail and Springfly Stoneflies) observed by dlevitis on 2020-07-17 + + + Observation 53441187 + + + + + [53441189] species: Sequoia sempervirens (coast redwood) observed by dlevitis on 2020-07-17 + + + Observation 53441189 + + + + + [53441190] species: Aquarius remigis (Common Water Strider) observed by dlevitis on 2020-07-17 + + + Observation 53441190 + + + + + [53441192] species: Sequoia sempervirens (coast redwood) observed by dlevitis on 2020-07-17 + + + Observation 53441192 + + + + + [53441194] species: Polygonia satyrus (Satyr Comma) observed by dlevitis on 2020-07-17 + + + Observation 53441194 + + + + + [53441196] tribe: Agabini (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53441196 + + + + + [53441197] species: Urtica dioica (stinging nettle) observed by dlevitis on 2020-07-17 + + + Observation 53441197 + + + + + [53441198] subfamily: Larentiinae (Carpet Moths) observed by dlevitis on 2020-07-17 + + + Observation 53441198 + + + + + [53441199] genus: Lepraria (Dust Lichens) observed by dlevitis on 2020-07-17 + Growing on a shaded bit of bark just over the creek + + Observation 53441199 + + + + + [53441200] subspecies: Odocoileus hemionus columbianus (Columbian Black-tailed Deer) observed by dlevitis on 2020-07-17 + + + Observation 53441200 + + + + + [53441202] family: Salmonidae (Salmons, Trouts, and Whitefishes) observed by dlevitis on 2020-07-17 + + + Observation 53441202 + + + + + [53441203] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53441203 + + + + + [53441205] species: Cyperus eragrostis (tall flatsedge) observed by dlevitis on 2020-07-17 + + + Observation 53441205 + + + + + [53441207] species: Erythranthe cardinalis (scarlet monkeyflower) observed by dlevitis on 2020-07-17 + + + Observation 53441207 + + + + + [53441208] species: Sequoia sempervirens (coast redwood) observed by dlevitis on 2020-07-17 + + + Observation 53441208 + + + + + [53441211] family: Chrysopidae (Green Lacewings) observed by dlevitis on 2020-07-17 + + + Observation 53441211 + + + + + [53441550] species: Lestes congener (Spotted Spreadwing) observed by dlevitis on 2020-07-17 + + + Observation 53441550 + + + + + [53441551] genus: Cirsium (thistles) observed by dlevitis on 2020-07-17 + + + Observation 53441551 + + + + + [53441552] genus: Cirsium (thistles) observed by dlevitis on 2020-07-17 + + + Observation 53441552 + + + + + [53441556] species: Centaurea solstitialis (Yellow Starthistle) observed by dlevitis on 2020-07-17 + + + Observation 53441556 + + + + + [53441558] genus: Cirsium (thistles) observed by dlevitis on 2020-07-17 + + + Observation 53441558 + + + + + [53441559] species: Icaricia acmon (Acmon Blue) observed by dlevitis on 2020-07-17 + + + Observation 53441559 + + + + + [53441562] species: Eriogonum nudum (Naked Buckwheat) observed by dlevitis on 2020-07-17 + + + Observation 53441562 + + + + + [53441564] species: Ochlodes sylvanoides (Woodland Skipper) observed by dlevitis on 2020-07-17 + + + Observation 53441564 + + + + + [53441565] genus: Stephanomeria (wirelettuce) observed by dlevitis on 2020-07-17 + + + Observation 53441565 + + + + + [53441569] species: Cercyonis pegala (Common Wood-Nymph) observed by dlevitis on 2020-07-17 + + + Observation 53441569 + + + + + [53441571] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-17 + + + Observation 53441571 + + + + + [53441572] genus: Cercyonis (Wood-Nymphs) observed by dlevitis on 2020-07-17 + + + Observation 53441572 + + + + + [53441581] subfamily: Pooideae (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53441581 + + + + + [53441582] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-17 + + + Observation 53441582 + + + + + [53441583] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-17 + + + Observation 53441583 + + + + + [53441589] species: Trimerotropis fontana (Fontana Grasshopper) observed by dlevitis on 2020-07-17 + + + Observation 53441589 + + + + + [53441590] species: Acer macrophyllum (bigleaf maple) observed by dlevitis on 2020-07-17 + + + Observation 53441590 + + + + + [53441595] epifamily: Anthophila (Bees) observed by dlevitis on 2020-07-17 + on morning glory + + Observation 53441595 + + + + + [53441602] genus: Calystegia (false bindweeds) observed by dlevitis on 2020-07-17 + + + Observation 53441602 + + + + + [53517458] order: Lepidoptera (Butterflies and Moths) observed by dlevitis on 2020-07-17 + + + Observation 53517458 + + + + + [53517460] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-17 + + + Observation 53517460 + + + + + [53517462] family: Convolvulaceae (bindweed family) observed by dlevitis on 2020-07-17 + + + Observation 53517462 + + + + + [53517472] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-17 + + + Observation 53517472 + + + + + [53517473] species: Marmara arbutiella (Madrone Skin Miner) observed by dlevitis on 2020-07-17 + + + Observation 53517473 + + + + + [53517476] species: Notholithocarpus densiflorus (Tanoak) observed by dlevitis on 2020-07-17 + + + Observation 53517476 + + + + + [53517481] species: Cyanocitta stelleri (Steller's Jay) observed by dlevitis on 2020-07-17 + + + Observation 53517481 + + + + + [53517486] species: Eurybia radulina (roughleaf aster) observed by dlevitis on 2020-07-17 + + + Observation 53517486 + + + + + [53517488] genus: Asteromyia (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53517488 + + + + + [53517493] genus: Botryosphaeria (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53517493 + + + + + [53517508] species: Eurybia radulina (roughleaf aster) observed by dlevitis on 2020-07-17 + + + Observation 53517508 + + + + + [53517512] genus: Asteromyia (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53517512 + + + + + [53517514] genus: Botryosphaeria (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53517514 + + + + + [53517523] species: Eurybia radulina (roughleaf aster) observed by dlevitis on 2020-07-17 + + + Observation 53517523 + + + + + [53517537] genus: Asteromyia (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53517537 + + + + + [53517560] genus: Botryosphaeria (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53517560 + + + + + [53517570] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-17 + + + Observation 53517570 + + + + + [53517595] subspecies: Odocoileus hemionus columbianus (Columbian Black-tailed Deer) observed by dlevitis on 2020-07-17 + + + Observation 53517595 + + + + + [53517606] genus: Quercus (oaks) observed by dlevitis on 2020-07-17 + + + Observation 53517606 + + + + + [53517626] order: Araneae (Spiders) observed by dlevitis on 2020-07-17 + + + Observation 53517626 + + + + + [53517638] genus: Arctostaphylos (bearberries and manzanitas) observed by dlevitis on 2020-07-17 + + + Observation 53517638 + + + + + [53517654] subfamily: Hydrophylloideae (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53517654 + + + + + [53517693] genus: Calystegia (false bindweeds) observed by dlevitis on 2020-07-17 + + + Observation 53517693 + + + + + [53517741] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-17 + + + Observation 53517741 + + + + + [53517774] species: Adenostoma fasciculatum (chamise) observed by dlevitis on 2020-07-17 + + + Observation 53517774 + + + + + [53517807] species: Cynosurus echinatus (bristly dogtail grass) observed by dlevitis on 2020-07-17 + + + Observation 53517807 + + + + + [53521747] order: Psocodea (Barklice, Booklice, and Parasitic Lice) observed by dlevitis on 2020-07-17 + + + Observation 53521747 + + + + + [53521749] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-17 + + + Observation 53521749 + + + + + [53521750] species: Cynosurus echinatus (bristly dogtail grass) observed by dlevitis on 2020-07-17 + + + Observation 53521750 + + + + + [53521759] species: Vespula pensylvanica (Western Yellowjacket) observed by dlevitis on 2020-07-17 + Flew through while I was photographic grass + + Observation 53521759 + + + + + [53521763] phylum: Bryophyta (mosses) observed by dlevitis on 2020-07-17 + + + Observation 53521763 + + + + + [53521772] species: Graphopsocus cruciatus (F-Winged Barklouse) observed by dlevitis on 2020-07-17 + Found dead on a leaf of toyon + + Observation 53521772 + + + + + [53521784] class: Mammalia (Mammals) observed by dlevitis on 2020-07-17 + A cluster of similar sized burrow openings + + Observation 53521784 + + + + + [53521794] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-17 + + + Observation 53521794 + + + + + [53521805] species: Poecile rufescens (Chestnut-backed Chickadee) observed by dlevitis on 2020-07-17 + + + Observation 53521805 + + + + + [53521821] species: Vitis californica (California wild grape) observed by dlevitis on 2020-07-17 + + + Observation 53521821 + + + + + [53521826] class: Insecta (Insects) observed by dlevitis on 2020-07-17 + + + Observation 53521826 + + + + + [53521830] species: Carex nudata (torrent sedge) observed by dlevitis on 2020-07-17 + + + Observation 53521830 + + + + + [53521843] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-07-17 + + + Observation 53521843 + + + + + [53521845] tribe: Gerrini (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53521845 + + + + + [53521856] species: Argia vivida (Vivid Dancer) observed by dlevitis on 2020-07-17 + + + Observation 53521856 + + + + + [53521860] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-17 + + + Observation 53521860 + + + + + [53521865] species: Ficus carica (common fig) observed by dlevitis on 2020-07-17 + + + Observation 53521865 + + + + + [53521870] genus: Dryopteris (wood ferns) observed by dlevitis on 2020-07-17 + + + Observation 53521870 + + + + + [53521872] genus: Rosa (roses) observed by dlevitis on 2020-07-17 + + + Observation 53521872 + + + + + [53521879] species: Adiantum jordanii (California Maidenhair Fern) observed by dlevitis on 2020-07-17 + + + Observation 53521879 + + + + + [53521891] species: Atypoides riversi (California Turret Spider) observed by dlevitis on 2020-07-17 + + + Observation 53521891 + + + + + [53521900] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-17 + + + Observation 53521900 + + + + + [53521903] family: Apiaceae (carrot family) observed by dlevitis on 2020-07-17 + + + Observation 53521903 + + + + + [53521912] kingdom: Animalia (Animals) observed by dlevitis on 2020-07-17 + Who folds up bay leaves like this, please? + + Observation 53521912 + + + + + [53521923] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-17 + + + Observation 53521923 + + + + + [53521930] tribe: Satyrini (Alpines, Arctics, Nymphs and Satyrs) observed by dlevitis on 2020-07-17 + + + Observation 53521930 + + + + + [53521932] species: Acer macrophyllum (bigleaf maple) observed by dlevitis on 2020-07-17 + + + Observation 53521932 + + + + + [53521940] genus: Quercus (oaks) observed by dlevitis on 2020-07-17 + + + Observation 53521940 + + + + + [53521943] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-17 + + + Observation 53521943 + + + + + [53521953] genus: Scudderia (Scudder's Bush Katydids) observed by dlevitis on 2020-07-17 + + + Observation 53521953 + + + + + [53521957] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-17 + + + Observation 53521957 + + + + + [53521962] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-17 + + + Observation 53521962 + + + + + [53521968] species: Eurybia radulina (roughleaf aster) observed by dlevitis on 2020-07-17 + + + Observation 53521968 + + + + + [53521976] genus: Asteromyia (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53521976 + + + + + [53521982] genus: Botryosphaeria (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53521982 + + + + + [53521986] species: Eurybia radulina (roughleaf aster) observed by dlevitis on 2020-07-17 + + + Observation 53521986 + + + + + [53521991] genus: Asteromyia (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53521991 + + + + + [53521997] genus: Botryosphaeria (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53521997 + + + + + [53522002] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-17 + + + Observation 53522002 + + + + + [53561843] species: Heteromeles arbutifolia (Toyon) observed by dlevitis on 2020-07-17 + + + Observation 53561843 + + + + + [53561844] species: Stereum hirsutum (hairy curtain crust) observed by dlevitis on 2020-07-17 + + + Observation 53561844 + + + + + [53561846] species: Naematelia aurantia (golden ear) observed by dlevitis on 2020-07-17 + + + Observation 53561846 + + + + + [53561848] tribe: Heliantheae (sunflowers and allies) observed by dlevitis on 2020-07-17 + + + Observation 53561848 + + + + + [53561849] genus: Arctostaphylos (bearberries and manzanitas) observed by dlevitis on 2020-07-17 + + + Observation 53561849 + + + + + [53561850] genus: Pseudognaphalium (rabbit-tobaccos) observed by dlevitis on 2020-07-17 + + + Observation 53561850 + + + + + [53561852] genus: Arctostaphylos (bearberries and manzanitas) observed by dlevitis on 2020-07-17 + + + Observation 53561852 + + + + + [53561853] species: Quercus agrifolia (coast live oak) observed by dlevitis on 2020-07-17 + + + Observation 53561853 + + + + + [53561855] genus: Stigmella (no common name) observed by dlevitis on 2020-07-17 + Leaf miner in oak + + Observation 53561855 + + + + + [53561856] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-17 + + + Observation 53561856 + + + + + [53561860] genus: Usnea (beard lichens) observed by dlevitis on 2020-07-17 + + + Observation 53561860 + + + + + [53561864] subfamily: Parmelioideae (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53561864 + + + + + [53561865] species: Evernia prunastri (Oakmoss) observed by dlevitis on 2020-07-17 + + + Observation 53561865 + + + + + [53561866] species: Stereum hirsutum (hairy curtain crust) observed by dlevitis on 2020-07-17 + + + Observation 53561866 + + + + + [53561867] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-17 + + + Observation 53561867 + + + + + [53561868] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-17 + + + Observation 53561868 + + + + + [53561869] species: Diplacus aurantiacus (orange bush monkeyflower) observed by dlevitis on 2020-07-17 + sticky to the touch + + Observation 53561869 + + + + + [53561870] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-17 + + + Observation 53561870 + + + + + [53561871] phylum: Arthropoda (Arthropods) observed by dlevitis on 2020-07-17 + Found crawling on my arm after falling in the brush + + Observation 53561871 + + + + + [53561872] species: Achillea millefolium (common yarrow) observed by dlevitis on 2020-07-17 + + + Observation 53561872 + + + + + [53561874] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-17 + + + Observation 53561874 + + + + + [53561875] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-17 + + + Observation 53561875 + + + + + [53561878] suborder: Brachycera (Brachyceran Flies) observed by dlevitis on 2020-07-17 + + + Observation 53561878 + + + + + [53561880] tribe: Heliantheae (sunflowers and allies) observed by dlevitis on 2020-07-17 + + + Observation 53561880 + + + + + [53561884] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-17 + + + Observation 53561884 + + + + + [53561885] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-17 + + + Observation 53561885 + + + + + [53561886] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-17 + + + Observation 53561886 + + + + + [53563224] genus: Homalothecium (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53563224 + + + + + [53563225] species: Cosmopepla conspicillaris (Hedge Nettle Stink Bug) observed by dlevitis on 2020-07-17 + + + Observation 53563225 + + + + + [53563227] species: Maianthemum stellatum (star-flowered lily-of-the-valley) observed by dlevitis on 2020-07-17 + + + Observation 53563227 + + + + + [53563229] species: Eurybia radulina (roughleaf aster) observed by dlevitis on 2020-07-17 + + + Observation 53563229 + + + + + [53563231] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-17 + + + Observation 53563231 + + + + + [53563232] subfamily: Caprifolioideae (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53563232 + + + + + [53563237] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-17 + + + Observation 53563237 + + + + + [53563239] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-17 + + + Observation 53563239 + + + + + [53563240] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-17 + + + Observation 53563240 + + + + + [53563242] species: Trimerotropis fontana (Fontana Grasshopper) observed by dlevitis on 2020-07-17 + + + Observation 53563242 + + + + + [53563243] genus: Argia (Dancers) observed by dlevitis on 2020-07-17 + + + Observation 53563243 + + + + + [53563246] family: Convolvulaceae (bindweed family) observed by dlevitis on 2020-07-17 + + + Observation 53563246 + + + + + [53563250] species: Eudrepanulatrix rectifascia (no common name) observed by dlevitis on 2020-07-17 + + + Observation 53563250 + + + + + [53563252] genus: Madia (tarweeds) observed by dlevitis on 2020-07-17 + + + Observation 53563252 + + + + + [53563253] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-17 + What causes these spots on toyon, please? + + Observation 53563253 + + + + + [53630712] species: Stephanomeria exigua (small wirelettuce) observed by tomlstedman on 2020-07-19 + + Observation 53630712 + + + + + [53681568] species: Adelpha californica (California Sister) observed by eastbaybirding on 2020-07-19 + + + Observation 53681568 + + + + + [53769432] species: Mentha suaveolens (Apple Mint) observed by jrlynx on 2020-07-20 + + Observation 53769432 + + + + + [53769434] species: Acmispon glaber (deerweed) observed by jrlynx on 2020-07-20 + + Observation 53769434 + + + + + [53769435] species: Elymus glaucus (blue wild rye) observed by jrlynx on 2020-07-20 + + Observation 53769435 + + + + + [53769447] species: Rosa californica (California Wild Rose) observed by jrlynx on 2020-07-20 + + Observation 53769447 + + + + + [53769448] species: Toxicodendron diversilobum (Pacific poison oak) observed by jrlynx on 2020-07-20 + + Observation 53769448 + + + + + [53769449] species: Chlorogalum pomeridianum (wavy-leafed soap plant) observed by jrlynx on 2020-07-20 + + Observation 53769449 + + + + + [53769462] species: Hirschfeldia incana (Shortpod Mustard) observed by jrlynx on 2020-07-20 + + Observation 53769462 + + + + + [53769465] species: Perideridia kelloggii (Kellogg's yampah) observed by jrlynx on 2020-07-20 + + Observation 53769465 + + + + + [53769467] species: Agoseris grandiflora (bigflower agoseris) observed by jrlynx on 2020-07-20 + + Observation 53769467 + + + + + [53769473] species: Eriogonum nudum (Naked Buckwheat) observed by jrlynx on 2020-07-20 + + Observation 53769473 + + + + + [53769474] species: Umbellularia californica (California bay) observed by jrlynx on 2020-07-20 + + Observation 53769474 + + + + + [53769478] species: Cirsium vulgare (bull thistle) observed by jrlynx on 2020-07-20 + + Observation 53769478 + + + + + [53769481] species: Cuscuta californica (California dodder) observed by jrlynx on 2020-07-20 + + Observation 53769481 + + + + + [53769492] species: Eriogonum luteolum (wicker buckwheat) observed by jrlynx on 2020-07-20 + + Observation 53769492 + + + + + [53769496] species: Prunus domestica (Plum) observed by jrlynx on 2020-07-20 + + Observation 53769496 + + + + + [53769497] species: Phacelia imbricata (Mountain Phacelia) observed by jrlynx on 2020-07-20 + + Observation 53769497 + + + + + [53769512] species: Aculops rhois (Poison Ivy Leaf Mite) observed by jrlynx on 2020-07-20 + + Observation 53769512 + + + + + [53769513] species: Eriophyllum lanatum (common woolly sunflower) observed by jrlynx on 2020-07-20 + + Observation 53769513 + + + + + [53769517] species: Fritillaria affinis (checker lily) observed by jrlynx on 2020-07-20 + + Observation 53769517 + + + + + [53769526] species: Frontinella pyramitela (Bowl-and-doily Spider) observed by jrlynx on 2020-07-20 + + Observation 53769526 + + + + + [53769528] species: Hypochaeris radicata (Common Cat's-ear) observed by jrlynx on 2020-07-20 + + Observation 53769528 + + + + + [53769529] genus: Erigeron (Fleabanes and Horseweeds) observed by jrlynx on 2020-07-20 + + Observation 53769529 + + + + + [53769537] species: Eurybia radulina (roughleaf aster) observed by jrlynx on 2020-07-20 + + Observation 53769537 + + + + + [53809275] species: Chilocorus orbus (no common name) observed by chilipossum on 2020-07-20 + + Observation 53809275 + + + + + [53810496] genus: Oecanthus (Common Tree Crickets) observed by chilipossum on 2020-07-19 + + Observation 53810496 + + + + + [53810528] species: Lepus californicus (Black-tailed Jackrabbit) observed by chilipossum on 2020-07-19 + + Observation 53810528 + + + + + [53811080] species: Taricha torosa (California Newt) observed by alexlash on 2020-07-19 + + Observation 53811080 + + + + + [53811257] suborder: Zygoptera (Damselflies) observed by chilipossum on 2020-07-19 + + Observation 53811257 + + + + + [53816313] species: Gammarotettix bilobatus (no common name) observed by chilipossum on 2020-07-18 + + Observation 53816313 + + + + + [53858401] species: Taricha granulosa (Rough-skinned Newt) observed by chilipossum on 2020-07-18 + + Observation 53858401 + + + + + [53861234] species: Colonus hesperus (no common name) observed by chilipossum on 2020-07-19 + + Observation 53861234 + + + + + [53880898] genus: Ceanothus (no common name) observed by cloudya on 2020-03-03 + + Observation 53880898 + + + + + [53881013] species: Fritillaria affinis (checker lily) observed by cloudya on 2020-03-03 + + Observation 53881013 + + + + + [53938379] species: Arbutus menziesii (Pacific madrone) observed by kbradshaw04 on 2020-05-31 + Regenerating from roots. Bays also regenerating, but not doug fir....although the ones in this pic are alive at the top + + Observation 53938379 + + + + + [53971281] species: Hemizonia congesta (Hayfield Tarweed) observed by enhunn323 on 2020-07-22 + + Observation 53971281 + + + + + [53971315] species: Perideridia kelloggii (Kellogg's yampah) observed by enhunn323 on 2020-07-22 + + Observation 53971315 + + + + + [53971342] species: Lonicera hispidula (Pink Honeysuckle) observed by enhunn323 on 2020-07-22 + + Observation 53971342 + + + + + [53971356] species: Angelica hendersonii (Henderson's angelica) observed by enhunn323 on 2020-07-22 + + Observation 53971356 + + + + + [53996749] species: Pseudognaphalium obtusifolium (sweet everlasting) observed by tomlstedman on 2020-07-19 + + Observation 53996749 + + + + + [54053232] species: Cichorium intybus (chicory) observed by romar60 on 2020-07-22 + + Observation 54053232 + + + + + [54053460] species: Hoita macrostachya (large leatherroot) observed by romar60 on 2020-07-21 + + Observation 54053460 + + + + + [54102589] species: Helenium puberulum (Rosilla) observed by annmb on 2020-07-23 + + Observation 54102589 + + + + + [54179184] genus: Hieracium (hawkweeds) observed by mtamm on 2020-07-24 + + Observation 54179184 + + + + + [54182926] species: Eriogonum luteolum (wicker buckwheat) observed by tomlstedman on 2020-07-24 + + Observation 54182926 + + + + + [54183231] species: Eurybia radulina (roughleaf aster) observed by tomlstedman on 2020-07-24 + + Observation 54183231 + + + + + [54185964] species: Frangula californica (coffeeberry) observed by tomlstedman on 2020-07-24 + + Observation 54185964 + + + + + [54235274] species: Eriogonum fasciculatum (California buckwheat) observed by tomlstedman on 2020-07-24 + + Observation 54235274 + + + + + [54344044] species: Umbellularia californica (California bay) observed by stephanieroutson on 2020-07-25 + + Observation 54344044 + + + + + [54477537] species: Acer macrophyllum (bigleaf maple) observed by dave-barry on 2020-07-26 + + Observation 54477537 + + + + + [54477601] species: Centaurea solstitialis (Yellow Starthistle) observed by dave-barry on 2020-07-26 + + Observation 54477601 + + + + + [54477617] species: Eriogonum nudum (Naked Buckwheat) observed by dave-barry on 2020-07-26 + + Observation 54477617 + + + + + [54477846] species: Dudleya cymosa (Canyon Live-forever) observed by dave-barry on 2020-07-26 + + Observation 54477846 + + + + + [54477885] species: Hemizonia congesta (Hayfield Tarweed) observed by dave-barry on 2020-07-26 + + Observation 54477885 + + + + + [54477952] species: Festuca californica (California fescue) observed by dave-barry on 2020-07-26 + + Observation 54477952 + + + + + [54478036] species: Toxicodendron diversilobum (Pacific poison oak) observed by dave-barry on 2020-07-26 + + Observation 54478036 + + + + + [54488886] species: Canis latrans (Coyote) observed by dlevitis on 2020-07-26 + + + Observation 54488886 + + + + + [54491092] species: Carex nudata (torrent sedge) observed by dlevitis on 2020-07-26 + + + Observation 54491092 + + + + + [54491093] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-26 + + + Observation 54491093 + + + + + [54491094] species: Dryopteris arguta (coastal woodfern) observed by dlevitis on 2020-07-26 + + + Observation 54491094 + + + + + [54491095] species: Hippodamia convergens (Convergent Lady Beetle) observed by dlevitis on 2020-07-26 + + + Observation 54491095 + + + + + [54491096] species: Toxicodendron diversilobum (Pacific poison oak) observed by dlevitis on 2020-07-26 + + + Observation 54491096 + + + + + [54491098] phylum: Bryophyta (mosses) observed by dlevitis on 2020-07-26 + + + Observation 54491098 + + + + + [54491099] species: Aristolochia californica (California Dutchman's Pipe) observed by dlevitis on 2020-07-26 + + + Observation 54491099 + + + + + [54491100] species: Artemisia douglasiana (California mugwort) observed by dlevitis on 2020-07-26 + + + Observation 54491100 + + + + + [54491101] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-26 + + + Observation 54491101 + + + + + [54491102] genus: Daldinia (no common name) observed by dlevitis on 2020-07-26 + growing out of a living California Bay + + Observation 54491102 + + + + + [54491103] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-26 + + + Observation 54491103 + + + + + [54491104] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-26 + + + Observation 54491104 + + + + + [54491106] species: Frangula californica (coffeeberry) observed by dlevitis on 2020-07-26 + + + Observation 54491106 + + + + + [54491108] species: Frangula californica (coffeeberry) observed by dlevitis on 2020-07-26 + + + Observation 54491108 + + + + + [54491109] genus: Stigmella (no common name) observed by dlevitis on 2020-07-26 + Leafmine in Coffeeberry + + Observation 54491109 + + + + + [54491111] genus: Galium (bedstraws) observed by dlevitis on 2020-07-26 + + + Observation 54491111 + + + + + [54491113] species: Achillea millefolium (common yarrow) observed by dlevitis on 2020-07-26 + + + Observation 54491113 + + + + + [54491114] genus: Stigmella (no common name) observed by dlevitis on 2020-07-26 + leafminer in coffeeberry + + Observation 54491114 + + + + + [54491115] species: Megascops kennicottii (Western Screech-Owl) observed by dlevitis on 2020-07-26 + + + Observation 54491115 + + + + + [54491116] species: Frangula californica (coffeeberry) observed by dlevitis on 2020-07-26 + + + Observation 54491116 + + + + + [54491117] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-26 + + + Observation 54491117 + + + + + [54491118] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-26 + + + Observation 54491118 + + + + + [54491120] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-26 + Fire killed, presumably in 2017 + + Observation 54491120 + + + + + [54491121] order: Polyporales (shelf fungi) observed by dlevitis on 2020-07-26 + Growing on a dead doug fir. + + Observation 54491121 + + + + + [54491122] genus: Quercus (oaks) observed by dlevitis on 2020-07-26 + + + Observation 54491122 + + + + + [54491123] species: Quercus berberidifolia (California scrub oak) observed by dlevitis on 2020-07-26 + + + Observation 54491123 + + + + + [54491124] subfamily: Asteroideae (no common name) observed by dlevitis on 2020-07-26 + + + Observation 54491124 + + + + + [54491125] tribe: Ruschieae (Brightfig Tribe) observed by dlevitis on 2020-07-26 + + + Observation 54491125 + + + + + [54491126] genus: Adenostoma (no common name) observed by dlevitis on 2020-07-26 + + + Observation 54491126 + + + + + [54491127] species: Odocoileus hemionus (Mule Deer) observed by dlevitis on 2020-07-26 + + + Observation 54491127 + + + + + [54491130] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-07-26 + + + Observation 54491130 + + + + + [54491131] genus: Monardella (no common name) observed by dlevitis on 2020-07-26 + + + Observation 54491131 + + + + + [54491132] species: Hemizonia congesta (Hayfield Tarweed) observed by dlevitis on 2020-07-26 + + + Observation 54491132 + + + + + [54491136] subphylum: Angiospermae (flowering plants) observed by dlevitis on 2020-07-26 + + + Observation 54491136 + + + + + [54491138] genus: Eriophyllum (Woolly sunflowers) observed by dlevitis on 2020-07-26 + + + Observation 54491138 + + + + + [54491140] phylum: Bryophyta (mosses) observed by dlevitis on 2020-07-26 + + + Observation 54491140 + + + + + [54491142] subspecies: Coenonympha tullia california (California Ringlet) observed by dlevitis on 2020-07-26 + + + Observation 54491142 + + + + + [54493468] species: Silene laciniata (cardinal catchfly) observed by dlevitis on 2020-07-26 + + + Observation 54493468 + + + + + [54493469] family: Agromyzidae (Leaf-miner Flies) observed by dlevitis on 2020-07-26 + Leaf mine in Silene + + Observation 54493469 + + + + + [54493470] genus: Eriogonum (wild buckwheats) observed by dlevitis on 2020-07-26 + + + Observation 54493470 + + + + + [54493472] infraorder: Entelegynae (Entelegyne Spiders) observed by dlevitis on 2020-07-26 + It had silked together some Toyon leaves for a house. + + Observation 54493472 + + + + + [54493473] order: Coleoptera (Beetles) observed by dlevitis on 2020-07-26 + + + Observation 54493473 + + + + + [54493476] species: Pickeringia montana (chaparral pea) observed by dlevitis on 2020-07-26 + + + Observation 54493476 + + + + + [54493477] species: Arbutus menziesii (Pacific madrone) observed by dlevitis on 2020-07-26 + + + Observation 54493477 + + + + + [54493478] phylum: Bryophyta (mosses) observed by dlevitis on 2020-07-26 + + + Observation 54493478 + + + + + [54493480] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-07-26 + Are the columns part of the lichen, or the moss? + + Observation 54493480 + + + + + [54493481] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-07-26 + Fungus growing on acorn husks under a rock beneath redwoods. + + Observation 54493481 + + + + + [54493482] genus: Lepraria (Dust Lichens) observed by dlevitis on 2020-07-26 + + + Observation 54493482 + + + + + [54493485] genus: Camponotus (Carpenter and Sugar Ants) observed by dlevitis on 2020-07-26 + Nesting under a rock + + Observation 54493485 + + + + + [54493486] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-26 + At first I thought this fluffy white stuff was spittlebug foam, but it looked more like cotton ball. It was stuck to this bay leaf. + + Observation 54493486 + + + + + [54493488] genus: Quercus (oaks) observed by dlevitis on 2020-07-26 + + + Observation 54493488 + + + + + [54493489] species: Adenocaulon bicolor (American trailplant) observed by dlevitis on 2020-07-26 + + + Observation 54493489 + + + + + [54493490] subclass: Pterygota (Winged and Once-winged Insects) observed by dlevitis on 2020-07-26 + + + Observation 54493490 + + + + + [54529503] species: Hemizonia congesta (Hayfield Tarweed) observed by woody54 on 2020-07-27 + + Observation 54529503 + + + + + [54562104] subspecies: Equisetum telmateia braunii (Giant Horsetail) observed by sunflowerguy on 2020-07-23 + + Observation 54562104 + + + + + [54603561] species: Melozone crissalis (California Towhee) observed by dlevitis on 2020-07-26 + + + Observation 54603561 + + + + + [54603562] species: Zenaida macroura (Mourning Dove) observed by dlevitis on 2020-07-26 + + + Observation 54603562 + + + + + [54603563] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-26 + + + Observation 54603563 + + + + + [54603565] species: Turdus migratorius (American Robin) observed by dlevitis on 2020-07-26 + + + Observation 54603565 + + + + + [54603566] species: Meleagris gallopavo (Wild Turkey) observed by dlevitis on 2020-07-26 + + + Observation 54603566 + + + + + [54603567] genus: Symphoricarpos (snowberries) observed by dlevitis on 2020-07-26 + + + Observation 54603567 + + + + + [54603569] stateofmatter: Life (no common name) observed by dlevitis on 2020-07-26 + What is causing the spots on these snowberry leaves, please? + + Observation 54603569 + + + + + [54603570] genus: Perideridia (yampahs) observed by dlevitis on 2020-07-26 + + + Observation 54603570 + + + + + [54603571] species: Spinus psaltria (Lesser Goldfinch) observed by dlevitis on 2020-07-26 + + + Observation 54603571 + + + + + [54603573] species: Pseudotsuga menziesii (common Douglas-fir) observed by dlevitis on 2020-07-26 + + + Observation 54603573 + + + + + [54603574] species: Melanerpes formicivorus (Acorn Woodpecker) observed by dlevitis on 2020-07-26 + + + Observation 54603574 + + + + + [54603575] species: Spinus psaltria (Lesser Goldfinch) observed by dlevitis on 2020-07-26 + + + Observation 54603575 + + + + + [54603576] species: Spinus psaltria (Lesser Goldfinch) observed by dlevitis on 2020-07-26 + + + Observation 54603576 + + + + + [54603577] genus: Prunus (plums, cherries, and allies) observed by dlevitis on 2020-07-26 + + + Observation 54603577 + + + + + [54603579] species: Cirsium vulgare (bull thistle) observed by dlevitis on 2020-07-26 + + + Observation 54603579 + + + + + [54603582] subclass: Pterygota (Winged and Once-winged Insects) observed by dlevitis on 2020-07-26 + leaf mine in bull thistle + + Observation 54603582 + + + + + [54603583] genus: Ramalina (bushy lichens) observed by dlevitis on 2020-07-26 + + + Observation 54603583 + + + + + [54603584] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-07-26 + + + Observation 54603584 + + + + + [54603586] phylum: Bryophyta (mosses) observed by dlevitis on 2020-07-26 + + + Observation 54603586 + + + + + [54603587] genus: Caliroa (no common name) observed by dlevitis on 2020-07-26 + Last picture is their sign on this bigleaf maple + + Observation 54603587 + + + + + [54603589] species: Heracleum maximum (common cowparsnip) observed by dlevitis on 2020-07-26 + + + Observation 54603589 + + + + + [54603590] complex: Phytomyza spondylii group (no common name) observed by dlevitis on 2020-07-26 + Leafmine in Cowparsnip + + Observation 54603590 + + + + + [54603591] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-26 + + + Observation 54603591 + + + + + [54603592] species: Spinus psaltria (Lesser Goldfinch) observed by dlevitis on 2020-07-26 + + + Observation 54603592 + + + + + [54603593] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-26 + + + Observation 54603593 + + + + + [54603594] species: Piranga ludoviciana (Western Tanager) observed by dlevitis on 2020-07-26 + Feeding on elderberry + + Observation 54603594 + + + + + [54603595] species: Umbellularia californica (California bay) observed by dlevitis on 2020-07-26 + + + Observation 54603595 + + + + + [54603596] genus: Sambucus (elders) observed by dlevitis on 2020-07-26 + + + Observation 54603596 + + + + + [54603597] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-26 + + + Observation 54603597 + + + + + [54603598] species: Vespula pensylvanica (Western Yellowjacket) observed by dlevitis on 2020-07-26 + + + Observation 54603598 + + + + + [54603599] species: Sambucus cerulea (blue elder) observed by dlevitis on 2020-07-26 + + + Observation 54603599 + + + + + [54603600] genus: Salix (willows) observed by dlevitis on 2020-07-26 + + + Observation 54603600 + + + + + [54603601] genus: Rubus (brambles) observed by dlevitis on 2020-07-26 + + + Observation 54603601 + + + + + [54603602] family: Araneidae (Orbweavers) observed by dlevitis on 2020-07-26 + + + Observation 54603602 + + + + + [54603603] family: Thomisidae (Crab Spiders) observed by dlevitis on 2020-07-26 + + + Observation 54603603 + + + + + [54603604] species: Sambucus cerulea (blue elder) observed by dlevitis on 2020-07-26 + + + Observation 54603604 + + + + + [54603605] species: Prunus domestica (Plum) observed by dlevitis on 2020-07-26 + + + Observation 54603605 + + + + + [54603606] species: Spinus psaltria (Lesser Goldfinch) observed by dlevitis on 2020-07-26 + + + Observation 54603606 + + + + + [54603607] kingdom: Fungi (Fungi Including Lichens) observed by dlevitis on 2020-07-26 + What is growing on the leaves of this elder, please? + + Observation 54603607 + + + + + [54603608] species: Aphelocoma californica (California Scrub-Jay) observed by dlevitis on 2020-07-26 + + + Observation 54603608 + + + + + [54603610] genus: Lupinus (lupines) observed by dlevitis on 2020-07-26 + + + Observation 54603610 + + + + + [54603801] species: Callipepla californica (California Quail) observed by dlevitis on 2020-07-26 + + + Observation 54603801 + + + + + [54603802] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-07-26 + + + Observation 54603802 + + + + + [54603803] species: Sayornis nigricans (Black Phoebe) observed by dlevitis on 2020-07-26 + + + Observation 54603803 + + + + + [54603806] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-26 + + + Observation 54603806 + + + + + [54603807] species: Melozone crissalis (California Towhee) observed by dlevitis on 2020-07-26 + + + Observation 54603807 + + + + + [54603808] species: Polioptila caerulea (Blue-gray Gnatcatcher) observed by dlevitis on 2020-07-26 + + + Observation 54603808 + + + + + [54603810] species: Cathartes aura (Turkey Vulture) observed by dlevitis on 2020-07-26 + + + Observation 54603810 + + + + + [54603811] species: Erodium botrys (Mediterranean Stork's-bill) observed by dlevitis on 2020-07-26 + + + Observation 54603811 + + + + + [54603812] species: Hypericum perforatum (common St. John's-wort) observed by dlevitis on 2020-07-26 + + + Observation 54603812 + + + + + [54603813] species: Erynnis tristis (Mournful Duskywing) observed by dlevitis on 2020-07-26 + + + Observation 54603813 + + + + + [54603814] species: Mentha pulegium (Pennyroyal) observed by dlevitis on 2020-07-26 + + + Observation 54603814 + + + + + [54603815] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by dlevitis on 2020-07-26 + + + Observation 54603815 + + + + + [54603817] species: Junco hyemalis (Dark-eyed Junco) observed by dlevitis on 2020-07-26 + + + Observation 54603817 + + + + + [54603818] species: Sceloporus occidentalis (Western Fence Lizard) observed by dlevitis on 2020-07-26 + + + Observation 54603818 + + + + + [54603819] species: Aphelocoma californica (California Scrub-Jay) observed by dlevitis on 2020-07-26 + + + Observation 54603819 + + + + + [54701524] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by reptile_galen on 2020-07-28 + + Observation 54701524 + + + + + [54774921] species: Eriogonum luteolum (wicker buckwheat) observed by sunflowerguy on 2020-07-29 + + Observation 54774921 + + + + + [54774993] species: Angelica hendersonii (Henderson's angelica) observed by sunflowerguy on 2020-07-29 + + Observation 54774993 + + + + + [54775020] species: Hemizonia congesta (Hayfield Tarweed) observed by sunflowerguy on 2020-07-29 + + Observation 54775020 + + + + + [54775050] species: Eriogonum fasciculatum (California buckwheat) observed by sunflowerguy on 2020-07-29 + + Observation 54775050 + + + + + [54775071] species: Wyethia angustifolia (narrowleaf mule-ears) observed by sunflowerguy on 2020-07-29 + + Observation 54775071 + + + + + [54775189] species: Cyperus eragrostis (tall flatsedge) observed by sunflowerguy on 2020-07-29 + + Observation 54775189 + + + + + [54775231] species: Umbellularia californica (California bay) observed by sunflowerguy on 2020-07-29 + + Observation 54775231 + + + + + [54775276] species: Mentha pulegium (Pennyroyal) observed by sunflowerguy on 2020-07-29 + + + Observation 54775276 + + + + + [54835039] species: Eurybia radulina (roughleaf aster) observed by sunflowerguy on 2020-07-29 + + Observation 54835039 + + + + + [54835412] species: Mentha longifolia (Horse Mint) observed by sunflowerguy on 2020-07-29 + + Observation 54835412 + + + + + [54928352] species: Meleagris gallopavo (Wild Turkey) observed by sunflowerguy on 2020-07-29 + + Observation 54928352 + + + + + [54978856] species: Hemizonia congesta (Hayfield Tarweed) observed by sunflowerguy on 2020-07-29 + + Observation 54978856 + + + + + [54979821] species: Quercus agrifolia (coast live oak) observed by sunflowerguy on 2020-07-29 + Look underside of leaves for Tufts of hairs where lateral veins meet midvein. + + Observation 54979821 + + + + + [54980094] species: Rubus bifrons (Himalayan Blackberry) observed by sunflowerguy on 2020-07-29 + + Observation 54980094 + + + + + [54980899] species: Prunus domestica (Plum) observed by sunflowerguy on 2020-07-29 + Plums seen a week before, but now all gone. We're delii. + + Observation 54980899 + + + + + [54983414] species: Hypochaeris radicata (Common Cat's-ear) observed by sunflowerguy on 2020-07-29 + + Observation 54983414 + + + + + [54983565] genus: Baccharis (no common name) observed by sunflowerguy on 2020-07-29 + + Observation 54983565 + + + + + [54984387] species: Pteridium aquilinum (common bracken) observed by sunflowerguy on 2020-07-29 + + Observation 54984387 + + + + + [54996488] subspecies: Equisetum telmateia braunii (Giant Horsetail) observed by sunflowerguy on 2020-07-29 + + Observation 54996488 + + + + + [54996678] species: Typha latifolia (broadleaf cattail) observed by sunflowerguy on 2020-07-29 + + Observation 54996678 + + + + + [54996926] species: Madia sativa (coast tarweed) observed by sunflowerguy on 2020-07-29 + + Observation 54996926 + + + + + [54997129] species: Urtica dioica (stinging nettle) observed by sunflowerguy on 2020-07-29 + + Observation 54997129 + + + + + [54997285] species: Mentha longifolia (Horse Mint) observed by sunflowerguy on 2020-07-29 + + Observation 54997285 + + + + + [54997395] species: Symphoricarpos albus (Common Snowberry) observed by sunflowerguy on 2020-07-29 + + Observation 54997395 + + + + + [54997575] species: Juglans hindsii (northern California black walnut) observed by sunflowerguy on 2020-07-29 + + Observation 54997575 + + + + + [54997689] species: Umbellularia californica (California bay) observed by sunflowerguy on 2020-07-29 + + Observation 54997689 + + + + + [55000760] species: Carex nudata (torrent sedge) observed by ameet on 2020-07-26 + Growing in Sonoma Creek, close to the last parking lot. + + Observation 55000760 + + + + + [55069051] species: Verbascum blattaria (moth mullein) observed by sunflowerguy on 2020-08-01 + + Observation 55069051 + + + + + [55069721] species: Nasturtium officinale (watercress) observed by sunflowerguy on 2020-08-01 + + Observation 55069721 + + + + + [55099705] genus: Physa (no common name) observed by chilipossum on 2020-07-19 + + Observation 55099705 + + + + + [55107642] species: Croton setiger (turkey mullein) observed by sunflowerguy on 2020-08-01 + + Observation 55107642 + + + + + [55107789] species: Oenanthe sarmentosa (water parsley) observed by sunflowerguy on 2020-08-01 + Growing in wet seepage on Hillside trail + + Observation 55107789 + + + + + [55125347] species: Heracleum maximum (common cowparsnip) observed by ameet on 2020-07-26 + Sugarloaf Ridge State Park, Sonoma County, California + + Observation 55125347 + + + + + [55173776] kingdom: Plantae (plants) observed by wtstacy on 2020-08-02 + + Observation 55173776 + + + + + [55194401] species: Sylvilagus bachmani (Brush Rabbit) observed by lockerobster on 2020-07-21 + + Observation 55194401 + + + + + [55197354] species: Sylvilagus bachmani (Brush Rabbit) observed by barneylocke on 2020-07-21 + + Observation 55197354 + + + + + [55283598] species: Toxicodendron diversilobum (Pacific poison oak) observed by wtstacy on 2020-08-03 + + Observation 55283598 + + + + + [55362426] species: Umbellularia californica (California bay) observed by ecodanielle on 2020-07-25 + + Observation 55362426 + + + + + [55436119] species: Calycadenia truncata (Oregon western rosinweed) observed by sunflowerguy on 2020-08-03 + + Observation 55436119 + + + + + [55545557] genus: Solidago (goldenrods) observed by woody54 on 2020-08-05 + + Observation 55545557 + + + + + [55546807] species: Quercus durata (leather oak) observed by woody54 on 2020-08-05 + + Observation 55546807 + + + + + [55553886] species: Hoita macrostachya (large leatherroot) observed by woody54 on 2020-08-05 + + Observation 55553886 + + + + + [55554078] species: Icaricia acmon (Acmon Blue) observed by woody54 on 2020-08-05 + + Observation 55554078 + + + + + [55561441] species: Mentha pulegium (Pennyroyal) observed by woody54 on 2020-08-05 + + Observation 55561441 + + + + + [55564323] species: Cirsium andersonii (Anderson's thistle) observed by alexfern on 2020-08-05 + + Observation 55564323 + + + + + [55564380] genus: Eriogonum (wild buckwheats) observed by alexfern on 2020-08-05 + + Observation 55564380 + + + + + [55564692] species: Hemizonia congesta (Hayfield Tarweed) observed by alexfern on 2020-08-05 + + Observation 55564692 + + + + + [55567363] species: Frangula californica (coffeeberry) observed by woody54 on 2020-08-05 + + Observation 55567363 + + + + + [55744552] species: Mentha spicata (spearmint) observed by woody54 on 2020-08-05 + + Observation 55744552 + + + + + [55751625] class: Magnoliopsida (dicots) observed by wtstacy on 2020-08-03 + + Observation 55751625 + + + + + [55754161] species: Erythranthe cardinalis (scarlet monkeyflower) observed by sunflowerguy on 2020-08-07 + In stream bed along with E guttata. + + Observation 55754161 + + + + + [55754194] species: Erythranthe guttata (seep monkeyflower) observed by sunflowerguy on 2020-08-07 + + Observation 55754194 + + + + + [55813853] species: Eriogonum luteolum (wicker buckwheat) observed by marymacwill on 2020-08-08 + + Observation 55813853 + + + + + [55814001] species: Phacelia cicutaria (caterpillar scorpionweed) observed by marymacwill on 2020-08-08 + + Observation 55814001 + + + + + [55816596] species: Pseudognaphalium californicum (California cudweed) observed by marymacwill on 2020-08-08 + + Observation 55816596 + + + + + [55831762] species: Poecilanthrax arethusa (no common name) observed by caenvsci on 2020-08-08 + + Observation 55831762 + + + + + [55831896] genus: Eriogonum (wild buckwheats) observed by caenvsci on 2020-08-08 + + Observation 55831896 + + + + + [55831922] species: Odocoileus hemionus (Mule Deer) observed by caenvsci on 2020-08-08 + + Observation 55831922 + + + + + [55831996] species: Sylvilagus bachmani (Brush Rabbit) observed by caenvsci on 2020-08-08 + + Observation 55831996 + + + + + [55832044] species: Calliphora vicina (Blue Blowfly) observed by caenvsci on 2020-08-08 + + Observation 55832044 + + + + + [55832113] species: Verbascum blattaria (moth mullein) observed by caenvsci on 2020-08-08 + + Observation 55832113 + + + + + [55832210] subgenus: Dialictus (Metallic Sweat Bees) observed by caenvsci on 2020-08-08 + + Observation 55832210 + + + + + [55832720] species: Symphoricarpos albus (Common Snowberry) observed by ten_salamanders on 2020-08-08 + + Observation 55832720 + + + + + [55832749] species: Hoita macrostachya (large leatherroot) observed by ten_salamanders on 2020-08-08 + + Observation 55832749 + + + + + [55832770] species: Silene laciniata (cardinal catchfly) observed by ten_salamanders on 2020-08-08 + + Observation 55832770 + + + + + [55832795] species: Epilobium canum (California fuchsia) observed by ten_salamanders on 2020-08-08 + + Observation 55832795 + + + + + [55832822] genus: Stephanomeria (wirelettuce) observed by ten_salamanders on 2020-08-08 + + Observation 55832822 + + + + + [55832852] species: Papilio zelicaon (Anise Swallowtail) observed by ten_salamanders on 2020-08-08 + + Observation 55832852 + + + + + [55832878] species: Augochlorella pomoniella (Peridot Sweat Bee) observed by ten_salamanders on 2020-08-08 + + Observation 55832878 + + + + + [55832930] species: Trimerotropis fontana (Fontana Grasshopper) observed by ten_salamanders on 2020-08-08 + + Observation 55832930 + + + + + [55955941] order: Polyporales (shelf fungi) observed by patrick-mcbride on 2020-08-09 + + Observation 55955941 + + + + + [55984540] species: Erythranthe cardinalis (scarlet monkeyflower) observed by woody54 on 2020-08-09 + + Observation 55984540 + + + + + [56002518] species: Eriogonum nudum (Naked Buckwheat) observed by ameet on 2020-07-26 + Meadow Trail, Sugarloaf Ridge State Park +Sonoma County, California + + Observation 56002518 + + + + + [56002519] subspecies: Crotalus oreganus oreganus (Northern Pacific Rattlesnake) observed by ameet on 2020-07-26 + Fairly large specimen seen crossing Meadow Trail. Looks like the same individual has been reported several times in the last couple of months. Does anybody know if this is a normal range for this species? + + Observation 56002519 + + + + + [56002522] species: Sambucus cerulea (blue elder) observed by ameet on 2020-07-26 + Sugarloaf Ridge State Park, Sonoma County, California + + Observation 56002522 + + + + + [56002524] species: Cercyonis pegala (Common Wood-Nymph) observed by ameet on 2020-07-26 + Nature Trail, Sugarloaf Ridge State Park, +Sonoma County, California + + Observation 56002524 + + + + + [56063137] species: Pseudognaphalium californicum (California cudweed) observed by ameet on 2020-07-26 + Gray Pine Trail, Sugarloaf Ridge State Park + + Observation 56063137 + + + + + [56063142] species: Lupinus formosus (Summer Lupine) observed by ameet on 2020-07-26 + With Yellow-fronted Bumble Bee feeding +Gray Pine Trail, Sugarloaf Ridge State Park + + Observation 56063142 + + + + + [56063146] species: Icaricia acmon (Acmon Blue) observed by ameet on 2020-07-26 + On Pennyroyal flowers +Gray Pine Trail, Sugarloaf Ridge State Park + + Observation 56063146 + + + + + [56063150] species: Phyciodes mylitta (Mylitta Crescent) observed by ameet on 2020-07-26 + On Pennyroyal flowers. +Gray Pine Trail, Sugarloaf Ridge State Park + + Observation 56063150 + + + + + [56063154] species: Mentha pulegium (Pennyroyal) observed by ameet on 2020-07-26 + Gray Pine Trail, Sugarloaf Ridge State Park + + Observation 56063154 + + + + + [56063155] species: Vespula sulphurea (California Yellowjacket) observed by ameet on 2020-07-26 + Sugarloaf Ridge State Park, Sonoma County, California + + Observation 56063155 + + + + + [56063161] species: Datisca glomerata (durango root) observed by ameet on 2020-07-26 + Gray Pine Trail, Sugarloaf Ridge State Park + + Observation 56063161 + + + + + [56078135] species: Augochlorella pomoniella (Peridot Sweat Bee) observed by sunflowerguy on 2020-08-10 + + Observation 56078135 + + + + + [56080824] species: Mentha suaveolens (Apple Mint) observed by ameet on 2020-07-26 + Sugarload Ridge State Park, Sonoma County, California + + Observation 56080824 + + + + + [56080829] species: Rosa californica (California Wild Rose) observed by ameet on 2020-07-26 + Sugarload Ridge State Park, Sonoma County, California + + Observation 56080829 + + + + + [56080833] species: Perideridia kelloggii (Kellogg's yampah) observed by ameet on 2020-07-26 + Sugarload Ridge State Park, Sonoma County, California + + Observation 56080833 + + + + + [56080838] species: Quercus garryana (Oregon oak) observed by ameet on 2020-07-26 + Sugarload Ridge State Park, Sonoma County, California + + Observation 56080838 + + + + + [56080841] species: Trimerotropis fontana (Fontana Grasshopper) observed by ameet on 2020-07-26 + Sugarload Ridge State Park, Sonoma County, California + + Observation 56080841 + + + + + [56175003] species: Lomatium dasycarpum (woollyfruit desertparsley) observed by alfresco on 2020-08-04 + + Observation 56175003 + + + + + [56273047] species: Hemizonia congesta (Hayfield Tarweed) observed by nickcecchi on 2020-08-12 + Amazing smell coming from the sticky sap(?) and easily carried in the wind. + + Observation 56273047 + + + + + [56273774] species: Prophysaon andersonii (Reticulate Taildropper) observed by nickcecchi on 2020-08-12 + Near running water in a creek bed. + + Observation 56273774 + + + + + [56274750] species: Baccharis pilularis (coyote brush) observed by nickcecchi on 2020-08-12 + Honestly not sure if this was a bad picture of a tiny oak species or a bad picture of a coyote brush. + + Observation 56274750 + + + + + [56274953] species: Umbellularia californica (California bay) observed by nickcecchi on 2020-08-12 + Smells amazing it's everywhere up here. 2nd photo has the seed, which I've never seen before + + Observation 56274953 + + + + + [56275660] species: Arbutus menziesii (Pacific madrone) observed by nickcecchi on 2020-08-12 + Growing everywhere up here. + + Observation 56275660 + + + + + [56279646] species: Buteo jamaicensis (Red-tailed Hawk) observed by nadiabasidiq on 2020-08-12 + + Observation 56279646 + + + + + [56279696] species: Lepus californicus (Black-tailed Jackrabbit) observed by nadiabasidiq on 2020-08-12 + + Observation 56279696 + + + + + [56279874] species: Quercus garryana (Oregon oak) observed by nadiabasidiq on 2020-08-12 + + Observation 56279874 + + + + + [56280156] species: Cirsium occidentale (Cobwebby Thistle) observed by nadiabasidiq on 2020-08-12 + + Observation 56280156 + + + + + [56280308] variety: Cirsium occidentale occidentale (cobwebby thistle) observed by nickcecchi on 2020-08-12 + + Observation 56280308 + + + + [56280507] species: Adelpha californica (California Sister) observed by nadiabasidiq on None + + Observation 56280507 + + + + + [56280570] species: Heteromeles arbutifolia (Toyon) observed by nickcecchi on 2020-08-12 + + Observation 56280570 + + + + + [56280782] species: Sceloporus occidentalis (Western Fence Lizard) observed by nadiabasidiq on 2020-08-12 + + Observation 56280782 + + + + + [56281304] species: Buteo jamaicensis (Red-tailed Hawk) observed by nickcecchi on 2020-08-12 + + Observation 56281304 + + + + + [56281605] species: Madia elegans (Common Madia) observed by nickcecchi on 2020-08-12 + + Observation 56281605 + + + + + [56281785] species: Sceloporus occidentalis (Western Fence Lizard) observed by nickcecchi on 2020-08-12 + + Observation 56281785 + + + + + [56282196] species: Microtus californicus (California Vole) observed by nickcecchi on 2020-08-12 + Lol he's looking right at me. His whole family lives here. + + Observation 56282196 + + + + + [56282483] species: Quercus shumardii (Shumard oak) observed by nickcecchi on 2020-08-12 + Could be Oregon oak, leaves were HUGE. + + Observation 56282483 + + + + + [56282911] species: Lupinus albifrons (Silver Lupine) observed by nickcecchi on 2020-08-12 + + Observation 56282911 + + + + + [56283266] species: Epilobium canum (California fuchsia) observed by nickcecchi on 2020-08-12 + + Observation 56283266 + + + + + [56283630] species: Quercus berberidifolia (California scrub oak) observed by nickcecchi on 2020-08-12 + The tiniest oak leaves I've ever seen. + + Observation 56283630 + + + + + [56284212] species: Augochlorella pomoniella (Peridot Sweat Bee) observed by nickcecchi on 2020-08-12 + + Observation 56284212 + + + + + [56284872] species: Toxicodendron diversilobum (Pacific poison oak) observed by nickcecchi on 2020-08-12 + Never would have guessed, good to know. + + Observation 56284872 + + + + + [56285244] species: Naematelia aurantia (golden ear) observed by nickcecchi on 2020-08-12 + + Observation 56285244 + + + + + [56285524] species: Lemna minor (common duckweed) observed by nickcecchi on 2020-08-12 + + Observation 56285524 + + + + + [56285823] species: Vitis vinifera (wine grape) observed by nickcecchi on 2020-08-12 + Uncertain but probable + + Observation 56285823 + + + + + [56297306] species: Aquilegia eximia (Van Houtte's Columbine) observed by nickcecchi on 2020-08-12 + Cropped and full pics provided for context. + + Observation 56297306 + + + + + [56371247] species: Okanagana vanduzeei (no common name) observed by easmeds on 2020-07-01 + + Observation 56371247 + + + + + [56371248] genus: Laphria (Bee-mimic Robber Flies) observed by easmeds on 2020-07-01 + + Observation 56371248 + + + + + [56642642] kingdom: Animalia (Animals) observed by barneylocke on 2020-08-05 + + Observation 56642642 + + + + + [56668505] species: Buteo jamaicensis (Red-tailed Hawk) observed by patrick-mcbride on 2020-08-16 + + Observation 56668505 + + + + + [56712691] species: Argiope aurantia (Yellow Garden Spider) observed by jackroney on 2020-08-14 + + Observation 56712691 + + + + + [56753725] species: Umbellularia californica (California bay) observed by globetrot on 2020-08-15 + + Observation 56753725 + + + + + [56847412] unknown taxon observed by redwoodsgirl on 2020-07-08 + + Observation 56847412 + + + + + [56847465] genus: Zeltnera (no common name) observed by redwoodsgirl on 2020-07-08 + + Observation 56847465 + + + + + [56847489] subphylum: Angiospermae (flowering plants) observed by redwoodsgirl on 2020-07-08 + + Observation 56847489 + + + + + [56903844] species: Silene laciniata (cardinal catchfly) observed by marymia on 2020-05-19 + + Observation 56903844 + + + + + [56903928] species: Cota tinctoria (Golden marguerite) observed by marymia on 2020-05-19 + + Observation 56903928 + + + + + [57073178] order: Araneae (Spiders) observed by woody54 on 2020-08-09 + + Observation 57073178 + + + + + [57073196] species: Cynips douglasii (Spined Turban Gall Wasp) observed by woody54 on 2020-08-20 + + Observation 57073196 + + + + + [57088730] species: Urtica dioica (stinging nettle) observed by woody54 on 2020-08-20 + + Observation 57088730 + + + + + [57093844] species: Artemisia douglasiana (California mugwort) observed by woody54 on 2020-08-20 + + Observation 57093844 + + + + + [57186969] species: Erythranthe cardinalis (scarlet monkeyflower) observed by pinkhockey75 on 2020-08-21 + + Observation 57186969 + + + + + [57467529] species: Glaucopsyche lygdamus (Silvery Blue) observed by tgfcelt on 2019-04-10 + + Observation 57467529 + + + + + [57593115] species: Typha latifolia (broadleaf cattail) observed by woody54 on 2020-08-25 + + Observation 57593115 + + + + + [57595551] species: Anaphalis margaritacea (pearly everlasting) observed by alfresco on 2020-08-22 + + Observation 57595551 + + + + + [57912059] species: Xylocopa californica (Western Carpenter) observed by coffearobusta42 on 2020-08-28 + + Observation 57912059 + + + + + [57933703] class: Magnoliopsida (dicots) observed by sinor on 2020-08-29 + + Observation 57933703 + + + + + [58211546] species: Ganoderma polychromum (no common name) observed by sunflowerguy on 2020-08-29 + + Observation 58211546 + + + + + [58211670] species: Alnus rhombifolia (white alder) observed by sunflowerguy on 2020-08-29 + + Observation 58211670 + + + + + [58211711] genus: Rosa (roses) observed by sunflowerguy on 2020-08-29 + + Observation 58211711 + + + + + [58298906] species: Okanagana triangulata (no common name) observed by easmeds on 2020-06-01 + + Observation 58298906 + + + + + [58298907] species: Okanagana triangulata (no common name) observed by easmeds on 2020-06-01 + + Observation 58298907 + + + + + [58298908] species: Okanagana triangulata (no common name) observed by easmeds on 2020-06-01 + + Observation 58298908 + + + + + [58298909] species: Okanagana triangulata (no common name) observed by easmeds on 2020-06-01 + + Observation 58298909 + + + + + [58298910] species: Okanagana triangulata (no common name) observed by easmeds on 2020-06-01 + + Observation 58298910 + + + + + [58298911] species: Okanagana triangulata (no common name) observed by easmeds on 2020-06-01 + + Observation 58298911 + + + + + [58480528] genus: Usnea (beard lichens) observed by woody54 on 2020-09-03 + + Observation 58480528 + + + + + [58544550] kingdom: Fungi (Fungi Including Lichens) observed by daniyang on 2020-01-02 + Found in an area that was heavily shaded by oak trees. + + Observation 58544550 + + + + + [58634460] species: Battus philenor (Pipevine Swallowtail) observed by wendy_h on 2020-09-05 + + Observation 58634460 + + + + + [58634642] species: Polypogon monspeliensis (rabbitfoot grass) observed by wendy_h on 2020-09-05 + + Observation 58634642 + + + + + [58634880] species: Lepus californicus (Black-tailed Jackrabbit) observed by wendy_h on 2020-09-05 + + Observation 58634880 + + + + + [59171484] species: Helenium puberulum (Rosilla) observed by woody54 on 2020-09-10 + + Observation 59171484 + + + + + [59208476] genus: Persicaria (knotweeds, smartweeds, and waterpeppers) observed by woody54 on 2020-09-10 + + Observation 59208476 + + + + + [59392988] species: Epilobium canum (California fuchsia) observed by woody54 on 2020-09-12 + + Observation 59392988 + + + + + [59642752] hybrid: Polystichum californicum × munitum (no common name) observed by woody54 on 2020-09-14 + + Observation 59642752 + + + + + [59753273] phylum: Chordata (Chordates) observed by mayerfishcreek on 2020-09-06 + + Observation 59753273 + + + + + [59753278] species: Umbellularia californica (California bay) observed by mayerfishcreek on 2020-09-06 + + Observation 59753278 + + + + + [59753286] kingdom: Fungi (Fungi Including Lichens) observed by mayerfishcreek on 2020-09-06 + + Observation 59753286 + + + + + [59938077] species: Fraxinus latifolia (Oregon Ash) observed by sunflowerguy on 2020-09-17 + + Observation 59938077 + + + + + [60012856] species: Canis latrans (Coyote) observed by sunflowerguy on 2020-09-18 + + Observation 60012856 + + + + + [60673796] species: Symphyotrichum chilense (Pacific Aster) observed by woody54 on 2020-09-24 + + Observation 60673796 + + + + + [60845247] species: Aquilegia eximia (Van Houtte's Columbine) observed by woody54 on 2020-09-26 + + Observation 60845247 + + + + + [60868716] species: Symphoricarpos albus (Common Snowberry) observed by woody54 on 2020-09-26 + + Observation 60868716 + + + + + [61052004] class: Aves (Birds) observed by patrick-mcbride on 2020-09-27 + + Observation 61052004 + + + + + [61179637] species: Epilobium canum (California fuchsia) observed by skylerrex on 2020-09-26 + + Observation 61179637 + + + + + \ No newline at end of file diff --git a/examples/sample_data/observations.tsp b/examples/sample_data/observations.tsp new file mode 100644 index 00000000..85d2581e --- /dev/null +++ b/examples/sample_data/observations.tsp @@ -0,0 +1,2382 @@ +NAME: project_observations +COMMENT: 2375 observations from project xx +TYPE: TSP +DIMENSION: 2375 +EDGE_WEIGHT_FORMAT: GEOM +NODE_COORD_SECTION: +1 38.4402770623 -122.5244447216 +2 38.4427825546 -122.5113890693 +3 38.4421741121 -122.5284053385 +4 38.4420527907 -122.5282544643 +5 38.4405822754 -122.5241241455 +6 38.4404393534 -122.5236943737 +7 38.4404678345 -122.5242233276 +8 38.4420916556 -122.528789565 +9 38.4418640137 -122.5288391113 +10 38.43565833 -122.51368667 +11 38.44261167 -122.530855 +12 38.4403762817 -122.5243988037 +13 38.4401550293 -122.522567749 +14 38.4399528503 -122.5235824585 +15 38.4403495789 -122.5245437622 +16 38.4403495789 -122.5245437622 +17 38.44585983 -122.50873256 +18 38.44264167 -122.52233833 +19 38.4397903545 -122.4985272351 +20 38.4488492629 -122.5012995244 +21 38.442691803 -122.5308685303 +22 38.443082441 -122.5304850563 +23 38.4427757263 -122.5317993164 +24 38.4427757263 -122.5317993164 +25 38.4427757263 -122.5317993164 +26 38.4427719116 -122.5317840576 +27 38.4429168701 -122.53150177 +28 38.4429168701 -122.53150177 +29 38.4429168701 -122.53150177 +30 38.4437141418 -122.5314025879 +31 38.4435937156 -122.5309360027 +32 38.4433364868 -122.5318603516 +33 38.4433364868 -122.5318603516 +34 38.4437103271 -122.5320053101 +35 38.4435119629 -122.5320358276 +36 38.4432907104 -122.5315856934 +37 38.4427871704 -122.5318222046 +38 38.4384422302 -122.5196838379 +39 38.442455 -122.52256167 +40 38.442505 -122.52255333 +41 38.4390907288 -122.520690918 +42 38.4387931824 -122.5217285156 +43 38.4388084412 -122.521812439 +44 38.4382024353 -122.5202504173 +45 38.4392852783 -122.5223846436 +46 38.4397392273 -122.5225372314 +47 38.4399579938 -122.522588633 +48 38.4391079254 -122.5216324255 +49 38.4412498474 -122.5255661011 +50 38.4424705505 -122.5293731689 +51 38.4418411255 -122.5266113281 +52 38.4424705505 -122.5293731689 +53 38.4425963723 -122.5297739357 +54 38.4429168701 -122.53150177 +55 38.4407498729 -122.5132193644 +56 38.4421415297 -122.5052737733 +57 38.43733667 -122.51601667 +58 38.44275 -122.53111333 +59 38.44268 -122.531145 +60 38.44075333 -122.50463 +61 38.4405556881 -122.5244447216 +62 38.457745 -122.50949667 +63 38.43813667 -122.50023667 +64 38.466555 -122.52177833 +65 38.46693 -122.52129167 +66 38.46702 -122.52126333 +67 38.4388166667 -122.5164111111 +68 38.4389027778 -122.5164194444 +69 38.4409944444 -122.5183638889 +70 38.4416361111 -122.5202722222 +71 38.4432138889 -122.5230638889 +72 38.4414333333 -122.5248416667 +73 38.4404666667 -122.5239805556 +74 38.4403916667 -122.5236583333 +75 38.4383611111 -122.5197527778 +76 38.4382900869 -122.5199807193 +77 38.4381141177 -122.5120311546 +78 38.442755 -122.53178333 +79 38.44324167 -122.52298667 +80 38.44033 -122.52441333 +81 38.44303667 -122.52348333 +82 38.44252 -122.52049167 +83 38.443255 -122.523025 +84 38.437545 -122.51563333 +85 38.4380743464 -122.5168044017 +86 38.43783833 -122.51467167 +87 38.44325 -122.52296333 +88 38.44043 -122.52393333 +89 38.44049167 -122.52397167 +90 38.44296333 -122.52166667 +91 38.44328 -122.52208667 +92 38.44004667 -122.51773833 +93 38.44203833 -122.52296333 +94 38.44337 -122.52226333 +95 38.43887833 -122.51651167 +96 38.438825 -122.51633333 +97 38.442805 -122.52143 +98 38.43895333 -122.51648 +99 38.44002833 -122.51779167 +100 38.44100833 -122.51827167 +101 38.442825 -122.52147 +102 38.43979667 -122.51779167 +103 38.44003333 -122.5178 +104 38.43843 -122.51981333 +105 38.44003667 -122.51781333 +106 38.43679667 -122.51473833 +107 38.44742167 -122.50073833 +108 38.43646333 -122.514725 +109 38.438900069 -122.5164659691 +110 38.4396862776 -122.5168567522 +111 38.4399587877 -122.5180631956 +112 38.4374972222 -122.5151666667 +113 38.4360913094 -122.5132394583 +114 38.4364700038 -122.5123505575 +115 38.4364398709 -122.512602769 +116 38.4363104543 -122.512461953 +117 38.4364707163 -122.5123736915 +118 38.4364102828 -122.5118926541 +119 38.4365170263 -122.5117169694 +120 38.436463885 -122.5114626625 +121 38.4359566961 -122.511083549 +122 38.435903471 -122.509743618 +123 38.4357743897 -122.5099961647 +124 38.4358373378 -122.5096580387 +125 38.4401954005 -122.5178477063 +126 38.4408486864 -122.5179616882 +127 38.4427912032 -122.5215386572 +128 38.4424755602 -122.5236090421 +129 38.4414116312 -122.524623836 +130 38.4411326934 -122.5252952631 +131 38.4401484759 -122.523266411 +132 38.43877 -122.52091167 +133 38.4354740793 -122.5092247719 +134 38.4355280049 -122.5103352068 +135 38.4355666109 -122.5097370741 +136 38.4363444851 -122.5117475185 +137 38.4362204145 -122.5115989871 +138 38.4363654194 -122.5119202948 +139 38.4359430851 -122.5112774007 +140 38.4363548768 -122.5121247001 +141 38.4363548768 -122.5121247001 +142 38.43684162 -122.51419731 +143 38.43641233 -122.51317122 +144 38.4364194 -122.51252065 +145 38.43638418 -122.5124678 +146 38.43582672 -122.50965682 +147 38.43586389 -122.50974323 +148 38.43581646 -122.50961341 +149 38.43602709 -122.50961552 +150 38.4532852173 -122.5081558228 +151 38.436705 -122.51335833 +152 38.437645 -122.51582167 +153 38.4498816229 -122.5040598508 +154 38.438 -122.4983966667 +155 38.43551667 -122.513725 +156 38.43556667 -122.51377167 +157 38.437 -122.51599167 +158 38.4427369433 -122.5213400648 +159 38.4427470435 -122.5213433338 +160 38.44041333 -122.52423833 +161 38.4439305556 -122.5115888889 +162 38.4439722222 -122.5135583333 +163 38.4373292358 -122.5159263611 +164 38.43475833 -122.503595 +165 38.438145 -122.50851667 +166 38.4358 -122.51401333 +167 38.43554167 -122.51109667 +168 38.4480254342 -122.492492709 +169 38.43806333 -122.509125 +170 38.43695833 -122.516495 +171 38.4471354683 -122.4988907528 +172 38.444771447 -122.510490406 +173 38.4535320656 -122.508784521 +174 38.444099999 -122.5108981017 +175 38.4421900528 -122.5031875751 +176 38.4803949902 -122.5691745804 +177 38.4800737119 -122.5696207491 +178 38.4808731359 -122.5692688768 +179 38.4808204556 -122.56944431 +180 38.4811518761 -122.569238199 +181 38.4810443782 -122.5692055934 +182 38.481775783 -122.5698411931 +183 38.4826028673 -122.5676701126 +184 38.48288232 -122.5674339944 +185 38.4859537846 -122.5626505261 +186 38.4861980332 -122.5623975602 +187 38.4862817685 -122.5624293276 +188 38.4862865461 -122.5623193571 +189 38.4862996638 -122.5621160959 +190 38.4863700299 -122.5618755353 +191 38.4860705864 -122.5605149848 +192 38.435695 -122.51145833 +193 38.4367355169 -122.5017702363 +194 38.43834667 -122.52044667 +195 38.43807 -122.51773 +196 38.438575 -122.520125 +197 38.43817833 -122.52006667 +198 38.43808333 -122.52028667 +199 38.43840333 -122.52053 +200 38.43825333 -122.52004167 +201 38.44104667 -122.525095 +202 38.437205 -122.49971667 +203 38.4431595799 -122.5055792131 +204 38.43723667 -122.49965 +205 38.43638333 -122.50137167 +206 38.44343 -122.51348 +207 38.44613667 -122.50464667 +208 38.44757167 -122.50112833 +209 38.447575 -122.50113667 +210 38.44589667 -122.505395 +211 38.44596333 -122.50830833 +212 38.44514667 -122.509245 +213 38.44277167 -122.51705333 +214 38.43717 -122.51580833 +215 38.445045 -122.50923833 +216 38.4416777778 -122.5246361111 +217 38.4414416667 -122.5246583333 +218 38.4434083333 -122.5179055556 +219 38.4439083333 -122.5148083333 +220 38.4412888889 -122.5248638889 +221 38.4416694444 -122.5248416667 +222 38.4432916667 -122.5236277778 +223 38.449405 -122.54123 +224 38.43749667 -122.51584667 +225 38.44329667 -122.51534167 +226 38.4524728172 -122.5031329786 +227 38.436506979 -122.513362232 +228 38.4464281313 -122.5046881529 +229 38.4362835282 -122.5113486221 +230 38.4458986806 -122.504606061 +231 38.448219088 -122.5046514647 +232 38.44268 -122.53098333 +233 38.44110833 -122.522875 +234 38.437395 -122.499345 +235 38.43579667 -122.502745 +236 38.4469861111 -122.5351305556 +237 38.4372694444 -122.5159611111 +238 38.4434277778 -122.5325 +239 38.4467638889 -122.5358805556 +240 38.4467916667 -122.5359222222 +241 38.4468777778 -122.5354277778 +242 38.437175 -122.49979667 +243 38.43865333 -122.497395 +244 38.4799717041 -122.5615832538 +245 38.44461167 -122.51075833 +246 38.44787167 -122.50016667 +247 38.43721167 -122.51609167 +248 38.43721333 -122.51245833 +249 38.43842167 -122.50873667 +250 38.43462154 -122.50915427 +251 38.43675213 -122.50012588 +252 38.43712 -122.515975 +253 38.44273333 -122.51582167 +254 38.44293 -122.51107833 +255 38.442395 -122.51083833 +256 38.4383 -122.51158333 +257 38.44406333 -122.51279167 +258 38.44298833 -122.51187167 +259 38.4409 -122.51128333 +260 38.44095333 -122.51126167 +261 38.44158 -122.530525 +262 38.43629667 -122.51518833 +263 38.4500302468 -122.4802168553 +264 38.4528518888 -122.4824938831 +265 38.45262167 -122.50408 +266 38.45268333 -122.503395 +267 38.452645 -122.503425 +268 38.449245 -122.5012 +269 38.44696167 -122.50215833 +270 38.44304667 -122.511895 +271 38.446095 -122.50816333 +272 38.4459 -122.50537167 +273 38.484395 -122.55818 +274 38.48590833 -122.5598 +275 38.4408 -122.51552 +276 38.44329167 -122.52242167 +277 38.44335333 -122.52356667 +278 38.44221667 -122.52368333 +279 38.44112833 -122.52530833 +280 38.4415803966 -122.5260062648 +281 38.43875 -122.52121667 +282 38.438225 -122.51377167 +283 38.44331667 -122.52342167 +284 38.44819167 -122.50686667 +285 38.4401352041 -122.4890741139 +286 38.4388480858 -122.4890743351 +287 38.4440812363 -122.4941424635 +288 38.4400456042 -122.4888864488 +289 38.4421917611 -122.4879166772 +290 38.442927205 -122.4841690784 +291 38.437010469 -122.5129711536 +292 38.4428669073 -122.4844469969 +293 38.4498484763 -122.501322943 +294 38.44759667 -122.50113 +295 38.4396539876 -122.4988383257 +296 38.4355545842 -122.503587997 +297 38.4365683963 -122.5082974771 +298 38.4370501443 -122.5170528547 +299 38.4370552564 -122.5170469762 +300 38.4381328904 -122.4981143174 +301 38.4370852296 -122.5172314226 +302 38.48281036 -122.57144628 +303 38.44273439 -122.53165524 +304 38.44155 -122.487945 +305 38.44243667 -122.48915 +306 38.439745 -122.48901333 +307 38.43992 -122.49772833 +308 38.440055 -122.49371333 +309 38.43967 -122.49738333 +310 38.438495 -122.50856167 +311 38.43793 -122.51403 +312 38.48262833 -122.56854167 +313 38.48263667 -122.56853667 +314 38.48264667 -122.56849667 +315 38.48595833 -122.559945 +316 38.48555 -122.56281333 +317 38.48263667 -122.56841333 +318 38.4426055556 -122.5148111111 +319 38.43607 -122.513755 +320 38.44013333 -122.49279667 +321 38.44582833 -122.53428667 +322 38.445795 -122.534155 +323 38.44964667 -122.542595 +324 38.44883667 -122.543305 +325 38.448745 -122.54270833 +326 38.44902833 -122.54252 +327 38.449475 -122.541345 +328 38.44778833 -122.54015333 +329 38.44780333 -122.54014667 +330 38.447495 -122.54001667 +331 38.4479 -122.540205 +332 38.44744167 -122.539955 +333 38.44725833 -122.539605 +334 38.44573833 -122.53976333 +335 38.44592833 -122.53881167 +336 38.44645333 -122.53708667 +337 38.44661333 -122.53530833 +338 38.44698667 -122.53506333 +339 38.44687167 -122.53479667 +340 38.44623 -122.53452167 +341 38.446 -122.534355 +342 38.446 -122.53434667 +343 38.445795 -122.53410333 +344 38.44541167 -122.53399667 +345 38.44541167 -122.53399667 +346 38.44541167 -122.53405833 +347 38.44541167 -122.534095 +348 38.44537167 -122.53404167 +349 38.44495 -122.534745 +350 38.44036333 -122.52388833 +351 38.441005 -122.524575 +352 38.441005 -122.52467167 +353 38.440975 -122.52491667 +354 38.440975 -122.52491667 +355 38.441125 -122.5251 +356 38.44298667 -122.52992167 +357 38.44474667 -122.53503333 +358 38.44355833 -122.53283 +359 38.4574102 -122.5094255 +360 38.439525 -122.4889972222 +361 38.4427833333 -122.4842527778 +362 38.4462647241 -122.4982655554 +363 38.4462647241 -122.4982655554 +364 38.4462647241 -122.4982655554 +365 38.4559776317 -122.4932873755 +366 38.4529867831 -122.4932879579 +367 38.4518774887 -122.4970208354 +368 38.4525261697 -122.495639812 +369 38.4516417793 -122.4972786916 +370 38.4507676748 -122.4965063897 +371 38.4477099637 -122.4970639876 +372 38.4529529493 -122.494918152 +373 38.4462647241 -122.4982655554 +374 38.4462647241 -122.4982655554 +375 38.4462647241 -122.4982655554 +376 38.4462647241 -122.4982655554 +377 38.4462647241 -122.4982655554 +378 38.4462647241 -122.4982655554 +379 38.4462647241 -122.4982655554 +380 38.4462647241 -122.4982655554 +381 38.4462647241 -122.4982655554 +382 38.4556077836 -122.4963987713 +383 38.4815016123 -122.5699037396 +384 38.43785 -122.518045 +385 38.4400835048 -122.5111888276 +386 38.45891667 -122.50900333 +387 38.46603333 -122.52214167 +388 38.46651333 -122.52181167 +389 38.450453 -122.49715 +390 38.4462647241 -122.4982655554 +391 38.4506497857 -122.5077653429 +392 38.45727167 -122.50987167 +393 38.43493333 -122.50604167 +394 38.44613333 -122.50459167 +395 38.43448833 -122.50728667 +396 38.43424167 -122.50773667 +397 38.43478333 -122.51081167 +398 38.43569667 -122.51359667 +399 38.436325 -122.51170333 +400 38.43702833 -122.516 +401 38.43828667 -122.51912 +402 38.43488 -122.510795 +403 38.44765833 -122.50102167 +404 38.441525 -122.52431333 +405 38.44274167 -122.52978667 +406 38.44266333 -122.5298 +407 38.44180333 -122.52741167 +408 38.44317167 -122.52963333 +409 38.4409833333 -122.5253444444 +410 38.4411916667 -122.5255805556 +411 38.4419638889 -122.5296944444 +412 38.4447472222 -122.5349972222 +413 38.4448805556 -122.5347833333 +414 38.4453972222 -122.5340722222 +415 38.4454 -122.5340638889 +416 38.4454166667 -122.5340583333 +417 38.4471361111 -122.5351861111 +418 38.4467972222 -122.5353305556 +419 38.4474111111 -122.5401916667 +420 38.4474305556 -122.5401305556 +421 38.4474444444 -122.5401222222 +422 38.4486166667 -122.5403833333 +423 38.4494833333 -122.5425861111 +424 38.4492194444 -122.5424805556 +425 38.449075 -122.5424194444 +426 38.4489694444 -122.5425194444 +427 38.4497444444 -122.5425111111 +428 38.4468777778 -122.53585 +429 38.4454 -122.5340638889 +430 38.44019667 -122.52345333 +431 38.44006667 -122.52338333 +432 38.44007833 -122.523505 +433 38.440105 -122.52353667 +434 38.440075 -122.523645 +435 38.44208 -122.528175 +436 38.436695 -122.50904167 +437 38.440205 -122.52359667 +438 38.43671333 -122.51335833 +439 38.43833 -122.51162833 +440 38.4369242611 -122.5124731847 +441 38.4368838603 -122.5125112386 +442 38.4369278653 -122.5125570876 +443 38.436896056 -122.5124068001 +444 38.436042904 -122.5109898393 +445 38.435910889 -122.5108268951 +446 38.4358759784 -122.5096494892 +447 38.4358452168 -122.5095932466 +448 38.4356395249 -122.5092956052 +449 38.4353965754 -122.5090718922 +450 38.4353361838 -122.5092362614 +451 38.4354515607 -122.509234585 +452 38.4343515197 -122.507714443 +453 38.4343470773 -122.5060774573 +454 38.4397638403 -122.4973726833 +455 38.439832069 -122.4973854237 +456 38.4397157282 -122.4973509741 +457 38.43549667 -122.50938333 +458 38.44007833 -122.49676333 +459 38.43650833 -122.512755 +460 38.436245 -122.51356333 +461 38.44257167 -122.507705 +462 38.44292833 -122.50721667 +463 38.442005 -122.50886667 +464 38.44183333 -122.50898667 +465 38.43463 -122.50360833 +466 38.437405 -122.51554167 +467 38.43733333 -122.516075 +468 38.439145 -122.523475 +469 38.440105 -122.52368 +470 38.434775 -122.51027833 +471 38.43524167 -122.51030833 +472 38.4432752 -122.53326453 +473 38.44364694 -122.53321048 +474 38.44422315 -122.53339829 +475 38.44348408 -122.53208585 +476 38.44353888 -122.53202529 +477 38.44384342 -122.53139839 +478 38.44365552 -122.53132408 +479 38.44380685 -122.53169291 +480 38.43455978 -122.50846982 +481 38.43464005 -122.50911067 +482 38.43494985 -122.51091401 +483 38.4371892 -122.51629 +484 38.435075 -122.510825 +485 38.43744167 -122.50096167 +486 38.43738333 -122.5009 +487 38.43818333 -122.50814167 +488 38.4378003981 -122.5039895995 +489 38.435245 -122.51142167 +490 38.43697833 -122.51542 +491 38.44035333 -122.51995833 +492 38.440425 -122.51998833 +493 38.4403 -122.49697167 +494 38.4463461249 -122.5392632584 +495 38.4434133333 -122.5329283333 +496 38.43733667 -122.51577833 +497 38.43712 -122.51622833 +498 38.4440706978 -122.5128569789 +499 38.43721333 -122.515595 +500 38.45745833 -122.50908667 +501 38.46487833 -122.51542 +502 38.4425742779 -122.514640658 +503 38.44304167 -122.51206167 +504 38.46305 -122.51158 +505 38.43573833 -122.50931667 +506 38.43448333 -122.50860833 +507 38.4357758356 -122.5095577283 +508 38.45612 -122.49478 +509 38.43694167 -122.51420833 +510 38.43851333 -122.50879667 +511 38.4385754015 -122.5087746348 +512 38.435825 -122.50981167 +513 38.4385 -122.50837833 +514 38.44064667 -122.52488667 +515 38.44002833 -122.49792167 +516 38.4367527871 -122.5086163357 +517 38.43523833 -122.51106333 +518 38.44262167 -122.51772167 +519 38.4437972222 -122.5317777778 +520 38.443475 -122.5326527778 +521 38.4434138889 -122.5339444444 +522 38.4445083333 -122.5339583333 +523 38.4453722222 -122.5340583333 +524 38.43517833 -122.51120833 +525 38.43529167 -122.51100333 +526 38.4400069623 -122.4982872106 +527 38.43568333 -122.51333 +528 38.43578333 -122.513695 +529 38.43608 -122.51363 +530 38.4366229948 -122.5157825256 +531 38.4809538125 -122.5690596063 +532 38.436575 -122.51468667 +533 38.43627167 -122.51458667 +534 38.48069167 -122.57021667 +535 38.47992833 -122.56934667 +536 38.48108833 -122.56924167 +537 38.48136667 -122.56958833 +538 38.48549667 -122.56428667 +539 38.48632167 -122.56251167 +540 38.44111667 -122.5226 +541 38.4409783666 -122.5223185002 +542 38.4396565939 -122.4972883613 +543 38.4396645148 -122.4973184523 +544 38.4439153131 -122.4968092517 +545 38.4461755352 -122.4969748781 +546 38.4440984577 -122.5128437487 +547 38.4411506681 -122.5115074218 +548 38.4458204658 -122.505530154 +549 38.44595 -122.50837 +550 38.445995 -122.50814167 +551 38.457355 -122.50955333 +552 38.43562833 -122.51372167 +553 38.4575629374 -122.5096536801 +554 38.4574401006 -122.5093469025 +555 38.4574277373 -122.5093305578 +556 38.4573799605 -122.50934992 +557 38.4459844279 -122.5082746894 +558 38.4459242039 -122.5084227138 +559 38.4458689252 -122.5083724224 +560 38.4459851822 -122.5082223025 +561 38.4458823363 -122.5056406764 +562 38.4458697215 -122.5055939892 +563 38.4458489344 -122.5055398421 +564 38.4458746249 -122.505538501 +565 38.43893333 -122.50882833 +566 38.43845833 -122.509025 +567 38.43830333 -122.50887167 +568 38.4407208672 -122.5244333223 +569 38.4400659258 -122.5239625946 +570 38.4426302475 -122.532520853 +571 38.43762833 -122.50962167 +572 38.43771667 -122.50934167 +573 38.43762167 -122.50965333 +574 38.4462647241 -122.4982655554 +575 38.4462647241 -122.4982655554 +576 38.4462647241 -122.4982655554 +577 38.437855 -122.51005833 +578 38.4364720457 -122.5091899931 +579 38.46120833 -122.50879667 +580 38.44820833 -122.50675833 +581 38.44830333 -122.50685833 +582 38.4478035196 -122.505921927 +583 38.44953667 -122.51778333 +584 38.44367167 -122.51489167 +585 38.44367 -122.51490333 +586 38.4420567297 -122.5251561776 +587 38.4369367313 -122.5110802914 +588 38.437095 -122.50871333 +589 38.4433975568 -122.5314184651 +590 38.4439020017 -122.531780228 +591 38.4433240299 -122.5336218998 +592 38.4429466782 -122.5317976624 +593 38.436855 -122.5123616667 +594 38.43725 -122.5014433333 +595 38.4372866667 -122.5014016667 +596 38.4373316667 -122.5012533333 +597 38.4375166667 -122.5009083333 +598 38.4375283333 -122.500885 +599 38.4393383333 -122.4991833333 +600 38.439875 -122.4986433333 +601 38.4420133333 -122.4967116667 +602 38.4420416667 -122.4966183333 +603 38.4462016667 -122.4984316667 +604 38.46120833 -122.50878833 +605 38.4426033917 -122.5303491961 +606 38.442555 -122.52988333 +607 38.4459088231 -122.5086311718 +608 38.4575304157 -122.5098826737 +609 38.4344621449 -122.5097658958 +610 38.43755333 -122.49876333 +611 38.44237167 -122.52899167 +612 38.44224167 -122.52835833 +613 38.437745 -122.516555 +614 38.43854167 -122.51912 +615 38.4392 -122.51895833 +616 38.43921333 -122.518945 +617 38.44182 -122.52251333 +618 38.4418 -122.52251333 +619 38.4418 -122.52251333 +620 38.44180333 -122.52253667 +621 38.437655 -122.50367 +622 38.43495833 -122.50605833 +623 38.44286667 -122.52239167 +624 38.43645 -122.51552 +625 38.44058667 -122.516495 +626 38.44081333 -122.51805 +627 38.438805 -122.51651167 +628 38.43735833 -122.51542833 +629 38.43748833 -122.51554167 +630 38.43736333 -122.51557167 +631 38.43613 -122.51612167 +632 38.43580333 -122.51622833 +633 38.43523 -122.51667 +634 38.436345 -122.51563833 +635 38.434895 -122.50997167 +636 38.43760333 -122.50361667 +637 38.43502833 -122.51583833 +638 38.43623 -122.51588333 +639 38.43585833 -122.515205 +640 38.43805333 -122.51188667 +641 38.441125 -122.525505 +642 38.43813667 -122.50847 +643 38.446615 -122.498975 +644 38.4466416667 -122.498975 +645 38.437295 -122.51551167 +646 38.43695833 -122.51560333 +647 38.43713 -122.51538 +648 38.4438943865 -122.496881336 +649 38.4458809722 -122.505498 +650 38.445737 -122.5019309722 +651 38.43785833 -122.51403833 +652 38.445717 -122.4974069722 +653 38.445568 -122.5020319722 +654 38.445971 -122.508441 +655 38.4459889722 -122.5083439722 +656 38.44507167 -122.49648333 +657 38.44743 -122.50076333 +658 38.44593 -122.49826167 +659 38.43735 -122.51556333 +660 38.436455 -122.51648667 +661 38.43679667 -122.51548833 +662 38.4427644167 -122.5144611918 +663 38.442728044 -122.5145514248 +664 38.4618588475 -122.5092437269 +665 38.436175 -122.51218333 +666 38.4374644153 -122.5144083476 +667 38.436355 -122.515525 +668 38.4381416667 -122.5118777778 +669 38.4382222222 -122.5116277778 +670 38.4383083333 -122.5115972222 +671 38.43845 -122.51175 +672 38.4406583333 -122.5110861111 +673 38.4414361111 -122.5117194444 +674 38.4420083333 -122.5115138889 +675 38.44205 -122.5107277778 +676 38.4420861111 -122.5107194444 +677 38.4434777778 -122.5129916667 +678 38.4434166667 -122.5135333333 +679 38.4440833333 -122.5127861111 +680 38.4441305556 -122.51265 +681 38.4448777778 -122.5103694444 +682 38.4460305556 -122.5083833333 +683 38.4459805556 -122.5061555556 +684 38.4463916667 -122.5043555556 +685 38.4466777778 -122.50225 +686 38.4467416667 -122.5023027778 +687 38.4468722222 -122.5023027778 +688 38.4477972222 -122.5023111111 +689 38.4481305556 -122.5014583333 +690 38.448075 -122.5016027778 +691 38.447825 -122.5014888889 +692 38.4476861111 -122.501275 +693 38.4476388889 -122.5011972222 +694 38.4476555556 -122.50055 +695 38.4477083333 -122.4993527778 +696 38.4460444444 -122.4982916667 +697 38.4457805556 -122.4977111111 +698 38.4462361111 -122.4971611111 +699 38.4430055556 -122.4966194444 +700 38.44305 -122.496575 +701 38.4418611111 -122.4966888889 +702 38.4398194444 -122.4969722222 +703 38.4370944444 -122.5016638889 +704 38.4380833333 -122.5044861111 +705 38.4385805556 -122.5087972222 +706 38.4391722222 -122.5112083333 +707 38.4382472222 -122.5114833333 +708 38.4388916667 -122.5116416667 +709 38.4406611111 -122.5111083333 +710 38.4407583333 -122.5111555556 +711 38.4408222222 -122.5112 +712 38.4418 -122.5104833333 +713 38.4430694444 -122.51175 +714 38.4460055556 -122.5082777778 +715 38.4471694444 -122.5022361111 +716 38.4478944444 -122.5017166667 +717 38.4483833333 -122.5013583333 +718 38.4481694444 -122.5015111111 +719 38.4477583333 -122.4993583333 +720 38.4478333333 -122.5003444444 +721 38.4459833333 -122.4982833333 +722 38.4427194444 -122.4966805556 +723 38.4459083333 -122.4977194444 +724 38.4407916667 -122.4967416667 +725 38.4394861111 -122.5105888889 +726 38.4379722222 -122.5002833333 +727 38.4383805556 -122.5134888889 +728 38.44496167 -122.510125 +729 38.44766167 -122.501245 +730 38.441345 -122.525345 +731 38.43984167 -122.51387833 +732 38.4381489893 -122.51243355 +733 38.4381918964 -122.5090196326 +734 38.438225 -122.5161222222 +735 38.4405416667 -122.5180805556 +736 38.4412527778 -122.5200944444 +737 38.4415083333 -122.5203166667 +738 38.4433944444 -122.5235277778 +739 38.4424944444 -122.5233833333 +740 38.4412214428 -122.5254856617 +741 38.4412130395 -122.5252970626 +742 38.44115 -122.5253305556 +743 38.4408388889 -122.5248555556 +744 38.4395611111 -122.5226361111 +745 38.4378768432 -122.5196322372 +746 38.4382833333 -122.5182888889 +747 38.4377497046 -122.5176809162 +748 38.4476166667 -122.5018083333 +749 38.442795 -122.516945 +750 38.443825 -122.51497 +751 38.44398 -122.51483 +752 38.44392 -122.51322167 +753 38.44597 -122.50872833 +754 38.44775 -122.50510333 +755 38.45665 -122.508645 +756 38.45674167 -122.50936167 +757 38.4821294994 -122.5690504444 +758 38.45729667 -122.50956667 +759 38.44497 -122.510155 +760 38.43572 -122.50896333 +761 38.45198333 -122.50690833 +762 38.46117833 -122.50877167 +763 38.46602167 -122.52208 +764 38.45913333 -122.50869667 +765 38.43587833 -122.51380833 +766 38.437195 -122.51629667 +767 38.4860578235 -122.5559734181 +768 38.483184 -122.5635649722 +769 38.4864749722 -122.5617699722 +770 38.483184 -122.5635649722 +771 38.486615 -122.561648 +772 38.487461 -122.56072 +773 38.448575 -122.54022167 +774 38.44335333 -122.531295 +775 38.4521728289 -122.4938524516 +776 38.44188667 -122.48648833 +777 38.44252833 -122.52989167 +778 38.43779667 -122.50848667 +779 38.4475 -122.5008333333 +780 38.43986667 -122.49844167 +781 38.43663333 -122.50073333 +782 38.434825 -122.505875 +783 38.43431167 -122.50763667 +784 38.4374621849 -122.4992241852 +785 38.43488333 -122.51072833 +786 38.43456167 -122.50996667 +787 38.434605 -122.51003667 +788 38.4368242197 -122.5079900399 +789 38.484448961 -122.5709631562 +790 38.438295 -122.51827167 +791 38.44310333 -122.48414667 +792 38.44807833 -122.48142167 +793 38.44803833 -122.481445 +794 38.44312 -122.48417833 +795 38.44176333 -122.486625 +796 38.44218833 -122.48906667 +797 38.44159667 -122.488075 +798 38.44016667 -122.49250833 +799 38.437855 -122.50041167 +800 38.437855 -122.50041167 +801 38.44015833 -122.49230833 +802 38.441525 -122.487975 +803 38.44227167 -122.48908333 +804 38.45314667 -122.482575 +805 38.4400751861 -122.4925287814 +806 38.4395281831 -122.4888244831 +807 38.4398400737 -122.4889820629 +808 38.4401763557 -122.488701353 +809 38.4401747212 -122.4887828251 +810 38.4403791977 -122.488815263 +811 38.4402837279 -122.4884668273 +812 38.4408069263 -122.4884148595 +813 38.4411481535 -122.4887510576 +814 38.4424623102 -122.4895587378 +815 38.4423092986 -122.4890381378 +816 38.4423038923 -122.4890482799 +817 38.4415426897 -122.4879794196 +818 38.441721811 -122.4865959027 +819 38.4420341207 -122.4845413306 +820 38.4431225527 -122.4841334672 +821 38.4431177331 -122.4840985147 +822 38.4431054537 -122.4840880373 +823 38.4431225947 -122.4841867761 +824 38.4480537102 -122.4814396073 +825 38.4524205141 -122.482379051 +826 38.4814403234 -122.5495691364 +827 38.44746167 -122.49302 +828 38.44295833 -122.4843 +829 38.43903667 -122.48873 +830 38.4378694444 -122.5143277778 +831 38.4382527778 -122.5145722222 +832 38.4383111111 -122.5145555556 +833 38.4383805556 -122.5145416667 +834 38.4383222222 -122.5144638889 +835 38.4384361111 -122.5146194444 +836 38.43845 -122.5145722222 +837 38.4384833333 -122.5145277778 +838 38.4385555556 -122.5144638889 +839 38.4385777778 -122.5143194444 +840 38.4385944444 -122.5142527778 +841 38.4386555556 -122.5142527778 +842 38.4387527778 -122.514175 +843 38.4388361111 -122.5142444444 +844 38.4388333333 -122.5141888889 +845 38.4387611111 -122.514175 +846 38.438675 -122.5139861111 +847 38.4386472222 -122.5138388889 +848 38.4382305556 -122.5132222222 +849 38.4380833333 -122.5130916667 +850 38.4381388889 -122.5125361111 +851 38.4380611111 -122.5123888889 +852 38.4381388889 -122.5121472222 +853 38.4381333333 -122.5120472222 +854 38.4379694444 -122.5119166667 +855 38.4379638889 -122.5118555556 +856 38.4380083333 -122.5117805556 +857 38.4381194444 -122.51175 +858 38.4382972222 -122.5115527778 +859 38.4388138889 -122.5116638889 +860 38.4389444444 -122.5115527778 +861 38.4391146566 -122.5112224016 +862 38.4391944444 -122.5101305556 +863 38.4391083333 -122.5101027778 +864 38.4383472222 -122.5088361111 +865 38.4382929571 -122.5088328498 +866 38.4381944444 -122.5084444444 +867 38.4380611111 -122.5082944444 +868 38.4380944444 -122.5083611111 +869 38.437925 -122.5083222222 +870 38.4369444444 -122.5084083333 +871 38.4369472222 -122.5084388889 +872 38.4367222222 -122.5109388889 +873 38.4388138889 -122.5116638889 +874 38.437205 -122.51586167 +875 38.48468833 -122.57086167 +876 38.4458139722 -122.5384329722 +877 38.448511 -122.543137 +878 38.4490309722 -122.542518 +879 38.4489 -122.542838 +880 38.449441 -122.542642 +881 38.4482679722 -122.5402579722 +882 38.446513 -122.5352019722 +883 38.447484 -122.539619 +884 38.447011 -122.5392279722 +885 38.449063 -122.540907 +886 38.4490309722 -122.542518 +887 38.4462959722 -122.537664 +888 38.4486539722 -122.5433519722 +889 38.44673667 -122.53456167 +890 38.4413063912 -122.4967125436 +891 38.4424163081 -122.4967148927 +892 38.442794561 -122.4965617239 +893 38.44430947 -122.4970156047 +894 38.4451180836 -122.4964321082 +895 38.4477180754 -122.4996002619 +896 38.4449351794 -122.5101313462 +897 38.4475824659 -122.5010634719 +898 38.4359477172 -122.5138246534 +899 38.4476001069 -122.5011256382 +900 38.444953278 -122.5100768174 +901 38.4450200047 -122.5100890196 +902 38.4359463436 -122.5138236667 +903 38.4475544898 -122.5011079221 +904 38.4474994693 -122.5010594197 +905 38.4475130334 -122.5010624548 +906 38.4441147244 -122.4970801452 +907 38.442389902 -122.4968225973 +908 38.48639167 -122.56195 +909 38.4860957743 -122.5708293755 +910 38.4434080556 -122.532745 +911 38.4486263889 -122.5426538889 +912 38.4466108333 -122.5345391667 +913 38.4464013889 -122.5376825 +914 38.4453638889 -122.5340722222 +915 38.485875 -122.559805 +916 38.48228833 -122.55391667 +917 38.44105833 -122.511095 +918 38.44452167 -122.51068833 +919 38.4854026818 -122.5701982136 +920 38.4860398228 -122.5705455971 +921 38.44493 -122.51007833 +922 38.44604167 -122.50823833 +923 38.44711333 -122.50218833 +924 38.447895 -122.50143333 +925 38.44717 -122.50222833 +926 38.43762167 -122.5084 +927 38.44346667 -122.51343667 +928 38.445995 -122.50830833 +929 38.44405333 -122.512795 +930 38.44207 -122.51156667 +931 38.44496167 -122.51008667 +932 38.44496167 -122.51008667 +933 38.448875 -122.501275 +934 38.44889667 -122.50121333 +935 38.44907 -122.50123667 +936 38.44907833 -122.50112167 +937 38.44951167 -122.50126667 +938 38.45018 -122.50289167 +939 38.45025 -122.50296167 +940 38.437595 -122.50837 +941 38.4350339183 -122.5102407485 +942 38.439646787 -122.4967563619 +943 38.44556 -122.5226169722 +944 38.4362611606 -122.5132776797 +945 38.4362159897 -122.5171370432 +946 38.43728833 -122.51632 +947 38.44111667 -122.52273833 +948 38.4455519722 -122.5224929722 +949 38.4468133333 -122.4995233333 +950 38.4476733333 -122.4994183333 +951 38.4476033333 -122.499385 +952 38.44774 -122.4995566667 +953 38.4477333333 -122.4996933333 +954 38.44784 -122.5002883333 +955 38.46740833 -122.52285 +956 38.46720833 -122.52308667 +957 38.46730833 -122.523095 +958 38.4487533333 -122.5013083333 +959 38.44741167 -122.51850833 +960 38.46642833 -122.52181167 +961 38.44988 -122.5016383333 +962 38.4437319722 -122.523503 +963 38.4450659722 -122.522755 +964 38.44449667 -122.5185 +965 38.449255 -122.51814167 +966 38.46494667 -122.51664667 +967 38.4530043 -122.48258918 +968 38.45231948 -122.5010999 +969 38.449915 -122.501455 +970 38.46645 -122.52184167 +971 38.4372810374 -122.5164107539 +972 38.44158667 -122.52400333 +973 38.43723 -122.51603 +974 38.4378527778 -122.5143722222 +975 38.4378444444 -122.5143805556 +976 38.4379111111 -122.5143194444 +977 38.4378611111 -122.5144888889 +978 38.4376861111 -122.5146722222 +979 38.4375138889 -122.5151611111 +980 38.4374055556 -122.5153638889 +981 38.4371027778 -122.5157388889 +982 38.4372944444 -122.5159 +983 38.4370944444 -122.5157083333 +984 38.4370111111 -122.5158472222 +985 38.4370027778 -122.5161222222 +986 38.4370444444 -122.5161138889 +987 38.4368638889 -122.5170444444 +988 38.4364138889 -122.5175305556 +989 38.4364805556 -122.5175166667 +990 38.4364194444 -122.5176472222 +991 38.436425 -122.5174944444 +992 38.4365611111 -122.5175694444 +993 38.4364555556 -122.5175638889 +994 38.4365388889 -122.5176555556 +995 38.4366361111 -122.5175555556 +996 38.4365805556 -122.5175861111 +997 38.4366666667 -122.5175472222 +998 38.4366972222 -122.5176027778 +999 38.4365888889 -122.517525 +1000 38.4365611111 -122.5174722222 +1001 38.4365583333 -122.5173416667 +1002 38.4366055556 -122.5171583333 +1003 38.4366694444 -122.5168388889 +1004 38.4367444444 -122.5167388889 +1005 38.436475 -122.5167611111 +1006 38.4364777778 -122.5167777778 +1007 38.4365555556 -122.5165388889 +1008 38.4366222222 -122.5162111111 +1009 38.4372722222 -122.5157777778 +1010 38.4373111111 -122.5155805556 +1011 38.4372138889 -122.5154722222 +1012 38.4373527778 -122.5153444444 +1013 38.4374555556 -122.5152194444 +1014 38.4374694444 -122.5151611111 +1015 38.4376138889 -122.5150138889 +1016 38.4376305556 -122.5148944444 +1017 38.4374861111 -122.5149 +1018 38.4375843206 -122.5147814 +1019 38.444794816 -122.5102236494 +1020 38.4466678519 -122.5084510446 +1021 38.4517848927 -122.5038394934 +1022 38.4521333333 -122.5058666667 +1023 38.4561722222 -122.495375 +1024 38.4561694444 -122.495375 +1025 38.4527666667 -122.4858305556 +1026 38.4515416667 -122.4813888889 +1027 38.4512777778 -122.4801861111 +1028 38.4479166667 -122.4797027778 +1029 38.4430527778 -122.4841972222 +1030 38.4426722222 -122.4848388889 +1031 38.4418972222 -122.4858305556 +1032 38.4415888889 -122.4873083333 +1033 38.4419916667 -122.4878305556 +1034 38.4419916667 -122.4878305556 +1035 38.4425416667 -122.4890138889 +1036 38.4425416667 -122.4890138889 +1037 38.4405194444 -122.4885027778 +1038 38.4405194444 -122.4885027778 +1039 38.4405194444 -122.4885027778 +1040 38.4380111111 -122.49875 +1041 38.4380111111 -122.49875 +1042 38.4447404597 -122.5104482844 +1043 38.43562 -122.51380333 +1044 38.43645833 -122.51532833 +1045 38.43668333 -122.51533667 +1046 38.43562 -122.51380333 +1047 38.4527282392 -122.4933852621 +1048 38.44365333 -122.533325 +1049 38.4406125597 -122.5246852051 +1050 38.44576667 -122.50875 +1051 38.452645 -122.50401333 +1052 38.45038333 -122.50336333 +1053 38.450425 -122.50305333 +1054 38.45211333 -122.50740833 +1055 38.44801667 -122.50672167 +1056 38.44518333 -122.50971167 +1057 38.45105833 -122.50631667 +1058 38.44632167 -122.50876667 +1059 38.45047 -122.50344667 +1060 38.44407167 -122.51273333 +1061 38.44222167 -122.51068833 +1062 38.44497 -122.51013833 +1063 38.4422413059 -122.5222907977 +1064 38.43723 -122.51615 +1065 38.438655 -122.49742167 +1066 38.43807167 -122.50038667 +1067 38.43975 -122.49818667 +1068 38.44093333 -122.51128333 +1069 38.44584167 -122.49775 +1070 38.45041667 -122.50306667 +1071 38.4417212243 -122.5173670427 +1072 38.4411713714 -122.5253207983 +1073 38.44403 -122.512755 +1074 38.45743833 -122.509605 +1075 38.43647394 -122.50097814 +1076 38.43742248 -122.49929252 +1077 38.43749673 -122.49903777 +1078 38.43994792 -122.49776358 +1079 38.43799655 -122.50025236 +1080 38.4430077778 -122.5307130556 +1081 38.457405 -122.50953667 +1082 38.45240333 -122.500695 +1083 38.44303833 -122.48418 +1084 38.45000333 -122.50232167 +1085 38.4452946932 -122.5320360068 +1086 38.445305 -122.53167833 +1087 38.44485833 -122.531525 +1088 38.44521667 -122.53211167 +1089 38.4484564168 -122.5305767908 +1090 38.44609167 -122.53225833 +1091 38.446205 -122.53215 +1092 38.446275 -122.53221167 +1093 38.4481 -122.530875 +1094 38.43787833 -122.507895 +1095 38.439641209 -122.4973098025 +1096 38.43769667 -122.50377 +1097 38.437225 -122.49961167 +1098 38.43739667 -122.4991 +1099 38.4817090553 -122.5701037371 +1100 38.481710472 -122.570103046 +1101 38.4831589228 -122.5636151155 +1102 38.4812019579 -122.5718100184 +1103 38.44578 -122.50749167 +1104 38.44405 -122.51093333 +1105 38.44745 -122.502205 +1106 38.442355 -122.52956333 +1107 38.44246333 -122.533705 +1108 38.44106667 -122.525275 +1109 38.44265333 -122.53325 +1110 38.439725 -122.52306333 +1111 38.44123 -122.52609167 +1112 38.441861 -122.4863589722 +1113 38.4379619722 -122.5005019722 +1114 38.4402559722 -122.4883999722 +1115 38.4415899722 -122.487532 +1116 38.441822 -122.485556 +1117 38.4423409722 -122.4843039722 +1118 38.4518410963 -122.4936563149 +1119 38.44827833 -122.53071667 +1120 38.44881167 -122.530595 +1121 38.44886167 -122.530045 +1122 38.448805 -122.53008333 +1123 38.44883333 -122.53047167 +1124 38.44858 -122.53056333 +1125 38.44793333 -122.53102833 +1126 38.44841333 -122.53063833 +1127 38.445675 -122.53234167 +1128 38.44303 -122.53015833 +1129 38.44189667 -122.52893 +1130 38.441095 -122.52612167 +1131 38.43985833 -122.523445 +1132 38.43720333 -122.51772167 +1133 38.449047534 -122.4965362851 +1134 38.4454211822 -122.507188593 +1135 38.4388089639 -122.5057932463 +1136 38.4385309394 -122.5070891435 +1137 38.4385395653 -122.5072977104 +1138 38.4357320963 -122.5092679307 +1139 38.440914824 -122.5112858535 +1140 38.4420843093 -122.510690869 +1141 38.4421970923 -122.5105253634 +1142 38.4423253854 -122.5105609334 +1143 38.443479505 -122.5133215551 +1144 38.43777872 -122.5146067 +1145 38.43809483 -122.50016595 +1146 38.43732284 -122.51628806 +1147 38.443604 -122.5134549722 +1148 38.447825 -122.49513333 +1149 38.44788333 -122.49518667 +1150 38.45488 -122.49251667 +1151 38.4389102593 -122.5142047543 +1152 38.441589097 -122.5245008624 +1153 38.4461513219 -122.4972541494 +1154 38.44628491 -122.53445923 +1155 38.44638577 -122.53458128 +1156 38.44646544 -122.5344336 +1157 38.44914329 -122.54246137 +1158 38.44926262 -122.54217095 +1159 38.44960716 -122.54209453 +1160 38.44835857 -122.54018786 +1161 38.451155 -122.47994167 +1162 38.4411 -122.49673333 +1163 38.45415833 -122.48815833 +1164 38.437295 -122.50132833 +1165 38.43799167 -122.50031333 +1166 38.44010833 -122.49299667 +1167 38.4405462334 -122.49684015 +1168 38.4397023742 -122.4981685998 +1169 38.4377828212 -122.5045780087 +1170 38.480025 -122.56897 +1171 38.481625 -122.56935 +1172 38.482005 -122.56951167 +1173 38.48258333 -122.56870333 +1174 38.48253333 -122.56664167 +1175 38.48129167 -122.56971667 +1176 38.48145 -122.569595 +1177 38.48108667 -122.56929667 +1178 38.44811667 -122.50736167 +1179 38.44396494 -122.53166397 +1180 38.44293336 -122.5315576 +1181 38.4816770812 -122.5706011017 +1182 38.481543588 -122.5698893785 +1183 38.4816794588 -122.5701046658 +1184 38.4816016248 -122.5698376922 +1185 38.4815828748 -122.5698351836 +1186 38.4815408816 -122.5691311906 +1187 38.4816584623 -122.5702941017 +1188 38.4801477962 -122.5697034738 +1189 38.4810646897 -122.5705730515 +1190 38.4812326631 -122.5704121189 +1191 38.4807875327 -122.5712972479 +1192 38.4373388889 -122.5148388889 +1193 38.4376444444 -122.5144805556 +1194 38.437625 -122.5143722222 +1195 38.4374694444 -122.5142277778 +1196 38.4370527778 -122.5136722222 +1197 38.4370583333 -122.5136638889 +1198 38.4369166667 -122.5134055556 +1199 38.4370388889 -122.5132972222 +1200 38.4368777778 -122.5132444444 +1201 38.4371916667 -122.5142277778 +1202 38.4373888889 -122.5143888889 +1203 38.437325 -122.5145333333 +1204 38.4374638889 -122.5145805556 +1205 38.4375138889 -122.514625 +1206 38.4371861111 -122.5152972222 +1207 38.4372583333 -122.5155333333 +1208 38.4375611111 -122.5161055556 +1209 38.4376055556 -122.5163111111 +1210 38.4377305556 -122.5172805556 +1211 38.4374222222 -122.51735 +1212 38.4372472222 -122.5173416667 +1213 38.4372611111 -122.5172111111 +1214 38.4371555556 -122.5172055556 +1215 38.4368722222 -122.5164027778 +1216 38.4376 -122.51651167 +1217 38.4346360788 -122.5103602045 +1218 38.4405658323 -122.5133500875 +1219 38.4405180832 -122.5131355108 +1220 38.43956333 -122.49598 +1221 38.4423527778 -122.4963833333 +1222 38.4423333333 -122.496375 +1223 38.4423333333 -122.496375 +1224 38.4422833333 -122.4964611111 +1225 38.4423111111 -122.4964138889 +1226 38.4404666667 -122.4968722222 +1227 38.4423111111 -122.4964138889 +1228 38.43430833 -122.5076 +1229 38.4496666667 -122.502255 +1230 38.4498116667 -122.50221 +1231 38.450275 -122.5027183333 +1232 38.4501733333 -122.5029616667 +1233 38.45015 -122.5030866667 +1234 38.4571283333 -122.50392 +1235 38.437595 -122.51746333 +1236 38.44595333 -122.50788 +1237 38.44778667 -122.49949667 +1238 38.4394182728 -122.4931791598 +1239 38.439216 -122.5206809722 +1240 38.443544333 -122.5053063002 +1241 38.43735833 -122.51501333 +1242 38.4380305556 -122.5146472222 +1243 38.4383555556 -122.5087333333 +1244 38.44262167 -122.53072167 +1245 38.44307833 -122.53092167 +1246 38.438555 -122.51907167 +1247 38.44399667 -122.51298667 +1248 38.4364694444 -122.5162583333 +1249 38.4364027778 -122.5164277778 +1250 38.43625 -122.5164194444 +1251 38.4362472222 -122.5164416667 +1252 38.4362055556 -122.5163888889 +1253 38.4361888889 -122.5163944444 +1254 38.4362166667 -122.5163555556 +1255 38.4362444444 -122.51635 +1256 38.4362055556 -122.5163277778 +1257 38.4361305556 -122.5162972222 +1258 38.4361305556 -122.5163416667 +1259 38.4361361111 -122.5162583333 +1260 38.4361361111 -122.5162972222 +1261 38.4361333333 -122.5163277778 +1262 38.4360944444 -122.5163111111 +1263 38.4360805556 -122.5162888889 +1264 38.4361027778 -122.5163111111 +1265 38.4359222222 -122.5161888889 +1266 38.4360027778 -122.5162277778 +1267 38.4360305556 -122.5162277778 +1268 38.4360333333 -122.5162361111 +1269 38.4360027778 -122.51625 +1270 38.4361055556 -122.5162361111 +1271 38.4361027778 -122.5161805556 +1272 38.4361694444 -122.5160527778 +1273 38.4362138889 -122.5160527778 +1274 38.4361638889 -122.5160138889 +1275 38.4362027778 -122.5160222222 +1276 38.4361 -122.5159972222 +1277 38.4360444444 -122.5159527778 +1278 38.4362055556 -122.5157861111 +1279 38.4361 -122.5159055556 +1280 38.4362777778 -122.5158611111 +1281 38.43625 -122.5157777778 +1282 38.4362916667 -122.5157027778 +1283 38.4361805556 -122.5157611111 +1284 38.4364805556 -122.515625 +1285 38.4364777778 -122.515625 +1286 38.4369138889 -122.5157944444 +1287 38.4368805556 -122.5158694444 +1288 38.4367972222 -122.5158472222 +1289 38.4368444444 -122.5158083333 +1290 38.4369194444 -122.5158777778 +1291 38.4372916667 -122.5164472222 +1292 38.4372527778 -122.5164472222 +1293 38.4371888889 -122.5163805556 +1294 38.4372861111 -122.5160055556 +1295 38.436867782 -122.5158519327 +1296 38.443755 -122.51326667 +1297 38.443775 -122.51338333 +1298 38.45732833 -122.50942167 +1299 38.4521110655 -122.5012043655 +1300 38.4418594236 -122.4857903358 +1301 38.44334667 -122.51307833 +1302 38.45340833 -122.50873667 +1303 38.4399995356 -122.4983265618 +1304 38.45550333 -122.50914667 +1305 38.45706667 -122.50949167 +1306 38.457195 -122.50431167 +1307 38.45586167 -122.496095 +1308 38.45405333 -122.48808333 +1309 38.45283667 -122.48523667 +1310 38.453045 -122.48483333 +1311 38.451795 -122.481705 +1312 38.44358333 -122.48329167 +1313 38.44185333 -122.48664667 +1314 38.44186167 -122.48689167 +1315 38.440595 -122.48851167 +1316 38.45488333 -122.50968833 +1317 38.45386333 -122.49316333 +1318 38.45415833 -122.48806167 +1319 38.45311167 -122.48628833 +1320 38.44337833 -122.49568 +1321 38.4491583333 -122.542625 +1322 38.43768 -122.51631667 +1323 38.437775 -122.51416667 +1324 38.44378333 -122.513275 +1325 38.44386333 -122.51413667 +1326 38.44362833 -122.51425833 +1327 38.44361333 -122.51423667 +1328 38.4368138529 -122.5208918915 +1329 38.4354054183 -122.508023819 +1330 38.4344270406 -122.5075083319 +1331 38.4346346185 -122.5068045873 +1332 38.4372939868 -122.4991260096 +1333 38.4408511408 -122.4883280229 +1334 38.4369405602 -122.5184553489 +1335 38.4360430313 -122.5130321735 +1336 38.43608833 -122.513145 +1337 38.4375161073 -122.5083108993 +1338 38.4375348827 -122.5082363841 +1339 38.44772167 -122.50118333 +1340 38.44612167 -122.508055 +1341 38.4382566065 -122.5048168191 +1342 38.4399438368 -122.4982302897 +1343 38.4404375777 -122.4988984411 +1344 38.4397802269 -122.4983107859 +1345 38.4392019594 -122.4994043728 +1346 38.4382386692 -122.5001446624 +1347 38.4377267864 -122.500487147 +1348 38.438326302 -122.5075843559 +1349 38.4371464234 -122.5079904591 +1350 38.4365303954 -122.5079312829 +1351 38.4356413689 -122.5092319866 +1352 38.43536167 -122.51259667 +1353 38.43649167 -122.50833 +1354 38.43745 -122.51604667 +1355 38.44952833 -122.54197833 +1356 38.4365426224 -122.5005885799 +1357 38.4342388889 -122.5077444444 +1358 38.4345361111 -122.5060666667 +1359 38.4364472222 -122.5005277778 +1360 38.4364694444 -122.5004972222 +1361 38.4382666667 -122.49765 +1362 38.4382027778 -122.4976666667 +1363 38.439475 -122.4889361111 +1364 38.4405638889 -122.4881972222 +1365 38.4407472222 -122.4883111111 +1366 38.441025 -122.4887083333 +1367 38.4424027778 -122.4896166667 +1368 38.44255 -122.4892666667 +1369 38.4415972222 -122.4872583333 +1370 38.4425388889 -122.4891055556 +1371 38.4417805556 -122.4860222222 +1372 38.4429472222 -122.4843138889 +1373 38.4418722222 -122.4847027778 +1374 38.4430722222 -122.4841833333 +1375 38.4441944444 -122.4831611111 +1376 38.4485805556 -122.4809027778 +1377 38.4486888889 -122.4803861111 +1378 38.4491916667 -122.4800416667 +1379 38.4496333333 -122.4800333333 +1380 38.449725 -122.4800861111 +1381 38.4509472222 -122.4799277778 +1382 38.452025 -122.4819861111 +1383 38.453 -122.4833055556 +1384 38.4530444444 -122.4847722222 +1385 38.4529833333 -122.4848333333 +1386 38.4545138889 -122.4892111111 +1387 38.4547194444 -122.4902416667 +1388 38.4547666667 -122.4906 +1389 38.4559111111 -122.49575 +1390 38.4558666667 -122.4962166667 +1391 38.4559222222 -122.4962083333 +1392 38.45235 -122.502 +1393 38.4481611111 -122.5014805556 +1394 38.4478444444 -122.5014111111 +1395 38.4475444444 -122.5010916667 +1396 38.4457888889 -122.4974222222 +1397 38.4403083333 -122.4969777778 +1398 38.4365916667 -122.5080555556 +1399 38.48115833 -122.56932833 +1400 38.484055 -122.55702167 +1401 38.483545 -122.5568 +1402 38.48644667 -122.56140833 +1403 38.48170833 -122.56966333 +1404 38.48178 -122.56931333 +1405 38.48209667 -122.56897 +1406 38.48152167 -122.569755 +1407 38.48223833 -122.57212 +1408 38.48170833 -122.56968667 +1409 38.48078667 -122.56907 +1410 38.48033 -122.56904667 +1411 38.48041167 -122.57002167 +1412 38.48158667 -122.56991667 +1413 38.48053667 -122.57025833 +1414 38.4819 -122.54820333 +1415 38.444905 -122.51912 +1416 38.44490833 -122.51913333 +1417 38.44248833 -122.5309 +1418 38.44452167 -122.53520833 +1419 38.44639667 -122.53453833 +1420 38.44568833 -122.534155 +1421 38.44308833 -122.5316 +1422 38.4416367997 -122.4872533 +1423 38.46521167 -122.51729667 +1424 38.4456873842 -122.5192568923 +1425 38.464675 -122.516555 +1426 38.46473 -122.51660833 +1427 38.44848833 -122.50686667 +1428 38.43987833 -122.52368 +1429 38.43608833 -122.50873667 +1430 38.4369150861 -122.5084279105 +1431 38.4371845332 -122.5163125992 +1432 38.4364057836 -122.5157266297 +1433 38.437005 -122.51554667 +1434 38.43696667 -122.51393833 +1435 38.4385556513 -122.513965331 +1436 38.4376333333 -122.5078805556 +1437 38.43437 -122.5083 +1438 38.443905 -122.51811167 +1439 38.4356293767 -122.5113658442 +1440 38.4350331312 -122.5108813014 +1441 38.4358305556 -122.5114666667 +1442 38.4358805556 -122.5114805556 +1443 38.4358388889 -122.5112916667 +1444 38.4358027778 -122.5112777778 +1445 38.4358277778 -122.5112777778 +1446 38.4355638889 -122.5110388889 +1447 38.4356222222 -122.5111 +1448 38.4356333333 -122.5109472222 +1449 38.4357055556 -122.5108944444 +1450 38.4356333333 -122.5108944444 +1451 38.4355833333 -122.5108638889 +1452 38.4363694444 -122.5110861111 +1453 38.4360777778 -122.5109777778 +1454 38.435975 -122.5110777778 +1455 38.4358805556 -122.5110555556 +1456 38.4358694444 -122.5110694444 +1457 38.4358444444 -122.5110944444 +1458 38.4358277778 -122.5108805556 +1459 38.4357805556 -122.5107888889 +1460 38.4357055556 -122.5105666667 +1461 38.4354694444 -122.5103 +1462 38.4353833333 -122.5103 +1463 38.4353 -122.5102694444 +1464 38.4352777778 -122.5102777778 +1465 38.4354027778 -122.5104972222 +1466 38.4353972222 -122.5106111111 +1467 38.4354055556 -122.5106055556 +1468 38.4354527778 -122.5106583333 +1469 38.4354944444 -122.5105972222 +1470 38.4357 -122.5109194444 +1471 38.4357055556 -122.510925 +1472 38.4356444444 -122.5105527778 +1473 38.4357555556 -122.5115527778 +1474 38.43575 -122.5115722222 +1475 38.4359861111 -122.5117194444 +1476 38.4355444444 -122.5118388889 +1477 38.4355972222 -122.5130083333 +1478 38.4361027778 -122.5133583333 +1479 38.4366861111 -122.5135888889 +1480 38.4366972222 -122.5136027778 +1481 38.4369138889 -122.5141222222 +1482 38.4369416667 -122.5142138889 +1483 38.4357444444 -122.5133138889 +1484 38.4357027778 -122.5132055556 +1485 38.4355861111 -122.5130861111 +1486 38.4353722222 -122.5125805556 +1487 38.4354388889 -122.5119027778 +1488 38.4349805556 -122.5109194444 +1489 38.4349722222 -122.5108111111 +1490 38.4349611111 -122.5107944444 +1491 38.4349444444 -122.5107888889 +1492 38.4348777778 -122.5108111111 +1493 38.4348083333 -122.5107111111 +1494 38.4348805556 -122.5108194444 +1495 38.4344861111 -122.5102916667 +1496 38.4345277778 -122.5100861111 +1497 38.4345138889 -122.5099416667 +1498 38.4345388889 -122.5099638889 +1499 38.4345333333 -122.5099333333 +1500 38.4345777778 -122.5098888889 +1501 38.4345388889 -122.5099194444 +1502 38.434575 -122.50975 +1503 38.434575 -122.50975 +1504 38.4345805556 -122.5097111111 +1505 38.4349222222 -122.5082638889 +1506 38.4351055556 -122.5080722222 +1507 38.4354944444 -122.5084694444 +1508 38.4355194444 -122.5103305556 +1509 38.4357111111 -122.5105361111 +1510 38.4359277778 -122.5110638889 +1511 38.4366111111 -122.5115527778 +1512 38.4366416667 -122.5117333333 +1513 38.4368277778 -122.5120555556 +1514 38.4377416667 -122.5150972222 +1515 38.4369444444 -122.5126027778 +1516 38.4374194444 -122.5156638889 +1517 38.4371805556 -122.5162361111 +1518 38.4369861111 -122.5143888889 +1519 38.4369333333 -122.5142611111 +1520 38.4359416667 -122.5122972222 +1521 38.4357722222 -122.5117555556 +1522 38.4357388889 -122.5114138889 +1523 38.4359638889 -122.5114972222 +1524 38.4371388889 -122.5159527778 +1525 38.4371916667 -122.5162722222 +1526 38.4362416667 -122.5116972222 +1527 38.4361194444 -122.5109722222 +1528 38.4360277778 -122.5109777778 +1529 38.4358777778 -122.5109472222 +1530 38.4357777778 -122.5105277778 +1531 38.4355638889 -122.5101083333 +1532 38.4355694444 -122.5101305556 +1533 38.4358527778 -122.5107555556 +1534 38.4359388889 -122.5115138889 +1535 38.4365361111 -122.512925 +1536 38.4369083333 -122.5124666667 +1537 38.4369638889 -122.5125722222 +1538 38.4369944444 -122.5127722222 +1539 38.4376555556 -122.5140916667 +1540 38.4376833333 -122.5141833333 +1541 38.4378333333 -122.5140444444 +1542 38.4367444444 -122.5137388889 +1543 38.4364 -122.5134194444 +1544 38.435425 -122.5128111111 +1545 38.4353861111 -122.5127805556 +1546 38.4354472222 -122.5119777778 +1547 38.4354444444 -122.5128027778 +1548 38.4357472222 -122.5132694444 +1549 38.4357777778 -122.5132888889 +1550 38.4362944444 -122.5145416667 +1551 38.4366027778 -122.5155027778 +1552 38.4355333333 -122.5130222222 +1553 38.435375 -122.5126638889 +1554 38.4359305556 -122.5115888889 +1555 38.4356722222 -122.5115361111 +1556 38.4354555556 -122.5117027778 +1557 38.4366972222 -122.5134888889 +1558 38.4365888889 -122.51355 +1559 38.4367361111 -122.5134888889 +1560 38.4367111111 -122.5135805556 +1561 38.4349805556 -122.5125805556 +1562 38.4357888889 -122.5114972222 +1563 38.4357888889 -122.5121611111 +1564 38.4357611111 -122.5128388889 +1565 38.4355583333 -122.5133527778 +1566 38.4355555556 -122.51345 +1567 38.4356138889 -122.5137027778 +1568 38.4359555556 -122.5115583333 +1569 38.4404944444 -122.5248722222 +1570 38.4404222222 -122.5241777778 +1571 38.4403861111 -122.5242083333 +1572 38.4410666667 -122.5252138889 +1573 38.4411916667 -122.5258111111 +1574 38.43789667 -122.50053333 +1575 38.4370555556 -122.5162277778 +1576 38.4378165303 -122.5004666833 +1577 38.4348969357 -122.510796925 +1578 38.4458614234 -122.5077048038 +1579 38.4425959178 -122.5114283805 +1580 38.43436333 -122.510125 +1581 38.4410666667 -122.5252138889 +1582 38.4383789122 -122.5162733719 +1583 38.438044341 -122.5137403607 +1584 38.43672 -122.51087 +1585 38.4388472222 -122.4972388889 +1586 38.4382555556 -122.5133972222 +1587 38.4404777778 -122.523725 +1588 38.4416111111 -122.5245333333 +1589 38.4372058845 -122.5157007272 +1590 38.4450914223 -122.5102407877 +1591 38.44215833 -122.52909667 +1592 38.44210833 -122.52898333 +1593 38.44212 -122.52895333 +1594 38.44212 -122.52895333 +1595 38.44211167 -122.52906667 +1596 38.44200833 -122.52903 +1597 38.44261667 -122.51622 +1598 38.44393833 -122.51393833 +1599 38.44656333 -122.50856167 +1600 38.44825 -122.507255 +1601 38.45388 -122.50933833 +1602 38.45526167 -122.51023 +1603 38.45732167 -122.509445 +1604 38.45733833 -122.50958333 +1605 38.457355 -122.50961333 +1606 38.4448465782 -122.5103930894 +1607 38.43801667 -122.51408667 +1608 38.43828333 -122.50847 +1609 38.44313 -122.5121383333 +1610 38.4435716667 -122.513245 +1611 38.44732 -122.508095 +1612 38.454225 -122.50975 +1613 38.4553116667 -122.5102916667 +1614 38.4446 -122.495605 +1615 38.4554533333 -122.509995 +1616 38.4390033333 -122.5100866667 +1617 38.437189 -122.51629 +1618 38.437189 -122.51629 +1619 38.437189 -122.51629 +1620 38.43827833 -122.508475 +1621 38.435345 -122.512825 +1622 38.437795 -122.51642 +1623 38.4838039722 -122.5498599722 +1624 38.4377055952 -122.5165057581 +1625 38.4374256049 -122.5163538628 +1626 38.43770333 -122.51338833 +1627 38.437705 -122.50070333 +1628 38.43774667 -122.50061667 +1629 38.44136333 -122.49688 +1630 38.44665833 -122.49898667 +1631 38.445625 -122.49763667 +1632 38.43576333 -122.51396167 +1633 38.435895 -122.51380333 +1634 38.43588833 -122.51391667 +1635 38.43588833 -122.513895 +1636 38.436805 -122.51532 +1637 38.43537167 -122.51291667 +1638 38.4874388889 -122.5705416667 +1639 38.4874277778 -122.57055 +1640 38.4873083333 -122.5707027778 +1641 38.48725 -122.5708611111 +1642 38.4862888889 -122.5709388889 +1643 38.48738667 -122.570525 +1644 38.48742 -122.57051167 +1645 38.43625333 -122.51393833 +1646 38.43621167 -122.51455 +1647 38.43612 -122.51380833 +1648 38.43927833 -122.505745 +1649 38.442725 -122.53105333 +1650 38.44453 -122.531875 +1651 38.44488833 -122.53233333 +1652 38.44477833 -122.532355 +1653 38.44475333 -122.53222 +1654 38.44537833 -122.53209667 +1655 38.445805 -122.53237833 +1656 38.44661333 -122.52608667 +1657 38.44661333 -122.52608667 +1658 38.44577 -122.526725 +1659 38.44681667 -122.52071333 +1660 38.44662167 -122.51953833 +1661 38.446055 -122.51885333 +1662 38.446945 -122.52098833 +1663 38.4459138762 -122.5257158858 +1664 38.4475332891 -122.5266223009 +1665 38.4372277778 -122.51445 +1666 38.4377861111 -122.5145638889 +1667 38.4372388889 -122.5163277778 +1668 38.4372361111 -122.5163277778 +1669 38.4372472222 -122.5163027778 +1670 38.4371805556 -122.5160444444 +1671 38.4439472222 -122.5180055556 +1672 38.4441361111 -122.5180583333 +1673 38.444425 -122.5183861111 +1674 38.4446777778 -122.5185472222 +1675 38.4451916667 -122.5192194444 +1676 38.4473861111 -122.5214305556 +1677 38.4473805556 -122.5213555556 +1678 38.4472694444 -122.5209888889 +1679 38.4472638889 -122.5209972222 +1680 38.4471944444 -122.5209055556 +1681 38.4471083333 -122.5208361111 +1682 38.4470361111 -122.5208055556 +1683 38.4469638889 -122.5208138889 +1684 38.4469138889 -122.5208527778 +1685 38.4468972222 -122.5208277778 +1686 38.4467027778 -122.5202194444 +1687 38.4467027778 -122.5202333333 +1688 38.446725 -122.5201027778 +1689 38.4467138889 -122.5200277778 +1690 38.4466944444 -122.5199972222 +1691 38.4467138889 -122.5198972222 +1692 38.4466722222 -122.5195166667 +1693 38.4465797011 -122.5191719048 +1694 38.4464527778 -122.5188444444 +1695 38.4461694444 -122.518875 +1696 38.4451916667 -122.5192194444 +1697 38.4469361111 -122.523025 +1698 38.4468722222 -122.5230777778 +1699 38.4467972222 -122.5231111111 +1700 38.4468111111 -122.5231111111 +1701 38.4467333333 -122.523125 +1702 38.4464111111 -122.5239111111 +1703 38.4464222222 -122.5240944444 +1704 38.4464222222 -122.5242138889 +1705 38.4464222222 -122.5242305556 +1706 38.4463972222 -122.5242305556 +1707 38.4461527778 -122.5244305556 +1708 38.4461388889 -122.5243833333 +1709 38.4454666667 -122.5249194444 +1710 38.4454111111 -122.5249638889 +1711 38.4453194444 -122.5250944444 +1712 38.4452833333 -122.5251388889 +1713 38.4449888889 -122.5251861111 +1714 38.4448638889 -122.5252833333 +1715 38.4452111111 -122.5257861111 +1716 38.4452194444 -122.5258472222 +1717 38.4452055556 -122.5259555556 +1718 38.4452055556 -122.5259555556 +1719 38.4452027778 -122.5259555556 +1720 38.4452388889 -122.5260611111 +1721 38.4456277778 -122.5267555556 +1722 38.4457888889 -122.5267638889 +1723 38.4458777778 -122.5267805556 +1724 38.4457527778 -122.5267194444 +1725 38.4459722222 -122.5265361111 +1726 38.4460194444 -122.5264138889 +1727 38.4460111111 -122.5262611111 +1728 38.43648833 -122.5008 +1729 38.4461722222 -122.5262833333 +1730 38.4461888889 -122.5262611111 +1731 38.4463944444 -122.5259944444 +1732 38.4465555556 -122.5259694444 +1733 38.4467861111 -122.5259305556 +1734 38.4468222222 -122.5260388889 +1735 38.4469305556 -122.5260861111 +1736 38.4469361111 -122.5260944444 +1737 38.4469638889 -122.5261138889 +1738 38.4469694444 -122.5261138889 +1739 38.4469444444 -122.5261916667 +1740 38.4469972222 -122.5261611111 +1741 38.447025 -122.5261694444 +1742 38.4470555556 -122.5261388889 +1743 38.4470138889 -122.5261305556 +1744 38.4471611111 -122.5260305556 +1745 38.4471583333 -122.5260222222 +1746 38.4474861111 -122.5253611111 +1747 38.4476722222 -122.5250027778 +1748 38.4478583333 -122.5249638889 +1749 38.4478222222 -122.5249194444 +1750 38.4479194444 -122.524925 +1751 38.4479611111 -122.5249027778 +1752 38.4479666667 -122.5249861111 +1753 38.4479444444 -122.5249388889 +1754 38.4479611111 -122.5249472222 +1755 38.4481944444 -122.5250083333 +1756 38.4482 -122.5250083333 +1757 38.4481861111 -122.5250083333 +1758 38.4481805556 -122.5250166667 +1759 38.4481694444 -122.5249027778 +1760 38.4482305556 -122.5249861111 +1761 38.4481916667 -122.5250027778 +1762 38.4483305556 -122.5252222222 +1763 38.4482138889 -122.5252527778 +1764 38.4481833333 -122.5255722222 +1765 38.4480666667 -122.525925 +1766 38.4479166667 -122.5259694444 +1767 38.4480722222 -122.5266027778 +1768 38.4480888889 -122.5266111111 +1769 38.4479916667 -122.5266638889 +1770 38.4480555556 -122.5267027778 +1771 38.4480305556 -122.5267111111 +1772 38.4479388889 -122.5266888889 +1773 38.4479083333 -122.5267333333 +1774 38.4477777778 -122.5267111111 +1775 38.4475972222 -122.5267472222 +1776 38.4476555556 -122.5269166667 +1777 38.4476555556 -122.5269166667 +1778 38.4477472222 -122.5271222222 +1779 38.4477694444 -122.5271444444 +1780 38.4480805556 -122.5272444444 +1781 38.4483472222 -122.5276111111 +1782 38.4484222222 -122.5279083333 +1783 38.4484 -122.5279305556 +1784 38.4484222222 -122.5279777778 +1785 38.4484305556 -122.5279861111 +1786 38.4484361111 -122.5279916667 +1787 38.4484527778 -122.5281138889 +1788 38.4484555556 -122.5281138889 +1789 38.4484777778 -122.5282361111 +1790 38.4484527778 -122.5286333333 +1791 38.4485194444 -122.5287027778 +1792 38.4485972222 -122.5287694444 +1793 38.4484305556 -122.5286194444 +1794 38.4480333333 -122.5285944444 +1795 38.4480111111 -122.5285861111 +1796 38.4476361111 -122.5293361111 +1797 38.4476388889 -122.5294277778 +1798 38.4476305556 -122.5294277778 +1799 38.447575 -122.5294805556 +1800 38.4474527778 -122.5295027778 +1801 38.4470277778 -122.5298083333 +1802 38.4467055556 -122.5299138889 +1803 38.4462944444 -122.5300138889 +1804 38.4457722222 -122.5309972222 +1805 38.445775 -122.5309888889 +1806 38.4457027778 -122.5311888889 +1807 38.4457194444 -122.5315027778 +1808 38.4457166667 -122.5314111111 +1809 38.4461361111 -122.531875 +1810 38.4461583333 -122.5318694444 +1811 38.4465638889 -122.5320055556 +1812 38.4465777778 -122.5319805556 +1813 38.4463361111 -122.5319138889 +1814 38.4461361111 -122.5318916667 +1815 38.4461583333 -122.5321805556 +1816 38.4457194444 -122.5317611111 +1817 38.4449361111 -122.5314861111 +1818 38.4443916667 -122.5316388889 +1819 38.4428249542 -122.53150117 +1820 38.44560526 -122.53226011 +1821 38.44549476 -122.53213015 +1822 38.44548114 -122.53213738 +1823 38.44510736 -122.53213917 +1824 38.44517886 -122.53239755 +1825 38.44502076 -122.53230655 +1826 38.44491605 -122.53227051 +1827 38.44499568 -122.53217322 +1828 38.44486277 -122.53213253 +1829 38.44488072 -122.53209255 +1830 38.44483702 -122.53185296 +1831 38.4544314203 -122.5394603053 +1832 38.4376379722 -122.5169589722 +1833 38.43852 -122.50865833 +1834 38.447375 -122.51872833 +1835 38.466805 -122.52138667 +1836 38.4382498831 -122.5086107575 +1837 38.4460333333 -122.5321888889 +1838 38.4671 -122.521305 +1839 38.44605333 -122.50483 +1840 38.4397799112 -122.4988872305 +1841 38.4372361111 -122.5142611111 +1842 38.4372027778 -122.5142277778 +1843 38.4374611111 -122.5143361111 +1844 38.4371972222 -122.5163888889 +1845 38.4371555556 -122.5164805556 +1846 38.4422333333 -122.52935 +1847 38.4422611111 -122.5293055556 +1848 38.4422138889 -122.5294416667 +1849 38.4422222222 -122.5295472222 +1850 38.4422638889 -122.529625 +1851 38.4424194444 -122.5295944444 +1852 38.4423777778 -122.5296861111 +1853 38.4425138889 -122.5295944444 +1854 38.4423277778 -122.5295944444 +1855 38.4424027778 -122.5296111111 +1856 38.4425944444 -122.5296777778 +1857 38.4422861111 -122.529625 +1858 38.4424555556 -122.5295333333 +1859 38.442475 -122.5295333333 +1860 38.4425555556 -122.5294888889 +1861 38.4414527778 -122.5282361111 +1862 38.4420222222 -122.5280305556 +1863 38.4415222222 -122.5282055556 +1864 38.4416944444 -122.5281138889 +1865 38.4422388889 -122.5282805556 +1866 38.4422388889 -122.5282805556 +1867 38.44225 -122.5283583333 +1868 38.4421944444 -122.5284638889 +1869 38.4418444444 -122.5288083333 +1870 38.4419166667 -122.5287555556 +1871 38.4419194444 -122.5289611111 +1872 38.4419277778 -122.5289777778 +1873 38.4418277778 -122.5289833333 +1874 38.4419027778 -122.5290444444 +1875 38.4420833333 -122.5290305556 +1876 38.4421944444 -122.5293194444 +1877 38.4421527778 -122.5293444444 +1878 38.442175 -122.5293722222 +1879 38.4421555556 -122.5294416667 +1880 38.4421388889 -122.5294027778 +1881 38.4407972222 -122.5252916667 +1882 38.4408472222 -122.5252305556 +1883 38.4408555556 -122.5254666667 +1884 38.4409027778 -122.5256111111 +1885 38.4408444444 -122.5255666667 +1886 38.4408722222 -122.5255444444 +1887 38.4408722222 -122.5256805556 +1888 38.4408416667 -122.5257027778 +1889 38.4408472222 -122.5256722222 +1890 38.4407444444 -122.5256111111 +1891 38.4408694444 -122.5259944444 +1892 38.4408777778 -122.5260166667 +1893 38.4408166667 -122.5262972222 +1894 38.4408222222 -122.5262444444 +1895 38.4408944444 -122.5262611111 +1896 38.4411111111 -122.5261083333 +1897 38.4409638889 -122.5262222222 +1898 38.4411027778 -122.5264055556 +1899 38.441275 -122.5265055556 +1900 38.4412055556 -122.5265805556 +1901 38.4410194444 -122.52665 +1902 38.4412388889 -122.5268694444 +1903 38.4413805556 -122.5269166667 +1904 38.4413055556 -122.5271972222 +1905 38.4411027778 -122.5272666667 +1906 38.441175 -122.5272138889 +1907 38.4415722222 -122.527275 +1908 38.4415444444 -122.5273138889 +1909 38.4415138889 -122.5274444444 +1910 38.4414666667 -122.5274888889 +1911 38.4416972222 -122.5276638889 +1912 38.4417111111 -122.5276416667 +1913 38.441925 -122.5278944444 +1914 38.4425805556 -122.5264583333 +1915 38.4423222222 -122.5259777778 +1916 38.4422805556 -122.5257638889 +1917 38.4422611111 -122.525825 +1918 38.4422694444 -122.5257416667 +1919 38.4422888889 -122.5258638889 +1920 38.44225 -122.5257805556 +1921 38.4422638889 -122.525825 +1922 38.4422777778 -122.5257944444 +1923 38.4422277778 -122.5256277778 +1924 38.4421416667 -122.525475 +1925 38.4420277778 -122.5253611111 +1926 38.4419166667 -122.5253055556 +1927 38.4417083333 -122.5250777778 +1928 38.4416972222 -122.525475 +1929 38.4414361111 -122.5255277778 +1930 38.4413722222 -122.5257416667 +1931 38.4414194444 -122.5261527778 +1932 38.4414222222 -122.5261777778 +1933 38.4423361111 -122.5280611111 +1934 38.4423277778 -122.5280833333 +1935 38.4422861111 -122.5281055556 +1936 38.4423416667 -122.5280916667 +1937 38.4423055556 -122.5280138889 +1938 38.4423888889 -122.5278944444 +1939 38.4423722222 -122.5278305556 +1940 38.4425805556 -122.5275277778 +1941 38.4425805556 -122.5275277778 +1942 38.4425805556 -122.5275277778 +1943 38.4426444444 -122.5273222222 +1944 38.4426444444 -122.5273222222 +1945 38.4426444444 -122.5273222222 +1946 38.4426111111 -122.5273055556 +1947 38.4426111111 -122.5273055556 +1948 38.4426111111 -122.5273055556 +1949 38.4425638889 -122.5273972222 +1950 38.4424972222 -122.5272055556 +1951 38.4424222222 -122.5270138889 +1952 38.4424361111 -122.5270611111 +1953 38.4425361111 -122.5269388889 +1954 38.4425416667 -122.5268694444 +1955 38.4425555556 -122.5268638889 +1956 38.442575 -122.5268555556 +1957 38.4425666667 -122.5268111111 +1958 38.4425527778 -122.5266416667 +1959 38.4429722222 -122.5292722222 +1960 38.4427861111 -122.5292666667 +1961 38.4428222222 -122.5291972222 +1962 38.4428222222 -122.5291972222 +1963 38.4428027778 -122.5291611111 +1964 38.4425638889 -122.5290138889 +1965 38.4425944444 -122.5289138889 +1966 38.4425472222 -122.5288388889 +1967 38.4424972222 -122.5287944444 +1968 38.4425722222 -122.5288027778 +1969 38.4426138889 -122.5288861111 +1970 38.4425111111 -122.5287027778 +1971 38.4425611111 -122.5286777778 +1972 38.4425194444 -122.528725 +1973 38.4426416667 -122.5286194444 +1974 38.4425194444 -122.5287027778 +1975 38.4422222222 -122.5286944444 +1976 38.4424972222 -122.5287638889 +1977 38.4424638889 -122.5287472222 +1978 38.4425055556 -122.5287027778 +1979 38.4425472222 -122.5286861111 +1980 38.4423833333 -122.5287388889 +1981 38.4423666667 -122.5287111111 +1982 38.4422972222 -122.5285555556 +1983 38.4423027778 -122.5283666667 +1984 38.4422861111 -122.5282444444 +1985 38.4423194444 -122.5283138889 +1986 38.4424888889 -122.5282361111 +1987 38.4424555556 -122.5280611111 +1988 38.4422944444 -122.5283361111 +1989 38.4423833333 -122.5282277778 +1990 38.4424777778 -122.5281611111 +1991 38.4422944444 -122.5279861111 +1992 38.4422944444 -122.5279861111 +1993 38.4422944444 -122.5279861111 +1994 38.4423277778 -122.5279611111 +1995 38.4423277778 -122.5279611111 +1996 38.4423277778 -122.5279611111 +1997 38.4422277778 -122.5280305556 +1998 38.4434277778 -122.5306694444 +1999 38.4434444444 -122.5305777778 +2000 38.4434638889 -122.5305722222 +2001 38.4434416667 -122.5307527778 +2002 38.443425 -122.5304333333 +2003 38.4434083333 -122.5304333333 +2004 38.4434083333 -122.5304194444 +2005 38.443375 -122.53025 +2006 38.443375 -122.53025 +2007 38.4433472222 -122.5303638889 +2008 38.4434222222 -122.5303361111 +2009 38.4433861111 -122.53025 +2010 38.4433722222 -122.5302444444 +2011 38.4433611111 -122.53025 +2012 38.4434416667 -122.5301805556 +2013 38.4433555556 -122.5298861111 +2014 38.4434444444 -122.5298527778 +2015 38.4432555556 -122.5298777778 +2016 38.4432416667 -122.5298694444 +2017 38.4432916667 -122.5298611111 +2018 38.4431888889 -122.5298388889 +2019 38.4434027778 -122.5295027778 +2020 38.4431777778 -122.5296333333 +2021 38.4430972222 -122.5296166667 +2022 38.4431055556 -122.5296166667 +2023 38.4430277778 -122.5293638889 +2024 38.4429361111 -122.5293194444 +2025 38.4428300013 -122.531366483 +2026 38.4428902238 -122.5315918556 +2027 38.4429483951 -122.5315078535 +2028 38.4430075424 -122.5313792309 +2029 38.4430707285 -122.5312746247 +2030 38.4431191278 -122.5312072376 +2031 38.443147529 -122.5311574508 +2032 38.4431876392 -122.531076365 +2033 38.4433093716 -122.5310036356 +2034 38.4433147776 -122.5308707112 +2035 38.4433300852 -122.5308364355 +2036 38.4433839559 -122.5307066448 +2037 38.4433814806 -122.5305612832 +2038 38.4434150928 -122.5305583398 +2039 38.4435399011 -122.5305675969 +2040 38.48197167 -122.54847833 +2041 38.4371892 -122.51629 +2042 38.4343916667 -122.5060277778 +2043 38.4425888889 -122.4892722222 +2044 38.4425916667 -122.4889611111 +2045 38.4350166667 -122.5027305556 +2046 38.4351777778 -122.5028222222 +2047 38.4426527778 -122.4888166667 +2048 38.4397416667 -122.4970694444 +2049 38.4358833333 -122.5027 +2050 38.4425138889 -122.4889611111 +2051 38.4380611111 -122.5001972222 +2052 38.4359666667 -122.5022805556 +2053 38.4423027778 -122.4890583333 +2054 38.4379527778 -122.5040444444 +2055 38.438625 -122.5053638889 +2056 38.436 -122.5020972222 +2057 38.442275 -122.4890666667 +2058 38.4422027778 -122.4889833333 +2059 38.4418666667 -122.4859694444 +2060 38.4417805556 -122.4858027778 +2061 38.4388416667 -122.48915 +2062 38.4424055556 -122.4892583333 +2063 38.4425055556 -122.4891888889 +2064 38.4418361111 -122.4857555556 +2065 38.4363895223 -122.5152005423 +2066 38.4363352641 -122.5152595914 +2067 38.4383229513 -122.5080716805 +2068 38.4430328702 -122.5051458134 +2069 38.43734584 -122.5155159191 +2070 38.4364099748 -122.51509738 +2071 38.437306196 -122.5155157999 +2072 38.4363531476 -122.5149482897 +2073 38.4435027778 -122.5329 +2074 38.4436285278 -122.5318414167 +2075 38.44579174 -122.53990214 +2076 38.4381173 -122.5141744 +2077 38.43749694 -122.49885402 +2078 38.43950442 -122.49897773 +2079 38.43888455 -122.50558368 +2080 38.48445 -122.56365333 +2081 38.43747 -122.50333333 +2082 38.44227167 -122.496505 +2083 38.4435836822 -122.5050214215 +2084 38.4483627152 -122.5068950158 +2085 38.43771167 -122.50060333 +2086 38.44175 -122.48574167 +2087 38.44655333 -122.48275833 +2088 38.43776667 -122.50064167 +2089 38.436095413 -122.5143380155 +2090 38.48151333 -122.551095 +2091 38.48314167 -122.55632833 +2092 38.481525 -122.56977 +2093 38.48151333 -122.56978667 +2094 38.48372833 -122.55686167 +2095 38.48622833 -122.553855 +2096 38.48694667 -122.56022833 +2097 38.4360423119 -122.5061559567 +2098 38.4444097676 -122.5316372108 +2099 38.4444937971 -122.5315782023 +2100 38.4439111111 -122.5310805556 +2101 38.4446222222 -122.5314111111 +2102 38.4445944444 -122.5314722222 +2103 38.4447222222 -122.5314027778 +2104 38.4448388889 -122.5315388889 +2105 38.4449805556 -122.5319527778 +2106 38.4449 -122.5320527778 +2107 38.4448222222 -122.5321583333 +2108 38.4447861111 -122.5319527778 +2109 38.4447888889 -122.5319666667 +2110 38.4447527778 -122.5319611111 +2111 38.44465 -122.5319222222 +2112 38.4447388889 -122.5319611111 +2113 38.4446416667 -122.5322555556 +2114 38.4446805556 -122.5323027778 +2115 38.4446555556 -122.5323777778 +2116 38.4446916667 -122.5325166667 +2117 38.4445694444 -122.5322805556 +2118 38.4445194444 -122.5322416667 +2119 38.4445638889 -122.5323777778 +2120 38.4444472222 -122.5324166667 +2121 38.4444333333 -122.532425 +2122 38.4444388889 -122.532425 +2123 38.4444222222 -122.5324472222 +2124 38.4443777778 -122.5324333333 +2125 38.4443888889 -122.5324944444 +2126 38.4444222222 -122.5327777778 +2127 38.4443055556 -122.5328444444 +2128 38.4442861111 -122.5328833333 +2129 38.4442444444 -122.5332194444 +2130 38.4441638889 -122.5333555556 +2131 38.4441555556 -122.5333111111 +2132 38.4441444444 -122.533425 +2133 38.4441083333 -122.5333333333 +2134 38.4436611111 -122.5332944444 +2135 38.4436277778 -122.5333194444 +2136 38.4436194444 -122.5333027778 +2137 38.4435722222 -122.5333194444 +2138 38.4433027778 -122.5332805556 +2139 38.4432638889 -122.533425 +2140 38.4433805556 -122.5329888889 +2141 38.4439222222 -122.5315305556 +2142 38.4436555556 -122.5314777778 +2143 38.4436972222 -122.5314194444 +2144 38.4432888889 -122.5311888889 +2145 38.4430861111 -122.5315777778 +2146 38.4431805556 -122.5311722222 +2147 38.4420361111 -122.5325694444 +2148 38.4428055556 -122.5312111111 +2149 38.4429138889 -122.5312805556 +2150 38.4427277778 -122.5312111111 +2151 38.43688667 -122.512255 +2152 38.433892 -122.508419 +2153 38.4378361111 -122.5160527778 +2154 38.4378833333 -122.5159388889 +2155 38.4374888889 -122.5158083333 +2156 38.4372388889 -122.5160888889 +2157 38.4364222222 -122.5080333333 +2158 38.4351444444 -122.5080277778 +2159 38.4351444444 -122.5080277778 +2160 38.4343194444 -122.5089416667 +2161 38.4343277778 -122.5090722222 +2162 38.4343277778 -122.5090722222 +2163 38.4343694444 -122.5091166667 +2164 38.4343694444 -122.5091166667 +2165 38.4343805556 -122.509125 +2166 38.4343194444 -122.5094138889 +2167 38.43425 -122.5094916667 +2168 38.4343083333 -122.509475 +2169 38.4342888889 -122.5095527778 +2170 38.4343 -122.5095361111 +2171 38.4343055556 -122.5095222222 +2172 38.4343 -122.5095666667 +2173 38.4342666667 -122.5100638889 +2174 38.4344305556 -122.5103305556 +2175 38.4347944444 -122.5106583333 +2176 38.4347805556 -122.5106583333 +2177 38.4348138889 -122.5106666667 +2178 38.4347972222 -122.5106444444 +2179 38.4348388889 -122.5106666667 +2180 38.4348805556 -122.5106972222 +2181 38.4348833333 -122.5106972222 +2182 38.4348861111 -122.5106972222 +2183 38.4350111111 -122.5108555556 +2184 38.4350666667 -122.5106055556 +2185 38.4351472222 -122.5106194444 +2186 38.4351666667 -122.51065 +2187 38.4351527778 -122.5105666667 +2188 38.4350611111 -122.5107361111 +2189 38.4350333333 -122.5107111111 +2190 38.4349305556 -122.5106111111 +2191 38.43495 -122.5106444444 +2192 38.4349472222 -122.5106194444 +2193 38.4351861111 -122.5112138889 +2194 38.4356194444 -122.5116194444 +2195 38.4356194444 -122.5116194444 +2196 38.4354 -122.5120222222 +2197 38.4367916667 -122.5138111111 +2198 38.4368916667 -122.5140222222 +2199 38.4374055556 -122.512925 +2200 38.4372055556 -122.5124361111 +2201 38.4370583333 -122.5119944444 +2202 38.4369 -122.5115194444 +2203 38.43685 -122.5112305556 +2204 38.4368583333 -122.5112388889 +2205 38.4365527778 -122.510025 +2206 38.4365694444 -122.5093694444 +2207 38.4364194444 -122.5089111111 +2208 38.4364527778 -122.5088361111 +2209 38.4365516198 -122.5099514052 +2210 38.4386229722 -122.505367 +2211 38.4388509722 -122.5057549722 +2212 38.4386689722 -122.499779 +2213 38.4386379722 -122.49724 +2214 38.438124 -122.4980189722 +2215 38.435932 -122.502273 +2216 38.4357589722 -122.502437 +2217 38.435138 -122.5030629722 +2218 38.435098 -122.503476 +2219 38.434359 -122.506414 +2220 38.436404 -122.508002 +2221 38.4362929722 -122.5079369722 +2222 38.436433 -122.508222 +2223 38.436433 -122.508222 +2224 38.434784 -122.508458 +2225 38.436284 -122.507902 +2226 38.436297 -122.5078309722 +2227 38.436256 -122.5075649722 +2228 38.43422 -122.5077229722 +2229 38.43422 -122.5077229722 +2230 38.434359 -122.5064239722 +2231 38.4344419722 -122.5062049722 +2232 38.4344419722 -122.5062049722 +2233 38.434663 -122.504981 +2234 38.4344869722 -122.504517 +2235 38.4357589722 -122.502437 +2236 38.435878 -122.507621 +2237 38.4341819722 -122.5077469722 +2238 38.434415 -122.5100149722 +2239 38.4373784086 -122.5154146581 +2240 38.436481 -122.5134429722 +2241 38.4343379722 -122.506304 +2242 38.435503 -122.509957 +2243 38.4491836 -122.51822755 +2244 38.43423333 -122.50782833 +2245 38.4342716667 -122.5078583333 +2246 38.45219651 -122.5073509 +2247 38.4579667804 -122.5364269945 +2248 38.445981 -122.504839 +2249 38.435905 -122.509095 +2250 38.44763833 -122.49263 +2251 38.442375 -122.4964 +2252 38.442355 -122.496345 +2253 38.442355 -122.496345 +2254 38.4809738081 -122.5692625367 +2255 38.4811752889 -122.5694028152 +2256 38.4483738866 -122.4970159373 +2257 38.45203833 -122.48192 +2258 38.43441333 -122.50608833 +2259 38.45170347 -122.53921531 +2260 38.442382 -122.529582 +2261 38.442368 -122.5295909722 +2262 38.4573080821 -122.5098414225 +2263 38.4573080821 -122.5098414225 +2264 38.4573080821 -122.5098414225 +2265 38.45746167 -122.50923 +2266 38.45752 -122.50933 +2267 38.43508667 -122.50330333 +2268 38.434375 -122.50817833 +2269 38.43602833 -122.50745333 +2270 38.43428 -122.50788 +2271 38.43428 -122.50788 +2272 38.4369371177 -122.4970430748 +2273 38.44581667 -122.49746667 +2274 38.44782 -122.499345 +2275 38.45407167 -122.50941333 +2276 38.45745 -122.50998 +2277 38.45742833 -122.50965833 +2278 38.45614667 -122.508745 +2279 38.45220833 -122.50727833 +2280 38.43556127 -122.5093928352 +2281 38.44269167 -122.53116667 +2282 38.438836 -122.500296 +2283 38.438702 -122.506261 +2284 38.438668 -122.507892 +2285 38.436517 -122.511454 +2286 38.440484 -122.496899 +2287 38.440509 -122.496834 +2288 38.440492 -122.496834 +2289 38.440509 -122.496813 +2290 38.4405 -122.496834 +2291 38.440223 -122.496931 +2292 38.440223 -122.496931 +2293 38.443984 -122.522773 +2294 38.435608 -122.508212 +2295 38.434668 -122.50598 +2296 38.438332 -122.495047 +2297 38.436113 -122.496034 +2298 38.434517 -122.505282 +2299 38.4878757723 -122.5615324787 +2300 38.4386533611 -122.5099674444 +2301 38.4353753317 -122.509274818 +2302 38.4402508333 -122.5106536944 +2303 38.4420753743 -122.510339655 +2304 38.4431908935 -122.5121950731 +2305 38.446199 -122.508729 +2306 38.448472 -122.5070189722 +2307 38.4530409722 -122.507671 +2308 38.455497 -122.509994 +2309 38.4555018889 -122.5101360278 +2310 38.4523678001 -122.5074636564 +2311 38.4434742347 -122.5129548088 +2312 38.445804 -122.5092039722 +2313 38.4460236389 -122.5087312778 +2314 38.4483809722 -122.5072251944 +2315 38.4496371944 -122.5050096944 +2316 38.4508333333 -122.5061111111 +2317 38.4529415278 -122.5077733056 +2318 38.4555425278 -122.5100455 +2319 38.4541054228 -122.5095695257 +2320 38.4482539444 -122.5049468889 +2321 38.4482856138 -122.5068437308 +2322 38.4440656111 -122.5127538056 +2323 38.435515836 -122.5091222674 +2324 38.4354110487 -122.5092419609 +2325 38.4355192501 -122.5096466392 +2326 38.4355652094 -122.5088185072 +2327 38.4383722222 -122.5067666667 +2328 38.4353305556 -122.5093527778 +2329 38.4385219356 -122.5078113378 +2330 38.4532284774 -122.5330361724 +2331 38.43756333 -122.509055 +2332 38.43535333 -122.51143667 +2333 38.4862456005 -122.5624482707 +2334 38.4862691118 -122.5624737517 +2335 38.4862844088 -122.5624991489 +2336 38.4439 -122.5140083333 +2337 38.4443383333 -122.518455 +2338 38.43556167 -122.512825 +2339 38.43742 -122.51548833 +2340 38.43732833 -122.515655 +2341 38.43730333 -122.515695 +2342 38.440505 -122.5251 +2343 38.4371892 -122.51629 +2344 38.43413667 -122.507705 +2345 38.44775333 -122.479295 +2346 38.4456944444 -122.5056277778 +2347 38.45397786 -122.5094054719 +2348 38.435901 -122.509084 +2349 38.436655 -122.508701 +2350 38.4358649722 -122.510986 +2351 38.4378166667 -122.5077055556 +2352 38.4372 -122.5079277778 +2353 38.4367027778 -122.5077138889 +2354 38.4366333333 -122.5076444444 +2355 38.4363166667 -122.5075444444 +2356 38.4357527778 -122.5105277778 +2357 38.43648667 -122.51248833 +2358 38.4371863715 -122.5164413452 +2359 38.4400691092 -122.4968784862 +2360 38.4425772261 -122.4963847083 +2361 38.4388575051 -122.4969793205 +2362 38.43726667 -122.51561667 +2363 38.43723667 -122.51563333 +2364 38.4535 -122.50875833 +2365 38.4428571343 -122.5325787506 +2366 38.4435308717 -122.5314623386 +2367 38.44328333 -122.53357 +2368 38.443505 -122.53388333 +2369 38.4359749722 -122.513237 +2370 38.4444416667 -122.52275 +2371 38.43632167 -122.50911667 +2372 38.439275 -122.50576667 +2373 38.43461167 -122.50479167 +2374 38.4538932765 -122.5351437181 +2375 38.4464260475 -122.5043027941 +EOF diff --git a/pyinaturalist/__init__.py b/pyinaturalist/__init__.py index 18a23aa4..13961471 100644 --- a/pyinaturalist/__init__.py +++ b/pyinaturalist/__init__.py @@ -1,29 +1,24 @@ from logging import getLogger from os import getenv -__author__ = "Nicolas Noé" -__email__ = "nicolas@niconoe.eu" -__version__ = "0.11.0" - # These are imported here so they can be set with pyinaturalist. -from pyinaturalist.constants import DRY_RUN_ENABLED, DRY_RUN_WRITE_ONLY +from pyinaturalist.constants import DRY_RUN_ENABLED, DRY_RUN_WRITE_ONLY # noqa -DEFAULT_USER_AGENT = "Pyinaturalist/{version}".format(version=__version__) +__version__ = '0.12.0' +DEFAULT_USER_AGENT = f'pyinaturalist/{__version__}' user_agent = DEFAULT_USER_AGENT def get_prerelease_version(version: str) -> str: - """If we're running in a Travis CI job on the dev branch, get a prerelease version using the + """If we're running in a GitHub Action job on the dev branch, get a prerelease version using the current build number. For example: ``1.0.0 -> 1.0.0-dev.123`` - - This could also be done in ``.travis.yml``, but it's a bit cleaner to do in python. """ - if not (getenv("TRAVIS") == "true" and getenv("TRAVIS_BRANCH") == "dev"): - return version - new_version = "{}-dev.{}".format(version, getenv("TRAVIS_BUILD_NUMBER", "0")) - getLogger(__name__).info("Using pre-release version: {}".format(new_version)) - return new_version + if getenv('GITHUB_REF') == 'refs/heads/dev': + build_number = getenv('GITHUB_RUN_NUMBER', '0') + version = f'{version}.dev{build_number}' + getLogger(__name__).info(f'Using pre-release version: {version}') + return version -# This won't modify the version outside of Travis +# This won't modify the version outside of a GitHub Action __version__ = get_prerelease_version(__version__) diff --git a/pyinaturalist/api_docs.py b/pyinaturalist/api_docs.py index 1f9d3b46..336d9893 100644 --- a/pyinaturalist/api_docs.py +++ b/pyinaturalist/api_docs.py @@ -3,20 +3,19 @@ Each template function contains a portion of an endpoint's request parameters, with corresponding type annotations and docstrings. """ -from typing import Any, Dict, List, Iterable +from typing import Iterable, List from pyinaturalist.constants import ( - MultiInt, - MultiStr, Date, DateTime, - IntOrStr, FileOrPath, + IntOrStr, + MultiInt, + MultiStr, ObsFieldValues, ) from pyinaturalist.request_params import MULTIPLE_CHOICE_PARAMS - # Observations # -------------------- @@ -127,7 +126,7 @@ def _observation_node_only( captive: Captive or cultivated observations endemic: Observations whose taxa are endemic to their location geo: Observations that are georeferenced - id_please: Observations with the **deprecated** "ID, Please!" flag. + id_please: Observations with the **deprecated** 'ID, Please!' flag. Note that this will return observations, but that this attribute is no longer used. identified: Observations that have community identifications introduced: Observations whose taxa are introduced in their location @@ -207,7 +206,7 @@ def _observation_rest_only( h1: Date = None, h2: Date = None, extra: str = None, - response_format: str = "json", + response_format: str = 'json', ): """ has: Catch-all for some boolean selectors. This can be used multiple times, e.g. @@ -226,6 +225,16 @@ def _observation_rest_only( """ +def _observation_histogram( + date_field: str = 'observed', + interval: str = 'month_of_year', +): + """ + date_field: Histogram basis: either when the observation was created or observed + interval: Time interval for histogram, with groups starting on or contained within the group value. + """ + + def _create_observation( species_guess: str = None, taxon_id: int = None, @@ -247,7 +256,7 @@ def _create_observation( local_photos: Iterable[FileOrPath] = None, ): """ - species_guess: Equivalent to the "What did you see?" field on the observation form. + species_guess: Equivalent to the 'What did you see?' field on the observation form. iNat will try to choose a single taxon based on this, but it may fail if it's ambuguous taxon_id: ID of the taxon to associate with this observation observed_on_string: Date/time of the observation. Time zone will default to the user's @@ -299,6 +308,7 @@ def _projects_params( radius: int = 500, featured: bool = None, noteworthy: bool = None, + place_id: MultiInt = None, site_id: int = None, rule_details: bool = None, type: MultiStr = None, @@ -315,6 +325,7 @@ def _projects_params( radius: Distance from center (``(lat, lng)``) to search, in kilometers. Defaults to 500km. featured: Must be marked featured for the relevant site noteworthy: Must be marked noteworthy for the relevant site + place_id: Must be in the place with this ID site_id: Site ID that applies to ``featured`` and ``noteworthy``. Defaults to the site of the authenticated user, or to the main iNaturalist site rule_details: Return more information about project rules, for example return a full taxon @@ -409,13 +420,6 @@ def _geojson_properties(properties: List[str] = None): """ -# TODO: Remove deprecated `params` arg in 0.12 -def _legacy_params(params: Dict[str, Any] = None): - """ - params: [DEPRECATED] Request parameters as a dict instead of keyword arguments - """ - - def _minify(minify: str = None): """ minify: Condense each match into a single string containing taxon ID, rank, and name @@ -462,24 +466,22 @@ def _pagination( _get_observations = [ - _legacy_params, _observation_common, _observation_node_only, _bounding_box, ] -# TODO: Remove deprecated `search_query` kwarg in 0.12 -def _search_query(q: str = None, search_query: str = None): +def _search_query(q: str = None): """ q: Search query """ def _format_param_choices(): - return "\n".join( - [" * {}: {}".format(param, choices) for param, choices in MULTIPLE_CHOICE_PARAMS.items()] + return '\n'.join( + [f' * {param}: {choices}' for param, choices in MULTIPLE_CHOICE_PARAMS.items()] ) -MULTIPLE_CHOICE_PARAM_DOCS = "**Multiple-Choice Parameters:**\n" + _format_param_choices() +MULTIPLE_CHOICE_PARAM_DOCS = '**Multiple-Choice Parameters:**\n' + _format_param_choices() diff --git a/pyinaturalist/api_requests.py b/pyinaturalist/api_requests.py index 9c3113b3..45c239f3 100644 --- a/pyinaturalist/api_requests.py +++ b/pyinaturalist/api_requests.py @@ -1,42 +1,34 @@ """ Some common functions for HTTP requests used by both the Node and REST API modules """ +import threading from logging import getLogger from os import getenv from typing import Dict, List, Union from unittest.mock import Mock -from urllib.parse import urljoin import requests import pyinaturalist from pyinaturalist.constants import WRITE_HTTP_METHODS -from pyinaturalist.request_params import preprocess_request_params, convert_list, validate_ids +from pyinaturalist.forge_utils import copy_signature +from pyinaturalist.request_params import preprocess_request_params, validate_ids # Mock response content to return in dry-run mode MOCK_RESPONSE = Mock(spec=requests.Response) -MOCK_RESPONSE.json.return_value = {"results": [], "total_results": 0} +MOCK_RESPONSE.json.return_value = {'results': [], 'total_results': 0} logger = getLogger(__name__) +thread_local = threading.local() -# TODO: Copy function signature of request(), add `url`, and apply to these 4 wrapper functions -def delete(url: str, **kwargs) -> requests.Response: - """ Wrapper around :py:func:`requests.delete` that supports dry-run mode """ - return request("DELETE", url, **kwargs) - - -def get(url: str, **kwargs) -> requests.Response: - """ Wrapper around :py:func:`requests.get` that supports dry-run mode """ - return request("GET", url, **kwargs) - - -def post(url: str, **kwargs) -> requests.Response: - """ Wrapper around :py:func:`requests.post` that supports dry-run mode """ - return request("POST", url, **kwargs) - - -def put(url: str, **kwargs) -> requests.Response: - """ Wrapper around :py:func:`requests.put` that supports dry-run mode """ - return request("PUT", url, **kwargs) +def get_session() -> requests.Session: + """Get a Session object that will be reused across requests to take advantage of connection + pooling. This is especially relevant for large paginated requests. If used in a multi-threaded + context (for example, a :py:class:`~concurrent.futures.ThreadPoolExecutor`), a separate session + is used for each thread. + """ + if not hasattr(thread_local, "session"): + thread_local.session = requests.Session() + return thread_local.session def request( @@ -47,7 +39,8 @@ def request( ids: Union[str, List] = None, params: Dict = None, headers: Dict = None, - **kwargs + session: requests.Session = None, + **kwargs, ) -> requests.Response: """Wrapper around :py:func:`requests.request` that supports dry-run mode and adds appropriate headers. @@ -60,30 +53,56 @@ def request( ids: One or more integer IDs used as REST resource(s) to request params: Requests parameters headers: Request headers + session: Existing Session object to use instead of creating a new one Returns: API response """ # Set user agent and authentication headers, if specified + session = session or get_session() headers = headers or {} - headers["Accept"] = "application/json" - headers["User-Agent"] = user_agent or pyinaturalist.user_agent + headers['Accept'] = 'application/json' + headers['User-Agent'] = user_agent or pyinaturalist.user_agent if access_token: - headers["Authorization"] = "Bearer %s" % access_token + headers['Authorization'] = 'Bearer %s' % access_token params = preprocess_request_params(params) - # If one or more REST resources are requested, update the request URL accordignly + # If one or more REST resources are requested by ID, update the request URL accordingly if ids: - url = url.rstrip("/") + "/" + validate_ids(ids) + url = url.rstrip('/') + '/' + validate_ids(ids) # Run either real request or mock request depending on settings if is_dry_run_enabled(method): - logger.debug("Dry-run mode enabled; mocking request") + logger.debug('Dry-run mode enabled; mocking request') log_request(method, url, params=params, headers=headers, **kwargs) return MOCK_RESPONSE else: - return requests.request(method, url, params=params, headers=headers, **kwargs) + return session.request(method, url, params=params, headers=headers, **kwargs) + + +@copy_signature(request, exclude='method') +def delete(url: str, **kwargs) -> requests.Response: + """ Wrapper around :py:func:`requests.delete` that supports dry-run mode """ + return request('DELETE', url, **kwargs) + + +@copy_signature(request, exclude='method') +def get(url: str, **kwargs) -> requests.Response: + """ Wrapper around :py:func:`requests.get` that supports dry-run mode """ + return request('GET', url, **kwargs) + + +@copy_signature(request, exclude='method') +def post(url: str, **kwargs) -> requests.Response: + """ Wrapper around :py:func:`requests.post` that supports dry-run mode """ + return request('POST', url, **kwargs) + + +@copy_signature(request, exclude='method') +def put(url: str, **kwargs) -> requests.Response: + """ Wrapper around :py:func:`requests.put` that supports dry-run mode """ + return request('PUT', url, **kwargs) def is_dry_run_enabled(method: str) -> bool: @@ -91,10 +110,10 @@ def is_dry_run_enabled(method: str) -> bool: a constant or an environment variable. Dry-run mode may be enabled for either write requests, or all requests. """ - dry_run_enabled = pyinaturalist.DRY_RUN_ENABLED or env_to_bool("DRY_RUN_ENABLED") + dry_run_enabled = pyinaturalist.DRY_RUN_ENABLED or env_to_bool('DRY_RUN_ENABLED') if method in WRITE_HTTP_METHODS: return ( - dry_run_enabled or pyinaturalist.DRY_RUN_WRITE_ONLY or env_to_bool("DRY_RUN_WRITE_ONLY") + dry_run_enabled or pyinaturalist.DRY_RUN_WRITE_ONLY or env_to_bool('DRY_RUN_WRITE_ONLY') ) return dry_run_enabled @@ -104,10 +123,10 @@ def env_to_bool(environment_variable: str) -> bool: variations (case, None vs. False, etc.) """ env_value = getenv(environment_variable) - return bool(env_value) and str(env_value).lower() not in ["false", "none"] + return bool(env_value) and str(env_value).lower() not in ['false', 'none'] def log_request(*args, **kwargs): """ Log all relevant information about an HTTP request """ - kwargs_strs = ["{}={}".format(k, v) for k, v in kwargs.items()] - logger.info("Request: {}".format(", ".join(list(args) + kwargs_strs))) + kwargs_strs = [f'{k}={v}' for k, v in kwargs.items()] + logger.info('Request: {}'.format(', '.join(list(args) + kwargs_strs))) diff --git a/pyinaturalist/auth.py b/pyinaturalist/auth.py new file mode 100644 index 00000000..d4951c53 --- /dev/null +++ b/pyinaturalist/auth.py @@ -0,0 +1,131 @@ +from logging import getLogger +from os import getenv +from typing import Dict + +from pyinaturalist.api_requests import post +from pyinaturalist.constants import INAT_BASE_URL, INAT_KEYRING_KEY +from pyinaturalist.exceptions import AuthenticationError + +logger = getLogger(__name__) + + +def get_access_token( + username: str = None, + password: str = None, + app_id: str = None, + app_secret: str = None, + user_agent: str = None, +) -> str: + """Get an access token using the user's iNaturalist username and password. + You still need an iNaturalist app to do this. + + **API reference:** https://www.inaturalist.org/pages/api+reference#auth + + See :ref:`auth` for additional options for storing credentials. + + Examples: + + With direct keyword arguments: + + >>> from pyinaturalist.auth import get_access_token + >>> access_token = get_access_token( + >>> username='my_username', + >>> password='my_password', + >>> app_id='33f27dc63bdf27f4ca6cd95dd9dcd5df', + >>> app_secret='bbce628be722bfe2abd5fc566ba83de4', + >>> ) + + With environment variables or keyring configured: + + >>> access_token = get_access_token() + + If you would like to run custom requests for endpoints not yet implemented in pyinaturalist, + you can authenticate these requests by putting the token in your HTTP headers as follows: + + >>> import requests + >>> requests.get( + >>> 'https://www.inaturalist.org/observations/1234', + >>> headers={'Authorization': f'Bearer {access_token}'}, + >>> ) + + Args: + username: iNaturalist username + password: iNaturalist password + app_id: iNaturalist application ID + app_secret: iNaturalist application secret + user_agent: a user-agent string that will be passed to iNaturalist. + + Raises: + :py:exc:`requests.HTTPError` (401) if credentials are invalid + """ + payload = { + 'username': username or getenv('INAT_USERNAME'), + 'password': password or getenv('INAT_PASSWORD'), + 'client_id': app_id or getenv('INAT_APP_ID'), + 'client_secret': app_secret or getenv('INAT_APP_SECRET'), + 'grant_type': 'password', + } + + # If neither args nor envars were given, then check the keyring + if not all(payload.values()): + payload.update(get_keyring_credentials()) + if not all(payload.values()): + raise AuthenticationError('Not all authentication parameters were provided') + + response = post( + f'{INAT_BASE_URL}/oauth/token', + json=payload, + user_agent=user_agent, + ) + response.raise_for_status() + return response.json()['access_token'] + + +def get_keyring_credentials() -> Dict[str, str]: + """Attempt to get iNaturalist credentials from the system keyring + + Returns: + OAuth-compatible credentials dict + """ + # Quietly fail if keyring package is not installed + try: + from keyring import get_password + from keyring.errors import KeyringError + except ImportError: + logger.warning('Optional dependency `keyring` not installed') + return {} + + # Quietly fail if keyring backend is not available + try: + return { + 'username': get_password(INAT_KEYRING_KEY, 'username'), + 'password': get_password(INAT_KEYRING_KEY, 'password'), + 'client_id': get_password(INAT_KEYRING_KEY, 'app_id'), + 'client_secret': get_password(INAT_KEYRING_KEY, 'app_secret'), + } + except KeyringError as e: + logger.warning(str(e)) + return {} + + +def set_keyring_credentials( + username: str, + password: str, + app_id: str, + app_secret: str, +): + """ + Store iNaturalist credentials in the system keyring for future use. + + Args: + username: iNaturalist username + password: iNaturalist password + app_id: iNaturalist application ID + app_secret: iNaturalist application secret + """ + from keyring import set_password + + set_password(INAT_KEYRING_KEY, 'username', username) + set_password(INAT_KEYRING_KEY, 'password', password) + set_password(INAT_KEYRING_KEY, 'app_id', app_id) + set_password(INAT_KEYRING_KEY, 'app_secret', app_secret) diff --git a/pyinaturalist/constants.py b/pyinaturalist/constants.py index ed5b224a..74c4d415 100644 --- a/pyinaturalist/constants.py +++ b/pyinaturalist/constants.py @@ -1,26 +1,30 @@ from datetime import date, datetime from typing import Any, BinaryIO, Dict, List, Union -INAT_NODE_API_BASE_URL = "https://api.inaturalist.org/v1/" -INAT_BASE_URL = "https://www.inaturalist.org" +INAT_NODE_API_BASE_URL = 'https://api.inaturalist.org/v1/' +INAT_BASE_URL = 'https://www.inaturalist.org' +INAT_KEYRING_KEY = '/inaturalist' -PER_PAGE_RESULTS = 30 # Number of records per page for paginated queries -THROTTLING_DELAY = 1 # In seconds, support <1 floats such as 0.1 +PER_PAGE_RESULTS = 200 # Default number of records per page for paginated queries +THROTTLING_DELAY = 1.0 # Delay between paginated queries, in seconds # Toggle dry-run mode: this will run and log mock HTTP requests instead of real ones DRY_RUN_ENABLED = False # Mock all requests, including GET DRY_RUN_WRITE_ONLY = False # Only mock 'write' requests -WRITE_HTTP_METHODS = ["PATCH", "POST", "PUT", "DELETE"] +WRITE_HTTP_METHODS = ['PATCH', 'POST', 'PUT', 'DELETE'] # Type aliases Date = Union[date, datetime, str] DateTime = Union[date, datetime, str] +DateOrInt = Union[date, datetime, int] FileOrPath = Union[BinaryIO, str] +HistogramResponse = Dict[DateOrInt, int] IntOrStr = Union[int, str] JsonResponse = Dict[str, Any] ListResponse = List[Dict[str, Any]] ObsFieldValues = Union[Dict, List[Dict]] RequestParams = Dict[str, Any] +ResponseObject = Dict[str, Any] MultiInt = Union[int, List[int]] MultiStr = Union[str, List[str]] -TemplateFunction = Any # Cannot use Callable/Protocol, as they will not allow a mix of signatures +TemplateFunction = Any # Cannot use Callable/Protocol, as these will not allow a mix of signatures diff --git a/pyinaturalist/exceptions.py b/pyinaturalist/exceptions.py index 34a87cbf..1a2d6e5a 100644 --- a/pyinaturalist/exceptions.py +++ b/pyinaturalist/exceptions.py @@ -1,7 +1,13 @@ -# TODO: Make these extend `requests.HTTPError` to simplify error handling in client code? -class AuthenticationError(Exception): +from requests import HTTPError + + +class AuthenticationError(HTTPError): + pass + + +class ObservationNotFound(HTTPError): pass -class ObservationNotFound(Exception): +class TaxonNotFound(HTTPError): pass diff --git a/pyinaturalist/forge_utils.py b/pyinaturalist/forge_utils.py index 3bd4e5bb..9d56be5c 100644 --- a/pyinaturalist/forge_utils.py +++ b/pyinaturalist/forge_utils.py @@ -1,10 +1,10 @@ -""" -Utilities built on top of ``python-forge`` used to simplify usage for API docs, mainly by -combining function signature modification with docstring modification. +"""Utilities built on top of ``python-forge`` that simplify defining API docs by combining +function signature modification with docstring modification. +This module makes ``python-forge`` optional; if not installed, these functions will quietly fail +without modifying the target functions. """ from inspect import cleandoc from itertools import chain -from functools import wraps from logging import getLogger from typing import Callable, List @@ -13,7 +13,7 @@ logger = getLogger(__name__) -def document_request_params(template_functions: List[TemplateFunction]): +def document_request_params(template_functions: List[TemplateFunction]) -> Callable: """Document a function with both docstrings and function signatures from one or more template functions. @@ -50,7 +50,7 @@ def document_request_params(template_functions: List[TemplateFunction]): """ template_functions = [*template_functions, _user_agent] - def f(func): + def wrapper(func): # Modify docstring func = copy_docstrings(func, template_functions) @@ -58,16 +58,11 @@ def f(func): try: func = copy_signatures(func, template_functions) except ImportError: - logger.debug("Forge not installed; skipping runtime API documentation") - - # Call modified function - @wraps(func) - def g(*args, **kwargs): - return func(*args, **kwargs) + logger.debug('Forge not installed; skipping runtime API documentation') - return g + return func - return f + return wrapper def copy_docstrings( @@ -81,33 +76,48 @@ def copy_docstrings( template_functions: Functions containing docstrings to apply to ``target_function`` """ # Start with initial function description only - docstring, return_value_desc = _split_docstring(target_function.__doc__ or "") + docstring, return_value_desc = _split_docstring(target_function.__doc__ or '') # Insert 'Args' section - docstring += "\n\nArgs:" + docstring += '\n\nArgs:' for template_function in template_functions: - docstring += template_function.__doc__ or "" + docstring += template_function.__doc__ or '' docstring = docstring.rstrip() # Insert 'Returns' section, if present if return_value_desc: - docstring += "\n\nReturns:\n " + return_value_desc + docstring += f'\n\nReturns:\n {return_value_desc}' target_function.__doc__ = docstring return target_function def _split_docstring(docstring): - """ Split a docstring into a function description + return value description, if present. """ - if "Returns:" in docstring: - function_desc, return_value_desc = docstring.split("Returns:") + """Split a docstring into a function description + return value description, if present""" + if 'Returns:' in docstring: + function_desc, return_value_desc = docstring.split('Returns:') else: function_desc = docstring - return_value_desc = "" + return_value_desc = '' return cleandoc(function_desc.strip()), cleandoc(return_value_desc.strip()) +def copy_signature(template_function: Callable, include=None, exclude=None) -> Callable: + """A wrapper around :py:func:`forge.copy` that silently fails if forge is not installed""" + + def wrapper(target_function: Callable): + try: + import forge + except ImportError: + return target_function + + revision = forge.copy(template_function, include=include, exclude=exclude) + return revision(target_function) + + return wrapper + + def copy_signatures( target_function: Callable, template_functions: List[TemplateFunction] ) -> Callable: @@ -122,7 +132,7 @@ def copy_signatures( def _get_combined_revision(template_functions: List[TemplateFunction]): - """ Create a :py:class:`forge.Revision` from the combined parameters of multiple functions """ + """Create a :py:class:`forge.Revision` from the combined parameters of multiple functions""" import forge # Use forge.copy to create a revision for each template function diff --git a/pyinaturalist/node_api.py b/pyinaturalist/node_api.py index ee06bbef..1d35a096 100644 --- a/pyinaturalist/node_api.py +++ b/pyinaturalist/node_api.py @@ -17,46 +17,124 @@ from typing import List import requests -from urllib.parse import urljoin from pyinaturalist import api_docs as docs +from pyinaturalist.api_requests import get from pyinaturalist.constants import ( INAT_NODE_API_BASE_URL, PER_PAGE_RESULTS, THROTTLING_DELAY, - MultiInt, + HistogramResponse, JsonResponse, - RequestParams, + MultiInt, ) -from pyinaturalist.exceptions import ObservationNotFound +from pyinaturalist.exceptions import ObservationNotFound, TaxonNotFound from pyinaturalist.forge_utils import document_request_params from pyinaturalist.request_params import ( DEFAULT_OBSERVATION_ATTRS, NODE_OBS_ORDER_BY_PROPERTIES, PROJECT_ORDER_BY_PROPERTIES, - check_deprecated_params, translate_rank_range, validate_multiple_choice_param, ) from pyinaturalist.response_format import ( - format_taxon, as_geojson_feature_collection, + convert_all_coordinates, + convert_all_place_coordinates, + convert_all_timestamps, + convert_observation_timestamps, flatten_nested_params, - convert_location_to_float, + format_histogram, + format_taxon, ) -from pyinaturalist.api_requests import get logger = getLogger(__name__) -def make_inaturalist_api_get_call(endpoint: str, **kwargs) -> requests.Response: +def node_api_get(endpoint: str, **kwargs) -> requests.Response: """Make an API call to iNaturalist. Args: - endpoint: The name of an endpoint not including the base URL e.g. 'observations' + endpoint: The name of an endpoint resource, not including the base URL e.g. 'observations' kwargs: Arguments for :py:func:`.api_requests.request` """ - return get(urljoin(INAT_NODE_API_BASE_URL, endpoint), **kwargs) + return get(f'{INAT_NODE_API_BASE_URL}{endpoint}', **kwargs) + + +# Controlled Terms +# -------------------- + + +def get_controlled_terms(taxon_id: int = None, user_agent: str = None) -> JsonResponse: + """List controlled terms and their possible values. + A taxon ID can optionally be provided to show only terms that are valid for that taxon. + Otherwise, all controlled terms will be returned. + + **API reference:** + + * https://api.inaturalist.org/v1/docs/#!/Controlled_Terms/get_controlled_terms + * https://api.inaturalist.org/v1/docs/#!/Controlled_Terms/get_controlled_terms_for_taxon + + Example: + + >>> response = get_controlled_terms() + >>> # Show a condensed list of terms and values + >>> for term in response['results']: + >>> values = [f' {value["id"]}: {value["label"]}' for value in term['values']] + >>> print(f'{term["id"]}: {term["label"]}' + '\\n'.join(values)) + 1: Life Stage + 2: Adult + 3: Teneral + 4: Pupa + 5: Nymph + 6: Larva + 7: Egg + 8: Juvenile + 16: Subimago + 9: Sex + 10: Female + 11: Male + 20: Cannot Be Determined + 12: Plant Phenology + 13: Flowering + 14: Fruiting + 15: Flower Budding + 21: No Evidence of Flowering + 17: Alive or Dead + 18: Alive + 19: Dead + 20: Cannot Be Determined + + .. admonition:: Example Response (all terms) + :class: toggle + + .. literalinclude:: ../sample_data/get_controlled_terms.json + :language: JSON + + .. admonition:: Example Response (for a specific taxon) + :class: toggle + + .. literalinclude:: ../sample_data/get_controlled_terms_for_taxon.json + :language: JSON + Args: + taxon_id: ID of taxon to get controlled terms for + user_agent: a user-agent string that will be passed to iNaturalist. + + Returns: + A dict containing details on controlled terms and their values + + Raises: + :py:exc:`.TaxonNotFound` If an invalid taxon_id is specified + """ + # This is actually two endpoints, but they are so similar it seems best to combine them + endpoint = 'controlled_terms/for_taxon' if taxon_id else 'controlled_terms' + response = node_api_get(endpoint, params={'taxon_id': taxon_id}, user_agent=user_agent) + + # controlled_terms/for_taxon returns a 422 if the specified taxon does not exist + if response.status_code in (404, 422): + raise TaxonNotFound + response.raise_for_status() + return response.json() # Observations @@ -64,7 +142,7 @@ def make_inaturalist_api_get_call(endpoint: str, **kwargs) -> requests.Response: def get_observation(observation_id: int, user_agent: str = None) -> JsonResponse: - """Get details about an observation. + """Get details about a single observation by ID **API reference:** https://api.inaturalist.org/v1/docs/#!/Observations/get_observations_id @@ -75,8 +153,7 @@ def get_observation(observation_id: int, user_agent: str = None) -> JsonResponse .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_observation.json - :language: JSON + .. literalinclude:: ../sample_data/get_observation.py Args: observation_id: Observation ID @@ -86,29 +163,83 @@ def get_observation(observation_id: int, user_agent: str = None) -> JsonResponse A dict with details on the observation Raises: - :py:exc:`.ObservationNotFound` + :py:exc:`.ObservationNotFound` If an invalid observation is specified """ r = get_observations(id=observation_id, user_agent=user_agent) - if r["results"]: - return r["results"][0] + if r['results']: + return convert_observation_timestamps(r['results'][0]) raise ObservationNotFound() +@document_request_params([*docs._get_observations, docs._observation_histogram]) +def get_observation_histogram(user_agent: str = None, **params) -> HistogramResponse: + """Search observations and return histogram data for the given time interval + + **API reference:** https://api.inaturalist.org/v1/docs/#!/Observations/get_observations_histogram + + **Notes:** + + * Search parameters are the same as :py:func:`.get_observations()`, with the addition of + ``date_field`` and ``interval``. + * ``date_field`` may be either 'observed' (default) or 'created'. + * Observed date ranges can be filtered by parameters ``d1`` and ``d2`` + * Created date ranges can be filtered by parameters ``created_d1`` and ``created_d2`` + * ``interval`` may be one of: 'year', 'month', 'week', 'day', 'hour', 'month_of_year', or + 'week_of_year'; spaces are also allowed instead of underscores, e.g. 'month of year'. + * The year, month, week, day, and hour interval options will set default values for ``d1`` and + ``created_d1``, to limit the number of groups returned. You can override those values if you + want data from a longer or shorter time span. + * The 'hour' interval only works with ``date_field='created'`` + + Example: + + Get observations per month during 2020 in Austria (place ID 8057) + + >>> response = get_observation_histogram( + >>> interval='month', + >>> d1='2020-01-01', + >>> d2='2020-12-31', + >>> place_id=8057, + >>> ) + + .. admonition:: Example Response (observations per month of year) + :class: toggle + + .. literalinclude:: ../sample_data/get_observation_histogram_month_of_year.py + + .. admonition:: Example Response (observations per month) + :class: toggle + + .. literalinclude:: ../sample_data/get_observation_histogram_month.py + + .. admonition:: Example Response (observations per day) + :class: toggle + + .. literalinclude:: ../sample_data/get_observation_histogram_day.py + + Returns: + Dict of ``{time_key: observation_count}``. Keys are ints for 'month of year' and\ + 'week of year' intervals, and :py:class:`~datetime.datetime` objects for all other intervals. + """ + r = node_api_get('observations/histogram', params=params, user_agent=user_agent) + r.raise_for_status() + return format_histogram(r.json()) + + @document_request_params([*docs._get_observations, docs._pagination, docs._only_id]) -def get_observations( - params: RequestParams = None, user_agent: str = None, **kwargs -) -> JsonResponse: +def get_observations(user_agent: str = None, **params) -> JsonResponse: """Search observations. **API reference:** http://api.inaturalist.org/v1/docs/#!/Observations/get_observations Example: - >>> # Get observations of Monarch butterflies with photos + public location info, - >>> # on a specific date in the provice of Saskatchewan, CA - >>> observations = get_observations( + Get observations of Monarch butterflies with photos + public location info, + on a specific date in the provice of Saskatchewan, CA (place ID 7953): + + >>> response = get_observations( >>> taxon_name='Danaus plexippus', >>> created_on='2020-08-27', >>> photos=True, @@ -117,32 +248,44 @@ def get_observations( >>> place_id=7953, >>> ) + Get basic info for observations in response: + + >>> from pyinaturalist.response_format import format_observation + >>> for obs in response['results']: + >>> print(format_observation(obs)) + '[57754375] Species: Danaus plexippus (Monarch) observed by samroom on 2020-08-27 at Railway Ave, Wilcox, SK' + '[57707611] Species: Danaus plexippus (Monarch) observed by ingridt3 on 2020-08-26 at Michener Dr, Regina, SK' + .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_observations_node_page1.json - :language: JSON + .. literalinclude:: ../sample_data/get_observations_node.py Returns: JSON response containing observation records """ - kwargs = check_deprecated_params(params, **kwargs) - validate_multiple_choice_param(kwargs, "order_by", NODE_OBS_ORDER_BY_PROPERTIES) - r = make_inaturalist_api_get_call("observations", params=kwargs, user_agent=user_agent) - return r.json() + validate_multiple_choice_param(params, 'order_by', NODE_OBS_ORDER_BY_PROPERTIES) + r = node_api_get('observations', params=params, user_agent=user_agent) + r.raise_for_status() + observations = r.json() + observations['results'] = convert_all_coordinates(observations['results']) + observations['results'] = convert_all_timestamps(observations['results']) + return observations + + +# TODO: Consolidate logic from get_all_*() functions into a generic pagination function +# This would have two variations: by page number (most common) and by ID (as seen below) @document_request_params([*docs._get_observations, docs._only_id]) -def get_all_observations( - params: RequestParams = None, user_agent: str = None, **kwargs -) -> List[JsonResponse]: +def get_all_observations(user_agent: str = None, **params) -> List[JsonResponse]: """Like :py:func:`get_observations()`, but handles pagination and returns all results in one call. Explicit pagination parameters will be ignored. Notes on pagination from the iNaturalist documentation: - "The large size of the observations index prevents us from supporting the page parameter when + 'The large size of the observations index prevents us from supporting the page parameter when retrieving records from large result sets. If you need to retrieve large numbers of records, - use the ``per_page`` and ``id_above`` or ``id_below`` parameters instead." + use the ``per_page`` and ``id_above`` or ``id_below`` parameters instead.' Example: @@ -152,37 +295,36 @@ def get_all_observations( >>> ) Returns: - Combined list of observation records. Response format is the same as the inner "results" + Combined list of observation records. Response format is the same as the inner 'results'\ object returned by :py:func:`.get_observations()`. """ - kwargs = check_deprecated_params(params, **kwargs) - results = [] # type: List[JsonResponse] + results: List[JsonResponse] = [] id_above = 0 pagination_params = { - **kwargs, + **params, **{ - "order_by": "id", - "order": "asc", - "per_page": PER_PAGE_RESULTS, - "user_agent": user_agent, + 'order_by': 'id', + 'order': 'asc', + 'per_page': PER_PAGE_RESULTS, + 'user_agent': user_agent, }, } while True: - pagination_params["id_above"] = id_above + pagination_params['id_above'] = id_above page_obs = get_observations(**pagination_params) - results = results + page_obs.get("results", []) + results = results + page_obs.get('results', []) - if page_obs["total_results"] <= PER_PAGE_RESULTS: + if page_obs['total_results'] <= PER_PAGE_RESULTS: return results sleep(THROTTLING_DELAY) - id_above = results[-1]["id"] + id_above = results[-1]['id'] @document_request_params([*docs._get_observations, docs._pagination]) -def get_observation_species_counts(user_agent: str = None, **kwargs) -> JsonResponse: - """Get all species (or other "leaf taxa") associated with observations matching the search +def get_observation_species_counts(user_agent: str = None, **params) -> JsonResponse: + """Get all species (or other 'leaf taxa') associated with observations matching the search criteria, and the count of observations they are associated with. **Leaf taxa** are the leaves of the taxonomic tree, e.g., species, subspecies, variety, etc. @@ -194,15 +336,14 @@ def get_observation_species_counts(user_agent: str = None, **kwargs) -> JsonResp .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_observation_species_counts.json - :language: JSON + .. literalinclude:: ../sample_data/get_observation_species_counts.py Returns: JSON response containing taxon records with counts """ - r = make_inaturalist_api_get_call( - "observations/species_counts", - params=kwargs, + r = node_api_get( + 'observations/species_counts', + params=params, user_agent=user_agent, ) r.raise_for_status() @@ -210,7 +351,7 @@ def get_observation_species_counts(user_agent: str = None, **kwargs) -> JsonResp @document_request_params(docs._get_observations) -def get_all_observation_species_counts(user_agent: str = None, **kwargs) -> List[JsonResponse]: +def get_all_observation_species_counts(user_agent: str = None, **params) -> List[JsonResponse]: """Like :py:func:`get_observation_species_counts()`, but handles pagination and returns all results in one call. Explicit pagination parameters will be ignored. @@ -235,24 +376,23 @@ def get_all_observation_species_counts(user_agent: str = None, **kwargs) -> List Returns: Combined list of taxon records with counts """ - kwargs = check_deprecated_params(**kwargs) - results = [] # type: List[JsonResponse] + results: List[JsonResponse] = [] page = 1 pagination_params = { - **kwargs, + **params, **{ - "per_page": PER_PAGE_RESULTS, - "user_agent": user_agent, + 'per_page': PER_PAGE_RESULTS, + 'user_agent': user_agent, }, } while True: - pagination_params["page"] = page + pagination_params['page'] = page page_obs = get_observation_species_counts(**pagination_params) - results = results + page_obs.get("results", []) + results = results + page_obs.get('results', []) - if len(results) == page_obs["total_results"]: + if len(results) == page_obs['total_results']: return results sleep(THROTTLING_DELAY) @@ -260,13 +400,13 @@ def get_all_observation_species_counts(user_agent: str = None, **kwargs) -> List @document_request_params([*docs._get_observations, docs._geojson_properties]) -def get_geojson_observations(properties: List[str] = None, **kwargs) -> JsonResponse: +def get_geojson_observations(properties: List[str] = None, **params) -> JsonResponse: """Get all observation results combined into a GeoJSON ``FeatureCollection``. By default this includes some basic observation properties as GeoJSON ``Feature`` properties. The ``properties`` argument can be used to override these defaults. Example: - >>> get_geojson_observations(observation_id=16227955, properties=["photo_url"]) + >>> get_geojson_observations(observation_id=16227955, properties=['photo_url']) .. admonition:: Example Response :class: toggle @@ -277,27 +417,89 @@ def get_geojson_observations(properties: List[str] = None, **kwargs) -> JsonResp Returns: A ``FeatureCollection`` containing observation results as ``Feature`` dicts. """ - kwargs["mappable"] = True - observations = get_all_observations(**kwargs) + params['mappable'] = True + observations = get_all_observations(**params) return as_geojson_feature_collection( (flatten_nested_params(obs) for obs in observations), properties=properties if properties is not None else DEFAULT_OBSERVATION_ATTRS, ) -# Places -# -------------------- +@document_request_params([*docs._get_observations, docs._pagination]) +def get_observation_observers(user_agent: str = None, **params) -> JsonResponse: + """Get observers of observations matching the search criteria and the count of + observations and distinct taxa of rank species they have observed. -# TODO: Use updated sample responses with converted coords + Notes: + Options for ``order_by`` are 'observation_count' (default) or 'species_count'. + ``GET /observations/observers`` API node is buggy. It's currently limited to + 500 results and using ``order_by=species_count`` may produce unusual results. + See this issue for more details: https://github.com/inaturalist/iNaturalistAPI/issues/235 -def _convert_all_locations_to_float(response): - """ Convert locations for both standard (curated) and community-contributed places to floats """ - response["results"] = { - "standard": convert_location_to_float(response["results"].get("standard")), - "community": convert_location_to_float(response["results"].get("community")), - } - return response + **API reference:** https://api.inaturalist.org/v1/docs/#!/Observations/get_observations_observers + + Example: + >>> get_observation_observers(place_id=72645, order_by='species_count') + + .. admonition:: Example Response + :class: toggle + + .. literalinclude:: ../sample_data/get_observation_observers_ex_results.json + :language: JSON + + Returns: + JSON response of observers + """ + + params.setdefault('per_page', 500) # patch for API issue #235 + + r = node_api_get( + 'observations/observers', + params=params, + user_agent=user_agent, + ) + r.raise_for_status() + return r.json() + + +@document_request_params([*docs._get_observations, docs._pagination]) +def get_observation_identifiers(user_agent: str = None, **params) -> JsonResponse: + """Get identifiers of observations matching the search criteria and the count of + observations they have identified. By default, results are sorted by ID count in descending. + + **API reference:** https://api.inaturalist.org/v1/docs/#!/Observations/get_observations_identifiers + + Example: + >>> get_observation_identifiers(place_id=72645) + + .. admonition:: Example Response + :class: toggle + + .. literalinclude:: ../sample_data/get_observation_identifiers_ex_results.json + :language: JSON + + Returns: + JSON response of identifiers + + Notes: + The ``GET /observations/identifiers`` API node is currently limited to + 500 results. See this issue for more details: https://github.com/inaturalist/iNaturalistAPI/issues/236 + """ + + params.setdefault('per_page', 500) # patch until issue #236 is resolved + + r = node_api_get( + 'observations/identifiers', + params=params, + user_agent=user_agent, + ) + r.raise_for_status() + return r.json() + + +# Places +# -------------------- def get_places_by_id(place_id: MultiInt, user_agent: str = None) -> JsonResponse: @@ -307,13 +509,17 @@ def get_places_by_id(place_id: MultiInt, user_agent: str = None) -> JsonResponse **API reference:** https://api.inaturalist.org/v1/docs/#!/Places/get_places_id Example: - >>> get_places_by_id([93735, 89191]) + >>> response = get_places_by_id([67591, 89191]) + >>> print({p['id']: p['name'] for p in response['results']}) + { + 67591: 'Riversdale Beach', + 89191: 'Conservation Area Riversdale', + } .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_places_by_id.json - :language: JSON + .. literalinclude:: ../sample_data/get_places_by_id.py Args: place_id: Get a place with this ID. Multiple values are allowed. @@ -321,17 +527,17 @@ def get_places_by_id(place_id: MultiInt, user_agent: str = None) -> JsonResponse Returns: JSON response containing place records """ - r = make_inaturalist_api_get_call("places", ids=place_id, user_agent=user_agent) + r = node_api_get('places', ids=place_id, user_agent=user_agent) r.raise_for_status() # Convert coordinates to floats response = r.json() - response["results"] = convert_location_to_float(response["results"]) + response['results'] = convert_all_coordinates(response['results']) return response @document_request_params([docs._bounding_box, docs._name]) -def get_places_nearby(user_agent: str = None, **kwargs) -> JsonResponse: +def get_places_nearby(user_agent: str = None, **params) -> JsonResponse: """ Given an bounding box, and an optional name query, return standard iNaturalist curator approved and community non-curated places nearby @@ -340,20 +546,51 @@ def get_places_nearby(user_agent: str = None, **kwargs) -> JsonResponse: Example: >>> bounding_box = (150.0, -50.0, -149.999, -49.999) - >>> get_places_nearby(*bounding_box) + >>> response = get_places_nearby(*bounding_box) + + Show basic info for standard (curated) places in response: + + >>> print({p['id']: p['name'] for p in response['results']['standard']}) + { + 97394: 'North America', + 97395: 'Asia', + 97393: 'Oceania', + 97392: 'Africa', + 97391: 'Europe', + 97389: 'South America', + 7161: 'Russia', + 1: 'United States', + 6712: 'Canada', + 10316: 'United States Minor Outlying Islands', + } + + Show basic info for community-contributed places in response: + + >>> print({p['id']: p['name'] for p in response['results']['community']}) + { + 11770: 'Mehedinti', + 119755: 'Mahurangi College', + 150981: 'Ceap Breatainn', + 136758: 'Willapa Hills (US EPA Level IV Ecoregion)', + 119694: 'Tetlin National Wildlife Refuge', + 70630: 'Murray - Sunset', + 141915: 'Fundy Pollinator Trail', + 72346: 'Sucunduri', + 50505: 'Great Salt Lake', + 72230: 'Rio Novo', + } .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_places_nearby.json - :language: JSON + .. literalinclude:: ../sample_data/get_places_nearby.py Returns: - JSON response containing place records + JSON response containing place records, divided into 'standard' and 'community' places. """ - r = make_inaturalist_api_get_call("places/nearby", params=kwargs, user_agent=user_agent) + r = node_api_get('places/nearby', params=params, user_agent=user_agent) r.raise_for_status() - return _convert_all_locations_to_float(r.json()) + return convert_all_place_coordinates(r.json()) def get_places_autocomplete(q: str, user_agent: str = None) -> JsonResponse: @@ -362,13 +599,19 @@ def get_places_autocomplete(q: str, user_agent: str = None) -> JsonResponse: **API reference:** https://api.inaturalist.org/v1/docs/#!/Places/get_places_autocomplete Example: - >>> get_places_autocomplete('Irkutsk') + >>> response = get_places_autocomplete('Irkutsk') + >>> print({p['id']: p['name'] for p in response['results']}) + { + 11803: 'Irkutsk', + 41853: 'Irkutsk', + 41854: 'Irkutskiy rayon', + 163077: 'Irkutsk agglomeration', + } .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_places_autocomplete.json - :language: JSON + .. literalinclude:: ../sample_data/get_places_autocomplete.py Args: q: Name must begin with this value @@ -376,12 +619,12 @@ def get_places_autocomplete(q: str, user_agent: str = None) -> JsonResponse: Returns: JSON response containing place records """ - r = make_inaturalist_api_get_call("places/autocomplete", params={"q": q}, user_agent=user_agent) + r = node_api_get('places/autocomplete', params={'q': q}, user_agent=user_agent) r.raise_for_status() # Convert coordinates to floats response = r.json() - response["results"] = convert_location_to_float(response["results"]) + response['results'] = convert_all_coordinates(response['results']) return response @@ -390,7 +633,7 @@ def get_places_autocomplete(q: str, user_agent: str = None) -> JsonResponse: @document_request_params([docs._projects_params, docs._pagination]) -def get_projects(user_agent: str = None, **kwargs) -> JsonResponse: +def get_projects(user_agent: str = None, **params) -> JsonResponse: """Given zero to many of following parameters, get projects matching the search criteria. **API reference:** https://api.inaturalist.org/v1/docs/#!/Projects/get_projects @@ -409,30 +652,30 @@ def get_projects(user_agent: str = None, **kwargs) -> JsonResponse: Show basic info for projects in response: - >>> project_info = {p["id"]: p["title"] for p in response["results"]} - >>> print('\\n'.join([f'{k}:\\t{v}' for k, v in project_info.items()])) - 8291: PNW Invasive Plant EDDR - 19200: King County (WA) Noxious and Invasive Weeds - 8527: WA Invasives - 2344: Invasive & Huntable Animals - 6432: CBWN Invasive Plants + >>> print({p['id']: p['title'] for p in response['results']}) + { + 8291: 'PNW Invasive Plant EDDR', + 19200: 'King County (WA) Noxious and Invasive Weeds', + 8527: 'WA Invasives', + 2344: 'Invasive & Huntable Animals', + 6432: 'CBWN Invasive Plants', + } .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_projects.json - :language: JSON + .. literalinclude:: ../sample_data/get_projects.py Returns: JSON response containing project records """ - validate_multiple_choice_param(kwargs, "order_by", PROJECT_ORDER_BY_PROPERTIES) - r = make_inaturalist_api_get_call("projects", params=kwargs, user_agent=user_agent) + validate_multiple_choice_param(params, 'order_by', PROJECT_ORDER_BY_PROPERTIES) + r = node_api_get('projects', params=params, user_agent=user_agent) r.raise_for_status() - # Convert coordinates to floats response = r.json() - response["results"] = convert_location_to_float(response["results"]) + response['results'] = convert_all_coordinates(response['results']) + response['results'] = convert_all_timestamps(response['results']) return response @@ -450,8 +693,7 @@ def get_projects_by_id( .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_projects_by_id.json - :language: JSON + .. literalinclude:: ../sample_data/get_projects_by_id.py Args: project_id: Get projects with this ID. Multiple values are allowed. @@ -461,17 +703,17 @@ def get_projects_by_id( Returns: JSON response containing project records """ - r = make_inaturalist_api_get_call( - "projects", + r = node_api_get( + 'projects', ids=project_id, - params={"rule_details": rule_details}, + params={'rule_details': rule_details}, user_agent=user_agent, ) r.raise_for_status() - # Convert coordinates to floats response = r.json() - response["results"] = convert_location_to_float(response["results"]) + response['results'] = convert_all_coordinates(response['results']) + response['results'] = convert_all_timestamps(response['results']) return response @@ -480,16 +722,16 @@ def get_projects_by_id( @document_request_params([docs._taxon_params, docs._taxon_id_params]) -def get_taxa(user_agent: str = None, **kwargs) -> JsonResponse: +def get_taxa(user_agent: str = None, **params) -> JsonResponse: """Given zero to many of following parameters, get taxa matching the search criteria. **API reference:** https://api.inaturalist.org/v1/docs/#!/Taxa/get_taxa Example: - >>> response = get_taxa(q="vespi", rank=["genus", "family"]) - >>> print({taxon["id"]: taxon["name"] for taxon in response["results"]}) - {52747: "Vespidae", 84737: "Vespina", 92786: "Vespicula", 646195: "Vespiodes", ...} + >>> response = get_taxa(q='vespi', rank=['genus', 'family']) + >>> print({taxon['id']: taxon['name'] for taxon in response['results']}) + {52747: 'Vespidae', 84737: 'Vespina', 92786: 'Vespicula', 646195: 'Vespiodes', ...} .. admonition:: Example Response :class: toggle @@ -500,10 +742,13 @@ def get_taxa(user_agent: str = None, **kwargs) -> JsonResponse: Returns: JSON response containing taxon records """ - kwargs = translate_rank_range(kwargs) - r = make_inaturalist_api_get_call("taxa", params=kwargs, user_agent=user_agent) + params = translate_rank_range(params) + r = node_api_get('taxa', params=params, user_agent=user_agent) r.raise_for_status() - return r.json() + + taxa = r.json() + taxa['results'] = convert_all_timestamps(taxa['results']) + return taxa def get_taxa_by_id(taxon_id: MultiInt, user_agent: str = None) -> JsonResponse: @@ -514,20 +759,19 @@ def get_taxa_by_id(taxon_id: MultiInt, user_agent: str = None) -> JsonResponse: Example: >>> response = get_taxa_by_id(343248) - >>> basic_fields = ["preferred_common_name", "observations_count", "wikipedia_url", "wikipedia_summary"] - >>> print({f: response["results"][0][f] for f in basic_fields}) + >>> basic_fields = ['preferred_common_name', 'observations_count', 'wikipedia_url', 'wikipedia_summary'] + >>> print({f: response['results'][0][f] for f in basic_fields}) { - "preferred_common_name": "Paper Wasps", - "observations_count": 69728, - "wikipedia_url": "http://en.wikipedia.org/wiki/Polistinae", - "wikipedia_summary": "The Polistinae are eusocial wasps closely related to the more familiar yellow jackets...", + 'preferred_common_name': 'Paper Wasps', + 'observations_count': 69728, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Polistinae', + 'wikipedia_summary': 'The Polistinae are eusocial wasps closely related to yellow jackets...', } .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_taxa_by_id.json - :language: JSON + .. literalinclude:: ../sample_data/get_taxa_by_id.py Args: taxon_id: Get taxa with this ID. Multiple values are allowed. @@ -535,13 +779,16 @@ def get_taxa_by_id(taxon_id: MultiInt, user_agent: str = None) -> JsonResponse: Returns: JSON response containing taxon records """ - r = make_inaturalist_api_get_call("taxa", ids=taxon_id, user_agent=user_agent) + r = node_api_get('taxa', ids=taxon_id, user_agent=user_agent) r.raise_for_status() - return r.json() + + taxa = r.json() + taxa['results'] = convert_all_timestamps(taxa['results']) + return taxa @document_request_params([docs._taxon_params, docs._minify]) -def get_taxa_autocomplete(user_agent: str = None, **kwargs) -> JsonResponse: +def get_taxa_autocomplete(user_agent: str = None, **params) -> JsonResponse: """Given a query string, returns taxa with names starting with the search term **API reference:** https://api.inaturalist.org/v1/docs/#!/Taxa/get_taxa_autocomplete @@ -571,11 +818,11 @@ def get_taxa_autocomplete(user_agent: str = None, **kwargs) -> JsonResponse: Returns: JSON response containing taxon records """ - kwargs = translate_rank_range(kwargs) - r = make_inaturalist_api_get_call("taxa/autocomplete", params=kwargs, user_agent=user_agent) + params = translate_rank_range(params) + r = node_api_get('taxa/autocomplete', params=params, user_agent=user_agent) r.raise_for_status() json_response = r.json() - if kwargs.get("minify"): - json_response["results"] = [format_taxon(t) for t in json_response["results"]] + if params.get('minify'): + json_response['results'] = [format_taxon(t, align=True) for t in json_response['results']] return json_response diff --git a/pyinaturalist/request_params.py b/pyinaturalist/request_params.py index 4898315d..04045edc 100644 --- a/pyinaturalist/request_params.py +++ b/pyinaturalist/request_params.py @@ -1,147 +1,151 @@ """Helper functions for processing and validating request parameters""" +import warnings from datetime import date, datetime +from dateutil.parser import parse as parse_timestamp +from dateutil.relativedelta import relativedelta +from dateutil.tz import tzlocal from io import BytesIO from logging import getLogger from os.path import abspath, expanduser -from typing import Any, BinaryIO, Dict, Iterable, Optional -import warnings +from typing import Any, BinaryIO, Dict, Iterable, List, Optional, Tuple -from dateutil.parser import parse as parse_timestamp -from dateutil.tz import tzlocal from pyinaturalist.constants import FileOrPath, RequestParams - # Basic observation attributes to include by default in geojson responses DEFAULT_OBSERVATION_ATTRS = [ - "id", - "photo_url", - "positional_accuracy", - "preferred_common_name", - "quality_grade", - "taxon_id", - "taxon_name", - "time_observed_at", - "uri", + 'id', + 'photo_url', + 'positional_accuracy', + 'quality_grade', + 'taxon_id', + 'taxon_name', + 'taxon_common_name', + 'time_observed_at', + 'uri', ] # All request parameters from both Node API and REST (Rails) API that accept date or datetime strings DATETIME_PARAMS = [ - "created_after", - "created_d1", - "created_d2", - "created_on", - "d1", - "d2", - "newer_than", - "observation_created_d1", - "observation_created_d2", - "observed_d1", - "observed_d2", - "observed_on", - "older_than", - "on", - "since", - "updated_since", # TODO: test if this one behaves differently in Node API vs REST API + 'created_after', + 'created_d1', + 'created_d2', + 'created_on', + 'd1', + 'd2', + 'newer_than', + 'observation_created_d1', + 'observation_created_d2', + 'observed_d1', + 'observed_d2', + 'observed_on', + 'older_than', + 'on', + 'since', + 'updated_since', # TODO: test if this one behaves differently in Node API vs REST API ] -# Reponse formats supported by GET /observations endpoint -OBSERVATION_FORMATS = ["atom", "csv", "dwc", "json", "kml", "widget"] +# Response formats supported by GET /observations endpoint +OBSERVATION_FORMATS = ['atom', 'csv', 'dwc', 'json', 'kml', 'widget'] # Creative Commons license codes -CC_LICENSES = ["CC-BY", "CC-BY-NC", "CC-BY-ND", "CC-BY-SA", "CC-BY-NC-ND", "CC-BY-NC-SA", "CC0"] +CC_LICENSES = ['CC-BY', 'CC-BY-NC', 'CC-BY-ND', 'CC-BY-SA', 'CC-BY-NC-ND', 'CC-BY-NC-SA', 'CC0'] # IUCN Conservation status codes; for more info, see: https://www.iucnredlist.org -CONSERVATION_STATUSES = ["LC", "NT", "VU", "EN", "CR", "EW", "EX"] +CONSERVATION_STATUSES = ['LC', 'NT', 'VU', 'EN', 'CR', 'EW', 'EX'] -# Taxon ID and name of main taxa "categories" that can be filtered on +# Taxon ID and name of main taxa 'categories' that can be filtered on ICONIC_TAXA = { - 0: "Unknown", - 1: "Animalia", - 3: "Aves", - 20978: "Amphibia", - 26036: "Reptilia", - 40151: "Mammalia", - 47178: "Actinopterygii", - 47115: "Mollusca", - 47119: "Arachnida", - 47158: "Insecta", - 47126: "Plantae", - 47170: "Fungi", - 48222: "Chromista", - 47686: "Protozoa", + 0: 'Unknown', + 1: 'Animalia', + 3: 'Aves', + 20978: 'Amphibia', + 26036: 'Reptilia', + 40151: 'Mammalia', + 47178: 'Actinopterygii', + 47115: 'Mollusca', + 47119: 'Arachnida', + 47158: 'Insecta', + 47126: 'Plantae', + 47170: 'Fungi', + 48222: 'Chromista', + 47686: 'Protozoa', } # Taxonomic ranks that can be filtered on RANKS = [ - "form", - "variety", - "subspecies", - "hybrid", - "species", - "genushybrid", - "subgenus", - "genus", - "subtribe", - "tribe", - "supertribe", - "subfamily", - "family", - "epifamily", - "superfamily", - "infraorder", - "suborder", - "order", - "superorder", - "infraclass", - "subclass", - "class", - "superclass", - "subphylum", - "phylum", - "kingdom", + 'form', + 'variety', + 'subspecies', + 'hybrid', + 'species', + 'genushybrid', + 'subgenus', + 'genus', + 'subtribe', + 'tribe', + 'supertribe', + 'subfamily', + 'family', + 'epifamily', + 'superfamily', + 'infraorder', + 'suborder', + 'order', + 'superorder', + 'infraclass', + 'subclass', + 'class', + 'superclass', + 'subphylum', + 'phylum', + 'kingdom', ] # Endpoint-specific options for multiple choice parameters -NODE_OBS_ORDER_BY_PROPERTIES = ["created_at", "id", "observed_on", "species_guess", "votes"] -REST_OBS_ORDER_BY_PROPERTIES = ["date_added", "observed_on"] -PROJECT_ORDER_BY_PROPERTIES = ["created", "distance", "featured", "recent_posts", "updated"] +NODE_OBS_ORDER_BY_PROPERTIES = ['created_at', 'id', 'observed_on', 'species_guess', 'votes'] +REST_OBS_ORDER_BY_PROPERTIES = ['date_added', 'observed_on'] +PROJECT_ORDER_BY_PROPERTIES = ['created', 'distance', 'featured', 'recent_posts', 'updated'] # Options for multiple choice parameters (non-endpoint-specific) -COMMUNITY_ID_STATUSES = ["most_agree", "most_disagree", "some_agree"] -EXTRA_PROPERTIES = ["fields", "identifications", "projects"] -GEOPRIVACY_LEVELS = ["obscured", "obscured_private", "open", "private"] -HAS_PROPERTIES = ["photo", "geo"] -ORDER_DIRECTIONS = ["asc", "desc"] -PROJECT_TYPES = ["collection", "umbrella"] -QUALITY_GRADES = ["casual", "needs_id", "research"] -SEARCH_PROPERTIES = ["names", "tags", "description", "place"] +COMMUNITY_ID_STATUSES = ['most_agree', 'most_disagree', 'some_agree'] +EXTRA_PROPERTIES = ['fields', 'identifications', 'projects'] +GEOPRIVACY_LEVELS = ['obscured', 'obscured_private', 'open', 'private'] +HAS_PROPERTIES = ['photo', 'geo'] +HISTOGRAM_DATE_FIELDS = ['created', 'observed'] +HISTOGRAM_INTERVALS = ['year', 'month', 'week', 'day', 'hour', 'month_of_year', 'week_of_year'] +ORDER_DIRECTIONS = ['asc', 'desc'] +PROJECT_TYPES = ['collection', 'umbrella'] +QUALITY_GRADES = ['casual', 'needs_id', 'research'] +SEARCH_PROPERTIES = ['names', 'tags', 'description', 'place'] # Multiple-choice request parameters, with keys mapped to their possible choices (non-endpoint-specific) MULTIPLE_CHOICE_PARAMS = { - "csi": CONSERVATION_STATUSES, - "extra": EXTRA_PROPERTIES, - "geoprivacy": GEOPRIVACY_LEVELS, - "has": HAS_PROPERTIES, - "hrank": RANKS, - "iconic_taxa": list(ICONIC_TAXA.values()), - "identifications": COMMUNITY_ID_STATUSES, - "license": CC_LICENSES, - "lrank": RANKS, - "max_rank": RANKS, - "min_rank": RANKS, - "order": ORDER_DIRECTIONS, - "photo_license": CC_LICENSES, - "quality_grade": QUALITY_GRADES, - "rank": RANKS, - "search_on": SEARCH_PROPERTIES, - "sound_license": CC_LICENSES, - "taxon_geoprivacy": GEOPRIVACY_LEVELS, - "type": PROJECT_TYPES, + 'csi': CONSERVATION_STATUSES, + 'date_field': HISTOGRAM_DATE_FIELDS, + 'extra': EXTRA_PROPERTIES, + 'geoprivacy': GEOPRIVACY_LEVELS, + 'has': HAS_PROPERTIES, + 'hrank': RANKS, + 'iconic_taxa': list(ICONIC_TAXA.values()), + 'identifications': COMMUNITY_ID_STATUSES, + 'interval': HISTOGRAM_INTERVALS, + 'license': CC_LICENSES, + 'lrank': RANKS, + 'max_rank': RANKS, + 'min_rank': RANKS, + 'order': ORDER_DIRECTIONS, + 'photo_license': CC_LICENSES, + 'quality_grade': QUALITY_GRADES, + 'rank': RANKS, + 'search_on': SEARCH_PROPERTIES, + 'sound_license': CC_LICENSES, + 'taxon_geoprivacy': GEOPRIVACY_LEVELS, + 'type': PROJECT_TYPES, } MULTIPLE_CHOICE_ERROR_MSG = ( - "Parameter '{}' must have one of the following values: {}\n\tValue provided: {}" + 'Parameter "{}" must have one of the following values: {}\n\tValue provided: {}' ) logger = getLogger(__name__) @@ -152,7 +156,7 @@ def preprocess_request_params(params: Optional[Dict[str, Any]]) -> Dict[str, Any if not params: return {} - validate_multiple_choice_params(params) + params = validate_multiple_choice_params(params) params = convert_bool_params(params) params = convert_datetime_params(params) params = convert_list_params(params) @@ -160,21 +164,6 @@ def preprocess_request_params(params: Optional[Dict[str, Any]]) -> Dict[str, Any return params -# TODO: Remove in 0.12 -def check_deprecated_params(params=None, **kwargs) -> Dict[str, Any]: - """Check for usage of request parameters that are deprecated but still supported for - backwards-compatibility - """ - if params: - warn("The 'params' argument is deprecated; please use keyword arguments instead") - kwargs.update(params) - if kwargs.get("search_query"): - warn("The 'search_query' argument is deprecated; use 'q' instead") - kwargs["q"] = kwargs.pop("search_query") - - return kwargs - - def convert_bool_params(params: RequestParams) -> RequestParams: """ Convert any boolean request parameters to javascript-style boolean strings """ for k, v in params.items(): @@ -195,18 +184,77 @@ def convert_datetime_params(params: RequestParams) -> RequestParams: for k, v in params.items(): if isinstance(v, datetime) or isinstance(v, date): params[k] = _isoformat(v) - if v is not None and k in DATETIME_PARAMS: + elif v is not None and k in DATETIME_PARAMS: params[k] = _isoformat(parse_timestamp(v)) return params +def convert_list(obj: Any) -> str: + """Convert list parameters into an API-compatible (comma-delimited) string""" + if not obj: + return obj + if isinstance(obj, list): + return ','.join(map(str, obj)) + return str(obj) + + +def convert_list_params(params: RequestParams) -> RequestParams: + """Convert any list parameters into an API-compatible (comma-delimited) string. + Will be url-encoded by requests. For example: `['k1', 'k2', 'k3'] -> k1%2Ck2%2Ck3` + """ + return {k: convert_list(v) for k, v in params.items()} + + +def convert_observation_fields(params: RequestParams) -> RequestParams: + """Translate simplified format of observation field values into API-compatible format""" + if 'observation_fields' in params: + params['observation_field_values_attributes'] = params.pop('observation_fields') + obs_fields = params.get('observation_field_values_attributes') + if isinstance(obs_fields, dict): + params['observation_field_values_attributes'] = [ + {'observation_field_id': k, 'value': v} for k, v in obs_fields.items() + ] + return params + + +def get_interval_ranges( + start_date: datetime, end_date: datetime, interval='monthly' +) -> List[Tuple[datetime, datetime]]: + """Get a list of date ranges between ``start_date`` and ``end_date`` with the specified interval + Example: + >>> # Get date ranges representing months of a year + >>> get_interval_ranges(datetime(2020, 1, 1), datetime(2020, 12, 31), 'monthly') + + Args: + start_date: Starting date of date ranges (inclusive) + end_date: End date of date ranges (inclusive) + interval: Either 'monthly' or 'yearly' + + Returns: + List of date ranges in the format: ``[(start_date, end_date), (start_date, end_date), ...]`` + """ + if interval == 'monthly': + delta = relativedelta(months=1) + elif interval == 'yearly': + delta = relativedelta(years=1) + else: + raise ValueError(f'Invalid interval: {interval}') + + incremental_date = start_date + interval_ranges = [] + while incremental_date <= end_date: + interval_ranges.append((incremental_date, incremental_date + delta - relativedelta(days=1))) + incremental_date += delta + return interval_ranges + + def ensure_file_obj(photo: FileOrPath) -> BinaryIO: """Given a file objects or path, read it into a file-like object if it's a path""" if isinstance(photo, str): file_path = abspath(expanduser(photo)) - logger.info("Reading from file: {}".format(file_path)) - with open(file_path, "rb") as f: + logger.info(f'Reading from file: {file_path}') + with open(file_path, 'rb') as f: return BytesIO(f.read()) return photo @@ -220,44 +268,16 @@ def ensure_list(values: Any): """If the value is a string or comma-separated list of values, convert it into a list""" if not values: values = [] - elif isinstance(values, str) and "," in values: - values = values.split(",") + elif isinstance(values, str) and ',' in values: + values = values.split(',') elif not isinstance(values, list): values = [values] return values -def convert_list(obj: Any) -> str: - """Convert list parameters into an API-compatible (comma-delimited) string""" - if not obj: - return "" - if isinstance(obj, list): - return ",".join(map(str, obj)) - return str(obj) - - -def convert_list_params(params: RequestParams) -> RequestParams: - """Convert any list parameters into an API-compatible (comma-delimited) string. - Will be url-encoded by requests. For example: `['k1', 'k2', 'k3'] -> k1%2Ck2%2Ck3` - """ - return {k: convert_list(v) for k, v in params.items()} - - -def convert_observation_fields(params: RequestParams) -> RequestParams: - """Translate simplified format of observation field values into API-compatible format""" - if "observation_fields" in params: - params["observation_field_values_attributes"] = params.pop("observation_fields") - obs_fields = params.get("observation_field_values_attributes") - if isinstance(obs_fields, dict): - params["observation_field_values_attributes"] = [ - {"observation_field_id": k, "value": v} for k, v in obs_fields.items() - ] - return params - - def strip_empty_params(params: RequestParams) -> RequestParams: """Remove any request parameters with empty or ``None`` values.""" - return {k: v for k, v in params.items() if v or v is False} + return {k: v for k, v in params.items() if v or v in [False, 0, 0.0]} def is_int(value: Any) -> bool: @@ -281,7 +301,7 @@ def validate_ids(ids: Any) -> str: :py:exc:`ValueError` if any values are not valid integers """ if not is_int_list(ids): - raise ValueError("Invalid ID(s): {}; must specify integers only".format(ids)) + raise ValueError(f'Invalid ID(s): {ids}; must specify integers only') return convert_list([int(id) for id in ensure_list(ids)]) @@ -294,23 +314,34 @@ def is_valid_multiple_choice_option(value: Any, choices: Iterable) -> bool: return all([v in choices for v in value]) -def validate_multiple_choice_param(params: RequestParams, key: str, choices: Iterable): - """Verify if a multiple-choice request parameter contains valid value(s); - if not, raise an error. **Used for endpoint-specific params.** +def validate_multiple_choice_param( + params: RequestParams, key: str, choices: Iterable +) -> RequestParams: + """Verify that a multiple-choice request parameter contains valid value(s); + if not, raise an error. + **Used for endpoint-specific params.** + + Returns: + Parameters with modifications (if any) Raises: :py:exc:`ValueError` """ - if key in params and not is_valid_multiple_choice_option(params[key], choices): - raise ValueError(MULTIPLE_CHOICE_ERROR_MSG.format(key, choices, params[key])) + params, error_msg = _validate_multiple_choice_param(params, key, choices) + if error_msg: + raise ValueError(error_msg) + return params -def validate_multiple_choice_params(params: RequestParams): +def validate_multiple_choice_params(params: RequestParams) -> RequestParams: """Verify that multiple-choice request parameters contain a valid value. **Note:** This does not check endpoint-specific params, i.e., those that have the same name but different values across different endpoints. + Returns: + Parameters with modifications (if any) + Raises: :py:exc:`ValueError`; Error message will contain info on all validation errors, if there are multiple @@ -318,12 +349,42 @@ def validate_multiple_choice_params(params: RequestParams): # Collect info on any validation errors errors = [] for key, choices in MULTIPLE_CHOICE_PARAMS.items(): - if key in params and not is_valid_multiple_choice_option(params[key], choices): - errors.append(MULTIPLE_CHOICE_ERROR_MSG.format(key, choices, params[key])) + params, error_msg = _validate_multiple_choice_param(params, key, choices) + if error_msg: + errors.append(error_msg) # Combine all messages (if multiple) into one error message if errors: - raise ValueError("\n".join(errors)) + raise ValueError('\n'.join(errors)) + return params + + +def _validate_multiple_choice_param( + params: RequestParams, key: str, choices: Iterable +) -> Tuple[RequestParams, Optional[str]]: + """Verify that a multiple-choice request parameter contains valid value(s); + if not, return an error message. + + Returns: + Parameters with modifications (if any), and a validation error message (if any) + """ + error_msg = None + if key in params: + params[key] = _normalize_multiple_choice_value(params[key]) + if not is_valid_multiple_choice_option(params.get(key), choices): + error_msg = MULTIPLE_CHOICE_ERROR_MSG.format(key, choices, params[key]) + return params, error_msg + + +def _normalize_multiple_choice_value(value): + """Convert any spaces in a multiple choice value to underscores; + e.g. treat 'month of year' as equivalent to 'month_of_year' + """ + if not value: + return value + if isinstance(value, list): + return [v.replace(' ', '_') for v in value] + return value.replace(' ', '_') def _isoformat(d): @@ -340,15 +401,15 @@ def translate_rank_range(params: RequestParams) -> RequestParams: def _get_rank_index(rank: str) -> int: if rank not in RANKS: - raise ValueError("Invalid rank") + raise ValueError('Invalid rank') return RANKS.index(rank) - min_rank, max_rank = params.pop("min_rank", None), params.pop("max_rank", None) + min_rank, max_rank = params.pop('min_rank', None), params.pop('max_rank', None) if min_rank or max_rank: # Use indices in RANKS list to determine range of ranks to include min_rank_index = _get_rank_index(min_rank) if min_rank else 0 max_rank_index = _get_rank_index(max_rank) + 1 if max_rank else len(RANKS) - params["rank"] = RANKS[min_rank_index:max_rank_index] + params['rank'] = RANKS[min_rank_index:max_rank_index] return params diff --git a/pyinaturalist/response_format.py b/pyinaturalist/response_format.py index 5007bdaa..2f0860c2 100644 --- a/pyinaturalist/response_format.py +++ b/pyinaturalist/response_format.py @@ -1,9 +1,33 @@ """ Helper functions for formatting API responses """ -from typing import Any, Dict, List, Iterable, Optional +from datetime import datetime, timedelta +from dateutil.parser import parse as parse_date +from dateutil.parser._parser import UnknownTimezoneWarning +from dateutil.tz import tzoffset +from logging import getLogger +from typing import Any, Dict, Iterable, List, Optional +from warnings import catch_warnings, simplefilter + +from pyinaturalist.constants import HistogramResponse, JsonResponse, ResponseObject + +GENERIC_TIME_FIELDS = ('created_at', 'updated_at') +OBSERVATION_TIME_FIELDS = ( + 'created_at_details', + 'created_time_zone', + 'observed_on', + 'observed_on_details', + 'observed_on_string', + 'observed_time_zone', + 'time_zone_offset', +) +logger = getLogger(__name__) + + +# GeoJSON conversion +# -------------------- def as_geojson_feature_collection( - results: Iterable[Dict[str, Any]], properties: List[str] = None + results: Iterable[ResponseObject], properties: List[str] = None ) -> Dict[str, Any]: """ Convert results from an API response into a @@ -16,12 +40,12 @@ def as_geojson_feature_collection( properties: Whitelist of specific properties to include """ return { - "type": "FeatureCollection", - "features": [as_geojson_feature(record, properties) for record in results], + 'type': 'FeatureCollection', + 'features': [as_geojson_feature(record, properties) for record in results], } -def as_geojson_feature(result: Dict[str, Any], properties: List[str] = None) -> Dict[str, Any]: +def as_geojson_feature(result: ResponseObject, properties: List[str] = None) -> ResponseObject: """ Convert an individual response item to a geojson Feature object, optionally with specific response properties included. @@ -30,73 +54,241 @@ def as_geojson_feature(result: Dict[str, Any], properties: List[str] = None) -> result: A single response item properties: Whitelist of specific properties to include """ - result["geojson"]["coordinates"] = [float(i) for i in result["geojson"]["coordinates"]] + result['geojson']['coordinates'] = [float(i) for i in result['geojson']['coordinates']] return { - "type": "Feature", - "geometry": result["geojson"], - "properties": {k: result.get(k) for k in properties or []}, + 'type': 'Feature', + 'geometry': result['geojson'], + 'properties': {k: result.get(k) for k in properties or []}, } -def flatten_nested_params(observation: Dict[str, Any]) -> Dict[str, Any]: - """Extract some nested observation properties to include at the top level; - this makes it easier to specify these as properties for - :py:func:`.as_as_geojson_feature_collection`. +# Wrapper functions to apply conversions to all response objects +# -------------------- + + +def convert_all_coordinates(results: List[ResponseObject]) -> List[ResponseObject]: + """Convert coordinate pairs in response items from strings to floats, if valid Args: - observation: A single observation result + results: Results from API response; expects coordinates in either 'location' key or + 'latitude' and 'longitude' keys """ - taxon = observation.get("taxon", {}) - photos = observation.get("photos", [{}]) - observation["taxon_id"] = taxon.get("id") - observation["taxon_name"] = taxon.get("name") - observation["taxon_rank"] = taxon.get("rank") - observation["preferred_common_name"] = taxon.get("preferred_common_name") - observation["photo_url"] = photos[0].get("url") - return observation + results = [convert_lat_long(result) for result in results] + results = [convert_location(result) for result in results] + return results -def format_taxon(taxon: Dict) -> str: - """Format a taxon result into a single string containing taxon ID, rank, and name - (including common name, if available). - """ - # Visually align taxon IDs (< 7 chars) and ranks (< 11 chars) - common = taxon.get("preferred_common_name") - return "{:>8}: {:>12} {}{}".format( - taxon["id"], - taxon["rank"].title(), - taxon["name"], - " ({})".format(common) if common else "", - ) +def convert_all_place_coordinates(response: JsonResponse) -> JsonResponse: + """ Convert locations for both standard and community-contributed places to floats """ + response['results'] = { + 'standard': convert_all_coordinates(response['results'].get('standard')), + 'community': convert_all_coordinates(response['results'].get('community')), + } + return response + +def convert_all_timestamps(results: List[ResponseObject]) -> List[ResponseObject]: + """Replace all date/time info with datetime objects, where possible""" + results = [convert_generic_timestamps(result) for result in results] + results = [convert_observation_timestamps(result) for result in results] + return results -def convert_location_to_float(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """Convert coordinate pairs in response items from strings to floats, if valid. - Args: - results: Results from API response; expects coordinates in the "location" key +# Conversion functions +# -------------------- + + +def convert_lat_long(result: ResponseObject) -> ResponseObject: + """Convert a coordinate pair in a response item from strings to floats, if valid""" + if 'latitude' in result and 'longitude' in result: + result['latitude'] = try_float(result['latitude']) + result['longitude'] = try_float(result['longitude']) + return result + + +def convert_location(result: ResponseObject): + """Convert a coordinate pairs in a response item from strings to floats, if valid""" + if ',' in (result.get('location') or ''): + result['location'] = [try_float(coord) for coord in result['location'].split(',')] + return result + + +def convert_generic_timestamps(result: ResponseObject) -> ResponseObject: + """Replace generic created/updated info that's returned by multiple endpoints. + **Note:** Compared to observation timestamps, these are generally more reliable. These seem to + be consistently in ISO 8601 format. """ - for result in results or []: - if "," in (result["location"] or ""): - result["location"] = [convert_float(coord) for coord in result["location"].split(",")] - return results + for field in GENERIC_TIME_FIELDS: + datetime_obj = try_datetime(result.get(field, '')) + if datetime_obj: + result[field] = datetime_obj + return result -def convert_lat_long_to_float(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """Convert coordinate pairs in response items from strings to floats, if valid +def convert_observation_timestamps(result: ResponseObject) -> ResponseObject: + """Replace observation date/time info with datetime objects""" + if 'created_at_details' not in result and 'observed_on_string' not in result: + return result + + observation = result.copy() + tz_offset = observation.get('time_zone_offset', '') + tz_name = observation.get('observed_time_zone') + + created_datetime = observation.get('created_at') + if not isinstance(created_datetime, datetime): + created_datetime = try_datetime(observation.get('created_at_details', {}).get('date')) + created_datetime = convert_offset(created_datetime, tz_offset, tz_name) + + # Ignore any timezone info in observed_on timestamp; offset field is more reliable + observed_datetime = try_datetime(observation.get('observed_on_string', ''), ignoretz=True) + observed_datetime = convert_offset(observed_datetime, tz_offset, tz_name) + + # If valid, add the datetime objects and remove all other redundant date/time fields + if created_datetime and observed_datetime: + for field in OBSERVATION_TIME_FIELDS: + observation.pop(field, None) + observation['created_at'] = created_datetime + observation['observed_on'] = observed_datetime + + return observation + + +def convert_offset( + datetime_obj: Optional[datetime], tz_offset: str = None, tz_name: str = None +) -> Optional[datetime]: + """Use timezone offset info to replace a datetime's tzinfo""" + if not datetime_obj or not tz_offset: + return datetime_obj + + try: + offset = parse_offset(tz_offset, tz_name) + return datetime_obj.replace(tzinfo=offset) + except (AttributeError, TypeError, ValueError) as e: + logger.warning(f'Could not parse offset: {tz_offset}: {str(e)}') + return None + + +def parse_offset(tz_offset: str, tz_name: str = None) -> tzoffset: + """Convert a timezone offset string to a tzoffset object, accounting for some common variations + in format + + Examples: + + >>> parse_offset('GMT-08:00', 'PST') + tzoffset('PST', -28800) + >>> parse_offset('-06:00') + tzoffset(None, -21600) + >>> parse_offset('+05:30') + tzoffset(None, 19800) + >>> parse_offset('0530') + tzoffset(None, 19800) - Args: - results: Results from API response; expects coordinates in "latitude" and "longitude" keys """ - for result in results: - result["latitude"] = convert_float(result["latitude"]) - result["longitude"] = convert_float(result["longitude"]) - return results + + def remove_prefixes(text, prefixes): + for prefix in prefixes: + if text.startswith(prefix): + text = text[len(prefix) :] # noqa # black and flake8 fight over this one + return text + + # Parse hours, minutes, and sign from offset string; account for either `hh:mm` or `hhmm` format + tz_offset = remove_prefixes(tz_offset, ['GMT', 'UTC']).strip() + multiplier = -1 if tz_offset.startswith('-') else 1 + tz_offset = tz_offset.lstrip('+-') + if ':' in tz_offset: + hours, minutes = tz_offset.split(':') + else: + hours, minutes = tz_offset[:2], tz_offset[2:] + + # Convert to a timezone offset in +/- seconds + delta = timedelta(hours=int(hours), minutes=int(minutes)) + return tzoffset(tz_name, delta.total_seconds() * multiplier) -def convert_float(value: Any) -> Optional[float]: +def try_datetime(timestamp: str, **kwargs) -> Optional[datetime]: + """ Parse a timestamp string into a datetime, if valid; return ``None`` otherwise """ + if isinstance(timestamp, datetime): + return timestamp + + try: + # Suppress UnknownTimezoneWarning + with catch_warnings(): + simplefilter('ignore', category=UnknownTimezoneWarning) + return parse_date(timestamp, **kwargs) + except (AttributeError, TypeError, ValueError) as e: + logger.warning(f'Could not parse timestamp: {timestamp}: {str(e)}') + return None + + +def try_float(value: Any) -> Optional[float]: """ Convert a value to a float, if valid; return ``None`` otherwise """ try: return float(value) except (TypeError, ValueError): return None + + +# Formatting functions +# -------------------- + + +def flatten_nested_params(observation: ResponseObject) -> ResponseObject: + """Extract some nested observation properties to include at the top level; + this makes it easier to specify these as properties for + :py:func:`.as_as_geojson_feature_collection`. + + Args: + observation: A single observation result + """ + taxon = observation.get('taxon', {}) + photos = observation.get('photos', [{}]) + observation['taxon_id'] = taxon.get('id') + observation['taxon_name'] = taxon.get('name') + observation['taxon_rank'] = taxon.get('rank') + observation['taxon_common_name'] = taxon.get('preferred_common_name') + observation['photo_url'] = photos[0].get('url') + return observation + + +# TODO: Is any error handling necessary here? Responses seem fairly consistent and predictable. +def format_histogram(response: JsonResponse) -> HistogramResponse: + """Format a response containing time series data into a single ``{date: value}`` dict""" + # The inner result object's key will be the name of the interval requested + interval = next(iter(response['results'].keys())) + histogram = response['results'][interval] + + # Convert keys to appropriate type depending on interval + if interval in ['month_of_year', 'week_of_year']: + return {int(k): v for k, v in histogram.items()} + else: + return {parse_date(k): v for k, v in histogram.items()} + + +def format_observation(observation: ResponseObject) -> str: + """Make a condensed summary from basic observation details: what, who, when, where""" + taxon_str = format_taxon(observation.get('taxon') or {}) + location = observation.get('place_guess') or observation.get('location') + return ( + f"[{observation['id']}] {taxon_str} " + f"observed by {observation['user']['login']} " + f"on {observation['observed_on']} " + f'at {location}' + ) + + +def format_taxon(taxon: ResponseObject, align: bool = False) -> str: + """Format a taxon result into a single string containing taxon ID, rank, and name + (including common name, if available). + """ + if not taxon: + return 'unknown taxon' + common_name = taxon.get('preferred_common_name') + name = f"{taxon['name']}" + (f' ({common_name})' if common_name else '') + rank = taxon['rank'].title() + + # Visually align taxon IDs (< 7 chars) and ranks (< 11 chars) + if align: + # return '{:>8}: {:>12} {}'.format(taxon['id'], rank, name) + return f"{taxon['id']:>8}: {rank:>12} {name}" + else: + return f'{rank}: {name}' diff --git a/pyinaturalist/rest_api.py b/pyinaturalist/rest_api.py index 916957fe..12f0b75c 100644 --- a/pyinaturalist/rest_api.py +++ b/pyinaturalist/rest_api.py @@ -11,71 +11,29 @@ """ from time import sleep -from typing import Dict, Any, List, Union - -from urllib.parse import urljoin +from typing import Any, Dict, List, Union from pyinaturalist import api_docs as docs +from pyinaturalist.api_requests import delete, get, post, put +from pyinaturalist.auth import get_access_token # noqa from pyinaturalist.constants import ( - THROTTLING_DELAY, INAT_BASE_URL, + THROTTLING_DELAY, FileOrPath, JsonResponse, ListResponse, - RequestParams, ) -from pyinaturalist.exceptions import AuthenticationError, ObservationNotFound -from pyinaturalist.api_requests import delete, get, post, put +from pyinaturalist.exceptions import ObservationNotFound from pyinaturalist.forge_utils import document_request_params from pyinaturalist.request_params import ( OBSERVATION_FORMATS, REST_OBS_ORDER_BY_PROPERTIES, - check_deprecated_params, convert_observation_fields, ensure_file_obj, ensure_file_objs, validate_multiple_choice_param, - warn, ) -from pyinaturalist.response_format import convert_lat_long_to_float - - -def get_access_token( - username: str, password: str, app_id: str, app_secret: str, user_agent: str = None -) -> str: - """Get an access token using the user's iNaturalist username and password. - You still need an iNaturalist app to do this. - - **API reference:** https://www.inaturalist.org/pages/api+reference#auth - - Example: - >>> access_token = get_access_token('...') - >>> headers = {"Authorization": f"Bearer {access_token}"} - - Args: - username: iNaturalist username - password: iNaturalist password - app_id: iNaturalist application ID - app_secret: iNaturalist application secret - user_agent: a user-agent string that will be passed to iNaturalist. - """ - payload = { - "client_id": app_id, - "client_secret": app_secret, - "grant_type": "password", - "username": username, - "password": password, - } - - response = post( - "{base_url}/oauth/token".format(base_url=INAT_BASE_URL), - json=payload, - user_agent=user_agent, - ) - try: - return response.json()["access_token"] - except KeyError: - raise AuthenticationError("Authentication error, please check credentials.") +from pyinaturalist.response_format import convert_all_coordinates, convert_all_timestamps @document_request_params( @@ -86,7 +44,7 @@ def get_access_token( docs._pagination, ] ) -def get_observations(user_agent: str = None, **kwargs) -> Union[List, str]: +def get_observations(user_agent: str = None, **params) -> Union[List, str]: """Get observation data, optionally in an alternative format. Also see :py:func:`.get_geojson_observations` for GeoJSON format (not included here because it wraps a separate API endpoint). @@ -95,7 +53,7 @@ def get_observations(user_agent: str = None, **kwargs) -> Union[List, str]: Example: - >>> get_observations(id=45414404, format="atom") + >>> get_observations(id=45414404, format='atom') .. admonition:: Example Response (atom) :class: toggle @@ -133,30 +91,32 @@ def get_observations(user_agent: str = None, **kwargs) -> Union[List, str]: :language: javascript Returns: - Return type will be ``dict`` for the ``json`` response format, and ``str`` for all - others. + Return type will be ``dict`` for the ``json`` response format, and ``str`` for all others. """ - response_format = kwargs.pop("response_format", "json") - if response_format == "geojson": - raise ValueError("For geojson format, use pyinaturalist.node_api.get_geojson_observations") + response_format = params.pop('response_format', 'json') + if response_format == 'geojson': + raise ValueError('For geojson format, use pyinaturalist.node_api.get_geojson_observations') if response_format not in OBSERVATION_FORMATS: - raise ValueError("Invalid response format") - validate_multiple_choice_param(kwargs, "order_by", REST_OBS_ORDER_BY_PROPERTIES) + raise ValueError('Invalid response format') + validate_multiple_choice_param(params, 'order_by', REST_OBS_ORDER_BY_PROPERTIES) response = get( - urljoin(INAT_BASE_URL, "observations.{}".format(response_format)), - params=kwargs, + f'{INAT_BASE_URL}/observations.{response_format}', + params=params, user_agent=user_agent, ) - if response_format == "json": - return convert_lat_long_to_float(response.json()) + if response_format == 'json': + observations = response.json() + observations = convert_all_coordinates(observations) + observations = convert_all_timestamps(observations) + return observations else: return response.text @document_request_params([docs._search_query, docs._page]) -def get_observation_fields(user_agent: str = None, **kwargs) -> ListResponse: +def get_observation_fields(user_agent: str = None, **params) -> ListResponse: """Search observation fields. Observation fields are basically typed data fields that users can attach to observation. @@ -172,23 +132,25 @@ def get_observation_fields(user_agent: str = None, **kwargs) -> ListResponse: .. admonition:: Example Response :class: toggle - .. literalinclude:: ../sample_data/get_observation_fields_page1.json - :language: javascript + .. literalinclude:: ../sample_data/get_observation_fields.py Returns: Observation fields as a list of dicts """ - kwargs = check_deprecated_params(**kwargs) response = get( - "{base_url}/observation_fields.json".format(base_url=INAT_BASE_URL), - params=kwargs, + f'{INAT_BASE_URL}/observation_fields.json', + params=params, user_agent=user_agent, ) - return response.json() + response.raise_for_status() + + obs_fields = response.json() + obs_fields = convert_all_timestamps(obs_fields) + return obs_fields @document_request_params([docs._search_query]) -def get_all_observation_fields(**kwargs) -> ListResponse: +def get_all_observation_fields(**params) -> ListResponse: """ Like :py:func:`.get_observation_fields()`, but handles pagination for you. @@ -197,19 +159,19 @@ def get_all_observation_fields(**kwargs) -> ListResponse: >>> get_all_observation_fields(q='number of') Returns: - Observation fields as a list of dicts. Response format is the same as the inner - "results" object returned by :py:func:`.get_observation_fields`. + Observation fields as a list of dicts. Response format is the same as the inner\ + 'results' object returned by :py:func:`.get_observation_fields`. """ results = [] # type: List[Dict[str, Any]] page = 1 while True: - r = get_observation_fields(page=page, **kwargs) + response = get_observation_fields(page=page, **params) - if not r: + if not response: return results - results += r + results += response page += 1 sleep(THROTTLING_DELAY) @@ -259,21 +221,19 @@ def put_observation_field_values( user_agent: A user-agent string that will be passed to iNaturalist. Returns: - The nwely updated field value record + The newly updated field value record """ payload = { - "observation_field_value": { - "observation_id": observation_id, - "observation_field_id": observation_field_id, - "value": value, + 'observation_field_value': { + 'observation_id': observation_id, + 'observation_field_id': observation_field_id, + 'value': value, } } response = put( - "{base_url}/observation_field_values/{id}".format( - base_url=INAT_BASE_URL, id=observation_field_id - ), + f'{INAT_BASE_URL}/observation_field_values/{observation_field_id}', access_token=access_token, user_agent=user_agent, json=payload, @@ -283,29 +243,15 @@ def put_observation_field_values( return response.json() -def create_observations(params: RequestParams = None, **kwargs): - """Create a new observation. - Note: Creating multiple observations sould be possible according to the docs, but it does not - appear to work. - """ - warn( - "create_observations() has been deprecated, as creating multiple observations is not " - "currently functional. Please use create_observation() instead." - ) - create_observation(params, **kwargs) - - # TODO: more thorough usage example -@document_request_params([docs._legacy_params, docs._access_token, docs._create_observation]) -def create_observation( - params: RequestParams = None, access_token: str = None, user_agent: str = None, **kwargs -) -> ListResponse: +@document_request_params([docs._access_token, docs._create_observation]) +def create_observation(access_token: str = None, user_agent: str = None, **params) -> ListResponse: """Create a new observation. **API reference:** https://www.inaturalist.org/pages/api+reference#post-observations Example: - >>> token = get_access_token('...') + >>> token = get_access_token() >>> create_observation( >>> access_token=token, >>> species_guess='Pieris rapae', @@ -335,17 +281,16 @@ def create_observation( ``response`` attribute gives more details about the errors. """ # Accept either top-level params (like most other endpoints) - # or nested {"observation": params} (like the iNat API accepts directly) - if "observation" in kwargs: - kwargs.update(kwargs.pop("observation")) - kwargs = check_deprecated_params(params, **kwargs) - kwargs = convert_observation_fields(kwargs) - if "local_photos" in kwargs: - kwargs["local_photos"] = ensure_file_objs(kwargs["local_photos"]) + # or nested {'observation': params} (like the iNat API accepts directly) + if 'observation' in params: + params.update(params.pop('observation')) + params = convert_observation_fields(params) + if 'local_photos' in params: + params['local_photos'] = ensure_file_objs(params['local_photos']) response = post( - url="{base_url}/observations.json".format(base_url=INAT_BASE_URL), - json={"observation": kwargs}, + url=f'{INAT_BASE_URL}/observations.json', + json={'observation': params}, access_token=access_token, user_agent=user_agent, ) @@ -356,7 +301,6 @@ def create_observation( @document_request_params( [ docs._observation_id, - docs._legacy_params, docs._access_token, docs._create_observation, docs._update_observation, @@ -364,10 +308,9 @@ def create_observation( ) def update_observation( observation_id: int, - params: RequestParams = None, access_token: str = None, user_agent: str = None, - **kwargs + **params, ) -> ListResponse: """ Update a single observation. @@ -383,11 +326,11 @@ def update_observation( Example: - >>> token = get_access_token('...') + >>> token = get_access_token() >>> update_observation( >>> 17932425, >>> access_token=token, - >>> description="updated description!", + >>> description='updated description!', >>> ) .. admonition:: Example Response @@ -405,23 +348,22 @@ def update_observation( """ # Accept either top-level params (like most other endpoints) # or nested params (like the iNat API actually accepts) - if "observation" in kwargs: - kwargs.update(kwargs.pop("observation")) - kwargs = check_deprecated_params(params, **kwargs) - kwargs = convert_observation_fields(kwargs) - if "local_photos" in kwargs: - kwargs["local_photos"] = ensure_file_objs(kwargs["local_photos"]) + if 'observation' in params: + params.update(params.pop('observation')) + params = convert_observation_fields(params) + if 'local_photos' in params: + params['local_photos'] = ensure_file_objs(params['local_photos']) # This is the one Boolean parameter that's specified as an int, for some reason. # Also, set it to True if not specified, which seems like much saner default behavior. - if "ignore_photos" in kwargs: - kwargs["ignore_photos"] = int(kwargs["ignore_photos"]) + if 'ignore_photos' in params: + params['ignore_photos'] = int(params['ignore_photos']) else: - kwargs["ignore_photos"] = 1 + params['ignore_photos'] = 1 response = put( - url="{base_url}/observations/{id}.json".format(base_url=INAT_BASE_URL, id=observation_id), - json={"observation": kwargs}, + url=f'{INAT_BASE_URL}/observations/{observation_id}.json', + json={'observation': params}, access_token=access_token, user_agent=user_agent, ) @@ -441,7 +383,7 @@ def add_photo_to_observation( Example: - >>> token = get_access_token('...') + >>> token = get_access_token() >>> add_photo_to_observation( >>> 1234, >>> '~/observation_photos/2020_09_01_14003156.jpg', @@ -464,10 +406,10 @@ def add_photo_to_observation( Information about the newly created photo """ response = post( - url="{base_url}/observation_photos".format(base_url=INAT_BASE_URL), + url=f'{INAT_BASE_URL}/observation_photos', access_token=access_token, - data={"observation_photo[observation_id]": observation_id}, - files={"file": ensure_file_obj(photo)}, + data={'observation_photo[observation_id]': observation_id}, + files={'file': ensure_file_obj(photo)}, user_agent=user_agent, ) @@ -483,7 +425,7 @@ def delete_observation(observation_id: int, access_token: str = None, user_agent Example: - >>> token = get_access_token('...') + >>> token = get_access_token() >>> delete_observation(17932425, token) Returns: @@ -494,10 +436,10 @@ def delete_observation(observation_id: int, access_token: str = None, user_agent :py:exc:`requests.HTTPError` (403) if the observation belongs to another user """ response = delete( - url="{base_url}/observations/{id}.json".format(base_url=INAT_BASE_URL, id=observation_id), + url=f'{INAT_BASE_URL}/observations/{observation_id}.json', access_token=access_token, user_agent=user_agent, - headers={"Content-type": "application/json"}, + headers={'Content-type': 'application/json'}, ) if response.status_code == 404: raise ObservationNotFound diff --git a/pyproject.toml b/pyproject.toml index f0b73593..2ef4de89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,8 @@ +[build-system] +requires = ['setuptools', 'wheel'] + [tool.black] +skip-string-normalization = true line-length = 100 [tool.coverage.html] @@ -7,3 +11,11 @@ directory = 'test-reports' [tool.coverage.run] branch = true source = ['pyinaturalist'] + +[tool.isort] +profile = "black" +line_length = 100 +skip_gitignore = true +known_first_party = ['test'] +# Things that are common enough they may as well be grouped with stdlib imports +extra_standard_library = ['dateutil', 'pytest', 'setuptools'] diff --git a/setup.cfg b/setup.cfg index 5457b4fd..1daad340 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,30 +5,32 @@ long_description_content_type = text/markdown keywords = pyinaturalist, iNaturalist license = MIT license license_files = LICENSE +python_requires = '>=3.6', classifiers = - Development Status :: 3 - Alpha + Development Status :: 4 - Beta Intended Audience :: Developers License :: OSI Approved :: MIT License Natural Language :: English Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 -# Tell mypy to ignore external libraries without type annotations -[mypy-forge] -ignore_missing_imports = True +[flake8] +max_line_length = 120 +exclude = __pycache__,.tox,build,dist,test,venv +# Defer these rules to black: +ignore = + E225 # missing whitespace around operator + E501 # line too long + W503 # line break before binary operator + W504 # line break after binary operator -[mypy-pytest] -ignore_missing_imports = True +# Show 7 lines of context in debugger +[ipdb] +context = 7 -[mypy-requests_mock] -ignore_missing_imports = True - -[mypy-setuptools] +# Tell mypy to ignore external libraries without type annotations +[mypy] ignore_missing_imports = True - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py index 711ddf94..a842bc47 100644 --- a/setup.py +++ b/setup.py @@ -1,45 +1,55 @@ #!/usr/bin/env python from itertools import chain -from setuptools import setup, find_packages +from setuptools import find_packages, setup + from pyinaturalist import __version__ # These package categories allow tox and build environments to install only what they need extras_require = { # Packages used for CI jobs - "build": ["coveralls", "tox-travis"], + 'build': ['coveralls', 'twine', 'wheel'], # Packages used for documentation builds - "docs": [ - "m2r2", - "Sphinx~=3.2.1", - "sphinx-autodoc-typehints", - "sphinx-automodapi", - "sphinx-rtd-theme", - "sphinxcontrib-apidoc", + 'docs': [ + 'm2r2', + 'Sphinx~=3.2.1', + 'sphinx-autodoc-typehints', + 'sphinx-automodapi', + 'sphinx-rtd-theme', + 'sphinxcontrib-apidoc', ], # Packages used for testing both locally and in CI jobs - "test": [ - "black==20.8b1", - "flake8", - "mypy", - "pytest>=5.0", - "pytest-cov", - "requests-mock>=1.7", - "tox>=3.15", + 'test': [ + 'black==20.8b1', + 'flake8', + 'isort', + 'mypy', + 'pre-commit', + 'pytest>=5.0', + 'pytest-cov', + 'requests-mock>=1.7', + 'tox>=3.15', ], + # Packages used only for local debugging + 'debug': ['ipython', 'ipdb'], } # All development/testing packages combined -extras_require["dev"] = list(chain.from_iterable(extras_require.values())) +extras_require['dev'] = list(chain.from_iterable(extras_require.values())) setup( - name="pyinaturalist", + name='pyinaturalist', version=__version__, - author="Nicolas Noé", - author_email="nicolas@niconoe.eu", - url="https://github.com/niconoe/pyinaturalist", + author='Nicolas Noé', + author_email='nicolas@niconoe.eu', + url='https://github.com/niconoe/pyinaturalist', packages=find_packages(), include_package_data=True, - install_requires=["python-dateutil>=2.0", "python-forge", "requests>=2.24.0"], + install_requires=[ + 'keyring~=21.4.0', + 'python-dateutil>=2.0', + 'python-forge', + 'requests~=2.25.0', + ], extras_require=extras_require, zip_safe=False, ) diff --git a/test/conftest.py b/test/conftest.py index b3e156e4..e5cc88ce 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -4,15 +4,39 @@ """ import json import logging +import os import re -from inspect import getmembers, isfunction, signature, Parameter +from inspect import Parameter, getmembers, isfunction, signature from os.path import abspath, dirname, join from unittest.mock import MagicMock -HTTP_FUNC_PATTERN = re.compile(r"(get|put|post|delete)_.+") -SAMPLE_DATA_DIR = abspath(join(dirname(__file__), "sample_data")) +# If ipdb is installed, register it as the default debugger +try: + import ipdb # noqa: F401 + + os.environ['PYTHONBREAKPOINT'] = 'ipdb.set_trace' +except ImportError: + pass + + +HTTP_FUNC_PATTERN = re.compile(r'(get|put|post|delete)_.+') +SAMPLE_DATA_DIR = abspath(join(dirname(__file__), 'sample_data')) + +MOCK_CREDS_ENV = { + 'INAT_USERNAME': 'valid_username', + 'INAT_PASSWORD': 'valid_password', + 'INAT_APP_ID': 'valid_app_id', + 'INAT_APP_SECRET': 'valid_app_secret', +} +MOCK_CREDS_OAUTH = { + 'username': 'valid_username', + 'password': 'valid_password', + 'client_id': 'valid_app_id', + 'client_secret': 'valid_app_secret', +} + # Enable logging for urllib and other external loggers -logging.basicConfig(level="INFO") +logging.basicConfig(level='INFO') def get_module_functions(module): @@ -52,8 +76,8 @@ def _sample_data_path(filename): def load_sample_data(filename): - with open(_sample_data_path(filename), encoding="utf-8") as fh: - if filename.endswith("json"): + with open(_sample_data_path(filename), encoding='utf-8') as fh: + if filename.endswith('json'): return json.load(fh) else: return fh.read() diff --git a/test/manual_tests/obs_crud_test.py b/test/manual_tests/obs_crud_test.py index 9a4dbcc2..d9e5ffb9 100644 --- a/test/manual_tests/obs_crud_test.py +++ b/test/manual_tests/obs_crud_test.py @@ -1,19 +1,20 @@ #!/usr/bin/env python -"""A semi-automated script used to test all observation CRUD endpoints. -Must provide iNat credentials via environment variables. Usage example: +"""A semi-automated script used to test all observation CRUD endpoints. Must provide iNat +credentials via environment variables. See :py:func:`.get_access_token` for details. +Usage example: ``` -export INAT_USERNAME="" -export INAT_PASSWORD="" -export INAT_APP_ID="" -export INAT_APP_SECRET="" +export INAT_USERNAME='' +export INAT_PASSWORD='' +export INAT_APP_ID='' +export INAT_APP_SECRET='' python test/manual_tests/obs_crud_test.py ``` """ from datetime import datetime -from os import getenv from os.path import join from pprint import pprint +from pyinaturalist.node_api import get_observation from pyinaturalist.rest_api import ( add_photo_to_observation, create_observation, @@ -22,46 +23,39 @@ put_observation_field_values, update_observation, ) -from pyinaturalist.node_api import get_observation from test.conftest import SAMPLE_DATA_DIR -SAMPLE_PHOTO = join(SAMPLE_DATA_DIR, "obs_image.jpg") +SAMPLE_PHOTO = join(SAMPLE_DATA_DIR, 'obs_image.jpg') def run_observation_crud_test(): - # TODO: Built-in support for using envars for auth instead of function args might be useful - token = get_access_token( - username=getenv("INAT_USERNAME"), - password=getenv("INAT_PASSWORD"), - app_id=getenv("INAT_APP_ID"), - app_secret=getenv("INAT_APP_SECRET"), - ) - print("Received access token") + token = get_access_token() + print('Received access token') test_obs_id = create_test_obs(token) update_test_obs(test_obs_id, token) delete_test_obs(test_obs_id, token) - print("Test complete") + print('Test complete') def create_test_obs(token): response = create_observation( taxon_id=54327, observed_on_string=datetime.now().isoformat(), - description="This is a test observation used by pyinaturalist, and will be deleted shortly.", - tag_list="wasp, Belgium", + description='This is a test observation used by pyinaturalist, and will be deleted shortly.', + tag_list='wasp, Belgium', latitude=50.647143, longitude=4.360216, positional_accuracy=50, - geoprivacy="open", + geoprivacy='open', access_token=token, observation_fields={297: 1}, ) - test_obs_id = response[0]["id"] - print("Created new observation: {}".format(test_obs_id)) + test_obs_id = response[0]['id'] + print(f'Created new observation: {test_obs_id}') obs = get_observation(test_obs_id) - print("Fetched new observation:") + print('Fetched new observation:') pprint(obs, indent=2) return test_obs_id @@ -72,38 +66,39 @@ def update_test_obs(test_obs_id, token): photo=SAMPLE_PHOTO, access_token=token, ) - photo_id = response.get("photo").get("id") + photo_id = response.get('photo').get('id') assert photo_id - print("Added photo to observation: {}".format(photo_id)) + print(f'Added photo to observation: {photo_id}') # pprint(response, indent=2) response = update_observation( test_obs_id, - geoprivacy="obscured", + taxon_id=54327, + geoprivacy='obscured', ignore_photos=True, access_token=token, ) - new_geoprivacy = response[0]["geoprivacy"] - assert new_geoprivacy == "obscured" - print("Updated observation") + new_geoprivacy = response[0]['geoprivacy'] + assert new_geoprivacy == 'obscured' + print('Updated observation') # pprint(response, indent=2) - response = put_observation_field_values( - observation_id=test_obs_id, - observation_field_id=297, - value=2, - access_token=token, - ) - print("Added observation field value:") - pprint(response, indent=2) + # response = put_observation_field_values( + # observation_id=test_obs_id, + # observation_field_id=297, + # value=2, + # access_token=token, + # ) + # print('Added observation field value:') + # pprint(response, indent=2) def delete_test_obs(test_obs_id, token): response = delete_observation(test_obs_id, token) # Empty response is expected - print("Deleted observation") + print('Deleted observation') pprint(response, indent=2) -if __name__ == "__main__": +if __name__ == '__main__': run_observation_crud_test() diff --git a/test/sample_data/get_access_token.json b/test/sample_data/get_access_token.json new file mode 100644 index 00000000..0d852f49 --- /dev/null +++ b/test/sample_data/get_access_token.json @@ -0,0 +1,5 @@ +{ + "access_token": "604e5df329b98eecd22bb0a84f88b68", + "token_type": "Bearer", + "scope": "write" +} diff --git a/test/sample_data/get_access_token_401.json b/test/sample_data/get_access_token_401.json new file mode 100644 index 00000000..5939427c --- /dev/null +++ b/test/sample_data/get_access_token_401.json @@ -0,0 +1,4 @@ +{ + "error": "invalid_client", + "error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method." +} diff --git a/test/sample_data/get_all_observation_identifiers_ex_results.json b/test/sample_data/get_all_observation_identifiers_ex_results.json new file mode 100644 index 00000000..5cdc703c --- /dev/null +++ b/test/sample_data/get_all_observation_identifiers_ex_results.json @@ -0,0 +1,383 @@ +[ + { + "user_id": 15723, + "observation_count": 27, + "species_count": 6, + "user": { + "id": 15723, + "login": "reallifeecology", + "spam": false, + "suspended": false, + "created_at": "2013-04-27T19:32:15+00:00", + "login_autocomplete": "reallifeecology", + "login_exact": "reallifeecology", + "name": "Jonathan (JC) Carpenter", + "name_autocomplete": "Jonathan (JC) Carpenter", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/15723/thumb.jpg?1475535710", + "observations_count": 36365, + "identifications_count": 2086, + "journal_posts_count": 3, + "activity_count": 38454, + "species_count": 5740, + "universal_search_rank": 36365, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/15723/medium.jpg?1475535710" + } + }, + { + "user_id": 17201, + "observation_count": 18, + "species_count": 6, + "user": { + "id": 17201, + "login": "matthewherron", + "spam": false, + "suspended": false, + "created_at": "2013-05-29T02:13:48+00:00", + "login_autocomplete": "matthewherron", + "login_exact": "matthewherron", + "name": "Matthew Herron", + "name_autocomplete": "Matthew Herron", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/17201/thumb.jpeg?1475536451", + "observations_count": 7694, + "identifications_count": 1467, + "journal_posts_count": 0, + "activity_count": 9161, + "species_count": 2195, + "universal_search_rank": 7694, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/17201/medium.jpeg?1475536451" + } + }, + { + "user_id": 176684, + "observation_count": 17, + "species_count": 3, + "user": { + "id": 176684, + "login": "gmontgomery", + "spam": false, + "suspended": false, + "created_at": "2016-02-06T21:58:36+00:00", + "login_autocomplete": "gmontgomery", + "login_exact": "gmontgomery", + "name": "Graham Montgomery", + "name_autocomplete": "Graham Montgomery", + "orcid": "https://orcid.org/0000-0002-8217-8800", + "icon": "https://static.inaturalist.org/attachments/users/icons/176684/thumb.jpeg?1600827496", + "observations_count": 7986, + "identifications_count": 4072, + "journal_posts_count": 0, + "activity_count": 12058, + "species_count": 4146, + "universal_search_rank": 7986, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/176684/medium.jpeg?1600827496" + } + }, + { + "user_id": 53153, + "observation_count": 15, + "species_count": 2, + "user": { + "id": 53153, + "login": "willkuhn", + "spam": false, + "suspended": false, + "created_at": "2014-09-15T18:38:59+00:00", + "login_autocomplete": "willkuhn", + "login_exact": "willkuhn", + "name": "Will Kuhn", + "name_autocomplete": "Will Kuhn", + "orcid": "https://orcid.org/0000-0002-5506-6379", + "icon": "https://static.inaturalist.org/attachments/users/icons/53153/thumb.jpeg?1606488424", + "observations_count": 2945, + "identifications_count": 3895, + "journal_posts_count": 0, + "activity_count": 6840, + "species_count": 1230, + "universal_search_rank": 2945, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/53153/medium.jpeg?1606488424" + } + }, + { + "user_id": 2541814, + "observation_count": 14, + "species_count": 3, + "user": { + "id": 2541814, + "login": "leahncrow", + "spam": false, + "suspended": false, + "created_at": "2020-01-22T13:13:37+00:00", + "login_autocomplete": "leahncrow", + "login_exact": "leahncrow", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2541814/thumb.jpg?1579701006", + "observations_count": 128, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 128, + "species_count": 94, + "universal_search_rank": 128, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2541814/medium.jpg?1579701006" + } + }, + { + "user_id": 80369, + "observation_count": 12, + "species_count": 5, + "user": { + "id": 80369, + "login": "tremontinstitute", + "spam": false, + "suspended": false, + "created_at": "2015-03-04T21:01:27+00:00", + "login_autocomplete": "tremontinstitute", + "login_exact": "tremontinstitute", + "name": "Great Smoky Mountains Institute at Tremont", + "name_autocomplete": "Great Smoky Mountains Institute at Tremont", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/80369/thumb.jpg?1475545560", + "observations_count": 230, + "identifications_count": 53, + "journal_posts_count": 0, + "activity_count": 283, + "universal_search_rank": 230, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/80369/medium.jpg?1475545560" + } + }, + { + "user_id": 906019, + "observation_count": 11, + "species_count": 1, + "user": { + "id": 906019, + "login": "akfishmom", + "spam": false, + "suspended": false, + "created_at": "2018-04-26T20:56:39+00:00", + "login_autocomplete": "akfishmom", + "login_exact": "akfishmom", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": null, + "observations_count": 822, + "identifications_count": 6, + "journal_posts_count": 0, + "activity_count": 828, + "species_count": 322, + "universal_search_rank": 822, + "roles": [], + "site_id": 1, + "icon_url": null + } + }, + { + "user_id": 1206197, + "observation_count": 6, + "species_count": 0, + "user": { + "id": 1206197, + "login": "aclaborn", + "spam": false, + "suspended": false, + "created_at": "2018-09-13T16:53:51+00:00", + "login_autocomplete": "aclaborn", + "login_exact": "aclaborn", + "name": "Anna Claborn", + "name_autocomplete": "Anna Claborn", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1206197/thumb.jpg?1536866709", + "observations_count": 1519, + "identifications_count": 31, + "journal_posts_count": 0, + "activity_count": 1550, + "species_count": 605, + "universal_search_rank": 1519, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1206197/medium.jpg?1536866709" + } + }, + { + "user_id": 1019810, + "observation_count": 6, + "species_count": 2, + "user": { + "id": 1019810, + "login": "jduffy", + "spam": false, + "suspended": false, + "created_at": "2018-06-11T15:58:21+00:00", + "login_autocomplete": "jduffy", + "login_exact": "jduffy", + "name": "Jim", + "name_autocomplete": "Jim", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1019810/thumb.jpg?1551496934", + "observations_count": 823, + "identifications_count": 2, + "journal_posts_count": 0, + "activity_count": 825, + "species_count": 313, + "universal_search_rank": 823, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1019810/medium.jpg?1551496934" + } + }, + { + "user_id": 1000, + "observation_count": 6, + "species_count": 2, + "user": { + "id": 1000, + "login": "muir", + "spam": false, + "suspended": false, + "created_at": "2011-04-15T01:16:25+00:00", + "login_autocomplete": "muir", + "login_exact": "muir", + "name": "Matt Muir", + "name_autocomplete": "Matt Muir", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1000/thumb.jpg?1475527774", + "observations_count": 20693, + "identifications_count": 3867, + "journal_posts_count": 15, + "activity_count": 24575, + "species_count": 5349, + "universal_search_rank": 20693, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1000/medium.jpg?1475527774" + } + }, + { + "user_id": 776481, + "observation_count": 5, + "species_count": 1, + "user": { + "id": 776481, + "login": "heidi59", + "spam": false, + "suspended": false, + "created_at": "2018-02-28T04:20:06+00:00", + "login_autocomplete": "heidi59", + "login_exact": "heidi59", + "name": "Heidi Large", + "name_autocomplete": "Heidi Large", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/776481/thumb.jpg?1590966382", + "observations_count": 215, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 215, + "species_count": 146, + "universal_search_rank": 215, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/776481/medium.jpg?1590966382" + } + }, + { + "user_id": 638821, + "observation_count": 5, + "species_count": 3, + "user": { + "id": 638821, + "login": "amandahendricks", + "spam": false, + "suspended": false, + "created_at": "2017-09-21T17:15:06+00:00", + "login_autocomplete": "amandahendricks", + "login_exact": "amandahendricks", + "name": "Amanda Hendricks", + "name_autocomplete": "Amanda Hendricks", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/638821/thumb.jpeg?1506014105", + "observations_count": 258, + "identifications_count": 4, + "journal_posts_count": 2, + "activity_count": 264, + "species_count": 178, + "universal_search_rank": 258, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/638821/medium.jpeg?1506014105" + } + }, + { + "user_id": 634385, + "observation_count": 5, + "species_count": 2, + "user": { + "id": 634385, + "login": "moosegoose47", + "spam": false, + "suspended": false, + "created_at": "2017-09-18T21:53:52+00:00", + "login_autocomplete": "moosegoose47", + "login_exact": "moosegoose47", + "name": "Austin Waag", + "name_autocomplete": "Austin Waag", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/634385/thumb.jpg?1527545368", + "observations_count": 1466, + "identifications_count": 302, + "journal_posts_count": 0, + "activity_count": 1768, + "species_count": 859, + "universal_search_rank": 1466, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/634385/medium.jpg?1527545368" + } + }, + { + "user_id": 3361929, + "observation_count": 4, + "species_count": 2, + "user": { + "id": 3361929, + "login": "maraboustork", + "spam": false, + "suspended": false, + "created_at": "2020-07-21T14:45:46+00:00", + "login_autocomplete": "maraboustork", + "login_exact": "maraboustork", + "name": "Alice Thompson", + "name_autocomplete": "Alice Thompson", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/3361929/thumb.jpg?1596679334", + "observations_count": 701, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 701, + "species_count": 389, + "universal_search_rank": 701, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/3361929/medium.jpg?1596679334" + } + } +] diff --git a/test/sample_data/get_all_observation_observers_ex_results.json b/test/sample_data/get_all_observation_observers_ex_results.json new file mode 100644 index 00000000..92ace1bb --- /dev/null +++ b/test/sample_data/get_all_observation_observers_ex_results.json @@ -0,0 +1,545 @@ +[ + { + "user_id": 15723, + "observation_count": 27, + "species_count": 6, + "user": { + "id": 15723, + "login": "reallifeecology", + "spam": false, + "suspended": false, + "created_at": "2013-04-27T19:32:15+00:00", + "login_autocomplete": "reallifeecology", + "login_exact": "reallifeecology", + "name": "Jonathan (JC) Carpenter", + "name_autocomplete": "Jonathan (JC) Carpenter", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/15723/thumb.jpg?1475535710", + "observations_count": 36365, + "identifications_count": 2086, + "journal_posts_count": 3, + "activity_count": 38454, + "species_count": 5740, + "universal_search_rank": 36365, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/15723/medium.jpg?1475535710" + } + }, + { + "user_id": 17201, + "observation_count": 18, + "species_count": 6, + "user": { + "id": 17201, + "login": "matthewherron", + "spam": false, + "suspended": false, + "created_at": "2013-05-29T02:13:48+00:00", + "login_autocomplete": "matthewherron", + "login_exact": "matthewherron", + "name": "Matthew Herron", + "name_autocomplete": "Matthew Herron", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/17201/thumb.jpeg?1475536451", + "observations_count": 7694, + "identifications_count": 1467, + "journal_posts_count": 0, + "activity_count": 9161, + "species_count": 2195, + "universal_search_rank": 7694, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/17201/medium.jpeg?1475536451" + } + }, + { + "user_id": 176684, + "observation_count": 17, + "species_count": 3, + "user": { + "id": 176684, + "login": "gmontgomery", + "spam": false, + "suspended": false, + "created_at": "2016-02-06T21:58:36+00:00", + "login_autocomplete": "gmontgomery", + "login_exact": "gmontgomery", + "name": "Graham Montgomery", + "name_autocomplete": "Graham Montgomery", + "orcid": "https://orcid.org/0000-0002-8217-8800", + "icon": "https://static.inaturalist.org/attachments/users/icons/176684/thumb.jpeg?1600827496", + "observations_count": 7986, + "identifications_count": 4072, + "journal_posts_count": 0, + "activity_count": 12058, + "species_count": 4146, + "universal_search_rank": 7986, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/176684/medium.jpeg?1600827496" + } + }, + { + "user_id": 53153, + "observation_count": 15, + "species_count": 2, + "user": { + "id": 53153, + "login": "willkuhn", + "spam": false, + "suspended": false, + "created_at": "2014-09-15T18:38:59+00:00", + "login_autocomplete": "willkuhn", + "login_exact": "willkuhn", + "name": "Will Kuhn", + "name_autocomplete": "Will Kuhn", + "orcid": "https://orcid.org/0000-0002-5506-6379", + "icon": "https://static.inaturalist.org/attachments/users/icons/53153/thumb.jpeg?1606488424", + "observations_count": 2945, + "identifications_count": 3895, + "journal_posts_count": 0, + "activity_count": 6840, + "species_count": 1230, + "universal_search_rank": 2945, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/53153/medium.jpeg?1606488424" + } + }, + { + "user_id": 2541814, + "observation_count": 14, + "species_count": 3, + "user": { + "id": 2541814, + "login": "leahncrow", + "spam": false, + "suspended": false, + "created_at": "2020-01-22T13:13:37+00:00", + "login_autocomplete": "leahncrow", + "login_exact": "leahncrow", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2541814/thumb.jpg?1579701006", + "observations_count": 128, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 128, + "species_count": 94, + "universal_search_rank": 128, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2541814/medium.jpg?1579701006" + } + }, + { + "user_id": 80369, + "observation_count": 12, + "species_count": 5, + "user": { + "id": 80369, + "login": "tremontinstitute", + "spam": false, + "suspended": false, + "created_at": "2015-03-04T21:01:27+00:00", + "login_autocomplete": "tremontinstitute", + "login_exact": "tremontinstitute", + "name": "Great Smoky Mountains Institute at Tremont", + "name_autocomplete": "Great Smoky Mountains Institute at Tremont", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/80369/thumb.jpg?1475545560", + "observations_count": 230, + "identifications_count": 53, + "journal_posts_count": 0, + "activity_count": 283, + "universal_search_rank": 230, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/80369/medium.jpg?1475545560" + } + }, + { + "user_id": 906019, + "observation_count": 11, + "species_count": 1, + "user": { + "id": 906019, + "login": "akfishmom", + "spam": false, + "suspended": false, + "created_at": "2018-04-26T20:56:39+00:00", + "login_autocomplete": "akfishmom", + "login_exact": "akfishmom", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": null, + "observations_count": 822, + "identifications_count": 6, + "journal_posts_count": 0, + "activity_count": 828, + "species_count": 322, + "universal_search_rank": 822, + "roles": [], + "site_id": 1, + "icon_url": null + } + }, + { + "user_id": 1206197, + "observation_count": 6, + "species_count": 0, + "user": { + "id": 1206197, + "login": "aclaborn", + "spam": false, + "suspended": false, + "created_at": "2018-09-13T16:53:51+00:00", + "login_autocomplete": "aclaborn", + "login_exact": "aclaborn", + "name": "Anna Claborn", + "name_autocomplete": "Anna Claborn", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1206197/thumb.jpg?1536866709", + "observations_count": 1519, + "identifications_count": 31, + "journal_posts_count": 0, + "activity_count": 1550, + "species_count": 605, + "universal_search_rank": 1519, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1206197/medium.jpg?1536866709" + } + }, + { + "user_id": 1019810, + "observation_count": 6, + "species_count": 2, + "user": { + "id": 1019810, + "login": "jduffy", + "spam": false, + "suspended": false, + "created_at": "2018-06-11T15:58:21+00:00", + "login_autocomplete": "jduffy", + "login_exact": "jduffy", + "name": "Jim", + "name_autocomplete": "Jim", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1019810/thumb.jpg?1551496934", + "observations_count": 823, + "identifications_count": 2, + "journal_posts_count": 0, + "activity_count": 825, + "species_count": 313, + "universal_search_rank": 823, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1019810/medium.jpg?1551496934" + } + }, + { + "user_id": 1000, + "observation_count": 6, + "species_count": 2, + "user": { + "id": 1000, + "login": "muir", + "spam": false, + "suspended": false, + "created_at": "2011-04-15T01:16:25+00:00", + "login_autocomplete": "muir", + "login_exact": "muir", + "name": "Matt Muir", + "name_autocomplete": "Matt Muir", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1000/thumb.jpg?1475527774", + "observations_count": 20693, + "identifications_count": 3867, + "journal_posts_count": 15, + "activity_count": 24575, + "species_count": 5349, + "universal_search_rank": 20693, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1000/medium.jpg?1475527774" + } + }, + { + "user_id": 776481, + "observation_count": 5, + "species_count": 1, + "user": { + "id": 776481, + "login": "heidi59", + "spam": false, + "suspended": false, + "created_at": "2018-02-28T04:20:06+00:00", + "login_autocomplete": "heidi59", + "login_exact": "heidi59", + "name": "Heidi Large", + "name_autocomplete": "Heidi Large", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/776481/thumb.jpg?1590966382", + "observations_count": 215, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 215, + "species_count": 146, + "universal_search_rank": 215, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/776481/medium.jpg?1590966382" + } + }, + { + "user_id": 638821, + "observation_count": 5, + "species_count": 3, + "user": { + "id": 638821, + "login": "amandahendricks", + "spam": false, + "suspended": false, + "created_at": "2017-09-21T17:15:06+00:00", + "login_autocomplete": "amandahendricks", + "login_exact": "amandahendricks", + "name": "Amanda Hendricks", + "name_autocomplete": "Amanda Hendricks", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/638821/thumb.jpeg?1506014105", + "observations_count": 258, + "identifications_count": 4, + "journal_posts_count": 2, + "activity_count": 264, + "species_count": 178, + "universal_search_rank": 258, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/638821/medium.jpeg?1506014105" + } + }, + { + "user_id": 634385, + "observation_count": 5, + "species_count": 2, + "user": { + "id": 634385, + "login": "moosegoose47", + "spam": false, + "suspended": false, + "created_at": "2017-09-18T21:53:52+00:00", + "login_autocomplete": "moosegoose47", + "login_exact": "moosegoose47", + "name": "Austin Waag", + "name_autocomplete": "Austin Waag", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/634385/thumb.jpg?1527545368", + "observations_count": 1466, + "identifications_count": 302, + "journal_posts_count": 0, + "activity_count": 1768, + "species_count": 859, + "universal_search_rank": 1466, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/634385/medium.jpg?1527545368" + } + }, + { + "user_id": 3361929, + "observation_count": 4, + "species_count": 2, + "user": { + "id": 3361929, + "login": "maraboustork", + "spam": false, + "suspended": false, + "created_at": "2020-07-21T14:45:46+00:00", + "login_autocomplete": "maraboustork", + "login_exact": "maraboustork", + "name": "Alice Thompson", + "name_autocomplete": "Alice Thompson", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/3361929/thumb.jpg?1596679334", + "observations_count": 701, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 701, + "species_count": 389, + "universal_search_rank": 701, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/3361929/medium.jpg?1596679334" + } + }, + { + "user_id": 3253036, + "observation_count": 4, + "species_count": 1, + "user": { + "id": 3253036, + "login": "angie391", + "spam": false, + "suspended": false, + "created_at": "2020-06-28T04:26:12+00:00", + "login_autocomplete": "angie391", + "login_exact": "angie391", + "name": "Angie Miller Cable", + "name_autocomplete": "Angie Miller Cable", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/3253036/thumb.jpeg?1593318372", + "observations_count": 86, + "identifications_count": 13, + "journal_posts_count": 0, + "activity_count": 99, + "species_count": 74, + "universal_search_rank": 86, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/3253036/medium.jpeg?1593318372" + } + }, + { + "user_id": 2404235, + "observation_count": 4, + "species_count": 2, + "user": { + "id": 2404235, + "login": "rsimkovich", + "spam": false, + "suspended": false, + "created_at": "2019-10-29T17:29:30+00:00", + "login_autocomplete": "rsimkovich", + "login_exact": "rsimkovich", + "name": "Rebekkah Gresh Simkovich", + "name_autocomplete": "Rebekkah Gresh Simkovich", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2404235/thumb.jpeg?1572370170", + "observations_count": 125, + "identifications_count": 12, + "journal_posts_count": 0, + "activity_count": 137, + "species_count": 104, + "universal_search_rank": 125, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2404235/medium.jpeg?1572370170" + } + }, + { + "user_id": 2148023, + "observation_count": 4, + "species_count": 0, + "user": { + "id": 2148023, + "login": "christopher514", + "spam": false, + "suspended": false, + "created_at": "2019-08-19T02:14:52+00:00", + "login_autocomplete": "christopher514", + "login_exact": "christopher514", + "name": "Christopher Gontarski", + "name_autocomplete": "Christopher Gontarski", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2148023/thumb.jpeg?1566180892", + "observations_count": 567, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 567, + "species_count": 287, + "universal_search_rank": 567, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2148023/medium.jpeg?1566180892" + } + }, + { + "user_id": 2051728, + "observation_count": 4, + "species_count": 2, + "user": { + "id": 2051728, + "login": "jennypansing", + "spam": false, + "suspended": false, + "created_at": "2019-07-26T13:02:30+00:00", + "login_autocomplete": "jennypansing", + "login_exact": "jennypansing", + "name": "Jenny Pansing", + "name_autocomplete": "Jenny Pansing", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2051728/thumb.jpg?1564367221", + "observations_count": 1835, + "identifications_count": 574, + "journal_posts_count": 0, + "activity_count": 2409, + "species_count": 645, + "universal_search_rank": 1835, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2051728/medium.jpg?1564367221" + } + }, + { + "user_id": 1359300, + "observation_count": 4, + "species_count": 1, + "user": { + "id": 1359300, + "login": "spyingnaturalist", + "spam": false, + "suspended": false, + "created_at": "2018-11-20T01:19:01+00:00", + "login_autocomplete": "spyingnaturalist", + "login_exact": "spyingnaturalist", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1359300/thumb.jpg?1552909758", + "observations_count": 8286, + "identifications_count": 5371, + "journal_posts_count": 0, + "activity_count": 13657, + "species_count": 2749, + "universal_search_rank": 8286, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1359300/medium.jpg?1552909758" + } + }, + { + "user_id": 1085809, + "observation_count": 4, + "species_count": 2, + "user": { + "id": 1085809, + "login": "anneturner", + "spam": false, + "suspended": false, + "created_at": "2018-07-14T16:12:14+00:00", + "login_autocomplete": "anneturner", + "login_exact": "anneturner", + "name": "Anne Turner", + "name_autocomplete": "Anne Turner", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1085809/thumb.jpeg?1555073435", + "observations_count": 1441, + "identifications_count": 125, + "journal_posts_count": 0, + "activity_count": 1566, + "species_count": 553, + "universal_search_rank": 1441, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1085809/medium.jpeg?1555073435" + } + } +] diff --git a/test/sample_data/get_controlled_terms.json b/test/sample_data/get_controlled_terms.json new file mode 100644 index 00000000..bd0a6b3b --- /dev/null +++ b/test/sample_data/get_controlled_terms.json @@ -0,0 +1,292 @@ +{ + "total_results": 4, + "page": 1, + "per_page": 30, + "results": [ + { + "id": 12, + "ontology_uri": "", + "uri": "", + "is_value": false, + "multivalued": true, + "uuid": "e997f5f1-1e22-4f1e-bb51-425d70d07b1a", + "values": [ + { + "id": 21, + "ontology_uri": "", + "uri": "", + "blocking": true, + "uuid": "943f8dac-764a-4736-86b5-2f419bbd368b", + "taxon_ids": [ + 47125 + ], + "label": "No Evidence of Flowering" + }, + { + "id": 13, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "84aa676e-2921-42cf-ae01-034a98425b5c", + "taxon_ids": [ + 47125 + ], + "label": "Flowering" + }, + { + "id": 14, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "503cef8b-770b-4c2d-a0b0-e0ea9f7df465", + "taxon_ids": [ + 47125 + ], + "label": "Fruiting" + }, + { + "id": 15, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "9c9d28a1-4ad3-408e-9f2b-30af803f5eb9", + "taxon_ids": [ + 47125 + ], + "label": "Flower Budding" + } + ], + "taxon_ids": [ + 47126 + ], + "label": "Plant Phenology" + }, + { + "id": 9, + "ontology_uri": "", + "uri": "", + "is_value": false, + "multivalued": false, + "uuid": "6a659546-c3aa-45ff-8f6c-0706f2361cfe", + "values": [ + { + "id": 10, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "3288a7bd-6ca7-413e-9890-5c4c541331de", + "label": "Female" + }, + { + "id": 11, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "207a8f33-ee23-44c9-88a5-e6a036a7d950", + "label": "Male" + }, + { + "id": 20, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "1e75ade7-f1c2-4ef7-994b-c5b07d52713e", + "label": "Cannot Be Determined" + } + ], + "excepted_taxon_ids": [ + 47170, + 333586, + 63081, + 47113 + ], + "label": "Sex" + }, + { + "id": 17, + "ontology_uri": "", + "uri": "", + "is_value": false, + "multivalued": false, + "values": [ + { + "id": 18, + "ontology_uri": "", + "uri": "", + "blocking": false, + "label": "Alive" + }, + { + "id": 19, + "ontology_uri": "", + "uri": "", + "blocking": false, + "label": "Dead" + }, + { + "id": 20, + "ontology_uri": "", + "uri": "", + "blocking": false, + "label": "Cannot Be Determined" + } + ], + "taxon_ids": [ + 1 + ], + "label": "Alive or Dead" + }, + { + "id": 1, + "ontology_uri": "", + "uri": "", + "is_value": false, + "multivalued": false, + "uuid": "f895e9ce-fb2d-4a63-a486-ac424f497e02", + "values": [ + { + "id": 2, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "48722062-360d-4b6e-b72c-129c7066b3fa", + "taxon_ids": [ + 1 + ], + "label": "Adult" + }, + { + "id": 3, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "b3b6e6d0-cbce-4886-bb2b-55bd99d44586", + "taxon_ids": [ + 47792, + 50190 + ], + "label": "Teneral" + }, + { + "id": 4, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "c883d7b5-4eb5-4ad1-8cbc-56f5e2555869", + "taxon_ids": [ + 47208, + 48763, + 49369, + 47157, + 47822, + 47201, + 62164, + 47864, + 83204, + 83202, + 47794 + ], + "label": "Pupa" + }, + { + "id": 5, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "0f0c03c2-4741-46db-a19a-7cac8a96d667", + "taxon_ids": [ + 47793, + 81769, + 56834, + 47744, + 47792, + 47651, + 47504, + 48011, + 48112, + 53795, + 47198, + 83187, + 83201, + 83200 + ], + "label": "Nymph" + }, + { + "id": 6, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "8ff477bd-0949-4a7a-9c67-64a8e3786876", + "taxon_ids": [ + 20978, + 47208, + 47822, + 47201, + 47157, + 49369, + 47864, + 48763, + 47794, + 83204, + 83202, + 62164 + ], + "label": "Larva" + }, + { + "id": 7, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "fe58aedb-4375-4096-a61f-1cd718cf319f", + "taxon_ids": [ + 43233, + 47120, + 47114, + 47459, + 85497, + 47178, + 20978, + 3, + 49231, + 47273, + 60450, + 49099, + 26036 + ], + "label": "Egg" + }, + { + "id": 8, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "3245d167-ffff-44ce-a366-cfc8f711d73f", + "taxon_ids": [ + 1 + ], + "excepted_taxon_ids": [ + 184884 + ], + "label": "Juvenile" + }, + { + "id": 16, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "6ff4f9f8-7bc0-4d50-bee5-7072d11a04ef", + "taxon_ids": [ + 48011 + ], + "label": "Subimago" + } + ], + "taxon_ids": [ + 1 + ], + "label": "Life Stage" + } + ] +} diff --git a/test/sample_data/get_controlled_terms_for_taxon.json b/test/sample_data/get_controlled_terms_for_taxon.json new file mode 100644 index 00000000..26039487 --- /dev/null +++ b/test/sample_data/get_controlled_terms_for_taxon.json @@ -0,0 +1,48 @@ +{ + "total_results": 1, + "page": 1, + "per_page": 30, + "results": [ + { + "id": 9, + "ontology_uri": "", + "uri": "", + "is_value": false, + "multivalued": false, + "uuid": "6a659546-c3aa-45ff-8f6c-0706f2361cfe", + "values": [ + { + "id": 10, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "3288a7bd-6ca7-413e-9890-5c4c541331de", + "label": "Female" + }, + { + "id": 11, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "207a8f33-ee23-44c9-88a5-e6a036a7d950", + "label": "Male" + }, + { + "id": 20, + "ontology_uri": "", + "uri": "", + "blocking": false, + "uuid": "1e75ade7-f1c2-4ef7-994b-c5b07d52713e", + "label": "Cannot Be Determined" + } + ], + "excepted_taxon_ids": [ + 47170, + 333586, + 63081, + 47113 + ], + "label": "Sex" + } + ] +} diff --git a/test/sample_data/get_observation.py b/test/sample_data/get_observation.py new file mode 100644 index 00000000..8a60caf6 --- /dev/null +++ b/test/sample_data/get_observation.py @@ -0,0 +1,328 @@ +from datetime import datetime +from dateutil.tz import tzoffset + +{ + 'quality_grade': 'research', + 'time_observed_at': '2018-09-05T14:06:00+02:00', + 'taxon_geoprivacy': None, + 'annotations': [ + { + 'user_id': 886482, + 'concatenated_attr_val': '1|2', + 'controlled_attribute_id': 1, + 'votes': [], + 'uuid': '14bbd34f-73f8-4b99-b591-8517913788a1', + 'vote_score': 0, + 'controlled_value_id': 2, + 'user': { + 'id': 886482, + 'login': 'niconoe', + 'spam': False, + 'suspended': False, + 'created_at': '2018-04-23T17:11:14+00:00', + 'login_autocomplete': 'niconoe', + 'login_exact': 'niconoe', + 'name': 'Nicolas Noé', + 'name_autocomplete': 'Nicolas Noé', + 'orcid': 'https://orcid.org/0000-0002-9503-4750', + 'icon': 'https://static.inaturalist.org/attachments/users/icons/886482/thumb.jpg?1529671435', + 'observations_count': 928, + 'identifications_count': 118, + 'journal_posts_count': 0, + 'activity_count': 1046, + 'species_count': 395, + 'universal_search_rank': 928, + 'roles': [], + 'site_id': 1, + 'icon_url': 'https://static.inaturalist.org/attachments/users/icons/886482/medium.jpg?1529671435', + }, + } + ], + 'uuid': '6448d03a-7f9a-4099-86aa-ca09a7740b00', + 'id': 16227955, + 'cached_votes_total': 0, + 'identifications_most_agree': True, + 'species_guess': 'Lixus bardanae', + 'identifications_most_disagree': False, + 'tags': [], + 'positional_accuracy': 23, + 'comments_count': 2, + 'site_id': 1, + 'license_code': 'cc0', + 'quality_metrics': [], + 'public_positional_accuracy': 23, + 'reviewed_by': [180811, 886482, 1226913], + 'oauth_application_id': None, + 'flags': [], + 'created_at': datetime(2018, 9, 5, 14, 31, 8, tzinfo=tzoffset(None, 7200)), + 'description': '', + 'project_ids_with_curator_id': [], + 'updated_at': datetime(2018, 9, 22, 19, 19, 27, tzinfo=tzoffset(None, 7200)), + 'sounds': [], + 'place_ids': [ + 7008, + 8657, + 14999, + 59614, + 67952, + 80627, + 81490, + 96372, + 96794, + 97391, + 97582, + 108692, + ], + 'captive': False, + 'taxon': { + 'is_active': True, + 'ancestry': '48460/1/47120/372739/47158/184884/47208/71130/372852/60473/48736/272543/507383/71157/1101667', + 'min_species_ancestry': '48460,1,47120,372739,47158,184884,47208,71130,372852,60473,48736,272543,507383,71157,1101667,493595', + 'endemic': False, + 'iconic_taxon_id': 47158, + 'min_species_taxon_id': 493595, + 'threatened': False, + 'rank_level': 10, + 'introduced': False, + 'native': False, + 'parent_id': 1101667, + 'name': 'Lixus bardanae', + 'rank': 'species', + 'extinct': False, + 'id': 493595, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47208, + 71130, + 372852, + 60473, + 48736, + 272543, + 507383, + 71157, + 1101667, + 493595, + ], + 'photos_locked': False, + 'taxon_schemes_count': 1, + 'wikipedia_url': None, + 'current_synonymous_taxon_ids': None, + 'created_at': '2016-04-25T22:35:20+00:00', + 'taxon_changes_count': 0, + 'complete_species_count': None, + 'universal_search_rank': 44, + 'observations_count': 44, + 'flag_counts': {'unresolved': 0, 'resolved': 0}, + 'atlas_id': None, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/69087254/square.jpg?1587876286', + 'attribution': '(c) Andy Van de Velde, all rights reserved', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/69087254/medium.jpg?1587876286', + 'id': 69087254, + 'license_code': None, + 'original_dimensions': {'width': 2048, 'height': 1638}, + 'url': 'https://static.inaturalist.org/photos/69087254/square.jpg?1587876286', + }, + 'iconic_taxon_name': 'Insecta', + }, + 'ident_taxon_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47208, + 71130, + 372852, + 60473, + 48736, + 272543, + 507383, + 71157, + 1101667, + 493595, + ], + 'outlinks': [{'source': 'GBIF', 'url': 'http://www.gbif.org/occurrence/1914197587'}], + 'faves_count': 0, + 'ofvs': [], + 'num_identification_agreements': 2, + 'preferences': {'prefers_community_taxon': None}, + 'comments': [ + { + 'moderator_actions': [], + 'hidden': False, + 'flags': [], + 'created_at': '2018-09-05T14:08:09.350Z', + 'id': 2071611, + 'created_at_details': { + 'date': '2018-09-05', + 'week': 36, + 'month': 9, + 'hour': 14, + 'year': 2018, + 'day': 5, + }, + 'body': 'suspect L. bardanae - but sits on Solanum (non-host)', + 'uuid': 'e6fc62ea-e22b-4427-bb65-1ba3bc591c77', + 'user': { + 'created_at': '2016-02-18T10:19:19+00:00', + 'id': 180811, + 'login': 'borisb', + 'spam': False, + 'suspended': False, + 'login_autocomplete': 'borisb', + 'login_exact': 'borisb', + 'name': '', + 'name_autocomplete': '', + 'orcid': None, + 'icon': None, + 'observations_count': 0, + 'identifications_count': 286253, + 'journal_posts_count': 0, + 'activity_count': 286253, + 'species_count': 0, + 'universal_search_rank': 0, + 'roles': ['curator'], + 'site_id': 1, + 'icon_url': None, + }, + }, + { + 'moderator_actions': [], + 'hidden': False, + 'flags': [], + 'created_at': '2018-09-05T16:03:40.937Z', + 'id': 2071896, + 'created_at_details': { + 'date': '2018-09-05', + 'week': 36, + 'month': 9, + 'hour': 16, + 'year': 2018, + 'day': 5, + }, + 'body': 'I now see: Bonus species on observation! You may make a duplicate . . . \n(Flea beetle Epitrix pubescens on Solanum bud) \n', + 'uuid': '4d401d20-1b08-464a-8287-351f5b57443e', + 'user': { + 'created_at': '2016-02-18T10:19:19+00:00', + 'id': 180811, + 'login': 'borisb', + 'spam': False, + 'suspended': False, + 'login_autocomplete': 'borisb', + 'login_exact': 'borisb', + 'name': '', + 'name_autocomplete': '', + 'orcid': None, + 'icon': None, + 'observations_count': 0, + 'identifications_count': 286253, + 'journal_posts_count': 0, + 'activity_count': 286253, + 'species_count': 0, + 'universal_search_rank': 0, + 'roles': ['curator'], + 'site_id': 1, + 'icon_url': None, + }, + }, + ], + 'map_scale': 17, + 'uri': 'https://www.inaturalist.org/observations/16227955', + 'project_ids': [], + 'community_taxon_id': 493595, + 'geojson': {'coordinates': [4.360086, 50.646894], 'type': 'Point'}, + 'owners_identification_from_vision': True, + 'identifications_count': 2, + 'obscured': False, + 'num_identification_disagreements': 0, + 'geoprivacy': None, + 'location': [50.646894, 4.360086], + 'votes': [], + 'spam': False, + 'user': { + 'site_id': 1, + 'created_at': '2018-04-23T17:11:14+00:00', + 'id': 886482, + 'login': 'niconoe', + 'spam': False, + 'suspended': False, + 'preferences': {}, + 'login_autocomplete': 'niconoe', + 'login_exact': 'niconoe', + 'name': 'Nicolas Noé', + 'name_autocomplete': 'Nicolas Noé', + 'orcid': 'https://orcid.org/0000-0002-9503-4750', + 'icon': 'https://static.inaturalist.org/attachments/users/icons/886482/thumb.jpg?1529671435', + 'observations_count': 928, + 'identifications_count': 118, + 'journal_posts_count': 0, + 'activity_count': 1046, + 'species_count': 395, + 'universal_search_rank': 928, + 'roles': [], + 'icon_url': 'https://static.inaturalist.org/attachments/users/icons/886482/medium.jpg?1529671435', + }, + 'mappable': True, + 'identifications_some_agree': True, + 'project_ids_without_curator_id': [], + 'place_guess': '54 rue des Badauds', + 'identifications': 'TRUNCATED', + 'project_observations': [], + 'photos': [ + { + 'id': 24355315, + 'license_code': 'cc-by', + 'url': 'https://static.inaturalist.org/photos/24355315/square.jpeg?1536150664', + 'attribution': '(c) Nicolas Noé, some rights reserved (CC BY)', + 'original_dimensions': {'width': 1445, 'height': 1057}, + 'flags': [], + }, + { + 'id': 24355313, + 'license_code': 'cc-by', + 'url': 'https://static.inaturalist.org/photos/24355313/square.jpeg?1536150659', + 'attribution': '(c) Nicolas Noé, some rights reserved (CC BY)', + 'original_dimensions': {'width': 2048, 'height': 1364}, + 'flags': [], + }, + ], + 'observation_photos': [ + { + 'id': 22080796, + 'position': 0, + 'uuid': '76b54495-3497-4e96-b07c-7ad346939a02', + 'photo': { + 'id': 24355315, + 'license_code': 'cc-by', + 'url': 'https://static.inaturalist.org/photos/24355315/square.jpeg?1536150664', + 'attribution': '(c) Nicolas Noé, some rights reserved (CC BY)', + 'original_dimensions': {'width': 1445, 'height': 1057}, + 'flags': [], + }, + }, + { + 'id': 22080797, + 'position': 1, + 'uuid': '06b90a88-d98d-4447-b14c-5d354d2d68d1', + 'photo': { + 'id': 24355313, + 'license_code': 'cc-by', + 'url': 'https://static.inaturalist.org/photos/24355313/square.jpeg?1536150659', + 'attribution': '(c) Nicolas Noé, some rights reserved (CC BY)', + 'original_dimensions': {'width': 2048, 'height': 1364}, + 'flags': [], + }, + }, + ], + 'faves': [], + 'non_owner_ids': 'TRUNCATED', + 'observed_on': datetime(2018, 9, 5, 14, 6, tzinfo=tzoffset('Europe/Paris', 3600)), +} diff --git a/test/sample_data/get_observation_fields.py b/test/sample_data/get_observation_fields.py new file mode 100644 index 00000000..d3a76fa2 --- /dev/null +++ b/test/sample_data/get_observation_fields.py @@ -0,0 +1,395 @@ +from datetime import datetime +from dateutil.tz import tzutc + +[ + { + 'id': 12822, + 'name': 'Number of individuals (FNAI)', + 'datatype': 'numeric', + 'user_id': 465930, + 'description': 'Number of individuals you counted or estimated', + 'created_at': datetime(2020, 12, 7, 15, 22, 17, 165000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 29, 14, 30, 44, 266000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 5, + 'users_count': 4, + 'uuid': '13f481e7-0389-4c32-80af-44d471f92354', + }, + { + 'id': 12043, + 'name': 'WOW Number of individuals recorded', + 'datatype': 'numeric', + 'user_id': 3032127, + 'description': 'How many individual orchids did you observe?', + 'created_at': datetime(2020, 5, 24, 11, 0, 55, 54000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 26, 11, 42, 53, 385000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 2274, + 'users_count': 175, + 'uuid': '5441bb80-6bc8-49bd-948d-036fd3e57915', + }, + { + 'id': 11812, + 'name': 'Number of Individuals?', + 'datatype': 'numeric', + 'user_id': 785779, + 'description': 'How many individuals did you find?', + 'created_at': datetime(2020, 4, 7, 18, 41, 34, 883000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 4, 7, 18, 41, 34, 883000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': None, + 'users_count': None, + 'uuid': '5047f78e-4b89-47a1-b25d-7f4833a9957d', + }, + { + 'id': 11611, + 'name': 'Number of individuals heard', + 'datatype': 'text', + 'user_id': 394902, + 'description': 'If not visible, number of individuals that are heard and can be identified by sound, eg. amphibians, birds, etc', + 'created_at': datetime(2020, 2, 28, 14, 56, 36, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 5, 15, 31, 33, 772000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 5, + 'users_count': 4, + 'uuid': 'ee4863da-3a74-4a07-8292-71cc03e5c245', + }, + { + 'id': 10774, + 'name': 'Number of individuals observed (precise)', + 'datatype': 'numeric', + 'user_id': 618700, + 'description': '', + 'created_at': datetime(2019, 8, 7, 22, 15, 1, 913000, tzinfo=tzutc()), + 'updated_at': datetime(2019, 8, 7, 22, 15, 1, 913000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': None, + 'users_count': None, + 'uuid': '095b006d-253a-46df-aef2-b72258541a11', + }, + { + 'id': 10456, + 'name': 'WOWBETA Number of individuals recorded', + 'datatype': 'numeric', + 'user_id': 1591469, + 'description': 'How many individual orchids did you observe?', + 'created_at': datetime(2019, 5, 1, 5, 10, 53, 24000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 5, 24, 11, 0, 52, 967000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 3, + 'users_count': 2, + 'uuid': '57eec037-a17e-4902-a6ce-7e2f11065678', + }, + { + 'id': 10104, + 'name': 'Number of individuals spotted 8', + 'datatype': 'text', + 'user_id': 1451829, + 'description': '', + 'created_at': datetime(2019, 2, 20, 1, 2, 50, 952000, tzinfo=tzutc()), + 'updated_at': datetime(2019, 11, 17, 18, 31, 42, 103000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 2, + 'users_count': 2, + 'uuid': '672c3324-facc-4013-a980-3a777f742d11', + }, + { + 'id': 8231, + 'name': 'Number of Individuals in Patch', + 'datatype': 'text', + 'user_id': 19435, + 'description': 'Estimated number of individuals of the same species in the vicinity of inaturalist record', + 'created_at': datetime(2018, 2, 28, 22, 0, 48, 755000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 16, 20, 44, 53, 927000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 413, + 'users_count': 61, + 'uuid': '01660525-dfd9-4145-a7f0-a597d87ac431', + }, + { + 'id': 7836, + 'name': 'Number of Individuals Detected for the Observed Species', + 'datatype': 'numeric', + 'user_id': 732880, + 'description': "Record a separate observation for each species, along with a total species count for the same * 10m/30' * section of road and a differentiating letter for each co~occurring detection.", + 'created_at': datetime(2017, 12, 13, 15, 17, 39, 735000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 25, 1, 40, 29, 811000, tzinfo=tzutc()), + 'allowed_values': '1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|>20', + 'values_count': 538, + 'users_count': 2, + 'uuid': 'ed5403f0-e412-4de0-80fd-ece66f75a663', + }, + { + 'id': 6182, + 'name': 'Maximum number of individuals HEARD ONLY in the first 5 min', + 'datatype': 'numeric', + 'user_id': 423008, + 'description': 'Counts for just the first 5 min of your duration', + 'created_at': datetime(2017, 3, 28, 22, 34, 56, 45000, tzinfo=tzutc()), + 'updated_at': datetime(2018, 6, 13, 10, 35, 38, 439000, tzinfo=tzutc()), + 'allowed_values': '1|2|3', + 'values_count': 7, + 'users_count': 4, + 'uuid': 'ed1728cb-879b-4201-9869-9dd889511442', + }, + { + 'id': 6181, + 'name': 'Minimum number of individuals HEARD ONLY in the first 5 min', + 'datatype': 'numeric', + 'user_id': 423008, + 'description': 'Counts for just the first 5 min of your duration', + 'created_at': datetime(2017, 3, 28, 22, 34, 12, 315000, tzinfo=tzutc()), + 'updated_at': datetime(2018, 6, 13, 10, 35, 38, 199000, tzinfo=tzutc()), + 'allowed_values': '1|2|3', + 'values_count': 10, + 'users_count': 5, + 'uuid': '0299325a-c171-45a7-a25d-077cc0d9ad2a', + }, + { + 'id': 6112, + 'name': 'Number of individuals (accurate)', + 'datatype': 'numeric', + 'user_id': 423822, + 'description': 'Precise number of individuals seen.', + 'created_at': datetime(2017, 3, 15, 9, 56, 25, 162000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 10, 18, 16, 54, 37, 344000, tzinfo=tzutc()), + 'allowed_values': '1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|20-30|31-50|51-100|100+', + 'values_count': 106, + 'users_count': 5, + 'uuid': 'a76dbe87-fdc3-4af1-8aae-96641403f89f', + }, + { + 'id': 5465, + 'name': 'Number of individuals (estimate)', + 'datatype': 'numeric', + 'user_id': 53305, + 'description': '', + 'created_at': datetime(2016, 9, 29, 22, 45, 25, 149000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 4, 29, 20, 35, 47, 61000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 100, + 'users_count': 19, + 'uuid': 'f5c237c9-3f97-4d4e-ab23-5251f3b0a906', + }, + { + 'id': 4926, + 'name': 'Maximum number of individuals seen at one time during an hour', + 'datatype': 'numeric', + 'user_id': 1115, + 'description': 'This is a simple bird survey method that just asks for the count of the largest number of individuals of a species at a site *seen all at the same time* in an hour.', + 'created_at': datetime(2016, 6, 14, 0, 49, 1, 20000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 11, 22, 10, 52, 5, 968000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 33948, + 'users_count': 11, + 'uuid': '688bf36e-16f0-4484-b5de-5220a221216f', + }, + { + 'id': 3853, + 'name': 'Number of Individuals at this location', + 'datatype': 'numeric', + 'user_id': 152927, + 'description': 'Best estimate of the total number of individuals at this location', + 'created_at': datetime(2015, 12, 29, 18, 55, 25, 777000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 26, 3, 27, 2, 599000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 522, + 'users_count': 35, + 'uuid': 'ec5d2785-b8a1-4580-bc56-fd70074f7dc8', + }, + { + 'id': 3838, + 'name': 'Number of Individuals @ Siting', + 'datatype': 'numeric', + 'user_id': 152927, + 'description': 'Please state the number of individual(s) observed at the siting of the same species. Please do not round, and say the exact quantity of organism(s) at the siting.', + 'created_at': datetime(2015, 12, 24, 2, 52, 12, 973000, tzinfo=tzutc()), + 'updated_at': datetime(2018, 4, 22, 23, 23, 49, 771000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 62, + 'users_count': 2, + 'uuid': '7e0977e9-326c-46d9-b1ae-12f88b4dab6d', + }, + { + 'id': 3751, + 'name': 'Number of Individuals Found', + 'datatype': 'numeric', + 'user_id': 152927, + 'description': 'How many individuals found @ location?', + 'created_at': datetime(2015, 12, 6, 21, 30, 5, 125000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 11, 30, 18, 57, 14, 237000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 78, + 'users_count': 14, + 'uuid': '2bf3a9cf-7764-42bb-a962-2a5f91082101', + }, + { + 'id': 3102, + 'name': 'Total Number of Individuals - Census', + 'datatype': 'numeric', + 'user_id': 33986, + 'description': 'The number of individuals observed/detected. Estimates should use field below named ESTIMATE. Please enter 999 in this required field if using "ESTIMATE".', + 'created_at': datetime(2015, 8, 7, 23, 16, 34, 573000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 25, 5, 42, 15, 661000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 564, + 'users_count': 29, + 'uuid': '2ded7176-5a2d-4f23-bb32-c1d4c4c41ee4', + }, + { + 'id': 3081, + 'name': 'ESTIMATE: Total Number of Individuals', + 'datatype': 'text', + 'user_id': 33986, + 'description': 'number of plants in this occurence, if estimate is appropriate', + 'created_at': datetime(2015, 8, 1, 5, 41, 28, 332000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 10, 18, 16, 54, 37, 173000, tzinfo=tzutc()), + 'allowed_values': '1-3|4-10|11-30|31-100|101-300|301-1000|1001-3000|3001-10000|10001-30000|30001+', + 'values_count': 48, + 'users_count': 11, + 'uuid': '76309e72-0641-44a7-a095-b91f8ae189ba', + }, + { + 'id': 2891, + 'name': 'Monitoring_Plot Species Number of Individuals', + 'datatype': 'numeric', + 'user_id': 5302, + 'description': 'Total number of individuals of nominated species in sample plot', + 'created_at': datetime(2015, 6, 19, 7, 24, 20, 700000, tzinfo=tzutc()), + 'updated_at': datetime(2016, 6, 20, 19, 56, 7, 167000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 11, + 'users_count': 1, + 'uuid': '54275e78-1e5e-49a5-bc73-0c71a7aac328', + }, + { + 'id': 2866, + 'name': 'Number of Individuals Collected/Observed', + 'datatype': 'numeric', + 'user_id': 109343, + 'description': '', + 'created_at': datetime(2015, 6, 11, 19, 32, 2, 381000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 27, 19, 19, 55, 312000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 4754, + 'users_count': 35, + 'uuid': 'acc6b84c-6498-474e-b19c-6bc0f4eb7086', + }, + { + 'id': 2134, + 'name': 'Approximate Number of Individuals', + 'datatype': 'numeric', + 'user_id': 43371, + 'description': '', + 'created_at': datetime(2014, 12, 20, 14, 30, 43, 55000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 31, 19, 21, 9, 913000, tzinfo=tzutc()), + 'allowed_values': '1|2|3-5|5-10|10-20|20-50|50-100|100+', + 'values_count': 2184, + 'users_count': 65, + 'uuid': '30b46d87-bc5a-4d8a-bff6-4a62d412f942', + }, + { + 'id': 1924, + 'name': 'Number of individuals estimated from tracks', + 'datatype': 'numeric', + 'user_id': None, + 'description': None, + 'created_at': datetime(2014, 10, 11, 1, 48, 49, 756000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 11, 1, 19, 12, 22, 48000, tzinfo=tzutc()), + 'allowed_values': None, + 'values_count': 99, + 'users_count': 34, + 'uuid': 'be8c35c6-36fc-4424-994e-b66c66ec2671', + }, + { + 'id': 1893, + 'name': 'Estimated number of individuals', + 'datatype': 'text', + 'user_id': 59121, + 'description': 'If unable to count how many individuals were present, give your best estimate', + 'created_at': datetime(2014, 6, 18, 2, 49, 11, 372000, tzinfo=tzutc()), + 'updated_at': datetime(2019, 8, 27, 0, 57, 20, 220000, tzinfo=tzutc()), + 'allowed_values': None, + 'values_count': 244, + 'users_count': 20, + 'uuid': '9925f6f1-b343-4d09-94c0-ad748632fabd', + }, + { + 'id': 1156, + 'name': 'Number of individuals spotted', + 'datatype': 'numeric', + 'user_id': 38289, + 'description': '', + 'created_at': datetime(2014, 5, 19, 8, 26, 55, 913000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 31, 21, 22, 6, 334000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 9848, + 'users_count': 161, + 'uuid': 'b0f297a6-494d-4770-a3d3-32b919884dd9', + }, + { + 'id': 667, + 'name': 'Number of individuals observed, with sex and life stage if applicable.', + 'datatype': 'text', + 'user_id': 2470, + 'description': "for example, 'at least 10 adult dragonflies observed', '6 clumps, each with 10 to 20 stems', 'one patch of 1.5 square meters, with 8 flowering stems.'", + 'created_at': datetime(2013, 10, 16, 18, 32, 1, 144000, tzinfo=tzutc()), + 'updated_at': datetime(2019, 8, 1, 13, 52, 35, 924000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 3, + 'users_count': 3, + 'uuid': '6f182da0-1e5a-43ea-8c0e-32825d395fd3', + }, + { + 'id': 546, + 'name': 'Number of individuals observed', + 'datatype': 'text', + 'user_id': 20607, + 'description': 'Approximate number of individuals observed', + 'created_at': datetime(2013, 8, 21, 8, 35, 54, 980000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 29, 21, 12, 24, 962000, tzinfo=tzutc()), + 'allowed_values': '"1-2|3-5|6-10|10-50|more than 50"', + 'values_count': 1324, + 'users_count': 123, + 'uuid': '7ab4e6e6-4ee3-49e6-8adb-9a7df73904d7', + }, + { + 'id': 1790, + 'name': 'Number of individuals seen after first five minutes', + 'datatype': 'numeric', + 'user_id': 58004, + 'description': 'Your best estimate on the number of individuals seen after first five minutes.', + 'created_at': datetime(2013, 5, 2, 7, 48, 12, 58000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 3, 19, 1, 40, 38, 821000, tzinfo=tzutc()), + 'allowed_values': None, + 'values_count': 2, + 'users_count': 2, + 'uuid': '982aa921-34e9-42a8-85f9-9b004e99ac01', + }, + { + 'id': 297, + 'name': 'Number of individuals', + 'datatype': 'numeric', + 'user_id': None, + 'description': 'Number of individuals observed together', + 'created_at': datetime(2013, 4, 23, 20, 49, 21, 714000, tzinfo=tzutc()), + 'updated_at': datetime(2021, 1, 31, 23, 41, 3, 494000, tzinfo=tzutc()), + 'allowed_values': '', + 'values_count': 29738, + 'users_count': 1395, + 'uuid': '554a3758-936b-4b91-bb9e-0689d428c939', + }, + { + 'id': 1776, + 'name': 'Maximum number of individuals SEEN', + 'datatype': 'numeric', + 'user_id': 1115, + 'description': 'Whether you also heard them or not, you need to have seen them to count them here. If you made multiple sightings, this is the maximum number of individuals you could have seen had each sighting been a different individual.', + 'created_at': datetime(2013, 4, 21, 19, 58, 41, 750000, tzinfo=tzutc()), + 'updated_at': datetime(2020, 5, 26, 6, 40, 22, 72000, tzinfo=tzutc()), + 'allowed_values': None, + 'values_count': 2135, + 'users_count': 216, + 'uuid': '9573d60b-b164-4be4-9409-90bcc23730c6', + }, +] diff --git a/test/sample_data/get_observation_histogram_day.json b/test/sample_data/get_observation_histogram_day.json new file mode 100644 index 00000000..f988d3cd --- /dev/null +++ b/test/sample_data/get_observation_histogram_day.json @@ -0,0 +1,40 @@ +{ + "total_results": 31, + "page": 1, + "per_page": 31, + "results": { + "day": { + "2020-01-01": 11, + "2020-01-02": 6, + "2020-01-03": 2, + "2020-01-04": 3, + "2020-01-05": 13, + "2020-01-06": 4, + "2020-01-07": 4, + "2020-01-08": 0, + "2020-01-09": 1, + "2020-01-10": 5, + "2020-01-11": 6, + "2020-01-12": 7, + "2020-01-13": 2, + "2020-01-14": 7, + "2020-01-15": 11, + "2020-01-16": 0, + "2020-01-17": 5, + "2020-01-18": 13, + "2020-01-19": 1, + "2020-01-20": 5, + "2020-01-21": 3, + "2020-01-22": 8, + "2020-01-23": 21, + "2020-01-24": 7, + "2020-01-25": 14, + "2020-01-26": 62, + "2020-01-27": 10, + "2020-01-28": 17, + "2020-01-29": 7, + "2020-01-30": 9, + "2020-01-31": 7 + } + } +} diff --git a/test/sample_data/get_observation_histogram_day.py b/test/sample_data/get_observation_histogram_day.py new file mode 100644 index 00000000..1480c784 --- /dev/null +++ b/test/sample_data/get_observation_histogram_day.py @@ -0,0 +1,35 @@ +from datetime import datetime + +{ + datetime(2020, 1, 1, 0, 0): 11, + datetime(2020, 1, 2, 0, 0): 6, + datetime(2020, 1, 3, 0, 0): 2, + datetime(2020, 1, 4, 0, 0): 3, + datetime(2020, 1, 5, 0, 0): 13, + datetime(2020, 1, 6, 0, 0): 4, + datetime(2020, 1, 7, 0, 0): 4, + datetime(2020, 1, 8, 0, 0): 0, + datetime(2020, 1, 9, 0, 0): 1, + datetime(2020, 1, 10, 0, 0): 5, + datetime(2020, 1, 11, 0, 0): 6, + datetime(2020, 1, 12, 0, 0): 7, + datetime(2020, 1, 13, 0, 0): 2, + datetime(2020, 1, 14, 0, 0): 7, + datetime(2020, 1, 15, 0, 0): 11, + datetime(2020, 1, 16, 0, 0): 0, + datetime(2020, 1, 17, 0, 0): 5, + datetime(2020, 1, 18, 0, 0): 13, + datetime(2020, 1, 19, 0, 0): 1, + datetime(2020, 1, 20, 0, 0): 5, + datetime(2020, 1, 21, 0, 0): 3, + datetime(2020, 1, 22, 0, 0): 8, + datetime(2020, 1, 23, 0, 0): 21, + datetime(2020, 1, 24, 0, 0): 7, + datetime(2020, 1, 25, 0, 0): 14, + datetime(2020, 1, 26, 0, 0): 62, + datetime(2020, 1, 27, 0, 0): 10, + datetime(2020, 1, 28, 0, 0): 17, + datetime(2020, 1, 29, 0, 0): 7, + datetime(2020, 1, 30, 0, 0): 9, + datetime(2020, 1, 31, 0, 0): 7, +} diff --git a/test/sample_data/get_observation_histogram_hour.json b/test/sample_data/get_observation_histogram_hour.json new file mode 100644 index 00000000..0d174a42 --- /dev/null +++ b/test/sample_data/get_observation_histogram_hour.json @@ -0,0 +1,23 @@ +{ + "total_results": 14, + "page": 1, + "per_page": 14, + "results": { + "hour": { + "2020-01-01T14:00:00Z": 1, + "2020-01-01T15:00:00Z": 13, + "2020-01-01T16:00:00Z": 0, + "2020-01-01T17:00:00Z": 0, + "2020-01-01T18:00:00Z": 0, + "2020-01-01T19:00:00Z": 3, + "2020-01-01T20:00:00Z": 2, + "2020-01-01T21:00:00Z": 1, + "2020-01-01T22:00:00Z": 1, + "2020-01-01T23:00:00Z": 0, + "2020-01-02T00:00:00Z": 1, + "2020-01-02T01:00:00Z": 0, + "2020-01-02T02:00:00Z": 0, + "2020-01-02T03:00:00Z": 1 + } + } +} diff --git a/test/sample_data/get_observation_histogram_hour.py b/test/sample_data/get_observation_histogram_hour.py new file mode 100644 index 00000000..aae45b01 --- /dev/null +++ b/test/sample_data/get_observation_histogram_hour.py @@ -0,0 +1,19 @@ +from datetime import datetime +from dateutil.tz import tzutc + +{ + datetime(2020, 1, 1, 14, 0, tzinfo=tzutc()): 1, + datetime(2020, 1, 1, 15, 0, tzinfo=tzutc()): 13, + datetime(2020, 1, 1, 16, 0, tzinfo=tzutc()): 0, + datetime(2020, 1, 1, 17, 0, tzinfo=tzutc()): 0, + datetime(2020, 1, 1, 18, 0, tzinfo=tzutc()): 0, + datetime(2020, 1, 1, 19, 0, tzinfo=tzutc()): 3, + datetime(2020, 1, 1, 20, 0, tzinfo=tzutc()): 2, + datetime(2020, 1, 1, 21, 0, tzinfo=tzutc()): 1, + datetime(2020, 1, 1, 22, 0, tzinfo=tzutc()): 1, + datetime(2020, 1, 1, 23, 0, tzinfo=tzutc()): 0, + datetime(2020, 1, 2, 0, 0, tzinfo=tzutc()): 1, + datetime(2020, 1, 2, 1, 0, tzinfo=tzutc()): 0, + datetime(2020, 1, 2, 2, 0, tzinfo=tzutc()): 0, + datetime(2020, 1, 2, 3, 0, tzinfo=tzutc()): 1, +} diff --git a/test/sample_data/get_observation_histogram_month.json b/test/sample_data/get_observation_histogram_month.json new file mode 100644 index 00000000..d1edc507 --- /dev/null +++ b/test/sample_data/get_observation_histogram_month.json @@ -0,0 +1,21 @@ +{ + "total_results": 12, + "page": 1, + "per_page": 12, + "results": { + "month": { + "2020-01-01": 272, + "2020-02-01": 253, + "2020-03-01": 992, + "2020-04-01": 3925, + "2020-05-01": 7983, + "2020-06-01": 7080, + "2020-07-01": 9150, + "2020-08-01": 8895, + "2020-09-01": 8374, + "2020-10-01": 6060, + "2020-11-01": 920, + "2020-12-01": 382 + } + } +} diff --git a/test/sample_data/get_observation_histogram_month.py b/test/sample_data/get_observation_histogram_month.py new file mode 100644 index 00000000..1ff52663 --- /dev/null +++ b/test/sample_data/get_observation_histogram_month.py @@ -0,0 +1,16 @@ +from datetime import datetime + +{ + datetime(2020, 1, 1, 0, 0): 272, + datetime(2020, 2, 1, 0, 0): 253, + datetime(2020, 3, 1, 0, 0): 992, + datetime(2020, 4, 1, 0, 0): 3925, + datetime(2020, 5, 1, 0, 0): 7983, + datetime(2020, 6, 1, 0, 0): 7080, + datetime(2020, 7, 1, 0, 0): 9150, + datetime(2020, 8, 1, 0, 0): 8895, + datetime(2020, 9, 1, 0, 0): 8374, + datetime(2020, 10, 1, 0, 0): 6060, + datetime(2020, 11, 1, 0, 0): 920, + datetime(2020, 12, 1, 0, 0): 382, +} diff --git a/test/sample_data/get_observation_histogram_month_of_year.json b/test/sample_data/get_observation_histogram_month_of_year.json new file mode 100644 index 00000000..e99e9e78 --- /dev/null +++ b/test/sample_data/get_observation_histogram_month_of_year.json @@ -0,0 +1,21 @@ +{ + "total_results": 12, + "page": 1, + "per_page": 12, + "results": { + "month_of_year": { + "1": 272, + "2": 253, + "3": 992, + "4": 3925, + "5": 7983, + "6": 7079, + "7": 9150, + "8": 8895, + "9": 8374, + "10": 6060, + "11": 920, + "12": 382 + } + } +} diff --git a/test/sample_data/get_observation_histogram_month_of_year.py b/test/sample_data/get_observation_histogram_month_of_year.py new file mode 100644 index 00000000..3565ddae --- /dev/null +++ b/test/sample_data/get_observation_histogram_month_of_year.py @@ -0,0 +1,14 @@ +{ + 1: 272, + 2: 253, + 3: 992, + 4: 3925, + 5: 7983, + 6: 7079, + 7: 9150, + 8: 8895, + 9: 8374, + 10: 6060, + 11: 920, + 12: 382, +} diff --git a/test/sample_data/get_observation_histogram_week.json b/test/sample_data/get_observation_histogram_week.json new file mode 100644 index 00000000..9f57e879 --- /dev/null +++ b/test/sample_data/get_observation_histogram_week.json @@ -0,0 +1,62 @@ +{ + "total_results": 53, + "page": 1, + "per_page": 53, + "results": { + "week": { + "2019-12-30": 35, + "2020-01-06": 27, + "2020-01-13": 39, + "2020-01-20": 120, + "2020-01-27": 73, + "2020-02-03": 48, + "2020-02-10": 35, + "2020-02-17": 89, + "2020-02-24": 81, + "2020-03-02": 116, + "2020-03-09": 90, + "2020-03-16": 195, + "2020-03-23": 406, + "2020-03-30": 642, + "2020-04-06": 652, + "2020-04-13": 684, + "2020-04-20": 1393, + "2020-04-27": 1755, + "2020-05-04": 1251, + "2020-05-11": 1566, + "2020-05-18": 1986, + "2020-05-25": 2141, + "2020-06-01": 1581, + "2020-06-08": 1640, + "2020-06-15": 1406, + "2020-06-22": 1902, + "2020-06-29": 2078, + "2020-07-06": 1821, + "2020-07-13": 1854, + "2020-07-20": 2308, + "2020-07-27": 2637, + "2020-08-03": 2275, + "2020-08-10": 1717, + "2020-08-17": 1474, + "2020-08-24": 2234, + "2020-08-31": 2275, + "2020-09-07": 2180, + "2020-09-14": 1824, + "2020-09-21": 1609, + "2020-09-28": 1714, + "2020-10-05": 2849, + "2020-10-12": 1425, + "2020-10-19": 569, + "2020-10-26": 210, + "2020-11-02": 331, + "2020-11-09": 229, + "2020-11-16": 162, + "2020-11-23": 164, + "2020-11-30": 102, + "2020-12-07": 75, + "2020-12-14": 55, + "2020-12-21": 150, + "2020-12-28": 11 + } + } +} diff --git a/test/sample_data/get_observation_histogram_week.py b/test/sample_data/get_observation_histogram_week.py new file mode 100644 index 00000000..3d550a11 --- /dev/null +++ b/test/sample_data/get_observation_histogram_week.py @@ -0,0 +1,57 @@ +from datetime import datetime + +{ + datetime(2019, 12, 30, 0, 0): 35, + datetime(2020, 1, 6, 0, 0): 27, + datetime(2020, 1, 13, 0, 0): 39, + datetime(2020, 1, 20, 0, 0): 120, + datetime(2020, 1, 27, 0, 0): 73, + datetime(2020, 2, 3, 0, 0): 48, + datetime(2020, 2, 10, 0, 0): 35, + datetime(2020, 2, 17, 0, 0): 89, + datetime(2020, 2, 24, 0, 0): 81, + datetime(2020, 3, 2, 0, 0): 116, + datetime(2020, 3, 9, 0, 0): 90, + datetime(2020, 3, 16, 0, 0): 195, + datetime(2020, 3, 23, 0, 0): 406, + datetime(2020, 3, 30, 0, 0): 642, + datetime(2020, 4, 6, 0, 0): 652, + datetime(2020, 4, 13, 0, 0): 684, + datetime(2020, 4, 20, 0, 0): 1393, + datetime(2020, 4, 27, 0, 0): 1755, + datetime(2020, 5, 4, 0, 0): 1251, + datetime(2020, 5, 11, 0, 0): 1566, + datetime(2020, 5, 18, 0, 0): 1986, + datetime(2020, 5, 25, 0, 0): 2141, + datetime(2020, 6, 1, 0, 0): 1581, + datetime(2020, 6, 8, 0, 0): 1640, + datetime(2020, 6, 15, 0, 0): 1406, + datetime(2020, 6, 22, 0, 0): 1902, + datetime(2020, 6, 29, 0, 0): 2078, + datetime(2020, 7, 6, 0, 0): 1821, + datetime(2020, 7, 13, 0, 0): 1854, + datetime(2020, 7, 20, 0, 0): 2308, + datetime(2020, 7, 27, 0, 0): 2637, + datetime(2020, 8, 3, 0, 0): 2275, + datetime(2020, 8, 10, 0, 0): 1717, + datetime(2020, 8, 17, 0, 0): 1474, + datetime(2020, 8, 24, 0, 0): 2234, + datetime(2020, 8, 31, 0, 0): 2275, + datetime(2020, 9, 7, 0, 0): 2180, + datetime(2020, 9, 14, 0, 0): 1824, + datetime(2020, 9, 21, 0, 0): 1609, + datetime(2020, 9, 28, 0, 0): 1714, + datetime(2020, 10, 5, 0, 0): 2849, + datetime(2020, 10, 12, 0, 0): 1425, + datetime(2020, 10, 19, 0, 0): 569, + datetime(2020, 10, 26, 0, 0): 210, + datetime(2020, 11, 2, 0, 0): 331, + datetime(2020, 11, 9, 0, 0): 229, + datetime(2020, 11, 16, 0, 0): 162, + datetime(2020, 11, 23, 0, 0): 164, + datetime(2020, 11, 30, 0, 0): 102, + datetime(2020, 12, 7, 0, 0): 75, + datetime(2020, 12, 14, 0, 0): 55, + datetime(2020, 12, 21, 0, 0): 150, + datetime(2020, 12, 28, 0, 0): 11, +} diff --git a/test/sample_data/get_observation_histogram_week_of_year.json b/test/sample_data/get_observation_histogram_week_of_year.json new file mode 100644 index 00000000..2bf7383e --- /dev/null +++ b/test/sample_data/get_observation_histogram_week_of_year.json @@ -0,0 +1,62 @@ +{ + "total_results": 53, + "page": 1, + "per_page": 53, + "results": { + "week_of_year": { + "1": 35, + "2": 27, + "3": 39, + "4": 120, + "5": 73, + "6": 48, + "7": 35, + "8": 89, + "9": 81, + "10": 116, + "11": 90, + "12": 195, + "13": 406, + "14": 642, + "15": 652, + "16": 684, + "17": 1393, + "18": 1755, + "19": 1251, + "20": 1566, + "21": 1986, + "22": 2141, + "23": 1581, + "24": 1640, + "25": 1406, + "26": 1902, + "27": 2078, + "28": 1821, + "29": 1854, + "30": 2308, + "31": 2637, + "32": 2275, + "33": 1717, + "34": 1474, + "35": 2234, + "36": 2275, + "37": 2180, + "38": 1824, + "39": 1609, + "40": 1714, + "41": 2849, + "42": 1425, + "43": 569, + "44": 210, + "45": 331, + "46": 229, + "47": 162, + "48": 164, + "49": 102, + "50": 75, + "51": 55, + "52": 150, + "53": 11 + } + } +} diff --git a/test/sample_data/get_observation_histogram_week_of_year.py b/test/sample_data/get_observation_histogram_week_of_year.py new file mode 100644 index 00000000..c7b66650 --- /dev/null +++ b/test/sample_data/get_observation_histogram_week_of_year.py @@ -0,0 +1,55 @@ +{ + 1: 35, + 2: 27, + 3: 39, + 4: 120, + 5: 73, + 6: 48, + 7: 35, + 8: 89, + 9: 81, + 10: 116, + 11: 90, + 12: 195, + 13: 406, + 14: 642, + 15: 652, + 16: 684, + 17: 1393, + 18: 1755, + 19: 1251, + 20: 1566, + 21: 1986, + 22: 2141, + 23: 1581, + 24: 1640, + 25: 1406, + 26: 1902, + 27: 2078, + 28: 1821, + 29: 1854, + 30: 2308, + 31: 2637, + 32: 2275, + 33: 1717, + 34: 1474, + 35: 2234, + 36: 2275, + 37: 2180, + 38: 1824, + 39: 1609, + 40: 1714, + 41: 2849, + 42: 1425, + 43: 569, + 44: 210, + 45: 331, + 46: 229, + 47: 162, + 48: 164, + 49: 102, + 50: 75, + 51: 55, + 52: 150, + 53: 11, +} diff --git a/test/sample_data/get_observation_identifiers_ex_results.json b/test/sample_data/get_observation_identifiers_ex_results.json new file mode 100644 index 00000000..d6551933 --- /dev/null +++ b/test/sample_data/get_observation_identifiers_ex_results.json @@ -0,0 +1,385 @@ +{ + "total_results": 2999, + "page": 1, + "per_page": 500, + "results": [ + { + "user_id": 53153, + "count": 1787, + "user": { + "id": 53153, + "login": "willkuhn", + "spam": false, + "suspended": false, + "created_at": "2014-09-15T18:38:59+00:00", + "login_autocomplete": "willkuhn", + "login_exact": "willkuhn", + "name": "Will Kuhn", + "name_autocomplete": "Will Kuhn", + "orcid": "https://orcid.org/0000-0002-5506-6379", + "icon": "https://static.inaturalist.org/attachments/users/icons/53153/thumb.jpeg?1606488424", + "observations_count": 2945, + "identifications_count": 3895, + "journal_posts_count": 0, + "activity_count": 6840, + "species_count": 1230, + "universal_search_rank": 2945, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/53153/medium.jpeg?1606488424" + } + }, + { + "user_id": 105391, + "count": 1165, + "user": { + "id": 105391, + "login": "jtuttle", + "spam": false, + "suspended": false, + "created_at": "2015-05-25T21:56:21+00:00", + "login_autocomplete": "jtuttle", + "login_exact": "jtuttle", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/105391/thumb.jpg?1475546106", + "observations_count": 12473, + "identifications_count": 27628, + "journal_posts_count": 0, + "activity_count": 40101, + "species_count": 3217, + "universal_search_rank": 12473, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/105391/medium.jpg?1475546106" + } + }, + { + "user_id": 32654, + "count": 1012, + "user": { + "id": 32654, + "login": "eraskin", + "spam": false, + "suspended": false, + "created_at": "2014-03-19T18:35:39+00:00", + "login_autocomplete": "eraskin", + "login_exact": "eraskin", + "name": "Evan M. Raskin", + "name_autocomplete": "Evan M. Raskin", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/32654/thumb.jpg?1533126488", + "observations_count": 5150, + "identifications_count": 18213, + "journal_posts_count": 3, + "activity_count": 23366, + "species_count": 2320, + "universal_search_rank": 5150, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/32654/medium.jpg?1533126488" + } + }, + { + "user_id": 1064292, + "count": 907, + "user": { + "id": 1064292, + "login": "syrherp", + "spam": false, + "suspended": false, + "created_at": "2018-07-04T04:58:43+00:00", + "login_autocomplete": "syrherp", + "login_exact": "syrherp", + "name": "Anthony Brais", + "name_autocomplete": "Anthony Brais", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1064292/thumb.jpg?1537157949", + "observations_count": 3788, + "identifications_count": 33279, + "journal_posts_count": 0, + "activity_count": 37067, + "species_count": 472, + "universal_search_rank": 3788, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1064292/medium.jpg?1537157949" + } + }, + { + "user_id": 2233645, + "count": 851, + "user": { + "id": 2233645, + "login": "jak2113", + "spam": false, + "suspended": false, + "created_at": "2019-09-09T15:42:57+00:00", + "login_autocomplete": "jak2113", + "login_exact": "jak2113", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": null, + "observations_count": 287, + "identifications_count": 1623, + "journal_posts_count": 0, + "activity_count": 1910, + "species_count": 206, + "universal_search_rank": 287, + "roles": [], + "site_id": 1, + "icon_url": null + } + }, + { + "user_id": 19073, + "count": 781, + "user": { + "id": 19073, + "login": "choess", + "spam": false, + "suspended": false, + "created_at": "2013-07-13T18:27:48+00:00", + "login_autocomplete": "choess", + "login_exact": "choess", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/19073/thumb.jpg?1475537701", + "observations_count": 5067, + "identifications_count": 152943, + "journal_posts_count": 11, + "activity_count": 158021, + "species_count": 1284, + "universal_search_rank": 5067, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/19073/medium.jpg?1475537701" + } + }, + { + "user_id": 26905, + "count": 668, + "user": { + "id": 26905, + "login": "polemoniaceae", + "spam": false, + "suspended": false, + "created_at": "2013-12-04T20:28:55+00:00", + "login_autocomplete": "polemoniaceae", + "login_exact": "polemoniaceae", + "name": "Jeff", + "name_autocomplete": "Jeff", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/26905/thumb.jpg?1475541432", + "observations_count": 251, + "identifications_count": 129664, + "journal_posts_count": 0, + "activity_count": 129915, + "species_count": 222, + "universal_search_rank": 251, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/26905/medium.jpg?1475541432" + } + }, + { + "user_id": 854537, + "count": 622, + "user": { + "id": 854537, + "login": "maxallen", + "spam": false, + "suspended": false, + "created_at": "2018-04-16T22:12:08+00:00", + "login_autocomplete": "maxallen", + "login_exact": "maxallen", + "name": "", + "name_autocomplete": "", + "orcid": "https://orcid.org/0000-0001-8976-889X", + "icon": "https://static.inaturalist.org/attachments/users/icons/854537/thumb.jpg?1529523533", + "observations_count": 1938, + "identifications_count": 524561, + "journal_posts_count": 4, + "activity_count": 526503, + "species_count": 675, + "universal_search_rank": 1938, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/854537/medium.jpg?1529523533" + } + }, + { + "user_id": 755672, + "count": 621, + "user": { + "id": 755672, + "login": "ddennism", + "spam": false, + "suspended": false, + "created_at": "2018-01-30T02:28:08+00:00", + "login_autocomplete": "ddennism", + "login_exact": "ddennism", + "name": "Daniel McClosky", + "name_autocomplete": "Daniel McClosky", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/755672/thumb.jpg?1571254550", + "observations_count": 3369, + "identifications_count": 38204, + "journal_posts_count": 8, + "activity_count": 41581, + "species_count": 774, + "universal_search_rank": 3369, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/755672/medium.jpg?1571254550" + } + }, + { + "user_id": 2229996, + "count": 616, + "user": { + "id": 2229996, + "login": "annkatrinrose", + "spam": false, + "suspended": false, + "created_at": "2019-09-08T18:45:38+00:00", + "login_autocomplete": "annkatrinrose", + "login_exact": "annkatrinrose", + "name": "Annkatrin Rose", + "name_autocomplete": "Annkatrin Rose", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2229996/thumb.jpg?1567997735", + "observations_count": 3799, + "identifications_count": 9286, + "journal_posts_count": 6, + "activity_count": 13091, + "species_count": 1024, + "universal_search_rank": 3799, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2229996/medium.jpg?1567997735" + } + }, + { + "user_id": 324885, + "count": 599, + "user": { + "id": 324885, + "login": "lucareptile", + "spam": false, + "suspended": false, + "created_at": "2016-09-09T00:19:57+00:00", + "login_autocomplete": "lucareptile", + "login_exact": "lucareptile", + "name": "Luca Catanzaro", + "name_autocomplete": "Luca Catanzaro", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/324885/thumb.jpg?1566422568", + "observations_count": 21480, + "identifications_count": 109080, + "journal_posts_count": 0, + "activity_count": 130560, + "species_count": 2470, + "universal_search_rank": 21480, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/324885/medium.jpg?1566422568" + } + }, + { + "user_id": 1210028, + "count": 577, + "user": { + "id": 1210028, + "login": "pynklynx", + "spam": false, + "suspended": false, + "created_at": "2018-09-15T03:55:25+00:00", + "login_autocomplete": "pynklynx", + "login_exact": "pynklynx", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1210028/thumb.jpg?1538262214", + "observations_count": 6673, + "identifications_count": 74690, + "journal_posts_count": 0, + "activity_count": 81363, + "species_count": 1310, + "universal_search_rank": 6673, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1210028/medium.jpg?1538262214" + } + }, + { + "user_id": 1168695, + "count": 530, + "user": { + "id": 1168695, + "login": "trscavo", + "spam": false, + "suspended": false, + "created_at": "2018-08-28T18:14:51+00:00", + "login_autocomplete": "trscavo", + "login_exact": "trscavo", + "name": "Tom Scavo", + "name_autocomplete": "Tom Scavo", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1168695/thumb.png?1535480090", + "observations_count": 4541, + "identifications_count": 30780, + "journal_posts_count": 0, + "activity_count": 35321, + "species_count": 554, + "universal_search_rank": 4541, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1168695/medium.png?1535480090" + } + }, + { + "user_id": 187480, + "count": 468, + "user": { + "id": 187480, + "login": "wdvanhem", + "spam": false, + "suspended": false, + "created_at": "2016-03-03T13:54:17+00:00", + "login_autocomplete": "wdvanhem", + "login_exact": "wdvanhem", + "name": "Will Van Hemessen", + "name_autocomplete": "Will Van Hemessen", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/187480/thumb.jpeg?1586740535", + "observations_count": 8050, + "identifications_count": 145345, + "journal_posts_count": 1, + "activity_count": 153396, + "species_count": 2482, + "universal_search_rank": 8050, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/187480/medium.jpeg?1586740535" + } + } + ] +} diff --git a/test/sample_data/get_observation_identifiers_node_page1.json b/test/sample_data/get_observation_identifiers_node_page1.json new file mode 100644 index 00000000..e20fe5ad --- /dev/null +++ b/test/sample_data/get_observation_identifiers_node_page1.json @@ -0,0 +1,87 @@ +{ + "total_results": 6, + "page": 1, + "per_page": 3, + "results": [ + { + "user_id": 112514, + "count": 1, + "user": { + "id": 112514, + "login": "earthstephen", + "spam": false, + "suspended": false, + "created_at": "2015-06-27T17:16:40+00:00", + "login_autocomplete": "earthstephen", + "login_exact": "earthstephen", + "name": "Stephen", + "name_autocomplete": "Stephen", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/112514/thumb.jpg?1475548347", + "observations_count": 1231, + "identifications_count": 689, + "journal_posts_count": 0, + "activity_count": 1920, + "species_count": 576, + "universal_search_rank": 1231, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/112514/medium.jpg?1475548347" + } + }, + { + "user_id": 207263, + "count": 1, + "user": { + "id": 207263, + "login": "johntrent", + "spam": false, + "suspended": false, + "created_at": "2016-03-30T16:32:27+00:00", + "login_autocomplete": "johntrent", + "login_exact": "johntrent", + "name": "John Trent", + "name_autocomplete": "John Trent", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/207263/thumb.jpeg?1588380729", + "observations_count": 19066, + "identifications_count": 9253, + "journal_posts_count": 1, + "activity_count": 28320, + "species_count": 3565, + "universal_search_rank": 19066, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/207263/medium.jpeg?1588380729" + } + }, + { + "user_id": 324885, + "count": 1, + "user": { + "id": 324885, + "login": "lucareptile", + "spam": false, + "suspended": false, + "created_at": "2016-09-09T00:19:57+00:00", + "login_autocomplete": "lucareptile", + "login_exact": "lucareptile", + "name": "Luca Catanzaro", + "name_autocomplete": "Luca Catanzaro", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/324885/thumb.jpg?1566422568", + "observations_count": 21480, + "identifications_count": 109080, + "journal_posts_count": 0, + "activity_count": 130560, + "species_count": 2470, + "universal_search_rank": 21480, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/324885/medium.jpg?1566422568" + } + } + ] +} diff --git a/test/sample_data/get_observation_identifiers_node_page2.json b/test/sample_data/get_observation_identifiers_node_page2.json new file mode 100644 index 00000000..c2e203c0 --- /dev/null +++ b/test/sample_data/get_observation_identifiers_node_page2.json @@ -0,0 +1,85 @@ +{ + "total_results": 6, + "page": 2, + "per_page": 3, + "results": [ + { + "user_id": 769610, + "count": 1, + "user": { + "id": 769610, + "login": "bobbyfingers", + "spam": false, + "suspended": true, + "created_at": "2018-02-20T05:52:44+00:00", + "login_autocomplete": "bobbyfingers", + "login_exact": "bobbyfingers", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/769610/thumb.jpg?1589693736", + "observations_count": 856, + "identifications_count": 147074, + "journal_posts_count": 0, + "activity_count": 147930, + "species_count": 293, + "universal_search_rank": 856, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/769610/medium.jpg?1589693736" + } + }, + { + "user_id": 824100, + "count": 1, + "user": { + "id": 824100, + "login": "jakeschneider00", + "spam": false, + "suspended": false, + "created_at": "2018-04-02T20:41:12+00:00", + "login_autocomplete": "jakeschneider00", + "login_exact": "jakeschneider00", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": null, + "observations_count": 1308, + "identifications_count": 2414, + "journal_posts_count": 0, + "activity_count": 3722, + "species_count": 675, + "universal_search_rank": 1308, + "roles": [], + "site_id": 1, + "icon_url": null + } + }, + { + "user_id": 2129322, + "count": 1, + "user": { + "id": 2129322, + "login": "blissfulbufo", + "spam": false, + "suspended": false, + "created_at": "2019-08-13T23:11:09+00:00", + "login_autocomplete": "blissfulbufo", + "login_exact": "blissfulbufo", + "name": "Bliss M.", + "name_autocomplete": "Bliss M.", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2129322/thumb.jpeg?1572256973", + "observations_count": 473, + "identifications_count": 2775, + "journal_posts_count": 0, + "activity_count": 3248, + "species_count": 308, + "universal_search_rank": 473, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2129322/medium.jpeg?1572256973" + } + } + ] +} diff --git a/test/sample_data/get_observation_observers_ex_results.json b/test/sample_data/get_observation_observers_ex_results.json new file mode 100644 index 00000000..87837ee5 --- /dev/null +++ b/test/sample_data/get_observation_observers_ex_results.json @@ -0,0 +1,523 @@ +{ + "total_results": 3948, + "page": 1, + "per_page": 500, + "results": [ + { + "user_id": 15723, + "observation_count": 2346, + "species_count": 605, + "user": { + "id": 15723, + "login": "reallifeecology", + "spam": false, + "suspended": false, + "created_at": "2013-04-27T19:32:15+00:00", + "login_autocomplete": "reallifeecology", + "login_exact": "reallifeecology", + "name": "Jonathan (JC) Carpenter", + "name_autocomplete": "Jonathan (JC) Carpenter", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/15723/thumb.jpg?1475535710", + "observations_count": 36365, + "identifications_count": 2086, + "journal_posts_count": 3, + "activity_count": 38454, + "species_count": 5740, + "universal_search_rank": 36365, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/15723/medium.jpg?1475535710" + } + }, + { + "user_id": 1085809, + "observation_count": 1125, + "species_count": 393, + "user": { + "id": 1085809, + "login": "anneturner", + "spam": false, + "suspended": false, + "created_at": "2018-07-14T16:12:14+00:00", + "login_autocomplete": "anneturner", + "login_exact": "anneturner", + "name": "Anne Turner", + "name_autocomplete": "Anne Turner", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1085809/thumb.jpeg?1555073435", + "observations_count": 1441, + "identifications_count": 125, + "journal_posts_count": 0, + "activity_count": 1566, + "species_count": 553, + "universal_search_rank": 1441, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1085809/medium.jpeg?1555073435" + } + }, + { + "user_id": 53153, + "observation_count": 890, + "species_count": 324, + "user": { + "id": 53153, + "login": "willkuhn", + "spam": false, + "suspended": false, + "created_at": "2014-09-15T18:38:59+00:00", + "login_autocomplete": "willkuhn", + "login_exact": "willkuhn", + "name": "Will Kuhn", + "name_autocomplete": "Will Kuhn", + "orcid": "https://orcid.org/0000-0002-5506-6379", + "icon": "https://static.inaturalist.org/attachments/users/icons/53153/thumb.jpeg?1606488424", + "observations_count": 2945, + "identifications_count": 3895, + "journal_posts_count": 0, + "activity_count": 6840, + "species_count": 1230, + "universal_search_rank": 2945, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/53153/medium.jpeg?1606488424" + } + }, + { + "user_id": 176684, + "observation_count": 806, + "species_count": 348, + "user": { + "id": 176684, + "login": "gmontgomery", + "spam": false, + "suspended": false, + "created_at": "2016-02-06T21:58:36+00:00", + "login_autocomplete": "gmontgomery", + "login_exact": "gmontgomery", + "name": "Graham Montgomery", + "name_autocomplete": "Graham Montgomery", + "orcid": "https://orcid.org/0000-0002-8217-8800", + "icon": "https://static.inaturalist.org/attachments/users/icons/176684/thumb.jpeg?1600827496", + "observations_count": 7986, + "identifications_count": 4072, + "journal_posts_count": 0, + "activity_count": 12058, + "species_count": 4146, + "universal_search_rank": 7986, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/176684/medium.jpeg?1600827496" + } + }, + { + "user_id": 1019810, + "observation_count": 791, + "species_count": 279, + "user": { + "id": 1019810, + "login": "jduffy", + "spam": false, + "suspended": false, + "created_at": "2018-06-11T15:58:21+00:00", + "login_autocomplete": "jduffy", + "login_exact": "jduffy", + "name": "Jim", + "name_autocomplete": "Jim", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1019810/thumb.jpg?1551496934", + "observations_count": 823, + "identifications_count": 2, + "journal_posts_count": 0, + "activity_count": 825, + "species_count": 313, + "universal_search_rank": 823, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1019810/medium.jpg?1551496934" + } + }, + { + "user_id": 906019, + "observation_count": 693, + "species_count": 190, + "user": { + "id": 906019, + "login": "akfishmom", + "spam": false, + "suspended": false, + "created_at": "2018-04-26T20:56:39+00:00", + "login_autocomplete": "akfishmom", + "login_exact": "akfishmom", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": null, + "observations_count": 822, + "identifications_count": 6, + "journal_posts_count": 0, + "activity_count": 828, + "species_count": 322, + "universal_search_rank": 822, + "roles": [], + "site_id": 1, + "icon_url": null + } + }, + { + "user_id": 17201, + "observation_count": 661, + "species_count": 328, + "user": { + "id": 17201, + "login": "matthewherron", + "spam": false, + "suspended": false, + "created_at": "2013-05-29T02:13:48+00:00", + "login_autocomplete": "matthewherron", + "login_exact": "matthewherron", + "name": "Matthew Herron", + "name_autocomplete": "Matthew Herron", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/17201/thumb.jpeg?1475536451", + "observations_count": 7694, + "identifications_count": 1467, + "journal_posts_count": 0, + "activity_count": 9161, + "species_count": 2195, + "universal_search_rank": 7694, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/17201/medium.jpeg?1475536451" + } + }, + { + "user_id": 282998, + "observation_count": 640, + "species_count": 212, + "user": { + "id": 282998, + "login": "dunhamkc", + "spam": false, + "suspended": false, + "created_at": "2016-08-06T13:19:36+00:00", + "login_autocomplete": "dunhamkc", + "login_exact": "dunhamkc", + "name": "Keely Dunham", + "name_autocomplete": "Keely Dunham", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/282998/thumb.jpg?1526954269", + "observations_count": 2418, + "identifications_count": 725, + "journal_posts_count": 0, + "activity_count": 3143, + "species_count": 796, + "universal_search_rank": 2418, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/282998/medium.jpg?1526954269" + } + }, + { + "user_id": 843086, + "observation_count": 562, + "species_count": 235, + "user": { + "id": 843086, + "login": "b_georgic", + "spam": false, + "suspended": false, + "created_at": "2018-04-10T16:31:49+00:00", + "login_autocomplete": "b_georgic", + "login_exact": "b_georgic", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/843086/thumb.jpg?1606070123", + "observations_count": 1151, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 1151, + "species_count": 653, + "universal_search_rank": 1151, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/843086/medium.jpg?1606070123" + } + }, + { + "user_id": 574477, + "observation_count": 442, + "species_count": 171, + "user": { + "id": 574477, + "login": "pinus_taeda", + "spam": false, + "suspended": false, + "created_at": "2017-08-10T18:00:10+00:00", + "login_autocomplete": "pinus_taeda", + "login_exact": "pinus_taeda", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/574477/thumb.jpg?1527608024", + "observations_count": 1846, + "identifications_count": 1141, + "journal_posts_count": 0, + "activity_count": 2987, + "species_count": 551, + "universal_search_rank": 1846, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/574477/medium.jpg?1527608024" + } + }, + { + "user_id": 2148023, + "observation_count": 420, + "species_count": 149, + "user": { + "id": 2148023, + "login": "christopher514", + "spam": false, + "suspended": false, + "created_at": "2019-08-19T02:14:52+00:00", + "login_autocomplete": "christopher514", + "login_exact": "christopher514", + "name": "Christopher Gontarski", + "name_autocomplete": "Christopher Gontarski", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2148023/thumb.jpeg?1566180892", + "observations_count": 567, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 567, + "species_count": 287, + "universal_search_rank": 567, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2148023/medium.jpeg?1566180892" + } + }, + { + "user_id": 362446, + "observation_count": 398, + "species_count": 237, + "user": { + "id": 362446, + "login": "pwdeacon", + "spam": false, + "suspended": false, + "created_at": "2016-11-02T20:47:19+00:00", + "login_autocomplete": "pwdeacon", + "login_exact": "pwdeacon", + "name": "Pat Deacon", + "name_autocomplete": "Pat Deacon", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/362446/thumb.jpeg?1575167373", + "observations_count": 12962, + "identifications_count": 7883, + "journal_posts_count": 0, + "activity_count": 20845, + "species_count": 3029, + "universal_search_rank": 12962, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/362446/medium.jpeg?1575167373" + } + }, + { + "user_id": 93979, + "observation_count": 373, + "species_count": 23, + "user": { + "id": 93979, + "login": "mcaterino", + "spam": false, + "suspended": false, + "created_at": "2015-04-28T12:18:05+00:00", + "login_autocomplete": "mcaterino", + "login_exact": "mcaterino", + "name": "Michael Caterino", + "name_autocomplete": "Michael Caterino", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/93979/thumb.jpg?1568554017", + "observations_count": 1708, + "identifications_count": 153, + "journal_posts_count": 0, + "activity_count": 1861, + "species_count": 174, + "universal_search_rank": 1708, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/93979/medium.jpg?1568554017" + } + }, + { + "user_id": 17283, + "observation_count": 334, + "species_count": 252, + "user": { + "id": 17283, + "login": "layla", + "spam": false, + "suspended": false, + "created_at": "2013-05-30T20:06:01+00:00", + "login_autocomplete": "layla", + "login_exact": "layla", + "name": "Layla Dishman", + "name_autocomplete": "Layla Dishman", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/17283/thumb.jpg?1475536494", + "observations_count": 2831, + "identifications_count": 1414, + "journal_posts_count": 0, + "activity_count": 4245, + "universal_search_rank": 2831, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/17283/medium.jpg?1475536494" + } + }, + { + "user_id": 2346515, + "observation_count": 317, + "species_count": 77, + "user": { + "id": 2346515, + "login": "sarahroth", + "spam": false, + "suspended": false, + "created_at": "2019-10-08T15:37:36+00:00", + "login_autocomplete": "sarahroth", + "login_exact": "sarahroth", + "name": "Sarah Roth", + "name_autocomplete": "Sarah Roth", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2346515/thumb.png?1570549056", + "observations_count": 473, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 473, + "species_count": 126, + "universal_search_rank": 473, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2346515/medium.png?1570549056" + } + }, + { + "user_id": 159616, + "observation_count": 314, + "species_count": 149, + "user": { + "id": 159616, + "login": "cdance", + "spam": false, + "suspended": false, + "created_at": "2015-12-09T19:36:11+00:00", + "login_autocomplete": "cdance", + "login_exact": "cdance", + "name": "CDance", + "name_autocomplete": "CDance", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/159616/thumb.jpg?1566762916", + "observations_count": 2900, + "identifications_count": 264, + "journal_posts_count": 3, + "activity_count": 3167, + "species_count": 1165, + "universal_search_rank": 2900, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/159616/medium.jpg?1566762916" + } + }, + { + "user_id": 1359300, + "observation_count": 310, + "species_count": 148, + "user": { + "id": 1359300, + "login": "spyingnaturalist", + "spam": false, + "suspended": false, + "created_at": "2018-11-20T01:19:01+00:00", + "login_autocomplete": "spyingnaturalist", + "login_exact": "spyingnaturalist", + "name": "", + "name_autocomplete": "", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/1359300/thumb.jpg?1552909758", + "observations_count": 8286, + "identifications_count": 5371, + "journal_posts_count": 0, + "activity_count": 13657, + "species_count": 2749, + "universal_search_rank": 8286, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/1359300/medium.jpg?1552909758" + } + }, + { + "user_id": 899236, + "observation_count": 292, + "species_count": 177, + "user": { + "id": 899236, + "login": "palustris314", + "spam": false, + "suspended": false, + "created_at": "2018-04-25T17:22:45+00:00", + "login_autocomplete": "palustris314", + "login_exact": "palustris314", + "name": "Greg J Schmidt", + "name_autocomplete": "Greg J Schmidt", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/899236/thumb.jpg?1526651056", + "observations_count": 1599, + "identifications_count": 139, + "journal_posts_count": 0, + "activity_count": 1738, + "species_count": 1013, + "universal_search_rank": 1599, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/899236/medium.jpg?1526651056" + } + }, + { + "user_id": 187480, + "observation_count": 252, + "species_count": 172, + "user": { + "id": 187480, + "login": "wdvanhem", + "spam": false, + "suspended": false, + "created_at": "2016-03-03T13:54:17+00:00", + "login_autocomplete": "wdvanhem", + "login_exact": "wdvanhem", + "name": "Will Van Hemessen", + "name_autocomplete": "Will Van Hemessen", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/187480/thumb.jpeg?1586740535", + "observations_count": 8050, + "identifications_count": 145345, + "journal_posts_count": 1, + "activity_count": 153396, + "species_count": 2482, + "universal_search_rank": 8050, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/187480/medium.jpeg?1586740535" + } + } + ] +} diff --git a/test/sample_data/get_observation_observers_node_page1.json b/test/sample_data/get_observation_observers_node_page1.json new file mode 100644 index 00000000..cab63e08 --- /dev/null +++ b/test/sample_data/get_observation_observers_node_page1.json @@ -0,0 +1,61 @@ +{ + "total_results": 4, + "page": 1, + "per_page": 2, + "results": [ + { + "user_id": 53153, + "observation_count": 750, + "species_count": 264, + "user": { + "id": 53153, + "login": "willkuhn", + "spam": false, + "suspended": false, + "created_at": "2014-09-15T18:38:59+00:00", + "login_autocomplete": "willkuhn", + "login_exact": "willkuhn", + "name": "Will Kuhn", + "name_autocomplete": "Will Kuhn", + "orcid": "https://orcid.org/0000-0002-5506-6379", + "icon": "https://static.inaturalist.org/attachments/users/icons/53153/thumb.jpeg?1606488424", + "observations_count": 2945, + "identifications_count": 3895, + "journal_posts_count": 0, + "activity_count": 6840, + "species_count": 1230, + "universal_search_rank": 2945, + "roles": [], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/53153/medium.jpeg?1606488424" + } + }, + { + "user_id": 3151147, + "observation_count": 30, + "species_count": 25, + "user": { + "id": 3151147, + "login": "antnyreda", + "spam": false, + "suspended": false, + "created_at": "2020-06-09T17:06:27+00:00", + "login_autocomplete": "antnyreda", + "login_exact": "antnyreda", + "name": null, + "name_autocomplete": null, + "orcid": null, + "icon": null, + "observations_count": 35, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 35, + "species_count": 32, + "universal_search_rank": 35, + "roles": [], + "site_id": 1, + "icon_url": null + } + } + ] +} diff --git a/test/sample_data/get_observation_observers_node_page2.json b/test/sample_data/get_observation_observers_node_page2.json new file mode 100644 index 00000000..bbe79ee3 --- /dev/null +++ b/test/sample_data/get_observation_observers_node_page2.json @@ -0,0 +1,62 @@ +{ + "total_results": 4, + "page": 2, + "per_page": 2, + "results": [ + { + "user_id": 2688432, + "observation_count": 6, + "species_count": 4, + "user": { + "id": 2688432, + "login": "aliceguzov", + "spam": false, + "suspended": false, + "created_at": "2020-03-29T15:33:53+00:00", + "login_autocomplete": "aliceguzov", + "login_exact": "aliceguzov", + "name": "Alice Guzov", + "name_autocomplete": "Alice Guzov", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/2688432/thumb.jpeg?1585496033", + "observations_count": 6, + "identifications_count": 0, + "journal_posts_count": 0, + "activity_count": 6, + "universal_search_rank": 6, + "roles": [], + "site_id": null, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/2688432/medium.jpeg?1585496033" + } + }, + { + "user_id": 282859, + "observation_count": 2, + "species_count": 0, + "user": { + "id": 282859, + "login": "liuguangyu", + "spam": false, + "suspended": false, + "created_at": "2016-08-06T06:29:38+00:00", + "login_autocomplete": "liuguangyu", + "login_exact": "liuguangyu", + "name": "刘光裕 Liu Guangyu", + "name_autocomplete": "刘光裕 Liu Guangyu", + "orcid": null, + "icon": "https://static.inaturalist.org/attachments/users/icons/282859/thumb.jpg?1475530297", + "observations_count": 5275, + "identifications_count": 400, + "journal_posts_count": 0, + "activity_count": 5675, + "species_count": 1746, + "universal_search_rank": 5275, + "roles": [ + "curator" + ], + "site_id": 1, + "icon_url": "https://static.inaturalist.org/attachments/users/icons/282859/medium.jpg?1475530297" + } + } + ] +} diff --git a/test/sample_data/get_observation_species_counts.py b/test/sample_data/get_observation_species_counts.py new file mode 100644 index 00000000..ab888f09 --- /dev/null +++ b/test/sample_data/get_observation_species_counts.py @@ -0,0 +1,109 @@ +{ + 'total_results': 243, + 'page': 1, + 'per_page': 500, + 'results': [ + { + 'count': 32, + 'taxon': { + 'observations_count': 81696, + 'taxon_schemes_count': 2, + 'ancestry': '48460/1/47120/372739/47158/184884/47208/71130/372852/471714/48486/333790/333796/48487', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 3}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Harmonia_axyridis', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'rank_level': 10, + 'taxon_changes_count': 1, + 'atlas_id': 1241, + 'complete_species_count': None, + 'parent_id': 48487, + 'name': 'Harmonia axyridis', + 'rank': 'species', + 'extinct': False, + 'id': 48484, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/30978499/square.jpg?1549228178', + 'attribution': '(c) Paolo Mazzei, all rights reserved', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/30978499/medium.jpg?1549228178', + 'id': 30978499, + 'license_code': None, + 'original_dimensions': {'width': 800, 'height': 600}, + 'url': 'https://static.inaturalist.org/photos/30978499/square.jpg?1549228178', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47208, + 71130, + 372852, + 471714, + 48486, + 333790, + 333796, + 48487, + 48484, + ], + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Asian Lady Beetle', + }, + }, + { + 'count': 19, + 'taxon': { + 'observations_count': 43187, + 'taxon_schemes_count': 3, + 'ancestry': '48460/1/47120/372739/47158/184884/47208/71130/372852/471714/48486/333790/333796/51703', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 1}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Coccinella_septempunctata', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'rank_level': 10, + 'taxon_changes_count': 1, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 51703, + 'name': 'Coccinella septempunctata', + 'rank': 'species', + 'extinct': False, + 'id': 51702, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/5669923/square.jpg?1480658715', + 'attribution': '(c) Katja Schulz, some rights reserved (CC BY)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/5669923/medium.jpg?1480658715', + 'id': 5669923, + 'license_code': 'cc-by', + 'original_dimensions': {'width': 2048, 'height': 1536}, + 'url': 'https://static.inaturalist.org/photos/5669923/square.jpg?1480658715', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47208, + 71130, + 372852, + 471714, + 48486, + 333790, + 333796, + 51703, + 51702, + ], + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Seven-spotted Lady Beetle', + }, + }, + ], +} diff --git a/test/sample_data/get_observations.json b/test/sample_data/get_observations.json index f2539c34..360d9b71 100644 --- a/test/sample_data/get_observations.json +++ b/test/sample_data/get_observations.json @@ -128,4 +128,4 @@ } ] } -] \ No newline at end of file +] diff --git a/test/sample_data/get_observations.kml b/test/sample_data/get_observations.kml index 371cfe0d..4a8cc958 100644 --- a/test/sample_data/get_observations.kml +++ b/test/sample_data/get_observations.kml @@ -25,8 +25,8 @@

- - + +
View Observation diff --git a/test/sample_data/get_observations_node.py b/test/sample_data/get_observations_node.py new file mode 100644 index 00000000..b48b3564 --- /dev/null +++ b/test/sample_data/get_observations_node.py @@ -0,0 +1,526 @@ +from datetime import datetime +from dateutil.tz import tzoffset, tzutc + +{ + 'total_results': 2, + 'page': 1, + 'per_page': 30, + 'results': [ + { + 'quality_grade': 'research', + 'time_observed_at': '2020-08-27T08:57:22+00:00', + 'taxon_geoprivacy': 'open', + 'annotations': [], + 'uuid': 'c44b91f3-534d-48c4-933d-a8b4e1d8f87b', + 'id': 57754375, + 'cached_votes_total': 0, + 'identifications_most_agree': True, + 'species_guess': 'Monarch', + 'identifications_most_disagree': False, + 'tags': [], + 'positional_accuracy': None, + 'comments_count': 2, + 'site_id': 5, + 'license_code': 'cc-by-nc', + 'quality_metrics': [], + 'public_positional_accuracy': None, + 'reviewed_by': [115129, 2852555], + 'oauth_application_id': 3, + 'flags': [], + 'created_at': datetime(2020, 8, 27, 18, 0, 51, tzinfo=tzutc()), + 'description': None, + 'project_ids_with_curator_id': [], + 'updated_at': datetime(2020, 8, 28, 12, 4, 18, tzinfo=tzutc()), + 'sounds': [], + 'place_ids': [ + 6712, + 7953, + 9853, + 49224, + 59613, + 64422, + 64423, + 66741, + 78395, + 82256, + 97394, + 145030, + 155145, + 164401, + ], + 'captive': False, + 'taxon': { + 'is_active': True, + 'ancestry': '48460/1/47120/372739/47158/184884/47157/47224/47922/61244/134169/522900/48663', + 'min_species_ancestry': '48460,1,47120,372739,47158,184884,47157,47224,47922,61244,134169,522900,48663,48662', + 'endemic': False, + 'iconic_taxon_id': 47158, + 'min_species_taxon_id': 48662, + 'threatened': True, + 'rank_level': 10, + 'introduced': False, + 'native': True, + 'parent_id': 48663, + 'name': 'Danaus plexippus', + 'rank': 'species', + 'extinct': False, + 'id': 48662, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47157, + 47224, + 47922, + 61244, + 134169, + 522900, + 48663, + 48662, + ], + 'photos_locked': False, + 'taxon_schemes_count': 6, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Monarch_butterfly', + 'current_synonymous_taxon_ids': None, + 'created_at': '2008-10-26T20:24:33+00:00', + 'taxon_changes_count': 1, + 'complete_species_count': None, + 'universal_search_rank': 131627, + 'observations_count': 131627, + 'flag_counts': {'unresolved': 1, 'resolved': 7}, + 'atlas_id': 1231, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/13824507/square.jpg?1545715684', + 'attribution': '(c) fam-esquivel, some rights reserved (CC BY-NC)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/13824507/medium.jpg?1545715684', + 'id': 13824507, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 2048, 'height': 1361}, + 'url': 'https://static.inaturalist.org/photos/13824507/square.jpg?1545715684', + }, + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Monarch', + 'conservation_status': { + 'user_id': None, + 'status_name': 'imperiled', + 'iucn': 40, + 'authority': 'NatureServe', + 'geoprivacy': None, + 'source_id': 8, + 'place_id': 7953, + 'status': 's2b', + }, + }, + 'ident_taxon_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47157, + 47224, + 47922, + 61244, + 134169, + 522900, + 48663, + 48662, + ], + 'outlinks': [{'source': 'GBIF', 'url': 'http://www.gbif.org/occurrence/2856816457'}], + 'faves_count': 0, + 'ofvs': [], + 'num_identification_agreements': 1, + 'preferences': {'prefers_community_taxon': None}, + 'comments': [ + { + 'moderator_actions': [], + 'hidden': False, + 'flags': [], + 'created_at': '2020-08-28T12:04:18.407Z', + 'id': 5326888, + 'created_at_details': { + 'date': '2020-08-28', + 'week': 35, + 'month': 8, + 'hour': 12, + 'year': 2020, + 'day': 28, + }, + 'body': 'Thankyou ', + 'uuid': 'b4d00be4-132d-4e51-bfdd-8515a00f161c', + 'user': { + 'created_at': '2020-04-26T15:26:56+00:00', + 'id': 2852555, + 'login': 'samroom', + 'spam': False, + 'suspended': False, + 'login_autocomplete': 'samroom', + 'login_exact': 'samroom', + 'name': None, + 'name_autocomplete': None, + 'orcid': None, + 'icon': None, + 'observations_count': 59, + 'identifications_count': 0, + 'journal_posts_count': 0, + 'activity_count': 59, + 'species_count': 52, + 'universal_search_rank': 59, + 'roles': [], + 'site_id': 5, + 'icon_url': None, + }, + }, + { + 'moderator_actions': [], + 'hidden': False, + 'flags': [], + 'created_at': '2020-08-28T04:54:19.020Z', + 'id': 5325706, + 'created_at_details': { + 'date': '2020-08-28', + 'week': 35, + 'month': 8, + 'hour': 4, + 'year': 2020, + 'day': 28, + }, + 'body': 'Neat photo! ', + 'uuid': '25ac67d5-ba09-47a8-91e3-b6e3590a0b72', + 'user': { + 'created_at': '2018-03-18T01:34:25+00:00', + 'id': 792847, + 'login': 'ingridt3', + 'spam': False, + 'suspended': False, + 'login_autocomplete': 'ingridt3', + 'login_exact': 'ingridt3', + 'name': None, + 'name_autocomplete': None, + 'orcid': None, + 'icon': None, + 'observations_count': 51, + 'identifications_count': 29, + 'journal_posts_count': 0, + 'activity_count': 80, + 'species_count': 30, + 'universal_search_rank': 51, + 'roles': [], + 'site_id': 5, + 'icon_url': None, + }, + }, + ], + 'map_scale': None, + 'uri': 'https://www.inaturalist.org/observations/57754375', + 'project_ids': [], + 'community_taxon_id': 48662, + 'geojson': {'coordinates': [-104.71929167, 50.0949055], 'type': 'Point'}, + 'owners_identification_from_vision': True, + 'identifications_count': 1, + 'obscured': False, + 'num_identification_disagreements': 0, + 'geoprivacy': None, + 'location': [50.0949055, -104.71929167], + 'votes': [], + 'spam': False, + 'user': { + 'site_id': 5, + 'created_at': '2020-04-26T15:26:56+00:00', + 'id': 2852555, + 'login': 'samroom', + 'spam': False, + 'suspended': False, + 'preferences': {}, + 'login_autocomplete': 'samroom', + 'login_exact': 'samroom', + 'name': None, + 'name_autocomplete': None, + 'orcid': None, + 'icon': None, + 'observations_count': 59, + 'identifications_count': 0, + 'journal_posts_count': 0, + 'activity_count': 59, + 'species_count': 52, + 'universal_search_rank': 59, + 'roles': [], + 'icon_url': None, + }, + 'mappable': True, + 'identifications_some_agree': True, + 'project_ids_without_curator_id': [], + 'place_guess': 'Railway Ave, Wilcox, SK, CA', + 'identifications': 'TRUNCATED', + 'project_observations': [], + 'photos': [ + { + 'id': 92152429, + 'license_code': 'cc-by-nc', + 'url': 'https://static.inaturalist.org/photos/92152429/square.jpg?1598551272', + 'attribution': '(c) samroom, some rights reserved (CC BY-NC)', + 'original_dimensions': {'width': 1188, 'height': 1188}, + 'flags': [], + } + ], + 'observation_photos': [ + { + 'id': 85887066, + 'position': 0, + 'uuid': '6f73a9f2-019f-4566-a393-2c2242a3198d', + 'photo': { + 'id': 92152429, + 'license_code': 'cc-by-nc', + 'url': 'https://static.inaturalist.org/photos/92152429/square.jpg?1598551272', + 'attribution': '(c) samroom, some rights reserved (CC BY-NC)', + 'original_dimensions': {'width': 1188, 'height': 1188}, + 'flags': [], + }, + } + ], + 'faves': [], + 'non_owner_ids': 'TRUNCATED', + 'observed_on': datetime(2020, 8, 27, 8, 57, 22, tzinfo=tzoffset('Etc/UTC', 0)), + }, + { + 'quality_grade': 'research', + 'time_observed_at': '2020-08-26T22:46:10+00:00', + 'taxon_geoprivacy': 'open', + 'annotations': [], + 'uuid': '588c9ad4-c747-497c-a52f-bf856c30d2ff', + 'id': 57707611, + 'cached_votes_total': 0, + 'identifications_most_agree': True, + 'species_guess': 'Monarch', + 'identifications_most_disagree': False, + 'tags': [], + 'positional_accuracy': 374, + 'comments_count': 0, + 'site_id': 5, + 'license_code': None, + 'quality_metrics': [], + 'public_positional_accuracy': 374, + 'reviewed_by': [35013, 115129, 217571, 412189, 792847, 2173064], + 'oauth_application_id': 3, + 'flags': [], + 'created_at': datetime(2020, 8, 27, 4, 50, 17, tzinfo=tzutc()), + 'description': 'Only one today and likely the last one to eclose from the batch of caterpillars from 10-14 days ago. ', + 'project_ids_with_curator_id': [6905], + 'updated_at': datetime(2020, 11, 2, 19, 26, 40, tzinfo=tzutc()), + 'sounds': [], + 'place_ids': [ + 6712, + 7953, + 9853, + 49224, + 59613, + 64422, + 64423, + 66741, + 78395, + 82256, + 97394, + 113642, + 120614, + 120671, + 145030, + 155145, + 164401, + ], + 'captive': False, + 'taxon': { + 'is_active': True, + 'ancestry': '48460/1/47120/372739/47158/184884/47157/47224/47922/61244/134169/522900/48663', + 'min_species_ancestry': '48460,1,47120,372739,47158,184884,47157,47224,47922,61244,134169,522900,48663,48662', + 'endemic': False, + 'iconic_taxon_id': 47158, + 'min_species_taxon_id': 48662, + 'threatened': True, + 'rank_level': 10, + 'introduced': False, + 'native': True, + 'parent_id': 48663, + 'name': 'Danaus plexippus', + 'rank': 'species', + 'extinct': False, + 'id': 48662, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47157, + 47224, + 47922, + 61244, + 134169, + 522900, + 48663, + 48662, + ], + 'photos_locked': False, + 'taxon_schemes_count': 6, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Monarch_butterfly', + 'current_synonymous_taxon_ids': None, + 'created_at': '2008-10-26T20:24:33+00:00', + 'taxon_changes_count': 1, + 'complete_species_count': None, + 'universal_search_rank': 131627, + 'observations_count': 131627, + 'flag_counts': {'unresolved': 1, 'resolved': 7}, + 'atlas_id': 1231, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/13824507/square.jpg?1545715684', + 'attribution': '(c) fam-esquivel, some rights reserved (CC BY-NC)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/13824507/medium.jpg?1545715684', + 'id': 13824507, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 2048, 'height': 1361}, + 'url': 'https://static.inaturalist.org/photos/13824507/square.jpg?1545715684', + }, + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Monarch', + 'conservation_status': { + 'user_id': None, + 'status_name': 'imperiled', + 'iucn': 40, + 'authority': 'NatureServe', + 'geoprivacy': None, + 'source_id': 8, + 'place_id': 7953, + 'status': 's2b', + }, + }, + 'ident_taxon_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47157, + 47224, + 47922, + 61244, + 134169, + 522900, + 48663, + 48662, + ], + 'outlinks': [], + 'faves_count': 0, + 'ofvs': [], + 'num_identification_agreements': 5, + 'preferences': {'prefers_community_taxon': None}, + 'comments': [], + 'map_scale': None, + 'uri': 'https://www.inaturalist.org/observations/57707611', + 'project_ids': [6905], + 'community_taxon_id': 48662, + 'geojson': {'coordinates': [-104.5628221383, 50.433418274], 'type': 'Point'}, + 'owners_identification_from_vision': True, + 'identifications_count': 5, + 'obscured': False, + 'num_identification_disagreements': 0, + 'geoprivacy': None, + 'location': [50.433418274, -104.5628221383], + 'votes': [], + 'spam': False, + 'user': { + 'site_id': 5, + 'created_at': '2018-03-18T01:34:25+00:00', + 'id': 792847, + 'login': 'ingridt3', + 'spam': False, + 'suspended': False, + 'preferences': {}, + 'login_autocomplete': 'ingridt3', + 'login_exact': 'ingridt3', + 'name': None, + 'name_autocomplete': None, + 'orcid': None, + 'icon': None, + 'observations_count': 51, + 'identifications_count': 29, + 'journal_posts_count': 0, + 'activity_count': 80, + 'species_count': 30, + 'universal_search_rank': 51, + 'roles': [], + 'icon_url': None, + }, + 'mappable': True, + 'identifications_some_agree': True, + 'project_ids_without_curator_id': [], + 'place_guess': 'Michener Dr, Regina, SK, CA', + 'identifications': 'TRUNCATED', + 'project_observations': [ + { + 'id': 45561247, + 'uuid': '912138ca-651d-4d5f-99c1-d89e9c023bb5', + 'project': {'id': 6905}, + 'user_id': 35013, + 'preferences': {'allows_curator_coordinate_access': True}, + 'user': { + 'id': 35013, + 'login': 'a_b', + 'spam': False, + 'suspended': False, + 'created_at': '2014-04-09T03:27:04+00:00', + 'login_autocomplete': 'a_b', + 'login_exact': 'a_b', + 'name': 'Andrea Benville', + 'name_autocomplete': 'Andrea Benville', + 'orcid': None, + 'icon': 'https://static.inaturalist.org/attachments/users/icons/35013/thumb.jpg?1505750038', + 'observations_count': 787, + 'identifications_count': 533, + 'journal_posts_count': 0, + 'activity_count': 1320, + 'species_count': 432, + 'universal_search_rank': 787, + 'roles': [], + 'site_id': 5, + 'icon_url': 'https://static.inaturalist.org/attachments/users/icons/35013/medium.jpg?1505750038', + }, + } + ], + 'photos': [ + { + 'id': 92075215, + 'license_code': None, + 'url': 'https://static.inaturalist.org/photos/92075215/square.jpg?1598503820', + 'attribution': '(c) ingridt3, all rights reserved', + 'original_dimensions': {'width': 1183, 'height': 842}, + 'flags': [], + } + ], + 'observation_photos': [ + { + 'id': 85814399, + 'position': 0, + 'uuid': '98dd67ae-79b4-49c6-934b-6a814ea6d6ab', + 'photo': { + 'id': 92075215, + 'license_code': None, + 'url': 'https://static.inaturalist.org/photos/92075215/square.jpg?1598503820', + 'attribution': '(c) ingridt3, all rights reserved', + 'original_dimensions': {'width': 1183, 'height': 842}, + 'flags': [], + }, + } + ], + 'faves': [], + 'non_owner_ids': 'TRUNCATED', + 'observed_on': datetime(2020, 8, 26, 22, 46, 10, tzinfo=tzoffset('Etc/UTC', 0)), + }, + ], +} diff --git a/test/sample_data/get_places_autocomplete.json b/test/sample_data/get_places_autocomplete.json index f179dec1..1117c003 100644 --- a/test/sample_data/get_places_autocomplete.json +++ b/test/sample_data/get_places_autocomplete.json @@ -22,4 +22,4 @@ "geometry_geojson": null } ] -} \ No newline at end of file +} diff --git a/test/sample_data/get_places_autocomplete.py b/test/sample_data/get_places_autocomplete.py new file mode 100644 index 00000000..93201552 --- /dev/null +++ b/test/sample_data/get_places_autocomplete.py @@ -0,0 +1,119 @@ +{ + 'total_results': 4, + 'page': 1, + 'per_page': 4, + 'results': [ + { + 'ancestor_place_ids': [97395, 7161, 11803], + 'bounding_box_geojson': { + 'coordinates': [ + [ + [95.5820007324, 51.1188278198], + [95.5820007324, 64.2834396362], + [119.1222915649, 64.2834396362], + [119.1222915649, 51.1188278198], + [95.5820007324, 51.1188278198], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 309.898790854159, + 'admin_level': 1, + 'place_type': 21, + 'name': 'Irkutsk', + 'location': [57.0842832466, 106.2908666325], + 'id': 11803, + 'display_name': 'Irkutsk, RU', + 'uuid': '5a21bcbc-3975-4d22-81fa-0088543e3247', + 'slug': 'irkutsk', + 'geometry_geojson': { + 'coordinates': 'TRUNCATED', + 'type': 'Polygon', + }, + }, + { + 'ancestor_place_ids': [97395, 7161, 11803, 41853], + 'bounding_box_geojson': { + 'coordinates': [ + [ + [104.175743103, 52.3083381653], + [104.175743103, 52.3586273193], + [104.2688369751, 52.3586273193], + [104.2688369751, 52.3083381653], + [104.175743103, 52.3083381653], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 0.00468161207394086, + 'admin_level': 2, + 'place_type': 9, + 'name': 'Irkutsk', + 'location': [52.3330788792, 104.2213639696], + 'id': 41853, + 'display_name': 'Irkutsk, IK, RU', + 'uuid': 'ff9b00cd-491d-4414-a7a6-1d25bc16687b', + 'slug': 'irkutsk-irkutsk-ru', + 'geometry_geojson': { + 'coordinates': 'TRUNCATED', + 'type': 'MultiPolygon', + }, + }, + { + 'ancestor_place_ids': [97395, 7161, 11803, 41854], + 'bounding_box_geojson': { + 'coordinates': [ + [ + [103.3993835449, 51.8282775879], + [103.3993835449, 52.7414131165], + [105.9679870605, 52.7414131165], + [105.9679870605, 51.8282775879], + [103.3993835449, 51.8282775879], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 2.34548312891261, + 'admin_level': 2, + 'place_type': 102, + 'name': 'Irkutskiy rayon', + 'location': [52.2449129844, 104.5715345329], + 'id': 41854, + 'display_name': 'Irkutskiy rayon, IK, RU', + 'uuid': '23f656ae-4830-4934-b0e8-0fdc966565d1', + 'slug': 'irkutskiy-rayon', + 'geometry_geojson': { + 'coordinates': 'TRUNCATED', + 'type': 'Polygon', + }, + }, + { + 'ancestor_place_ids': [97395, 7161, 163077], + 'bounding_box_geojson': { + 'coordinates': [ + [ + [103.1875974, 51.6715918], + [103.1875974, 53.0498458], + [106.1725331, 53.0498458], + [106.1725331, 51.6715918], + [103.1875974, 51.6715918], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 4.1139995682678, + 'admin_level': None, + 'place_type': 1013, + 'name': 'Irkutsk agglomeration', + 'location': [52.2598446202, 104.6684418399], + 'id': 163077, + 'display_name': 'Irkutsk agglomeration, RU', + 'uuid': '553871d4-762e-4306-a48a-c4c5a3ac18b7', + 'slug': 'irkutsk-agglomeration', + 'geometry_geojson': { + 'coordinates': 'TRUNCATED', + 'type': 'Polygon', + }, + }, + ], +} diff --git a/test/sample_data/get_places_by_id.json b/test/sample_data/get_places_by_id.json index 885f8417..2d41b684 100644 --- a/test/sample_data/get_places_by_id.json +++ b/test/sample_data/get_places_by_id.json @@ -367,4 +367,4 @@ } } ] -} \ No newline at end of file +} diff --git a/test/sample_data/get_places_by_id.py b/test/sample_data/get_places_by_id.py new file mode 100644 index 00000000..ccaff6f3 --- /dev/null +++ b/test/sample_data/get_places_by_id.py @@ -0,0 +1,62 @@ +{ + 'total_results': 2, + 'page': 1, + 'per_page': 2, + 'results': [ + { + 'ancestor_place_ids': [97393, 6803, 8479, 40475, 89191], + 'bounding_box_geojson': { + 'coordinates': [ + [ + [172.22944733271785, -43.32610616632186], + [172.22944733271785, -43.32459953345999], + [172.2351855995691, -43.32459953345999], + [172.2351855995691, -43.32610616632186], + [172.22944733271785, -43.32610616632186], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 8.64546140806803e-06, + 'admin_level': None, + 'place_type': 100, + 'name': 'Conservation Area Riversdale', + 'location': [-43.3254578926, 172.2325124165], + 'id': 89191, + 'display_name': 'Conservation Area Riversdale, CA, NZ', + 'slug': 'conservation-area-riversdale', + 'geometry_geojson': { + 'coordinates': 'TRUNCATED', + 'type': 'MultiPolygon', + }, + }, + { + 'ancestor_place_ids': None, + 'bounding_box_geojson': { + 'coordinates': [ + [ + [176.0643786192, -41.1114421991], + [176.0643786192, -41.0804995501], + [176.0837817192, -41.0804995501], + [176.0837817192, -41.1114421991], + [176.0643786192, -41.1114421991], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 0.0006003833128119, + 'admin_level': None, + 'place_type': None, + 'name': 'Riversdale Beach', + 'location': [-41.0959708746, 176.0740801692], + 'id': 67591, + 'display_name': 'Riversdale Beach', + 'uuid': 'c5bca3b0-d9f2-44c3-bdb1-1189734a30a4', + 'slug': 'riversdale-beach', + 'geometry_geojson': { + 'coordinates': 'TRUNCATED', + 'type': 'MultiPolygon', + }, + }, + ], +} diff --git a/test/sample_data/get_places_nearby.json b/test/sample_data/get_places_nearby.json index 9a2c3679..e57f4519 100644 --- a/test/sample_data/get_places_nearby.json +++ b/test/sample_data/get_places_nearby.json @@ -877,4 +877,4 @@ } ] } -} \ No newline at end of file +} diff --git a/test/sample_data/get_places_nearby.py b/test/sample_data/get_places_nearby.py new file mode 100644 index 00000000..541c4979 --- /dev/null +++ b/test/sample_data/get_places_nearby.py @@ -0,0 +1,61 @@ +{ + 'total_results': 2, + 'page': 1, + 'per_page': 2, + 'results': { + 'standard': [ + { + 'ancestor_place_ids': None, + 'bounding_box_geojson': { + 'coordinates': [ + [ + [0.0132, 5.4508], + [0.0132, 83.7083], + [-0.0033, 83.7083], + [-0.0033, 5.4508], + [0.0132, 5.4508], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 28171.40875125, + 'admin_level': -1, + 'place_type': 29, + 'name': 'North America', + 'location': [56.7732555574, -179.68825], + 'id': 97394, + 'display_name': 'North America', + 'uuid': '22e0b04f-5efe-4383-a167-95079e1d5ce1', + 'slug': 'north-america', + 'geometry_geojson': {'coordinates': 'TRUNCATED', 'type': 'MultiPolygon'}, + } + ], + 'community': [ + { + 'ancestor_place_ids': [97391, 8858, 11770], + 'bounding_box_geojson': { + 'coordinates': [ + [ + [22.004324, 44.104093], + [22.004324, 45.119544], + [23.481443, 45.119544], + [23.481443, 44.104093], + [22.004324, 44.104093], + ] + ], + 'type': 'Polygon', + }, + 'bbox_area': 1.49994196566895, + 'admin_level': None, + 'place_type': 8, + 'name': 'Mehedinti', + 'location': [44.6118185, 22.7428835], + 'id': 11770, + 'display_name': 'Mehedinti, RO', + 'uuid': 'efa6a122-7d25-4342-a452-15c7036f6e44', + 'slug': 'mehedinti', + 'geometry_geojson': {'coordinates': 'TRUNCATED', 'type': 'Polygon'}, + } + ], + }, +} diff --git a/test/sample_data/get_projects.py b/test/sample_data/get_projects.py new file mode 100644 index 00000000..fcbd7325 --- /dev/null +++ b/test/sample_data/get_projects.py @@ -0,0 +1,134 @@ +from datetime import datetime +from dateutil.tz import tzutc + +{ + 'total_results': 5, + 'page': 1, + 'per_page': 5, + 'results': [ + { + 'icon': 'https://www.inaturalist.org/attachment_defaults/general/span2.png', + 'flags': [], + 'description': 'To identify plants to report to the Pacific Northwest Invasive Plant Council', + 'created_at': datetime(2016, 7, 20, 23, 0, 5, tzinfo=tzutc()), + 'title': 'PNW Invasive Plant EDDR', + 'banner_color': None, + 'project_observation_rules': [], + 'site_features': [], + 'project_observation_fields': [], + 'updated_at': datetime(2020, 12, 21, 18, 11, 26, tzinfo=tzutc()), + 'terms': None, + 'prefers_user_trust': False, + 'id': 8291, + 'slug': 'pnw-invasive-plant-eddr', + 'place_id': None, + 'icon_file_name': None, + 'project_type': '', + 'user_ids': [ + 19344, + 233188, + 300949, + 433100, + 450943, + 484450, + 550199, + 705929, + 800947, + 1257963, + 1299400, + 1325105, + 1447496, + 1559261, + 1572257, + 1574151, + 1781965, + 1935125, + 1973277, + 2025991, + 2026024, + 2069171, + 2162268, + 2269792, + 2354291, + 2410332, + 2453818, + 2499882, + 2581775, + 2714743, + 3145074, + 3272396, + 3387092, + 3908364, + ], + 'header_image_file_name': None, + 'search_parameters': [ + { + 'field': 'quality_grade', + 'value': ['research', 'needs_id'], + 'value_keyword': ['research', 'needs_id'], + } + ], + 'rule_preferences': [{'field': 'quality_grade', 'value': 'research,needs_id'}], + 'user_id': 233188, + 'hide_title': False, + 'location': [48.777404, -122.306929], + 'header_image_contain': False, + 'admins': [ + { + 'role': 'manager', + 'user_id': 233188, + 'project_id': 8291, + 'id': 142985, + 'user': { + 'id': 233188, + 'login': 'borsope', + 'spam': False, + 'suspended': False, + 'created_at': '2016-05-08T14:01:17+00:00', + 'login_autocomplete': 'borsope', + 'login_exact': 'borsope', + 'name': 'Pam Borso', + 'name_autocomplete': 'Pam Borso', + 'orcid': None, + 'icon': None, + 'observations_count': 3164, + 'identifications_count': 0, + 'journal_posts_count': 0, + 'activity_count': 3164, + 'species_count': 1346, + 'universal_search_rank': 3164, + 'roles': [], + 'site_id': 1, + 'icon_url': None, + }, + } + ], + 'header_image_url': None, + 'latitude': 48.777404, + 'longitude': -122.306929, + 'is_umbrella': False, + 'user': { + 'id': 233188, + 'login': 'borsope', + 'spam': False, + 'suspended': False, + 'created_at': '2016-05-08T14:01:17+00:00', + 'login_autocomplete': 'borsope', + 'login_exact': 'borsope', + 'name': 'Pam Borso', + 'name_autocomplete': 'Pam Borso', + 'orcid': None, + 'icon': None, + 'observations_count': 3164, + 'identifications_count': 0, + 'journal_posts_count': 0, + 'activity_count': 3164, + 'species_count': 1346, + 'universal_search_rank': 3164, + 'roles': [], + 'site_id': 1, + 'icon_url': None, + }, + }, + ], +} diff --git a/test/sample_data/get_projects_by_id.json b/test/sample_data/get_projects_by_id.json index fee3a02e..4ab475af 100644 --- a/test/sample_data/get_projects_by_id.json +++ b/test/sample_data/get_projects_by_id.json @@ -198,4 +198,4 @@ } } ] -} \ No newline at end of file +} diff --git a/test/sample_data/get_projects_by_id.py b/test/sample_data/get_projects_by_id.py new file mode 100644 index 00000000..1e6d479a --- /dev/null +++ b/test/sample_data/get_projects_by_id.py @@ -0,0 +1,99 @@ +from datetime import datetime +from dateutil.tz import tzutc + +{ + 'total_results': 1, + 'page': 1, + 'per_page': 1, + 'results': [ + { + 'icon': 'https://static.inaturalist.org/projects/8348-icon-span2.JPG?1505526661', + 'flags': [], + 'description': 'The purpose of this project is to document the native and invasive species (including plants, animals and fungus) present on the Tucson High campus. This is an on-going, open project, with the goal of helping students learn the difference between native and non-native species. ', + 'created_at': datetime(2016, 7, 26, 23, 8, 47, tzinfo=tzutc()), + 'title': 'Tucson High Native and Invasive Species Inventory', + 'banner_color': None, + 'project_observation_rules': [], + 'site_features': [], + 'project_observation_fields': [], + 'updated_at': datetime(2017, 9, 16, 1, 51, 1, tzinfo=tzutc()), + 'terms': None, + 'id': 8348, + 'slug': 'tucson-high-native-and-invasive-species-inventory', + 'place_id': 96103, + 'icon_file_name': 'IMG_1263.JPG', + 'project_type': '', + 'user_ids': [84403, 311743], + 'header_image_file_name': None, + 'search_parameters': [ + { + 'field': 'quality_grade', + 'value': ['research', 'needs_id'], + 'value_keyword': ['research', 'needs_id'], + }, + {'field': 'place_id', 'value_number': [96103], 'value': [96103]}, + ], + 'rule_preferences': [{'field': 'quality_grade', 'value': 'research,needs_id'}], + 'user_id': 84403, + 'hide_title': False, + 'location': [32.2264416406, -110.9617278383], + 'header_image_contain': False, + 'admins': [ + { + 'role': 'manager', + 'user_id': 84403, + 'project_id': 8348, + 'id': 144092, + 'user': { + 'id': 84403, + 'login': 'ebmartin', + 'spam': False, + 'suspended': False, + 'created_at': '2015-03-20T22:53:09+00:00', + 'login_autocomplete': 'ebmartin', + 'login_exact': 'ebmartin', + 'name': 'Elena Martin', + 'name_autocomplete': 'Elena Martin', + 'orcid': None, + 'icon': 'https://static.inaturalist.org/attachments/users/icons/84403/thumb.jpg?1475546770', + 'observations_count': 50, + 'identifications_count': 0, + 'journal_posts_count': 0, + 'activity_count': 50, + 'species_count': 29, + 'universal_search_rank': 50, + 'roles': [], + 'site_id': 1, + 'icon_url': 'https://static.inaturalist.org/attachments/users/icons/84403/medium.jpg?1475546770', + }, + } + ], + 'header_image_url': None, + 'latitude': 32.2264416406, + 'longitude': -110.9617278383, + 'is_umbrella': False, + 'user': { + 'id': 84403, + 'login': 'ebmartin', + 'spam': False, + 'suspended': False, + 'created_at': '2015-03-20T22:53:09+00:00', + 'login_autocomplete': 'ebmartin', + 'login_exact': 'ebmartin', + 'name': 'Elena Martin', + 'name_autocomplete': 'Elena Martin', + 'orcid': None, + 'icon': 'https://static.inaturalist.org/attachments/users/icons/84403/thumb.jpg?1475546770', + 'observations_count': 50, + 'identifications_count': 0, + 'journal_posts_count': 0, + 'activity_count': 50, + 'species_count': 29, + 'universal_search_rank': 50, + 'roles': [], + 'site_id': 1, + 'icon_url': 'https://static.inaturalist.org/attachments/users/icons/84403/medium.jpg?1475546770', + }, + } + ], +} diff --git a/test/sample_data/get_taxa.json b/test/sample_data/get_taxa.json index 9369402a..ac1dfa48 100644 --- a/test/sample_data/get_taxa.json +++ b/test/sample_data/get_taxa.json @@ -1410,4 +1410,4 @@ "iconic_taxon_name": "Insecta" } ] -} \ No newline at end of file +} diff --git a/test/sample_data/get_taxa.py b/test/sample_data/get_taxa.py new file mode 100644 index 00000000..e0d0f747 --- /dev/null +++ b/test/sample_data/get_taxa.py @@ -0,0 +1,85 @@ +{ + 'total_results': 2, + 'page': 1, + 'per_page': 30, + 'results': [ + { + 'observations_count': 235680, + 'taxon_schemes_count': 2, + 'ancestry': '48460/1/47120/372739/47158/184884/47201/124417/326777/48740', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 1}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Vespidae', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'rank_level': 30, + 'taxon_changes_count': 0, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 48740, + 'name': 'Vespidae', + 'rank': 'family', + 'extinct': False, + 'id': 52747, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/13765/square.jpg?1545358964', + 'attribution': '(c) Cécile Bassaglia, some rights reserved (CC BY-NC-SA)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/13765/medium.jpg?1545358964', + 'id': 13765, + 'license_code': 'cc-by-nc-sa', + 'original_dimensions': {'width': 1024, 'height': 679}, + 'url': 'https://static.inaturalist.org/photos/13765/square.jpg?1545358964', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47201, + 124417, + 326777, + 48740, + 52747, + ], + 'matched_term': 'Vespidae', + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Hornets, Paper Wasps, Potter Wasps, and Allies', + }, + { + 'observations_count': 4, + 'taxon_schemes_count': 1, + 'ancestry': '48460/1/2/355675/47178/47286/51544', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 0}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Vespicula', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47178, + 'rank_level': 20, + 'taxon_changes_count': 0, + 'atlas_id': None, + 'complete_species_count': 3, + 'parent_id': 51544, + 'complete_rank': 'subspecies', + 'name': 'Vespicula', + 'rank': 'genus', + 'extinct': False, + 'id': 92786, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/66470871/square.jpg?1586587863', + 'attribution': '(c) Tse Chung Yi, some rights reserved (CC BY-NC)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/66470871/medium.jpg?1586587863', + 'id': 66470871, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 2048, 'height': 1536}, + 'url': 'https://static.inaturalist.org/photos/66470871/square.jpg?1586587863', + }, + 'ancestor_ids': [48460, 1, 2, 355675, 47178, 47286, 51544, 92786], + 'matched_term': 'Vespicula', + 'iconic_taxon_name': 'Actinopterygii', + }, + ], +} diff --git a/test/sample_data/get_taxa_autocomplete.json b/test/sample_data/get_taxa_autocomplete.json index 3ab7699b..cb8de3b2 100644 --- a/test/sample_data/get_taxa_autocomplete.json +++ b/test/sample_data/get_taxa_autocomplete.json @@ -479,4 +479,4 @@ "iconic_taxon_name": "Actinopterygii" } ] -} \ No newline at end of file +} diff --git a/test/sample_data/get_taxa_autocomplete.py b/test/sample_data/get_taxa_autocomplete.py new file mode 100644 index 00000000..482e298d --- /dev/null +++ b/test/sample_data/get_taxa_autocomplete.py @@ -0,0 +1,146 @@ +{ + 'total_results': 3, + 'page': 1, + 'per_page': 10, + 'results': [ + { + 'observations_count': 235680, + 'taxon_schemes_count': 2, + 'ancestry': '48460/1/47120/372739/47158/184884/47201/124417/326777/48740', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 1}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Vespidae', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'rank_level': 30, + 'taxon_changes_count': 0, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 48740, + 'name': 'Vespidae', + 'rank': 'family', + 'extinct': False, + 'id': 52747, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/13765/square.jpg?1545358964', + 'attribution': '(c) Cécile Bassaglia, some rights reserved (CC BY-NC-SA)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/13765/medium.jpg?1545358964', + 'id': 13765, + 'license_code': 'cc-by-nc-sa', + 'original_dimensions': {'width': 1024, 'height': 679}, + 'url': 'https://static.inaturalist.org/photos/13765/square.jpg?1545358964', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47201, + 124417, + 326777, + 48740, + 52747, + ], + 'matched_term': 'Vespidae', + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Hornets, Paper Wasps, Potter Wasps, and Allies', + }, + { + 'observations_count': 74782, + 'taxon_schemes_count': 1, + 'ancestry': '48460/1/47120/372739/47158/184884/47201/124417/326777/48740/52747', + 'is_active': True, + 'flag_counts': {'unresolved': 1, 'resolved': 0}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Vespinae', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'rank_level': 27, + 'taxon_changes_count': 0, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 52747, + 'name': 'Vespinae', + 'rank': 'subfamily', + 'extinct': False, + 'id': 84738, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/11374710/square.jpg?1508777024', + 'attribution': '(c) Arnold Wijker, some rights reserved (CC BY-NC)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/11374710/medium.jpg?1508777024', + 'id': 11374710, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 800, 'height': 639}, + 'url': 'https://static.inaturalist.org/photos/11374710/square.jpg?1508777024', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47201, + 124417, + 326777, + 48740, + 52747, + 84738, + ], + 'matched_term': 'Vespinae', + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Hornets and Yellowjackets', + }, + { + 'observations_count': 610, + 'taxon_schemes_count': 1, + 'ancestry': '48460/1/47120/372739/47158/184884/47208/71130/372870/48311/53849/204079/53850', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 0}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Nicrophorus_vespillo', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'rank_level': 10, + 'taxon_changes_count': 0, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 53850, + 'name': 'Nicrophorus vespillo', + 'rank': 'species', + 'extinct': False, + 'id': 131878, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/24573164/square.jpg?1536478624', + 'attribution': '(c) Nikolai Vladimirov, some rights reserved (CC BY-NC)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/24573164/medium.jpg?1536478624', + 'id': 24573164, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 2048, 'height': 1365}, + 'url': 'https://static.inaturalist.org/photos/24573164/square.jpg?1536478624', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47208, + 71130, + 372870, + 48311, + 53849, + 204079, + 53850, + 131878, + ], + 'matched_term': 'Nicrophorus vespillo', + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Vespillo Burying Beetle', + }, + ], +} diff --git a/test/sample_data/get_taxa_autocomplete_minified.json b/test/sample_data/get_taxa_autocomplete_minified.json index 1f3a1ac4..39bcb948 100644 --- a/test/sample_data/get_taxa_autocomplete_minified.json +++ b/test/sample_data/get_taxa_autocomplete_minified.json @@ -14,4 +14,4 @@ " 621584: Species Vespicula cypho", " 621586: Species Vespicula zollingeri" ] -} \ No newline at end of file +} diff --git a/test/sample_data/get_taxa_by_id.json b/test/sample_data/get_taxa_by_id.json index 4703f041..2149e106 100644 --- a/test/sample_data/get_taxa_by_id.json +++ b/test/sample_data/get_taxa_by_id.json @@ -951,4 +951,4 @@ "wikipedia_summary": "Nicrophorus vespilloides is a burying beetle described by Johann Friedrich Wilhelm Herbst in 1783." } ] -} \ No newline at end of file +} diff --git a/test/sample_data/get_taxa_by_id.py b/test/sample_data/get_taxa_by_id.py new file mode 100644 index 00000000..22e18bc9 --- /dev/null +++ b/test/sample_data/get_taxa_by_id.py @@ -0,0 +1,209 @@ +{ + 'total_results': 1, + 'page': 1, + 'per_page': 30, + 'results': [ + { + 'photos_locked': False, + 'observations_count': 112516, + 'taxon_schemes_count': 1, + 'ancestry': '48460/1/47120/372739/47158/184884/47201/124417/326777/48740/52747', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 0}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Polistinae', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'taxon_photos': [ + { + 'taxon_id': 343248, + 'photo': { + 'flags': [], + 'type': 'LocalPhoto', + 'url': 'https://static.inaturalist.org/photos/22765568/square.jpeg?1533696452', + 'square_url': 'https://static.inaturalist.org/photos/22765568/square.jpeg?1533696452', + 'native_page_url': 'https://www.inaturalist.org/photos/22765568', + 'native_photo_id': '22765568', + 'small_url': 'https://static.inaturalist.org/photos/22765568/small.jpeg?1533696452', + 'original_url': 'https://static.inaturalist.org/photos/22765568/original.jpeg?1533696452', + 'attribution': '(c) copper, some rights reserved (CC BY-NC)', + 'medium_url': 'https://static.inaturalist.org/photos/22765568/medium.jpeg?1533696452', + 'id': 22765568, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 2048, 'height': 1639}, + 'large_url': 'https://static.inaturalist.org/photos/22765568/large.jpeg?1533696452', + }, + 'taxon': { + 'photos_locked': False, + 'taxon_schemes_count': 1, + 'ancestry': '48460/1/47120/372739/47158/184884/47201/124417/326777/48740/52747', + 'min_species_ancestry': '48460,1,47120,372739,47158,184884,47201,124417,326777,48740,52747,343248', + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Polistinae', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'created_at': '2014-02-19T09:17:40+00:00', + 'taxon_changes_count': 0, + 'complete_species_count': None, + 'rank': 'subfamily', + 'extinct': False, + 'id': 343248, + 'universal_search_rank': 112516, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47201, + 124417, + 326777, + 48740, + 52747, + 343248, + ], + 'observations_count': 112516, + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 0}, + 'min_species_taxon_id': 343248, + 'rank_level': 27, + 'atlas_id': None, + 'parent_id': 52747, + 'name': 'Polistinae', + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/22765568/square.jpeg?1533696452', + 'attribution': '(c) copper, some rights reserved (CC BY-NC)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/22765568/medium.jpeg?1533696452', + 'id': 22765568, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 2048, 'height': 1639}, + 'url': 'https://static.inaturalist.org/photos/22765568/square.jpeg?1533696452', + }, + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Paper Wasps', + }, + }, + ], + 'rank_level': 27, + 'taxon_changes_count': 0, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 52747, + 'name': 'Polistinae', + 'rank': 'subfamily', + 'extinct': False, + 'id': 343248, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/22765568/square.jpeg?1533696452', + 'attribution': '(c) copper, some rights reserved (CC BY-NC)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/22765568/medium.jpeg?1533696452', + 'id': 22765568, + 'license_code': 'cc-by-nc', + 'original_dimensions': {'width': 2048, 'height': 1639}, + 'url': 'https://static.inaturalist.org/photos/22765568/square.jpeg?1533696452', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47201, + 124417, + 326777, + 48740, + 52747, + ], + 'iconic_taxon_name': 'Insecta', + 'preferred_common_name': 'Paper Wasps', + 'ancestors': [ + { + 'observations_count': 32873107, + 'taxon_schemes_count': 2, + 'ancestry': '48460', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 9}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Animal', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 1, + 'rank_level': 70, + 'taxon_changes_count': 4, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 48460, + 'complete_rank': 'order', + 'name': 'Animalia', + 'rank': 'kingdom', + 'extinct': False, + 'id': 1, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/80678745/square.jpg?1593020613', + 'attribution': 'Ningún derecho reservado', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/80678745/medium.jpg?1593020613', + 'id': 80678745, + 'license_code': 'cc0', + 'original_dimensions': {'width': 2000, 'height': 2000}, + 'url': 'https://static.inaturalist.org/photos/80678745/square.jpg?1593020613', + }, + 'ancestor_ids': [48460, 1], + 'iconic_taxon_name': 'Animalia', + 'preferred_common_name': 'Animals', + }, + ], + 'children': [ + { + 'observations_count': 88534, + 'taxon_schemes_count': 0, + 'ancestry': '48460/1/47120/372739/47158/184884/47201/124417/326777/48740/52747/343248', + 'is_active': True, + 'flag_counts': {'unresolved': 0, 'resolved': 0}, + 'wikipedia_url': 'http://en.wikipedia.org/wiki/Polistes', + 'current_synonymous_taxon_ids': None, + 'iconic_taxon_id': 47158, + 'rank_level': 25, + 'taxon_changes_count': 0, + 'atlas_id': None, + 'complete_species_count': None, + 'parent_id': 343248, + 'name': 'Polistini', + 'rank': 'tribe', + 'extinct': False, + 'id': 638567, + 'default_photo': { + 'square_url': 'https://static.inaturalist.org/photos/2367678/square.jpg?1441941708', + 'attribution': '(c) Cheryl Harleston López Espino, some rights reserved (CC BY-NC-ND)', + 'flags': [], + 'medium_url': 'https://static.inaturalist.org/photos/2367678/medium.jpg?1441941708', + 'id': 2367678, + 'license_code': 'cc-by-nc-nd', + 'original_dimensions': {'width': 900, 'height': 1200}, + 'url': 'https://static.inaturalist.org/photos/2367678/square.jpg?1441941708', + }, + 'ancestor_ids': [ + 48460, + 1, + 47120, + 372739, + 47158, + 184884, + 47201, + 124417, + 326777, + 48740, + 52747, + 343248, + 638567, + ], + 'iconic_taxon_name': 'Insecta', + }, + ], + 'conservation_statuses': [], + 'listed_taxa_count': 0, + 'listed_taxa': [], + 'wikipedia_summary': 'The Polistinae are eusocial wasps closely related to the more familiar yellow jackets, but placed in their own subfamily, containing four tribes; with some 1100 species total, it is the second-most diverse subfamily within the Vespidae, and while most species are tropical or subtropical, they include some of the most frequently encountered large wasps in temperate regions. They are also known as paper wasps, which is a misleading term since other wasps (including yellow jackets) also...', + } + ], +} diff --git a/test/test_api_requests.py b/test/test_api_requests.py index 47c08705..31dac1d7 100644 --- a/test/test_api_requests.py +++ b/test/test_api_requests.py @@ -7,77 +7,82 @@ # Just test that the wrapper methods call requests.request with the appropriate HTTP method @pytest.mark.parametrize( - "function, http_method", - [(delete, "DELETE"), (get, "GET"), (post, "POST"), (put, "PUT")], + 'http_func, http_method', + [(delete, 'DELETE'), (get, 'GET'), (post, 'POST'), (put, 'PUT')], ) -@patch("pyinaturalist.api_requests.request") -def test_http_methods(mock_request, function, http_method): - function("https://url", param="value") - mock_request.assert_called_with(http_method, "https://url", param="value") +@patch('pyinaturalist.api_requests.requests.Session.request') +def test_http_methods(mock_request, http_func, http_method): + http_func('https://url', params={'key': 'value'}) + mock_request.assert_called_with( + http_method, + 'https://url', + params={'key': 'value'}, + headers={'Accept': 'application/json', 'User-Agent': pyinaturalist.user_agent}, + ) # Test that the requests() wrapper passes along expected headers; just tests kwargs, not mock response @pytest.mark.parametrize( - "input_kwargs, expected_headers", + 'input_kwargs, expected_headers', [ - ({}, {"Accept": "application/json", "User-Agent": pyinaturalist.user_agent}), + ({}, {'Accept': 'application/json', 'User-Agent': pyinaturalist.user_agent}), ( - {"user_agent": "CustomUA"}, - {"Accept": "application/json", "User-Agent": "CustomUA"}, + {'user_agent': 'CustomUA'}, + {'Accept': 'application/json', 'User-Agent': 'CustomUA'}, ), ( - {"access_token": "token"}, + {'access_token': 'token'}, { - "Accept": "application/json", - "User-Agent": pyinaturalist.user_agent, - "Authorization": "Bearer token", + 'Accept': 'application/json', + 'User-Agent': pyinaturalist.user_agent, + 'Authorization': 'Bearer token', }, ), ], ) -@patch("pyinaturalist.api_requests.requests.request") +@patch('pyinaturalist.api_requests.requests.Session.request') def test_request_headers(mock_request, input_kwargs, expected_headers): - request("GET", "https://url", **input_kwargs) + request('GET', 'https://url', **input_kwargs) request_kwargs = mock_request.call_args[1] - assert request_kwargs["headers"] == expected_headers + assert request_kwargs['headers'] == expected_headers # Test relevant combinations of dry-run settings and HTTP methods @pytest.mark.parametrize( - "enabled_const, enabled_env, write_only_const, write_only_env, method, expected_real_request", + 'enabled_const, enabled_env, write_only_const, write_only_env, method, expected_real_request', [ # DRY_RUN_ENABLED constant or envar should mock GETs, but not DRY_RUN_WRITE_ONLY - (False, False, False, False, "GET", True), - (False, False, True, True, "GET", True), - (False, False, False, False, "HEAD", True), - (True, False, False, False, "GET", False), - (False, True, False, False, "GET", False), + (False, False, False, False, 'GET', True), + (False, False, True, True, 'GET', True), + (False, False, False, False, 'HEAD', True), + (True, False, False, False, 'GET', False), + (False, True, False, False, 'GET', False), # Either DRY_RUN_ENABLED or DRY_RUN_WRITE_ONLY should mock POST requests - (False, False, False, False, "POST", True), - (True, False, False, False, "POST", False), - (False, True, False, False, "POST", False), - (False, False, True, False, "POST", False), - (False, False, False, True, "POST", False), - (False, False, True, False, "POST", False), + (False, False, False, False, 'POST', True), + (True, False, False, False, 'POST', False), + (False, True, False, False, 'POST', False), + (False, False, True, False, 'POST', False), + (False, False, False, True, 'POST', False), + (False, False, True, False, 'POST', False), # Same for the other write methods - (False, False, False, False, "PUT", True), - (False, False, False, False, "DELETE", True), - (False, False, False, True, "PUT", False), - (False, False, False, True, "DELETE", False), + (False, False, False, False, 'PUT', True), + (False, False, False, False, 'DELETE', True), + (False, False, False, True, 'PUT', False), + (False, False, False, True, 'DELETE', False), # Truthy environment variable strings should be respected - (False, "true", False, False, "GET", False), - (False, "True", False, "False", "PUT", False), - (False, False, False, "True", "DELETE", False), - # As well as "falsy" environment variable strings - (False, "false", False, False, "GET", True), - (False, "none", False, "False", "POST", True), - (False, False, False, "None", "DELETE", True), + (False, 'true', False, False, 'GET', False), + (False, 'True', False, 'False', 'PUT', False), + (False, False, False, 'True', 'DELETE', False), + # As well as 'falsy' environment variable strings + (False, 'false', False, False, 'GET', True), + (False, 'none', False, 'False', 'POST', True), + (False, False, False, 'None', 'DELETE', True), ], ) -@patch("pyinaturalist.api_requests.getenv") -@patch("pyinaturalist.api_requests.requests") +@patch('pyinaturalist.api_requests.getenv') +@patch('pyinaturalist.api_requests.requests.Session.request') def test_request_dry_run( - mock_requests, + mock_request, mock_getenv, enabled_const, enabled_env, @@ -89,31 +94,29 @@ def test_request_dry_run( # Mock any environment variables specified env_vars = {} if enabled_env is not None: - env_vars["DRY_RUN_ENABLED"] = enabled_env + env_vars['DRY_RUN_ENABLED'] = enabled_env if write_only_env is not None: - env_vars["DRY_RUN_WRITE_ONLY"] = write_only_env + env_vars['DRY_RUN_WRITE_ONLY'] = write_only_env mock_getenv.side_effect = env_vars.__getitem__ # Mock constants and run request - with patch("pyinaturalist.api_requests.pyinaturalist") as settings: + with patch('pyinaturalist.api_requests.pyinaturalist') as settings: settings.DRY_RUN_ENABLED = enabled_const settings.DRY_RUN_WRITE_ONLY = write_only_const - response = request(method, "http://url") + response = request(method, 'http://url') # Verify that the request was or wasn't mocked based on settings if expected_real_request: - assert mock_requests.request.call_count == 1 - assert response == mock_requests.request() + assert mock_request.call_count == 1 + assert response == mock_request() else: assert response == MOCK_RESPONSE - assert mock_requests.request.call_count == 0 + assert mock_request.call_count == 0 # In addition to the test cases above, ensure that the request/response isn't altered with dry-run disabled def test_request_dry_run_disabled(requests_mock): - real_response = {"results": ["I'm a real response object!"]} - requests_mock.get( - "http://url", json={"results": ["I'm a real response object!"]}, status_code=200 - ) + real_response = {'results': ['response object']} + requests_mock.get('http://url', json={'results': ['response object']}, status_code=200) - assert request("GET", "http://url").json() == real_response + assert request('GET', 'http://url').json() == real_response diff --git a/test/test_auth.py b/test/test_auth.py new file mode 100644 index 00000000..90d19058 --- /dev/null +++ b/test/test_auth.py @@ -0,0 +1,124 @@ +import os +import pytest +from unittest.mock import patch + +from keyring.errors import KeyringError +from requests import HTTPError + +from pyinaturalist.auth import get_access_token, get_keyring_credentials, set_keyring_credentials +from pyinaturalist.constants import INAT_BASE_URL +from pyinaturalist.exceptions import AuthenticationError +from test.conftest import MOCK_CREDS_ENV, MOCK_CREDS_OAUTH, load_sample_data + +token_accepted_json = load_sample_data('get_access_token.json') +token_rejected_json = load_sample_data('get_access_token_401.json') + + +# get_access_token +# -------------------- + + +@patch.dict(os.environ, {}, clear=True) +def test_get_access_token(requests_mock): + requests_mock.post( + f'{INAT_BASE_URL}/oauth/token', + json=token_accepted_json, + status_code=200, + ) + + token = get_access_token('valid_username', 'valid_password', 'valid_app_id', 'valid_app_secret') + assert token == '604e5df329b98eecd22bb0a84f88b68' + + +@patch.dict(os.environ, MOCK_CREDS_ENV) +@patch('pyinaturalist.auth.get_keyring_credentials') +def test_get_access_token__envars(mock_keyring_credentials, requests_mock): + requests_mock.post( + f'{INAT_BASE_URL}/oauth/token', + json=token_accepted_json, + status_code=200, + ) + + token = get_access_token() + assert token == '604e5df329b98eecd22bb0a84f88b68' + mock_keyring_credentials.assert_not_called() + + +@patch.dict( + os.environ, + { + 'INAT_USERNAME': 'valid_username', + 'INAT_PASSWORD': 'valid_password', + }, +) +def test_get_access_token__mixed_args_and_envars(requests_mock): + requests_mock.post( + f'{INAT_BASE_URL}/oauth/token', + json=token_accepted_json, + status_code=200, + ) + + token = get_access_token(app_id='valid_app_id', app_secret='valid_app_secret') + assert token == '604e5df329b98eecd22bb0a84f88b68' + + +@patch.dict(os.environ, {}, clear=True) +@patch('pyinaturalist.auth.get_keyring_credentials', return_value=MOCK_CREDS_OAUTH) +@patch('pyinaturalist.auth.post') +def test_get_access_token__keyring(mock_post, mock_keyring_credentials, requests_mock): + requests_mock.post( + f'{INAT_BASE_URL}/oauth/token', + json=token_accepted_json, + status_code=200, + ) + + get_access_token() + submitted_json = mock_post.call_args[1]['json'] + assert submitted_json == {'grant_type': 'password', **MOCK_CREDS_OAUTH} + mock_keyring_credentials.assert_called() + + +@patch.dict(os.environ, {}, clear=True) +@patch('pyinaturalist.auth.get_keyring_credentials') +def test_get_access_token__missing_creds(mock_keyring_credentials): + with pytest.raises(AuthenticationError): + get_access_token('username') + + +def test_get_access_token__invalid_creds(requests_mock): + requests_mock.post( + f'{INAT_BASE_URL}/oauth/token', + json=token_rejected_json, + status_code=401, + ) + + with pytest.raises(HTTPError): + get_access_token('username', 'password', 'app_id', 'app_secret') + + +# get_keyring_credentials +# ------------------------- + + +@patch('keyring.get_password', side_effect=list(MOCK_CREDS_OAUTH.values())) +def test_get_keyring_credentials(get_password): + assert get_keyring_credentials() == MOCK_CREDS_OAUTH + + +def test_get_keyring_credentials__not_installed(): + pass + + +@patch('keyring.get_password', side_effect=KeyringError) +def test_get_keyring_credentials__no_backend(get_password): + assert get_keyring_credentials() == {} + + +# get_keyring_credentials +# ------------------------- + + +@patch('keyring.set_password') +def test_set_keyring_credentials(set_password): + set_keyring_credentials('username', 'password', 'app_id', 'app_secret') + assert set_password.call_count == 4 diff --git a/test/test_forge_utils.py b/test/test_forge_utils.py index f7010db1..f8780080 100644 --- a/test/test_forge_utils.py +++ b/test/test_forge_utils.py @@ -1,4 +1,4 @@ -from pyinaturalist.forge_utils import document_request_params, copy_docstrings, copy_signatures +from pyinaturalist.forge_utils import copy_docstrings, copy_signatures, document_request_params def test_document_request_params(): diff --git a/test/test_node_api.py b/test/test_node_api.py index 7a4d559c..1e515e6b 100644 --- a/test/test_node_api.py +++ b/test/test_node_api.py @@ -1,29 +1,77 @@ +import os import pytest -from urllib.parse import urlencode, urljoin +from datetime import datetime +from dateutil.tz import tzoffset, tzutc +from pprint import pprint from unittest.mock import patch +from urllib.parse import urlencode, urljoin import pyinaturalist from pyinaturalist.constants import INAT_NODE_API_BASE_URL, PER_PAGE_RESULTS +from pyinaturalist.exceptions import ObservationNotFound from pyinaturalist.node_api import ( - get_observation, - get_observations, - get_all_observations, - get_observation_species_counts, get_all_observation_species_counts, + get_all_observations, + get_controlled_terms, get_geojson_observations, + get_observation, + get_observation_histogram, + get_observation_identifiers, + get_observation_observers, + get_observation_species_counts, + get_observations, + get_places_autocomplete, get_places_by_id, get_places_nearby, - get_places_autocomplete, get_projects, get_projects_by_id, get_taxa, - get_taxa_by_id, get_taxa_autocomplete, + get_taxa_by_id, ) -from pyinaturalist.exceptions import ObservationNotFound -from pyinaturalist.rest_api import get_access_token from test.conftest import load_sample_data +# Controlled Terms +# -------------------- + + +def test_get_controlled_terms(requests_mock): + requests_mock.get( + urljoin(INAT_NODE_API_BASE_URL, 'controlled_terms'), + json=load_sample_data('get_controlled_terms.json'), + status_code=200, + ) + response = get_controlled_terms() + assert len(response['results']) == 4 + first_result = response['results'][0] + + assert first_result['id'] == 12 + assert first_result['multivalued'] is True + assert first_result['label'] == 'Plant Phenology' + assert len(first_result['values']) == 4 + assert first_result['values'][0] + assert first_result['values'][0]['id'] == 21 + assert first_result['values'][0]['label'] == 'No Evidence of Flowering' + + +def test_get_controlled_terms_for_taxon(requests_mock): + requests_mock.get( + urljoin(INAT_NODE_API_BASE_URL, 'controlled_terms/for_taxon'), + json=load_sample_data('get_controlled_terms_for_taxon.json'), + status_code=200, + ) + response = get_controlled_terms(47651) + assert len(response['results']) == 1 + first_result = response['results'][0] + + assert first_result['id'] == 9 + assert first_result['multivalued'] is False + assert first_result['label'] == 'Sex' + assert len(first_result['values']) == 3 + assert first_result['values'][0] + assert first_result['values'][0]['id'] == 10 + assert first_result['values'][0]['label'] == 'Female' + # Observations # -------------------- @@ -31,55 +79,73 @@ def test_get_observation(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations"), - json=load_sample_data("get_observation.json"), + urljoin(INAT_NODE_API_BASE_URL, 'observations'), + json=load_sample_data('get_observation.json'), status_code=200, ) observation = get_observation(16227955) - assert observation["quality_grade"] == "research" - assert observation["id"] == 16227955 - assert observation["user"]["login"] == "niconoe" - assert len(observation["photos"]) == 2 + assert observation['quality_grade'] == 'research' + assert observation['id'] == 16227955 + assert observation['user']['login'] == 'niconoe' + assert len(observation['photos']) == 2 + + +def test_get_observation_histogram(requests_mock): + requests_mock.get( + urljoin(INAT_NODE_API_BASE_URL, 'observations/histogram'), + json=load_sample_data('get_observation_histogram_month.json'), + status_code=200, + ) + + histogram = get_observation_histogram( + interval='month', place_id=24, d1='2020-01-01', d2='2020-12-31' + ) + assert len(histogram) == 12 + assert histogram[datetime(2020, 1, 1, 0, 0)] == 272 + assert all([isinstance(k, datetime) for k in histogram.keys()]) def test_get_observations(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations"), - json=load_sample_data("get_observations_node_page1.json"), + urljoin(INAT_NODE_API_BASE_URL, 'observations'), + json=load_sample_data('get_observations_node_page1.json'), status_code=200, ) observations = get_observations( - taxon_name="Danaus plexippus", - created_on="2020-08-27", + taxon_name='Danaus plexippus', + created_on='2020-08-27', photos=True, geo=True, - geoprivacy="open", + geoprivacy='open', place_id=7953, ) - first_result = observations["results"][0] - - assert observations["total_results"] == 2 - assert len(observations["results"]) == 1 - assert first_result["taxon_geoprivacy"] == "open" - assert first_result["observed_on"] == "2020-08-27" - assert first_result["taxon"]["id"] == 48662 - assert len(first_result["place_ids"]) == 13 + first_result = observations['results'][0] + + assert observations['total_results'] == 2 + assert len(observations['results']) == 1 + assert first_result['taxon_geoprivacy'] == 'open' + assert first_result['created_at'] == datetime(2020, 8, 27, 18, 0, 51, tzinfo=tzutc()) + assert first_result['observed_on'] == datetime( + 2020, 8, 27, 8, 57, 22, tzinfo=tzoffset('Etc/UTC', 0) + ) + assert first_result['taxon']['id'] == 48662 + assert len(first_result['place_ids']) == 13 -@patch("pyinaturalist.node_api.sleep") +@patch('pyinaturalist.node_api.sleep') def test_get_all_observations(sleep, requests_mock): # Make response appear as though there are more pages - page_1 = load_sample_data("get_observations_node_page1.json") - page_1["total_results"] = PER_PAGE_RESULTS + 1 - page_2 = load_sample_data("get_observations_node_page2.json") + page_1 = load_sample_data('get_observations_node_page1.json') + page_1['total_results'] = PER_PAGE_RESULTS + 1 + page_2 = load_sample_data('get_observations_node_page2.json') requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations"), + urljoin(INAT_NODE_API_BASE_URL, 'observations'), [ - {"json": page_1, "status_code": 200}, - {"json": page_2, "status_code": 200}, + {'json': page_1, 'status_code': 200}, + {'json': page_2, 'status_code': 200}, ], ) @@ -89,39 +155,71 @@ def test_get_all_observations(sleep, requests_mock): assert len(observations) == 2 +def test_get_observation_observers(requests_mock): + requests_mock.get( + urljoin(INAT_NODE_API_BASE_URL, 'observations/observers'), + json=load_sample_data('get_observation_observers_node_page1.json'), + status_code=200, + ) + + observers = get_observation_observers(place_id=125323) + first_result = observers['results'][0] + + assert observers['total_results'] == 4 + assert len(observers['results']) == 2 + assert first_result['user']['spam'] == False + assert first_result['user']['suspended'] == False + + +def test_get_observation_identifiers(requests_mock): + requests_mock.get( + urljoin(INAT_NODE_API_BASE_URL, 'observations/identifiers'), + json=load_sample_data('get_observation_identifiers_node_page1.json'), + status_code=200, + ) + + identifiers = get_observation_identifiers(place_id=125323, iconic_taxa='Amphibia') + first_result = identifiers['results'][0] + + assert identifiers['total_results'] == 6 + assert len(identifiers['results']) == 3 + assert first_result['user']['spam'] == False + assert first_result['user']['suspended'] == False + + def test_get_geojson_observations(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations"), - json=load_sample_data("get_observation.json"), + urljoin(INAT_NODE_API_BASE_URL, 'observations'), + json=load_sample_data('get_observation.json'), status_code=200, ) geojson = get_geojson_observations(id=16227955) - feature = geojson["features"][0] - assert feature["geometry"]["coordinates"] == [4.360086, 50.646894] - assert feature["properties"]["id"] == 16227955 - assert feature["properties"]["taxon_id"] == 493595 + feature = geojson['features'][0] + assert feature['geometry']['coordinates'] == [4.360086, 50.646894] + assert feature['properties']['id'] == 16227955 + assert feature['properties']['taxon_id'] == 493595 def test_get_geojson_observations__custom_properties(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations"), - json=load_sample_data("get_observation.json"), + urljoin(INAT_NODE_API_BASE_URL, 'observations'), + json=load_sample_data('get_observation.json'), status_code=200, ) - properties = ["taxon_name", "taxon_rank"] + properties = ['taxon_name', 'taxon_rank'] geojson = get_geojson_observations(id=16227955, properties=properties) - feature = geojson["features"][0] - assert feature["properties"]["taxon_name"] == "Lixus bardanae" - assert feature["properties"]["taxon_rank"] == "species" - assert "id" not in feature["properties"] and "taxon_id" not in feature["properties"] + feature = geojson['features'][0] + assert feature['properties']['taxon_name'] == 'Lixus bardanae' + assert feature['properties']['taxon_rank'] == 'species' + assert 'id' not in feature['properties'] and 'taxon_id' not in feature['properties'] def test_get_non_existent_observation(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations"), - json=load_sample_data("get_nonexistent_observation.json"), + urljoin(INAT_NODE_API_BASE_URL, 'observations'), + json=load_sample_data('get_nonexistent_observation.json'), status_code=200, ) with pytest.raises(ObservationNotFound): @@ -130,49 +228,49 @@ def test_get_non_existent_observation(requests_mock): def test_get_observation_species_counts(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations/species_counts"), - json=load_sample_data("get_observation_species_counts.json"), + urljoin(INAT_NODE_API_BASE_URL, 'observations/species_counts'), + json=load_sample_data('get_observation_species_counts.json'), status_code=200, ) - response = get_observation_species_counts(user_login="my_username", quality_grade="research") - first_result = response["results"][0] + response = get_observation_species_counts(user_login='my_username', quality_grade='research') + first_result = response['results'][0] - assert first_result["count"] == 31 - assert first_result["taxon"]["id"] == 48484 - assert first_result["taxon"]["name"] == "Harmonia axyridis" + assert first_result['count'] == 31 + assert first_result['taxon']['id'] == 48484 + assert first_result['taxon']['name'] == 'Harmonia axyridis' def test_get_all_observation_species_counts(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations/species_counts"), + urljoin(INAT_NODE_API_BASE_URL, 'observations/species_counts'), [ { - "json": load_sample_data("get_all_observation_species_counts_page1.json"), - "status_code": 200, + 'json': load_sample_data('get_all_observation_species_counts_page1.json'), + 'status_code': 200, }, { - "json": load_sample_data("get_all_observation_species_counts_page2.json"), - "status_code": 200, + 'json': load_sample_data('get_all_observation_species_counts_page2.json'), + 'status_code': 200, }, ], ) response = get_all_observation_species_counts( - user_login="my_username", quality_grade="research" + user_login='my_username', quality_grade='research' ) first_result = response[0] last_result = response[-1] - assert first_result["count"] == 19 - assert first_result["taxon"]["id"] == 27805 - assert first_result["taxon"]["name"] == "Notophthalmus viridescens" - assert last_result["count"] == 1 - assert last_result["taxon"]["id"] == 39556 - assert last_result["taxon"]["name"] == "Apalone spinifera" + assert first_result['count'] == 19 + assert first_result['taxon']['id'] == 27805 + assert first_result['taxon']['name'] == 'Notophthalmus viridescens' + assert last_result['count'] == 1 + assert last_result['taxon']['id'] == 39556 + assert last_result['taxon']['name'] == 'Apalone spinifera' def test_get_observation_species_counts__invalid_multiple_choice_params(): with pytest.raises(ValueError): - get_observation_species_counts(quality_grade="None", iconic_taxa="slime molds") + get_observation_species_counts(quality_grade='None', iconic_taxa='slime molds') # Places @@ -181,23 +279,23 @@ def test_get_observation_species_counts__invalid_multiple_choice_params(): def test_get_places_by_id(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "places/93735,89191"), - json=load_sample_data("get_places_by_id.json"), + urljoin(INAT_NODE_API_BASE_URL, 'places/93735,89191'), + json=load_sample_data('get_places_by_id.json'), status_code=200, ) response = get_places_by_id([93735, 89191]) - result = response["results"][0] + result = response['results'][0] - assert response["total_results"] == len(response["results"]) == 2 - assert result["id"] == 93735 - assert result["name"] == "Springbok" - assert result["bbox_area"] == 0.000993854049 - assert result["location"] == [-29.665119, 17.88583] - assert len(result["ancestor_place_ids"]) == 4 + assert response['total_results'] == len(response['results']) == 2 + assert result['id'] == 93735 + assert result['name'] == 'Springbok' + assert result['bbox_area'] == 0.000993854049 + assert result['location'] == [-29.665119, 17.88583] + assert len(result['ancestor_place_ids']) == 4 -@pytest.mark.parametrize("place_id", ["asdf", [None], [1, "not a number"]]) +@pytest.mark.parametrize('place_id', ['asdf', [None], [1, 'not a number']]) def test_get_places_by_id__invalid_inputs(place_id): with pytest.raises(ValueError): get_places_by_id(place_id) @@ -205,41 +303,41 @@ def test_get_places_by_id__invalid_inputs(place_id): def test_get_places_nearby(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "places/nearby"), - json=load_sample_data("get_places_nearby.json"), + urljoin(INAT_NODE_API_BASE_URL, 'places/nearby'), + json=load_sample_data('get_places_nearby.json'), status_code=200, ) response = get_places_nearby(nelat=150.0, nelng=-50.0, swlat=-149.999, swlng=-49.999) - result = response["results"]["standard"][0] + result = response['results']['standard'][0] - assert response["total_results"] == 20 - assert len(response["results"]["standard"]) + len(response["results"]["community"]) == 20 + assert response['total_results'] == 20 + assert len(response['results']['standard']) + len(response['results']['community']) == 20 - assert result["id"] == 97394 - assert result["admin_level"] == -1 - assert result["name"] == "North America" - assert result["bbox_area"] == 28171.40875125 - assert result["location"] == [56.7732555574, -179.68825] - assert result["ancestor_place_ids"] is None + assert result['id'] == 97394 + assert result['admin_level'] == -1 + assert result['name'] == 'North America' + assert result['bbox_area'] == 28171.40875125 + assert result['location'] == [56.7732555574, -179.68825] + assert result['ancestor_place_ids'] is None def test_get_places_autocomplete(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "places/autocomplete"), - json=load_sample_data("get_places_autocomplete.json"), + urljoin(INAT_NODE_API_BASE_URL, 'places/autocomplete'), + json=load_sample_data('get_places_autocomplete.json'), status_code=200, ) - response = get_places_autocomplete("springbok") - result = response["results"][0] + response = get_places_autocomplete('springbok') + result = response['results'][0] - assert response["total_results"] == len(response["results"]) == 1 - assert result["id"] == 93735 - assert result["name"] == "Springbok" - assert result["bbox_area"] == 0.000993854049 - assert result["location"] == [-29.665119, 17.88583] - assert len(result["ancestor_place_ids"]) == 4 + assert response['total_results'] == len(response['results']) == 1 + assert result['id'] == 93735 + assert result['name'] == 'Springbok' + assert result['bbox_area'] == 0.000993854049 + assert result['location'] == [-29.665119, 17.88583] + assert len(result['ancestor_place_ids']) == 4 # Projects @@ -248,115 +346,119 @@ def test_get_places_autocomplete(requests_mock): def test_get_projects(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "projects"), - json=load_sample_data("get_projects.json"), + urljoin(INAT_NODE_API_BASE_URL, 'projects'), + json=load_sample_data('get_projects.json'), status_code=200, ) - response = get_projects(q="invasive", lat=49.27, lng=-123.08, radius=400, order_by="distance") - first_result = response["results"][0] + response = get_projects(q='invasive', lat=49.27, lng=-123.08, radius=400, order_by='distance') + first_result = response['results'][0] - assert response["total_results"] == len(response["results"]) == 5 - assert first_result["id"] == 8291 - assert first_result["title"] == "PNW Invasive Plant EDDR" - assert first_result["is_umbrella"] is False - assert len(first_result["user_ids"]) == 33 + assert response['total_results'] == len(response['results']) == 5 + assert first_result['id'] == 8291 + assert first_result['title'] == 'PNW Invasive Plant EDDR' + assert first_result['is_umbrella'] is False + assert len(first_result['user_ids']) == 33 + assert first_result['created_at'] == datetime(2016, 7, 20, 23, 0, 5, tzinfo=tzutc()) + assert first_result['updated_at'] == datetime(2020, 7, 28, 20, 9, 49, tzinfo=tzutc()) def test_get_projects_by_id(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "projects/8348,6432"), - json=load_sample_data("get_projects_by_id.json"), + urljoin(INAT_NODE_API_BASE_URL, 'projects/8348,6432'), + json=load_sample_data('get_projects_by_id.json'), status_code=200, ) response = get_projects_by_id([8348, 6432]) - first_result = response["results"][0] + first_result = response['results'][0] - assert response["total_results"] == len(response["results"]) == 2 - assert first_result["id"] == 8348 - assert first_result["title"] == "Tucson High Native and Invasive Species Inventory" - assert first_result["place_id"] == 96103 - assert first_result["location"] == [32.2264416406, -110.9617278383] + assert response['total_results'] == len(response['results']) == 2 + assert first_result['id'] == 8348 + assert first_result['title'] == 'Tucson High Native and Invasive Species Inventory' + assert first_result['place_id'] == 96103 + assert first_result['location'] == [32.2264416406, -110.9617278383] + assert first_result['created_at'] == datetime(2016, 7, 26, 23, 8, 47, tzinfo=tzutc()) + assert first_result['updated_at'] == datetime(2017, 9, 16, 1, 51, 1, tzinfo=tzutc()) # Taxa # -------------------- -CLASS_AND_HIGHER = ["class", "superclass", "subphylum", "phylum", "kingdom"] -SPECIES_AND_LOWER = ["form", "variety", "subspecies", "hybrid", "species"] -CLASS_THOUGH_PHYLUM = ["class", "superclass", "subphylum", "phylum"] +CLASS_AND_HIGHER = ['class', 'superclass', 'subphylum', 'phylum', 'kingdom'] +SPECIES_AND_LOWER = ['form', 'variety', 'subspecies', 'hybrid', 'species'] +CLASS_THOUGH_PHYLUM = ['class', 'superclass', 'subphylum', 'phylum'] def test_get_taxa(requests_mock): - params = urlencode({"q": "vespi", "rank": "genus,subgenus,species"}) + params = urlencode({'q': 'vespi', 'rank': 'genus,subgenus,species'}) requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "taxa?" + params), - json=load_sample_data("get_taxa.json"), + urljoin(INAT_NODE_API_BASE_URL, 'taxa?' + params), + json=load_sample_data('get_taxa.json'), status_code=200, ) - response = get_taxa(q="vespi", rank=["genus", "subgenus", "species"]) - first_result = response["results"][0] + response = get_taxa(q='vespi', rank=['genus', 'subgenus', 'species']) + first_result = response['results'][0] - assert len(response["results"]) == 30 - assert response["total_results"] == 35 - assert first_result["id"] == 70118 - assert first_result["name"] == "Nicrophorus vespilloides" - assert first_result["rank"] == "species" - assert first_result["is_active"] is True - assert len(first_result["ancestor_ids"]) == 14 + assert len(response['results']) == 30 + assert response['total_results'] == 35 + assert first_result['id'] == 70118 + assert first_result['name'] == 'Nicrophorus vespilloides' + assert first_result['rank'] == 'species' + assert first_result['is_active'] is True + assert len(first_result['ancestor_ids']) == 14 @pytest.mark.parametrize( - "params, expected_ranks", + 'params, expected_ranks', [ - ({"rank": "genus"}, "genus"), - ({"min_rank": "class"}, CLASS_AND_HIGHER), - ({"max_rank": "species"}, SPECIES_AND_LOWER), - ({"min_rank": "class", "max_rank": "phylum"}, CLASS_THOUGH_PHYLUM), - ({"max_rank": "species", "rank": "override_me"}, SPECIES_AND_LOWER), + ({'rank': 'genus'}, 'genus'), + ({'min_rank': 'class'}, CLASS_AND_HIGHER), + ({'max_rank': 'species'}, SPECIES_AND_LOWER), + ({'min_rank': 'class', 'max_rank': 'phylum'}, CLASS_THOUGH_PHYLUM), + ({'max_rank': 'species', 'rank': 'override_me'}, SPECIES_AND_LOWER), ], ) -@patch("pyinaturalist.node_api.make_inaturalist_api_get_call") +@patch('pyinaturalist.node_api.get') def test_get_taxa_by_rank_range( - mock_inaturalist_api_get_call, + mock_get, params, expected_ranks, ): # Make sure custom rank params result in the correct 'rank' param value get_taxa(**params) - kwargs = mock_inaturalist_api_get_call.call_args[1] - requested_rank = kwargs["params"]["rank"] + kwargs = mock_get.call_args[1] + requested_rank = kwargs['params']['rank'] assert requested_rank == expected_ranks # This is just a spot test of a case in which boolean params should be converted -@patch("pyinaturalist.api_requests.requests.request") +@patch('pyinaturalist.api_requests.requests.Session.request') def test_get_taxa_by_name_and_is_active(request): - get_taxa(q="Lixus bardanae", is_active=False) + get_taxa(q='Lixus bardanae', is_active=False) request_kwargs = request.call_args[1] - assert request_kwargs["params"] == {"q": "Lixus bardanae", "is_active": "false"} + assert request_kwargs['params'] == {'q': 'Lixus bardanae', 'is_active': 'false'} def test_get_taxa_by_id(requests_mock): taxon_id = 70118 requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "taxa/" + str(taxon_id)), - json=load_sample_data("get_taxa_by_id.json"), + urljoin(INAT_NODE_API_BASE_URL, 'taxa/' + str(taxon_id)), + json=load_sample_data('get_taxa_by_id.json'), status_code=200, ) response = get_taxa_by_id(taxon_id) - result = response["results"][0] - assert response["total_results"] == len(response["results"]) == 1 - assert result["id"] == taxon_id - assert result["name"] == "Nicrophorus vespilloides" - assert result["rank"] == "species" - assert result["is_active"] is True - assert len(result["ancestors"]) == 12 + result = response['results'][0] + assert response['total_results'] == len(response['results']) == 1 + assert result['id'] == taxon_id + assert result['name'] == 'Nicrophorus vespilloides' + assert result['rank'] == 'species' + assert result['is_active'] is True + assert len(result['ancestors']) == 12 -@pytest.mark.parametrize("taxon_id", ["asdf", [None], [1, "not a number"]]) +@pytest.mark.parametrize('taxon_id', ['asdf', [None], [1, 'not a number']]) def test_get_taxa_by_id__invalid_inputs(taxon_id): with pytest.raises(ValueError): get_taxa_by_id(taxon_id) @@ -364,117 +466,81 @@ def test_get_taxa_by_id__invalid_inputs(taxon_id): def test_get_taxa_autocomplete(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "taxa/autocomplete"), - json=load_sample_data("get_taxa_autocomplete.json"), + urljoin(INAT_NODE_API_BASE_URL, 'taxa/autocomplete'), + json=load_sample_data('get_taxa_autocomplete.json'), status_code=200, ) - response = get_taxa_autocomplete(q="vespi") - first_result = response["results"][0] + response = get_taxa_autocomplete(q='vespi') + first_result = response['results'][0] - assert len(response["results"]) == 10 - assert response["total_results"] == 47 - assert first_result["matched_term"] == "Vespidae" - assert first_result["id"] == 52747 - assert first_result["name"] == "Vespidae" - assert first_result["rank"] == "family" - assert first_result["is_active"] is True - assert len(first_result["ancestor_ids"]) == 11 + assert len(response['results']) == 10 + assert response['total_results'] == 47 + assert first_result['matched_term'] == 'Vespidae' + assert first_result['id'] == 52747 + assert first_result['name'] == 'Vespidae' + assert first_result['rank'] == 'family' + assert first_result['is_active'] is True + assert len(first_result['ancestor_ids']) == 11 # Test usage of format_taxon() with get_taxa_autocomplete() def test_get_taxa_autocomplete_minified(requests_mock): requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "taxa/autocomplete"), - json=load_sample_data("get_taxa_autocomplete.json"), + urljoin(INAT_NODE_API_BASE_URL, 'taxa/autocomplete'), + json=load_sample_data('get_taxa_autocomplete.json'), status_code=200, ) expected_results = [ - " 52747: Family Vespidae (Hornets, Paper Wasps, Potter Wasps, and Allies)", - " 84738: Subfamily Vespinae (Hornets and Yellowjackets)", - " 131878: Species Nicrophorus vespillo (Vespillo Burying Beetle)", - " 621585: Species Vespicula trachinoides (Vespicula Waspfish)", - " 495392: Species Vespidae st1", - " 70118: Species Nicrophorus vespilloides (Lesser Vespillo Burying Beetle)", - " 92786: Genus Vespicula", - " 646195: Genus Vespiodes", - " 621584: Species Vespicula cypho", - " 621586: Species Vespicula zollingeri", + ' 52747: Family Vespidae (Hornets, Paper Wasps, Potter Wasps, and Allies)', + ' 84738: Subfamily Vespinae (Hornets and Yellowjackets)', + ' 131878: Species Nicrophorus vespillo (Vespillo Burying Beetle)', + ' 621585: Species Vespicula trachinoides (Vespicula Waspfish)', + ' 495392: Species Vespidae st1', + ' 70118: Species Nicrophorus vespilloides (Lesser Vespillo Burying Beetle)', + ' 92786: Genus Vespicula', + ' 646195: Genus Vespiodes', + ' 621584: Species Vespicula cypho', + ' 621586: Species Vespicula zollingeri', ] - response = get_taxa_autocomplete(q="vespi", minify=True) - print(response["results"]) - assert response["results"] == expected_results + response = get_taxa_autocomplete(q='vespi', minify=True) + pprint(response['results']) + assert response['results'] == expected_results +@patch.dict(os.environ, {'GITHUB_REF': 'refs/heads/master'}, clear=True) def test_user_agent(requests_mock): - # TODO: test for all functions that access the inaturalist API? requests_mock.get( - urljoin(INAT_NODE_API_BASE_URL, "observations"), - json=load_sample_data("get_observation.json"), - status_code=200, - ) - accepted_json = { - "access_token": "604e5df329b98eecd22bb0a84f88b68a075a023ac437f2317b02f3a9ba414a08", - "token_type": "Bearer", - "scope": "write", - "created_at": 1539352135, - } - requests_mock.post( - "https://www.inaturalist.org/oauth/token", - json=accepted_json, + urljoin(INAT_NODE_API_BASE_URL, 'observations'), + json=load_sample_data('get_observation.json'), status_code=200, ) - - default_ua = "Pyinaturalist/{v}".format(v=pyinaturalist.__version__) + default_ua = pyinaturalist.DEFAULT_USER_AGENT # By default, we have a 'Pyinaturalist' user agent: get_observation(16227955) - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == default_ua - get_access_token("valid_username", "valid_password", "valid_app_id", "valid_app_secret") - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == default_ua + assert requests_mock._adapter.last_request._request.headers['User-Agent'] == default_ua # But if the user sets a custom one, it is indeed used: - get_observation(16227955, user_agent="CustomUA") - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "CustomUA" - get_access_token( - "valid_username", - "valid_password", - "valid_app_id", - "valid_app_secret", - user_agent="CustomUA", - ) - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "CustomUA" + get_observation(16227955, user_agent='CustomUA') + assert requests_mock._adapter.last_request._request.headers['User-Agent'] == 'CustomUA' # We can also set it globally: - pyinaturalist.user_agent = "GlobalUA" + pyinaturalist.user_agent = 'GlobalUA' get_observation(16227955) - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "GlobalUA" - get_access_token("valid_username", "valid_password", "valid_app_id", "valid_app_secret") - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "GlobalUA" + assert requests_mock._adapter.last_request._request.headers['User-Agent'] == 'GlobalUA' # And it persists across requests: get_observation(16227955) - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "GlobalUA" - get_access_token("valid_username", "valid_password", "valid_app_id", "valid_app_secret") - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "GlobalUA" + assert requests_mock._adapter.last_request._request.headers['User-Agent'] == 'GlobalUA' # But if we have a global and local one, the local has priority - get_observation(16227955, user_agent="CustomUA 2") - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "CustomUA 2" - get_access_token( - "valid_username", - "valid_password", - "valid_app_id", - "valid_app_secret", - user_agent="CustomUA 2", - ) - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == "CustomUA 2" + get_observation(16227955, user_agent='CustomUA 2') + assert requests_mock._adapter.last_request._request.headers['User-Agent'] == 'CustomUA 2' # We can reset the global settings to the default: pyinaturalist.user_agent = pyinaturalist.DEFAULT_USER_AGENT get_observation(16227955) - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == default_ua - get_access_token("valid_username", "valid_password", "valid_app_id", "valid_app_secret") - assert requests_mock._adapter.last_request._request.headers["User-Agent"] == default_ua + assert requests_mock._adapter.last_request._request.headers['User-Agent'] == default_ua diff --git a/test/test_request_params.py b/test/test_request_params.py index 8871a7d9..ef826210 100644 --- a/test/test_request_params.py +++ b/test/test_request_params.py @@ -1,3 +1,4 @@ +import os import pytest from datetime import date, datetime from dateutil.tz import gettz @@ -5,56 +6,56 @@ from tempfile import NamedTemporaryFile from unittest.mock import patch +import pyinaturalist.node_api +import pyinaturalist.rest_api from pyinaturalist.request_params import ( - preprocess_request_params, convert_bool_params, convert_datetime_params, convert_list_params, convert_observation_fields, ensure_file_obj, + get_interval_ranges, + preprocess_request_params, strip_empty_params, validate_ids, validate_multiple_choice_param, validate_multiple_choice_params, ) -import pyinaturalist.rest_api -import pyinaturalist.node_api -from test.conftest import get_module_http_functions, get_mock_args_for_signature - +from test.conftest import MOCK_CREDS_ENV, get_mock_args_for_signature, get_module_http_functions TEST_PARAMS = { - "is_active": False, - "only_id": "true", - "preferred_place_id": [1, 2], - "rank": ["phylum", "class"], - "q": "", - "locale": None, - "parent_id": 1, - "observation_fields": {1: "value"}, + 'is_active': False, + 'only_id': 'true', + 'preferred_place_id': [1, 2], + 'rank': ['phylum', 'class'], + 'q': '', + 'locale': None, + 'parent_id': 1, + 'observation_fields': {1: 'value'}, } def test_convert_bool_params(): params = convert_bool_params(TEST_PARAMS) - assert params["is_active"] == "false" - assert params["only_id"] == "true" + assert params['is_active'] == 'false' + assert params['only_id'] == 'true' # Test some recognized date(time) formats, with and without TZ info, in date and non-date params @pytest.mark.parametrize( - "param, value, expected", + 'param, value, expected', [ - ("created_d1", "19951231T235959", "1995-12-31T23:59:59-08:00"), - ("created_d2", "2008-08-08 08:08:08Z", "2008-08-08T08:08:08+00:00"), - ("created_on", "2010-10-10 10:10:10-05:00", "2010-10-10T10:10:10-05:00"), - ("created_on", "Jan 1 2000", "2000-01-01T00:00:00-08:00"), - ("d1", "19970716", "1997-07-16T00:00:00-07:00"), - ("q", date(1954, 2, 5), "1954-02-05"), - ("q", datetime(1954, 2, 5), "1954-02-05T00:00:00-08:00"), - ("q", "not a datetime", "not a datetime"), + ('created_d1', '19951231T235959', '1995-12-31T23:59:59-08:00'), + ('created_d2', '2008-08-08 08:08:08Z', '2008-08-08T08:08:08+00:00'), + ('created_on', '2010-10-10 10:10:10-05:00', '2010-10-10T10:10:10-05:00'), + ('created_on', 'Jan 1 2000', '2000-01-01T00:00:00-08:00'), + ('d1', '19970716', '1997-07-16T00:00:00-07:00'), + ('q', date(1954, 2, 5), '1954-02-05'), + ('q', datetime(1954, 2, 5), '1954-02-05T00:00:00-08:00'), + ('q', 'not a datetime', 'not a datetime'), ], ) -@patch("pyinaturalist.request_params.tzlocal", return_value=gettz("US/Pacific")) +@patch('pyinaturalist.request_params.tzlocal', return_value=gettz('US/Pacific')) def test_convert_datetime_params(tzlocal, param, value, expected): converted = convert_datetime_params({param: value}) assert converted[param] == expected @@ -63,65 +64,107 @@ def test_convert_datetime_params(tzlocal, param, value, expected): # Test both int and string lists def test_convert_list_params(): params = convert_list_params(TEST_PARAMS) - assert params["preferred_place_id"] == "1,2" - assert params["rank"] == "phylum,class" + assert params['preferred_place_id'] == '1,2' + assert params['rank'] == 'phylum,class' def test_convert_observation_fields(): params = convert_observation_fields(TEST_PARAMS) - assert params["observation_field_values_attributes"] == [ - {"observation_field_id": 1, "value": "value"} + assert params['observation_field_values_attributes'] == [ + {'observation_field_id': 1, 'value': 'value'} ] def test_ensure_file_obj__file_path(): with NamedTemporaryFile() as temp: - temp.write(b"test content") + temp.write(b'test content') temp.seek(0) file_obj = ensure_file_obj(temp.name) - assert file_obj.read() == b"test content" + assert file_obj.read() == b'test content' def test_ensure_file_obj__file_obj(): - file_obj = BytesIO(b"test content") + file_obj = BytesIO(b'test content') assert ensure_file_obj(file_obj) == file_obj +def test_get_interval_ranges__monthly(): + expected_ranges = [ + (datetime(2020, 1, 1, 0, 0), datetime(2020, 1, 31, 0, 0)), + (datetime(2020, 2, 1, 0, 0), datetime(2020, 2, 29, 0, 0)), + (datetime(2020, 3, 1, 0, 0), datetime(2020, 3, 31, 0, 0)), + (datetime(2020, 4, 1, 0, 0), datetime(2020, 4, 30, 0, 0)), + (datetime(2020, 5, 1, 0, 0), datetime(2020, 5, 31, 0, 0)), + (datetime(2020, 6, 1, 0, 0), datetime(2020, 6, 30, 0, 0)), + (datetime(2020, 7, 1, 0, 0), datetime(2020, 7, 31, 0, 0)), + (datetime(2020, 8, 1, 0, 0), datetime(2020, 8, 31, 0, 0)), + (datetime(2020, 9, 1, 0, 0), datetime(2020, 9, 30, 0, 0)), + (datetime(2020, 10, 1, 0, 0), datetime(2020, 10, 31, 0, 0)), + (datetime(2020, 11, 1, 0, 0), datetime(2020, 11, 30, 0, 0)), + (datetime(2020, 12, 1, 0, 0), datetime(2020, 12, 31, 0, 0)), + ] + ranges = get_interval_ranges(datetime(2020, 1, 1), datetime(2020, 12, 31), 'monthly') + assert ranges == expected_ranges + + +def test_get_interval_ranges__yearly(): + expected_ranges = [ + (datetime(2010, 1, 1, 0, 0), datetime(2010, 12, 31, 0, 0)), + (datetime(2011, 1, 1, 0, 0), datetime(2011, 12, 31, 0, 0)), + (datetime(2012, 1, 1, 0, 0), datetime(2012, 12, 31, 0, 0)), + (datetime(2013, 1, 1, 0, 0), datetime(2013, 12, 31, 0, 0)), + (datetime(2014, 1, 1, 0, 0), datetime(2014, 12, 31, 0, 0)), + (datetime(2015, 1, 1, 0, 0), datetime(2015, 12, 31, 0, 0)), + (datetime(2016, 1, 1, 0, 0), datetime(2016, 12, 31, 0, 0)), + (datetime(2017, 1, 1, 0, 0), datetime(2017, 12, 31, 0, 0)), + (datetime(2018, 1, 1, 0, 0), datetime(2018, 12, 31, 0, 0)), + (datetime(2019, 1, 1, 0, 0), datetime(2019, 12, 31, 0, 0)), + (datetime(2020, 1, 1, 0, 0), datetime(2020, 12, 31, 0, 0)), + ] + ranges = get_interval_ranges(datetime(2010, 1, 1), datetime(2020, 1, 1), 'yearly') + assert ranges == expected_ranges + + +def test_get_interval_ranges__invalid(): + with pytest.raises(ValueError): + get_interval_ranges(datetime(2020, 1, 1), datetime(2020, 12, 31), 'daily') + + def test_strip_empty_params(): params = strip_empty_params(TEST_PARAMS) assert len(params) == 6 - assert "q" not in params and "locale" not in params - assert "is_active" in params and "only_id" in params + assert 'q' not in params and 'locale' not in params + assert 'is_active' in params and 'only_id' in params @pytest.mark.parametrize( - "value, expected", + 'value, expected', [ - ("1", "1"), - (1, "1"), - ("1,2,3", "1,2,3"), - ([1, 2, 3], "1,2,3"), - ([1e5, 2e5], "100000,200000"), + ('1', '1'), + (1, '1'), + ('1,2,3', '1,2,3'), + ([1, 2, 3], '1,2,3'), + ([1e5, 2e5], '100000,200000'), ], ) def test_validate_ids(value, expected): assert validate_ids(value) == expected -@pytest.mark.parametrize("value", ["NaN", "", None, []]) +@pytest.mark.parametrize('value', ['NaN', '', None, []]) def test_validate_ids__invalid(value): with pytest.raises(ValueError): - validate_ids("not a number") + validate_ids('not a number') # This is just here so that tests will fail if one of the conversion steps is removed -@patch("pyinaturalist.request_params.convert_bool_params") -@patch("pyinaturalist.request_params.convert_datetime_params") -@patch("pyinaturalist.request_params.convert_list_params") -@patch("pyinaturalist.request_params.strip_empty_params") +@patch('pyinaturalist.request_params.convert_bool_params') +@patch('pyinaturalist.request_params.convert_datetime_params') +@patch('pyinaturalist.request_params.convert_list_params') +@patch('pyinaturalist.request_params.strip_empty_params') def test_preprocess_request_params(mock_bool, mock_datetime, mock_list, mock_strip): - preprocess_request_params({"id": 1}) + preprocess_request_params({'id': 1}) assert all([mock_bool.called, mock_datetime.called, mock_list.called, mock_strip.called]) @@ -129,47 +172,53 @@ def test_preprocess_request_params(mock_bool, mock_datetime, mock_list, mock_str # Almost all logic except the request is mocked out so this can generically apply to all API functions # Using parametrization here so that on failure, pytest will show the specific function that failed @pytest.mark.parametrize( - "http_function", get_module_http_functions(pyinaturalist.node_api).values() + 'http_function', get_module_http_functions(pyinaturalist.node_api).values() ) -@patch("pyinaturalist.request_params.translate_rank_range") -@patch("pyinaturalist.response_format.as_geojson_feature") -@patch("pyinaturalist.node_api.convert_location_to_float") -@patch("pyinaturalist.node_api._convert_all_locations_to_float") -@patch("pyinaturalist.api_requests.preprocess_request_params") -@patch("pyinaturalist.api_requests.requests.request") +@patch('pyinaturalist.request_params.translate_rank_range') +@patch('pyinaturalist.response_format.as_geojson_feature') +@patch('pyinaturalist.node_api.format_histogram') +@patch('pyinaturalist.node_api.convert_all_coordinates', side_effect=lambda x: x) +@patch('pyinaturalist.node_api.convert_all_place_coordinates', side_effect=lambda x: x) +@patch('pyinaturalist.api_requests.preprocess_request_params') +@patch('pyinaturalist.api_requests.requests.Session.request') def test_all_node_requests_use_param_conversion( request, preprocess_request_params, - convert_all_locations_to_float, - convert_location_to_float, - as_geojson, + convert_all_place_coordinates, + convert_all_coordinates, + format_histogram, + as_geojson_feature, translate_rank_range, http_function, ): - request().json.return_value = {"total_results": 1, "results": [{}]} + request().json.return_value = {'total_results': 1, 'results': [{'id': 1}]} mock_args = get_mock_args_for_signature(http_function) http_function(*mock_args) assert preprocess_request_params.call_count == 1 @pytest.mark.parametrize( - "http_function", get_module_http_functions(pyinaturalist.rest_api).values() + 'http_function', get_module_http_functions(pyinaturalist.rest_api).values() ) -@patch("pyinaturalist.rest_api.convert_lat_long_to_float") -@patch("pyinaturalist.rest_api.sleep") -@patch("pyinaturalist.api_requests.preprocess_request_params") -@patch("pyinaturalist.api_requests.requests.request") +@patch.dict(os.environ, MOCK_CREDS_ENV) +@patch('pyinaturalist.rest_api.convert_all_coordinates') +@patch('pyinaturalist.rest_api.sleep') +@patch('pyinaturalist.api_requests.preprocess_request_params') +@patch('pyinaturalist.api_requests.requests.Session.request') def test_all_rest_requests_use_param_conversion( - request, preprocess_request_params, sleep, convert_lat_long_to_float, http_function + request, preprocess_request_params, sleep, convert_all_place_coordinates, http_function ): # Handle the one API response that returns a list instead of a dict - if http_function == pyinaturalist.rest_api.get_all_observation_fields: + if http_function in [ + pyinaturalist.rest_api.get_observation_fields, + pyinaturalist.rest_api.get_all_observation_fields, + ]: request().json.return_value = [] else: request().json.return_value = { - "total_results": 1, - "access_token": "", - "results": [], + 'total_results': 1, + 'access_token': '', + 'results': [], } mock_args = get_mock_args_for_signature(http_function) @@ -179,39 +228,57 @@ def test_all_rest_requests_use_param_conversion( def test_validate_multiple_choice_param(): params = { - "param1": "valid_str", - "param2": "invalid_str", + 'param1': 'valid_str', + 'param2': 'invalid_str', } - choices = ["str1", "str2", "valid_str"] + choices = ['str1', 'str2', 'valid_str'] - validate_multiple_choice_param(params, "param1", choices) - assert True + validated_params = validate_multiple_choice_param(params, 'param1', choices) + assert params == validated_params with pytest.raises(ValueError): - validate_multiple_choice_param(params, "param2", choices) + validate_multiple_choice_param(params, 'param2', choices) @pytest.mark.parametrize( - "params", + 'params', [ - {"csi": "LC"}, - {"csi": ["EW", "EX"]}, - {"geoprivacy": "open"}, - {"iconic_taxa": "Animalia"}, - {"identifications": "most_agree"}, - {"license": "CC-BY-NC"}, - {"rank": "order"}, - {"quality_grade": "research"}, - {"search_on": "tags"}, - {"geoprivacy": ["open", "obscured"]}, - {"geoprivacy": "open", "iconic_taxa": "Animalia", "license": "CC-BY-NC"}, + {'csi': 'LC'}, + {'csi': ['EW', 'EX']}, + {'geoprivacy': 'open'}, + {'iconic_taxa': 'Animalia'}, + {'identifications': 'most_agree'}, + {'license': 'CC-BY-NC'}, + {'rank': 'order'}, + {'quality_grade': 'research'}, + {'search_on': 'tags'}, + {'geoprivacy': ['open', 'obscured']}, + {'geoprivacy': 'open', 'iconic_taxa': 'Animalia', 'license': 'CC-BY-NC'}, ], ) def test_validate_multiple_choice_params(params): - # If valid, there is no return value, but an exception should not be raised + # If valid, no exception should not be raised validate_multiple_choice_params(params) + # If invalid, an exception should be raised with pytest.raises(ValueError): - validate_multiple_choice_params({k: "Invalid_value" for k in params}) + validate_multiple_choice_params({k: 'Invalid_value' for k in params}) + # A valid + invalid value should also raise an exception + def append_invalid_value(value): + return [*value, 'Invalid_value'] if isinstance(value, list) else [value, 'Invalid_value'] + with pytest.raises(ValueError): - validate_multiple_choice_params({k: [v, "Invalid_value"] for k, v in params.items()}) + validate_multiple_choice_params({k: append_invalid_value(v) for k, v in params.items()}) + + +@pytest.mark.parametrize( + 'params, expected_value', + [ + ({'identifications': 'most agree'}, 'most_agree'), + ({'interval': 'month of year'}, 'month_of_year'), + ], +) +def test_validate_multiple_choice_params__normalization(params, expected_value): + validated_params = validate_multiple_choice_params(params) + value = next(iter(validated_params.values())) + assert value == expected_value diff --git a/test/test_response_format.py b/test/test_response_format.py new file mode 100644 index 00000000..6e20b53e --- /dev/null +++ b/test/test_response_format.py @@ -0,0 +1,134 @@ +import pytest +from datetime import datetime +from dateutil.tz import tzoffset + +from pyinaturalist.response_format import ( + convert_observation_timestamps, + convert_offset, + format_histogram, + format_observation, + format_taxon, +) +from test.conftest import load_sample_data + + +def test_format_histogram__datetime_keys(): + response = load_sample_data('get_observation_histogram_month.json') + histogram = format_histogram(response) + assert histogram[datetime(2020, 1, 1, 0, 0)] == 272 + assert all([isinstance(k, datetime) for k in histogram.keys()]) + assert all([isinstance(v, int) for v in histogram.values()]) + + +def test_format_histogram__int_keys(): + response = load_sample_data('get_observation_histogram_month_of_year.json') + histogram = format_histogram(response) + assert histogram[1] == 272 + assert all([isinstance(k, int) for k in histogram.keys()]) + assert all([isinstance(v, int) for v in histogram.values()]) + + +def test_format_observation(): + observation = load_sample_data('get_observation.json')['results'][0] + assert format_observation(observation) == ( + '[16227955] Species: Lixus bardanae ' + 'observed by niconoe on 2018-09-05 at 54 rue des Badauds' + ) + + +def test_format_taxon__with_common_name(): + taxon = load_sample_data('get_taxa.json')['results'][0] + assert ( + format_taxon(taxon) == 'Species: Nicrophorus vespilloides (Lesser Vespillo Burying Beetle)' + ) + + +def test_format_taxon__without_common_name(): + taxon = load_sample_data('get_taxa.json')['results'][2] + assert format_taxon(taxon) == 'Species: Temnostoma vespiforme' + + +def test_format_taxon__invalid(): + assert format_taxon(None) == 'unknown taxon' + + +@pytest.mark.parametrize( + 'observed_on, created_at, tz_offset, tz_name, expected_observed, expected_created', + [ + ( + 'Sat Sep 26 2020 12:09:51 GMT-0700 (PDT)', + None, + '-07:00', + None, + datetime(2020, 9, 26, 12, 9, 51, tzinfo=tzoffset(None, -25200)), + None, + ), + ( + '2020-09-27 9:20:02 AM PST', + '2020-10-01', + 'GMT-08:00', + 'PST', + datetime(2020, 9, 27, 9, 20, 2, tzinfo=tzoffset('PST', -28800)), + datetime(2020, 10, 1, tzinfo=tzoffset('PST', -28800)), + ), + ( + 'Dec 04 2020 21:00:00', + 'Dec 10 2020', + 'UTC +02:00', + 'EET', + datetime(2020, 12, 4, 21, 0, 0, tzinfo=tzoffset('EET', 7200)), + datetime(2020, 12, 10, tzinfo=tzoffset('EET', 7200)), + ), + ( + None, + 'Dec 10 2020', + 'UTC +02:00', + 'EET', + None, + datetime(2020, 12, 10, tzinfo=tzoffset('EET', 7200)), + ), + ], +) +def test_convert_observation_timestamps( + observed_on, created_at, tz_offset, tz_name, expected_observed, expected_created +): + observation = { + 'created_at_details': {'date': created_at}, + 'observed_on_string': observed_on, + 'time_zone_offset': tz_offset, + 'observed_time_zone': tz_name, + } + converted = convert_observation_timestamps(observation) + assert converted['observed_on'] == expected_observed + assert converted['created_at'] == expected_created + + +@pytest.mark.parametrize( + 'datetime_obj, tz_offset, tz_name, expected_date', + [ + ( + datetime(2020, 9, 26, 12, 9, 51), + '-07:00', + None, + datetime(2020, 9, 26, 12, 9, 51, tzinfo=tzoffset(None, -25200)), + ), + ( + datetime(2020, 9, 27, 9, 20, 2), + 'GMT-08:00', + 'PST', + datetime(2020, 9, 27, 9, 20, 2, tzinfo=tzoffset('PST', -28800)), + ), + ( + datetime(2020, 12, 4, 21, 0, 0), + 'UTC +02:00', + 'EET', + datetime(2020, 12, 4, 21, 0, 0, tzinfo=tzoffset('EET', 7200)), + ), + ], +) +def test_convert_offset(datetime_obj, tz_offset, tz_name, expected_date): + assert convert_offset(datetime_obj, tz_offset, tz_name) == expected_date + + +def test_convert_offset__invalid_offset(): + assert convert_offset(datetime(2020, 1, 1), 'invalid') is None diff --git a/test/test_rest_api.py b/test/test_rest_api.py index b3d2a21f..11d531dc 100644 --- a/test/test_rest_api.py +++ b/test/test_rest_api.py @@ -1,58 +1,65 @@ +import pytest +from copy import deepcopy from datetime import datetime, timedelta +from dateutil.tz import tzutc from io import BytesIO from unittest.mock import patch -import pytest from requests import HTTPError -from urllib.parse import urljoin from pyinaturalist.constants import INAT_BASE_URL +from pyinaturalist.exceptions import ObservationNotFound from pyinaturalist.rest_api import ( OBSERVATION_FORMATS, - get_access_token, + add_photo_to_observation, + create_observation, + delete_observation, get_all_observation_fields, - get_observations, get_observation_fields, + get_observations, put_observation_field_values, - create_observation, update_observation, - add_photo_to_observation, - delete_observation, ) -from pyinaturalist.exceptions import AuthenticationError, ObservationNotFound from test.conftest import load_sample_data -PAGE_1_JSON_RESPONSE = load_sample_data("get_observation_fields_page1.json") -PAGE_2_JSON_RESPONSE = load_sample_data("get_observation_fields_page2.json") +PAGE_1_JSON_RESPONSE = load_sample_data('get_observation_fields_page1.json') +PAGE_2_JSON_RESPONSE = load_sample_data('get_observation_fields_page2.json') def get_observations_response(response_format): - response_format = response_format.replace("widget", "js") - return load_sample_data("get_observations.{}".format(response_format)) + response_format = response_format.replace('widget', 'js') + return load_sample_data(f'get_observations.{response_format}') -@pytest.mark.parametrize("response_format", OBSERVATION_FORMATS) +@pytest.mark.parametrize('response_format', OBSERVATION_FORMATS) def test_get_observations(response_format, requests_mock): """ Test all supported observation data formats """ - response = get_observations_response(response_format) - key = "json" if response_format == "json" else "text" + mock_response = get_observations_response(response_format) + key = 'json' if response_format == 'json' else 'text' requests_mock.get( - urljoin(INAT_BASE_URL, "observations.{}".format(response_format)), + f'{INAT_BASE_URL}/observations.{response_format}', status_code=200, - **{key: response}, + **{key: mock_response}, ) observations = get_observations(taxon_id=493595, response_format=response_format) - # Ensure coordinate strings were converted to floats, for JSON format only - if response_format == "json": - response[0]["latitude"] = 50.646894 - response[0]["longitude"] = 4.360086 - assert observations == response - - -@pytest.mark.parametrize("response_format", ["geojson", "yaml"]) + # For JSON format, ensure type conversions were performed + if response_format == 'json': + assert observations[0]['latitude'] == 50.646894 + assert observations[0]['longitude'] == 4.360086 + assert observations[0]['created_at'] == datetime( + 2018, 9, 5, 12, 31, 8, 48000, tzinfo=tzutc() + ) + assert observations[0]['updated_at'] == datetime( + 2018, 9, 22, 17, 19, 27, 80000, tzinfo=tzutc() + ) + else: + assert observations == mock_response + + +@pytest.mark.parametrize('response_format', ['geojson', 'yaml']) def test_get_observations__invalid_format(response_format): with pytest.raises(ValueError): get_observations(taxon_id=493595, response_format=response_format) @@ -60,47 +67,52 @@ def test_get_observations__invalid_format(response_format): def test_get_observation_fields(requests_mock): """ get_observation_fields() work as expected (basic use)""" - requests_mock.get( - urljoin(INAT_BASE_URL, "observation_fields.json?q=sex&page=2"), + f'{INAT_BASE_URL}/observation_fields.json?q=sex&page=2', json=PAGE_2_JSON_RESPONSE, status_code=200, ) + expected_response = deepcopy(PAGE_2_JSON_RESPONSE) - obs_fields = get_observation_fields(q="sex", page=2) - assert obs_fields == PAGE_2_JSON_RESPONSE + response = get_observation_fields(q='sex', page=2) + first_result = response[0] + assert len(response) == 3 + assert first_result['id'] == 5 + assert first_result['datatype'] == 'text' + assert first_result['created_at'] == datetime(2012, 1, 23, 8, 12, 0, 138000, tzinfo=tzutc()) + assert first_result['updated_at'] == datetime(2018, 10, 16, 6, 47, 43, 975000, tzinfo=tzutc()) def test_get_all_observation_fields(requests_mock): """get_all_observation_fields() is able to paginate, accepts a search query and return correct results""" - requests_mock.get( - urljoin(INAT_BASE_URL, "observation_fields.json?q=sex&page=1"), + f'{INAT_BASE_URL}/observation_fields.json?q=sex&page=1', json=PAGE_1_JSON_RESPONSE, status_code=200, ) - requests_mock.get( - urljoin(INAT_BASE_URL, "observation_fields.json?q=sex&page=2"), + f'{INAT_BASE_URL}/observation_fields.json?q=sex&page=2', json=PAGE_2_JSON_RESPONSE, status_code=200, ) - - page_3_json_response = [] requests_mock.get( - urljoin(INAT_BASE_URL, "observation_fields.json?q=sex&page=3"), - json=page_3_json_response, + f'{INAT_BASE_URL}/observation_fields.json?q=sex&page=3', + json=[], status_code=200, ) + expected_response = PAGE_1_JSON_RESPONSE + PAGE_2_JSON_RESPONSE - all_fields = get_all_observation_fields(q="sex") - assert all_fields == PAGE_1_JSON_RESPONSE + PAGE_2_JSON_RESPONSE + response = get_all_observation_fields(q='sex') + assert len(response) == len(expected_response) + first_result = response[0] + assert first_result['created_at'] == datetime(2016, 5, 29, 16, 17, 8, 51000, tzinfo=tzutc()) + assert first_result['updated_at'] == datetime(2018, 1, 1, 1, 17, 56, 7000, tzinfo=tzutc()) def test_get_all_observation_fields_noparam(requests_mock): """get_all_observation_fields() can also be called without a search query without errors""" requests_mock.get( - urljoin(INAT_BASE_URL, "observation_fields.json?page=1"), + f'{INAT_BASE_URL}/observation_fields.json?page=1', json=[], status_code=200, ) @@ -110,196 +122,158 @@ def test_get_all_observation_fields_noparam(requests_mock): def test_put_observation_field_values(requests_mock): requests_mock.put( - urljoin(INAT_BASE_URL, "observation_field_values/31"), - json=load_sample_data("put_observation_field_value_result.json"), + f'{INAT_BASE_URL}/observation_field_values/31', + json=load_sample_data('put_observation_field_value_result.json'), status_code=200, ) - r = put_observation_field_values( + response = put_observation_field_values( observation_id=18166477, observation_field_id=31, # Animal behavior - value="fouraging", - access_token="valid token", + value='fouraging', + access_token='valid token', ) - assert r["id"] == 31 - assert r["observation_field_id"] == 31 - assert r["observation_id"] == 18166477 - assert r["value"] == "fouraging" - - -def test_get_access_token_fail(requests_mock): - """ If we provide incorrect credentials to get_access_token(), an AuthenticationError is raised""" - - rejection_json = { - "error": "invalid_client", - "error_description": "Client authentication failed due to " - "unknown client, no client authentication " - "included, or unsupported authentication " - "method.", - } - requests_mock.post( - urljoin(INAT_BASE_URL, "oauth/token"), - json=rejection_json, - status_code=401, - ) - - with pytest.raises(AuthenticationError): - get_access_token("username", "password", "app_id", "app_secret") - - -def test_get_access_token(requests_mock): - """ Test a successful call to get_access_token() """ - - accepted_json = { - "access_token": "604e5df329b98eecd22bb0a84f88b68a075a023ac437f2317b02f3a9ba414a08", - "token_type": "Bearer", - "scope": "write", - "created_at": 1539352135, - } - requests_mock.post( - urljoin(INAT_BASE_URL, "oauth/token"), - json=accepted_json, - status_code=200, - ) - - token = get_access_token("valid_username", "valid_password", "valid_app_id", "valid_app_secret") - - assert token == "604e5df329b98eecd22bb0a84f88b68a075a023ac437f2317b02f3a9ba414a08" + assert response['id'] == 31 + assert response['observation_field_id'] == 31 + assert response['observation_id'] == 18166477 + assert response['value'] == 'fouraging' def test_update_observation(requests_mock): requests_mock.put( - urljoin(INAT_BASE_URL, "observations/17932425.json"), - json=load_sample_data("update_observation_result.json"), + f'{INAT_BASE_URL}/observations/17932425.json', + json=load_sample_data('update_observation_result.json'), status_code=200, ) params = { - "ignore_photos": 1, - "description": "updated description v2 !", + 'ignore_photos': 1, + 'description': 'updated description v2 !', } - r = update_observation(17932425, access_token="valid token", **params) + response = update_observation(17932425, access_token='valid token', **params) # If all goes well we got a single element representing the updated observation, enclosed in a list. - assert len(r) == 1 - assert r[0]["id"] == 17932425 - assert r[0]["description"] == "updated description v2 !" + assert len(response) == 1 + assert response[0]['id'] == 17932425 + assert response[0]['description'] == 'updated description v2 !' -@patch("pyinaturalist.rest_api.ensure_file_objs") -@patch("pyinaturalist.rest_api.put") +@patch('pyinaturalist.rest_api.ensure_file_objs') +@patch('pyinaturalist.rest_api.put') def test_update_observation__local_photo(put, ensure_file_objs): - update_observation(1234, access_token="token", local_photos="photo.jpg") + update_observation(1234, access_token='token', local_photos='photo.jpg') # Make sure local_photos is replaced with the output of ensure_file_objs - called_params = put.call_args[1]["json"]["observation"] - assert called_params["local_photos"] == ensure_file_objs.return_value + called_params = put.call_args[1]['json']['observation'] + assert called_params['local_photos'] == ensure_file_objs.return_value def test_update_nonexistent_observation(requests_mock): - """When we try to update a non-existent observation, iNat returns an error 410 with "obs does not longer exists". """ + """When we try to update a non-existent observation, iNat returns an error 410 with + 'observation does not exist' + """ requests_mock.put( - urljoin(INAT_BASE_URL, "observations/999999999.json"), - json={"error": "Cette observation n’existe plus."}, + f'{INAT_BASE_URL}/observations/999999999.json', + json={'error': 'Cette observation n’existe plus.'}, status_code=410, ) params = { - "ignore_photos": 1, - "description": "updated description v2 !", + 'ignore_photos': 1, + 'description': 'updated description v2 !', } with pytest.raises(HTTPError) as excinfo: - update_observation(999999999, access_token="valid token", **params) + update_observation(999999999, access_token='valid token', **params) assert excinfo.value.response.status_code == 410 - assert excinfo.value.response.json() == {"error": "Cette observation n’existe plus."} + assert excinfo.value.response.json() == {'error': 'Cette observation n’existe plus.'} def test_update_observation_not_mine(requests_mock): - """When we try to update the obs of another user, iNat returns an error 410 with "obs does not longer exists".""" + """When we try to update the obs of another user, iNat returns an error 410 with 'obs does not longer exists'.""" requests_mock.put( - urljoin(INAT_BASE_URL, "observations/16227955.json"), - json={"error": "Cette observation n’existe plus."}, + f'{INAT_BASE_URL}/observations/16227955.json', + json={'error': 'Cette observation n’existe plus.'}, status_code=410, ) params = { - "ignore_photos": 1, - "description": "updated description v2 !", + 'ignore_photos': 1, + 'description': 'updated description v2 !', } with pytest.raises(HTTPError) as excinfo: - update_observation(16227955, access_token="valid token for another user", **params) + update_observation(16227955, access_token='valid token for another user', **params) assert excinfo.value.response.status_code == 410 - assert excinfo.value.response.json() == {"error": "Cette observation n’existe plus."} + assert excinfo.value.response.json() == {'error': 'Cette observation n’existe plus.'} def test_create_observation(requests_mock): requests_mock.post( - urljoin(INAT_BASE_URL, "observations.json"), - json=load_sample_data("create_observation_result.json"), + f'{INAT_BASE_URL}/observations.json', + json=load_sample_data('create_observation_result.json'), status_code=200, ) - r = create_observation(species_guess="Pieris rapae", access_token="valid token") - assert len(r) == 1 # We added a single one - assert r[0]["latitude"] is None - assert r[0]["taxon_id"] == 55626 # Pieris Rapae @ iNaturalist + response = create_observation(species_guess='Pieris rapae', access_token='valid token') + assert len(response) == 1 # We added a single one + assert response[0]['latitude'] is None + assert response[0]['taxon_id'] == 55626 # Pieris Rapae @ iNaturalist -@patch("pyinaturalist.rest_api.ensure_file_objs") -@patch("pyinaturalist.rest_api.post") +@patch('pyinaturalist.rest_api.ensure_file_objs') +@patch('pyinaturalist.rest_api.post') def test_create_observation__local_photo(post, ensure_file_objs): - create_observation(access_token="token", local_photos="photo.jpg") + create_observation(access_token='token', local_photos='photo.jpg') # Make sure local_photos is replaced with the output of ensure_file_objs - called_params = post.call_args[1]["json"]["observation"] - assert called_params["local_photos"] == ensure_file_objs.return_value + called_params = post.call_args[1]['json']['observation'] + assert called_params['local_photos'] == ensure_file_objs.return_value def test_create_observation_fail(requests_mock): params = { - "species_guess": "Pieris rapae", + 'species_guess': 'Pieris rapae', # Some invalid data so the observation is rejected... - "observed_on_string": (datetime.now() + timedelta(days=1)).isoformat(), - "latitude": 200, + 'observed_on_string': (datetime.now() + timedelta(days=1)).isoformat(), + 'latitude': 200, } requests_mock.post( - urljoin(INAT_BASE_URL, "observations.json"), - json=load_sample_data("create_observation_fail.json"), + f'{INAT_BASE_URL}/observations.json', + json=load_sample_data('create_observation_fail.json'), status_code=422, ) with pytest.raises(HTTPError) as excinfo: - create_observation(access_token="valid token", **params) + create_observation(access_token='valid token', **params) assert excinfo.value.response.status_code == 422 - assert "errors" in excinfo.value.response.json() # iNat also give details about the errors + assert 'errors' in excinfo.value.response.json() # iNat also give details about the errors def test_add_photo_to_observation(requests_mock): requests_mock.post( - urljoin(INAT_BASE_URL, "observation_photos"), - json=load_sample_data("add_photo_to_observation.json"), + f'{INAT_BASE_URL}/observation_photos', + json=load_sample_data('add_photo_to_observation.json'), status_code=200, ) - response = add_photo_to_observation(1234, BytesIO(), access_token="token") - assert response["id"] == 1234 - assert response["created_at"] == "2020-09-24T21:06:16.964-05:00" - assert response["photo"]["native_username"] == "username" + response = add_photo_to_observation(1234, BytesIO(), access_token='token') + assert response['id'] == 1234 + assert response['created_at'] == '2020-09-24T21:06:16.964-05:00' + assert response['photo']['native_username'] == 'username' def test_delete_observation(requests_mock): - requests_mock.delete(urljoin(INAT_BASE_URL, "observations/24774619.json"), status_code=200) - response = delete_observation(observation_id=24774619, access_token="valid token") + requests_mock.delete(f'{INAT_BASE_URL}/observations/24774619.json', status_code=200) + response = delete_observation(observation_id=24774619, access_token='valid token') assert response is None def test_delete_unexisting_observation(requests_mock): """ObservationNotFound is raised if the observation doesn't exists""" - requests_mock.delete(urljoin(INAT_BASE_URL, "observations/24774619.json"), status_code=404) + requests_mock.delete(f'{INAT_BASE_URL}/observations/24774619.json', status_code=404) with pytest.raises(ObservationNotFound): - delete_observation(observation_id=24774619, access_token="valid token") + delete_observation(observation_id=24774619, access_token='valid token') diff --git a/test/test_version.py b/test/test_version.py index 1ed5fdc9..979ce0da 100644 --- a/test/test_version.py +++ b/test/test_version.py @@ -1,18 +1,20 @@ -# A couple tests to make sure that versioning works as expected within Travis +# A couple tests to make sure that versioning works as expected within GitHub Actions # So, for example, the build would fail before accidentally publishing a bad version +import os from unittest.mock import patch -# Mocking out getenv() instead of actually setting envars so this doesn't affect other tests -@patch("pyinaturalist.getenv", side_effect=["true", "master", "123"]) -def test_version__stable_release(mock_getenv): +@patch.dict(os.environ, {'GITHUB_REF': 'refs/heads/master'}) +def test_version__stable_release(): import pyinaturalist - assert "dev" not in pyinaturalist.__version__ + print(os.getenv('GITHUB_REF')) + assert pyinaturalist.get_prerelease_version('1.0.0') == '1.0.0' -@patch("pyinaturalist.getenv", side_effect=["true", "dev", "123"]) -def test_version__pre_release(mock_getenv): +@patch.dict(os.environ, {'GITHUB_REF': 'refs/heads/dev', 'GITHUB_RUN_NUMBER': '123'}) +def test_version__pre_release(): import pyinaturalist - assert pyinaturalist.get_prerelease_version("1.0.0") == "1.0.0-dev.123" + print(os.getenv('GITHUB_REF')) + assert pyinaturalist.get_prerelease_version('1.0.0') == '1.0.0.dev123' diff --git a/tox.ini b/tox.ini index a2080ad7..21a07493 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,7 @@ [tox] minversion = 3.15 envlist = - py35, py36, py37, py38, py39, - coverage, mypy, style, dist-test, docs, lint + py36, py37, py38, py39, coverage, docs [testenv] setenv = @@ -10,47 +9,14 @@ setenv = extras = test commands = pytest --basetemp={envtmpdir} whitelist_externals = - printf make -# Run all "code quality" checks: coverage, annotations, and style [testenv:coverage] commands = pytest --basetemp={envtmpdir} --cov --cov-report=term --cov-report=html - printf '\n\n' - -[testenv:mypy] -commands = - mypy --config-file={toxinidir}/setup.cfg . - printf '\n\n' - -[testenv:style] -commands = - black --check . - printf '\n\n' - -# Install only minimal dependencies for older interpreters -[testenv:py35] -deps = - pytest==4.6.9 - requests-mock>=1.7 -extras = [testenv:docs] extras = test docs commands = make -C docs all - -[testenv:lint] -commands = python setup.py flake8 - -# Build and check distributions without deploying, just to make sure they can build correctly -[testenv:dist-test] -deps = twine -commands = - python setup.py sdist bdist_wheel - twine check dist/* - -[flake8] -max-line-length = 119