diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000000000000000000000000000000000000..e72032fd27f96d73c6b3cc12e6804190ac71edd3 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,8 @@ +======= +Credits +======= + +Developers +------- +* Daniel Wieferich <dwieferich@usgs.gov>, https://orcid.org/0000-0003-1554-7992 +* Brandon Serna <bserna@usgs.gov>, https://orcid.org/0000-0002-5284-6230 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..1d883cae88a8e860984ad508bb101bbaba208796 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +Code of Conduct +=============== + +All contributions to- and interactions surrounding- this project will abide by +the [USGS Code of Scientific Conduct][1]. + + + +[1]: https://www.usgs.gov/human-capital/us-geological-survey-code-conduct diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..37cbfd5a1e0306a18d0a108576c3c492ec7dc0cb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +Contributing +============ + +Contributions are welcome from the community. Questions can be asked on the +[issues page][1]. Before creating a new issue, please take a moment to search +and make sure a similar issue does not already exist. If one does exist, you +can comment (most simply even with just a `:+1:`) to show your support for that +issue. + +If you have direct contributions you would like considered for incorporation +into the project you can [fork this repository][2] and +submit a pull request for review. + + + +[1]: https://code.usgs.gov/sas/bioscience/hlt/hydrolink/-/issues +[2]: https://code.usgs.gov/sas/bioscience/hlt/hydrolink/-/forks/new diff --git a/DISCLAIMER.md b/DISCLAIMER.md new file mode 100644 index 0000000000000000000000000000000000000000..8b2562afaa21b96106ab914136d1668e6a893ede --- /dev/null +++ b/DISCLAIMER.md @@ -0,0 +1 @@ +This software is preliminary or provisional and is subject to revision. It is being provided to meet the need for timely best science. The software has not received final approval by the U.S. Geological Survey (USGS). No warranty, expressed or implied, is made by the USGS or the U.S. Government as to the functionality of the software and related material nor shall the fact of release constitute any such warranty. The software is provided on the condition that neither the USGS nor the U.S. Government shall be held liable for any damages resulting from the authorized or unauthorized use of the software. \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..c6cd62eb3349ee32055f8a40563d296deedac9c2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,26 @@ +unlicense + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <http://unlicense.org/> diff --git a/README.md b/README.md index 000635b12aa763a95b15f34007e162e002f6451c..8e276ad1989a4418c5ec635cafc2a9b2c97753b8 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,120 @@ # hydrolink + +Hydrolink refers to the linkage of spatial data to a stream network. This is similar to the analogy of providing an address on a road network and provides locational context and position within a stream network. Hydrolinking data to a common stream network allows information to be centralized helping support landscape scale analyses and modeling efforts. Versions of the National Hydrography Dataset are often used as a spatial framework for modeling and describing aquatic biological and hydrologic systems at larger geographic scales. -## Getting started +Abundant data are currently available to describe aquatic systems, yet systematic, efficient, and accurate methods to hydrolink thousands or millions of data points are limited. This Python package contains methods and a command line tool that help hydrolink geospatial information to the National Hydrography Datasets. Further, these methods help provide levels of certainty that can be helpful to determine where code-based methods are sufficient and when manual review for quality is warranted. -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +#### Current functionality and highlights -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +* No downloads of stream network data required (uses web services) +* Supported Stream Networks: The National Hydrography Dataset Plus Version 2.1 (NHDPlusV21); The National Hydrography Dataset High Resolution (NHDHR) +* Supported Input Geometries: Point data +* Tool: Includes a command line tool called hydrolinker that allows a user to process multiple Points by passing information by way of a comma separated values (csv) file. Results are also returned in csv format. +* Examples: A Jupyter Notebook is available to highlight a hydrolink example for each available stream network. +* Other highlights: Hydrolink uses multiple parameters to determine certainty and most likely hydrolink. The more parameters used for a hydrolink the more likely results with have a higher level of certainty. Currently available parameters include geospatial proximity, stream name, -## Add your files +## Contact Information -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +Daniel Wieferich - dwieferich@usgs.gov +## USGS Software Release Information + +Wieferich, D.J., Serna, B. 2023. hydrolink Version-0.1.1. U.S. Geological Survey software release. https://doi.org/10.5066/XXXXXX. + +IP-`XXXXX` + + +## Quick Start + +Requirements.txt shows a condensed version of packages for general use, while requirements_dev shows a full list of packages used in development. + +Before installing hydrolink it is recommended to have operating system specific dependencies in place for GDAL. It is also recommended to create a working Python environment. + +#### Installation for Linux and Mac + +After activating your new Python environment, pip install the current version of the package from main branch using the below command. Users can also install working branches of the repository by adding @branch_name after hydrolink.git. + +```sh +# Install Package from main branch +pip install git+https://code.usgs.gov/sas/bioscience/hlt/hydrolink.git ``` -cd existing_repo -git remote add origin https://code.usgs.gov/sas/bioscience/hlt/hydrolink.git -git branch -M main -git push -uf origin main +or + +```sh +# Install Package from working branch +pip install git+https://code.usgs.gov/sas/bioscience/hlt/hydrolink.git@branch_name ``` -## Integrate with your tools +#### Installation for Windows -- [ ] [Set up project integrations](https://code.usgs.gov/sas/bioscience/hlt/hydrolink/-/settings/integrations) +For Windows machines it may be easiest to build a local environment using Conda and the hydrolink.yml file that is included in the package. An example using anaconda prompt is included below. Prior to the steps below a user must clone or download the hydrolink package to the local machine. Using Anaconda prompt (or alike) change directories to the top level of the hydrolink repository. -## Collaborate with your team +```sh +# First download or clone the repository to the local machine +# Navigate into the hydrolink directory, replace 'path/to' with the path to the directory +cd path/to/hydrolink +# Example Creating a Conda environment using Python 3.9 in Windows +conda env create -f hydrolink.yml +# Activate the new environment called 'hyrolink' +conda activate hydrolink +# Install the hydrolink package metadata +pip install -e . --no-deps +``` -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +#### Quick Start -## Test and Deploy +Using the hydrolinker command line tool you can hydrolink multiple points by passing information using a csv file. -Use the built-in continuous integration in GitLab. +```sh +# Access help menu +python -m hydrolink.hydrolinker --help +# Example running with default options, where user updates file_name.csv to the directory and name of file +python -m hydrolink.hydrolinker --input_file=file_name.csv +``` -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +Two Jupyter Notebooks are included to show a few basic capabilities for both NHD versions. These examples can be helpful if building hydrolink into a workflow and using these methods a user has more flexibility of returned information. -*** +* example-using-single-point-nhd-high-resolution.ipynb -> Jupyter notebook with descriptions on how to run hydrolink methods on NHD High Resolution for a single point location. +* example-using-single-point-nhd-medium-resolution.ipynb -> Jupyter notebook with descriptions on how to run hydrolink methods on NHDPlusV2.1 for a single point location. -# Editing this README -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. +## Testing + +Python pytest is used to test this code. To run tests first clone the repository and navigate in your prefered command line interface to the hydrolink directory. Ensure your environment is activated and all development requirements from requirements_dev.txt are installed and run python -m pytest. If adding new tests use the prefix 'test_' to the name of the new module and each test function. + +```sh +# After cloning the repository navigate to the directory +cd hydrolink +# Run pytests +python -m pytest +``` -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. -## Name -Choose a self-explaining name for your project. +## Versioning -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. +Bumpversion is used to version this code. To version, create a development environment that includes bumpversion and use the appropriate command below. -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +#### Small adjustments to code, improved documentation, and/or updated tests +``bumpversion patch --allow-dirty`` -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. +#### Improved methods within modules +``bumpversion minor --allow-dirty`` -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +#### New module, or release +``bumpversion major --allow-dirty`` -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. +## Copyright and License -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. +This USGS product is considered to be in the U.S. public domain, and is licensed under unlicense_ -## Contributing -State if you are open to contributions and what your requirements are for accepting them. +.. _unlicense: https://unlicense.org/ -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. +This software is preliminary or provisional and is subject to revision. It is being provided to meet the need for timely best science. The software has not received final approval by the U.S. Geological Survey (USGS). No warranty, expressed or implied, is made by the USGS or the U.S. Government as to the functionality of the software and related material nor shall the fact of release constitute any such warranty. The software is provided on the condition that neither the USGS nor the U.S. Government shall be held liable for any damages resulting from the authorized or unauthorized use of the software. -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. -## License -For open source projects, say how it is licensed. -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +The official USGS software release can be found at https://doi.org/10.5066/XXXXXX. The main branch will have the most up-to-date version of the code. \ No newline at end of file diff --git a/code.json b/code.json new file mode 100644 index 0000000000000000000000000000000000000000..74ffecf61f29e530cead359f87ce786553f3d80d --- /dev/null +++ b/code.json @@ -0,0 +1,51 @@ +[ + { + "name": "hydrolink", + "organization": "U.S. Geological Survey", + "description": "Python package to assist with summarization of landscape information to stream watershed drainages.", + "version": "0.1.1", + "status": "Development", + + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "name": "unlicense", + "URL": "https://code.usgs.gov/sas/bioscience/hlt/hydrolink/-/raw/0.1.1/LICENSE.md" + } + ] + }, + + "homepageURL": "https://code.usgs.gov/sas/bioscience/hlt/hydrolink", + "downloadURL": "https://code.usgs.gov/sas/bioscience/hlt/hydrolink/-/archive/0.1.1/hydrolink-0.1.1.zip", + "disclaimerURL": "https://code.usgs.gov/sas/bioscience/hlt/hydrolink/-/raw/0.1.1/DISCLAIMER.md", + "repositoryURL": "https://code.usgs.gov/sas/bioscience/hlt/hydrolink.git", + "vcs": "git", + + "laborHours": 0, + + "tags": [ + "Python", + "USGS", + "usgs_ma:Core Science Systems", + "usgs_sc:Science Analytics and Synthesis", + "stream network", + "doi|https://doi.org/10.5066/XXXXXX", + "ipds|IP-XXXXX" + ], + + "languages": [ + "Python" + ], + + "contact": { + "name": "Daniel Wieferich", + "email": "dwieferich@usgs.gov" + }, + + "date": { + "metadataLastUpdated": "2023-03-24" + } + } + ] + diff --git a/examples/example-using-single-point-nhd-high-resolution.ipynb b/examples/example-using-single-point-nhd-high-resolution.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..86d0f94d34bde87c0f9471f134b859c8eb464d59 --- /dev/null +++ b/examples/example-using-single-point-nhd-high-resolution.ipynb @@ -0,0 +1,1196 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### This example shows how to use hydrolinking methods to address a point to the NHD High Resolution.\n", + "\n", + "#### For this example we use a point near the Red Cedar River in East Lansing, MI." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### First set required parameters.\n", + "Note: The input parameters are the same for NHDPlusV2.1 and NHD High Resolution hydrolinking methods.\n", + "\n", + "Required parameters include:\n", + "\n", + " *input_identifier: str, user supplied identifier\n", + " *input_lat: float, latitude of the point to be hydrolinked\n", + " *input_lon: float, longitude of the point to be hydrolinked\n", + " \n", + "Other optional parameters include:\n", + "\n", + " *input_crs: int, coordinate reference system, default is 4269 (NAD83)\n", + " *water_name: str, name of the water feature that the point is intended to be linked to\n", + " *buffer_m = int, search distance in meters, default is 1000, max is 2000\n", + " *method = str, link data to 'closest' or 'name_match' (closest having a matching water name), default 'name_match'\n", + " *hydro_type = str, link to 'waterbody' or 'flowline' NHD features, default 'flowline'" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#identifier -> number or string\n", + "input_identifier = 1\n", + "#latitude\n", + "input_lat = 42.7284\n", + "#longitude\n", + "input_lon = -84.5026\n", + "#coordinate system\n", + "input_crs = 4269 \n", + "#In this case we want the point to link to the Red Cedar River so water_name is a stream name\n", + "water_name = 'The Red Cedar River'\n", + "#buffer\n", + "buffer_m = 2000\n", + "\n", + "\n", + "method = 'name_match'\n", + "hydro_type = 'flowline'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First the module for National Hydrography Dataset High Resolution is imported. This HydroLink module is called nhd_hr." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#Import module from local folder hydrolink\n", + "from hydrolink import nhd_hr\n", + "import warnings; warnings.simplefilter('ignore')" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "The next cell iniates the HydroLink object for HydroLinking a point.\n", + "\n", + "Note: It is recommended to use crs 4269, although this code attempts to accomidate other common coordinate systems by using some simple logic not all crs have been tested. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Initiate a Python object, this tries reprojecting data to nad83 and checks if data within bounding box of U.S.\n", + "hydrolink = nhd_hr.HighResPoint(input_identifier, input_lat, input_lon, water_name=water_name, input_crs=input_crs, buffer_m=buffer_m)\n", + "\n", + "# Runs the HydroLink process using name_match method and flowline hydro_type. \n", + "#This writes data out to a CSV in working directory named 'nhdhr_hydrolink_output.csv'\n", + "hydrolink.hydrolink_method(method=method, hydro_type=hydro_type)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'displayFieldName': 'gnis_name',\n", + " 'hasM': True,\n", + " 'fieldAliases': {'gnis_name': 'GNIS_NAME',\n", + " 'lengthkm': 'LENGTHKM',\n", + " 'permanent_identifier': 'PERMANENT_IDENTIFIER',\n", + " 'reachcode': 'REACHCODE'},\n", + " 'geometryType': 'esriGeometryPolyline',\n", + " 'spatialReference': {'wkid': 4269, 'latestWkid': 4269},\n", + " 'fields': [{'name': 'gnis_name',\n", + " 'type': 'esriFieldTypeString',\n", + " 'alias': 'GNIS_NAME',\n", + " 'length': 65},\n", + " {'name': 'lengthkm', 'type': 'esriFieldTypeDouble', 'alias': 'LENGTHKM'},\n", + " {'name': 'permanent_identifier',\n", + " 'type': 'esriFieldTypeString',\n", + " 'alias': 'PERMANENT_IDENTIFIER',\n", + " 'length': 40},\n", + " {'name': 'reachcode',\n", + " 'type': 'esriFieldTypeString',\n", + " 'alias': 'REACHCODE',\n", + " 'length': 14}],\n", + " 'features': [{'attributes': {'gnis_name': None,\n", + " 'lengthkm': 0.011,\n", + " 'permanent_identifier': '152093560',\n", + " 'reachcode': '04050004002359'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.50938251259817, 42.72651486701329, 1.83965],\n", + " [-84.50947937926469, 42.726588067013154, 0]]]}},\n", + " {'attributes': {'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 1.746,\n", + " 'permanent_identifier': '152093412',\n", + " 'reachcode': '04050004000126'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.50947937926469, 42.726588067013154, 19.83811],\n", + " [-84.50969557926436, 42.726443267013394, 19.56608],\n", + " [-84.50972357926429, 42.726413867013434, 19.52071],\n", + " [-84.50988497926409, 42.72628020034699, 19.29466],\n", + " [-84.51016877926361, 42.72608306701392, 18.93152],\n", + " [-84.5103387125967, 42.72595520034747, 18.70539],\n", + " [-84.51109851259548, 42.72539966701498, 17.70881],\n", + " [-84.51119997926202, 42.72532266701512, 17.57321],\n", + " [-84.51126371259528, 42.72526900034853, 17.4831],\n", + " [-84.51182737926104, 42.724827067015894, 16.71682],\n", + " [-84.51217951259383, 42.724579400349626, 16.26357],\n", + " [-84.51224357926037, 42.72452600034967, 16.17352],\n", + " [-84.5123805792602, 42.72442440034985, 15.99255],\n", + " [-84.51264891259308, 42.72421666701683, 15.63019],\n", + " [-84.51305857925911, 42.72396320035057, 15.13208],\n", + " [-84.5130931125924, 42.7239378003506, 15.08665],\n", + " [-84.51312011259239, 42.72390786701732, 15.04125],\n", + " [-84.51315377925897, 42.72388166701734, 14.99566],\n", + " [-84.51323257925884, 42.723840200350764, 14.90549],\n", + " [-84.51368357925816, 42.72363020035107, 14.40864],\n", + " [-84.51383757925794, 42.723542000351244, 14.22701],\n", + " [-84.51394217925775, 42.72346700035132, 14.09112],\n", + " [-84.51397437925772, 42.72344006701803, 14.04577],\n", + " [-84.5140471792576, 42.72339240035143, 13.95509],\n", + " [-84.51420771259069, 42.72331126701823, 13.77381],\n", + " [-84.51433157925715, 42.72325426701832, 13.6378],\n", + " [-84.51454211259016, 42.72316386701846, 13.41086],\n", + " [-84.51476257925651, 42.72308840035191, 13.18444],\n", + " [-84.51499617925612, 42.723036667018675, 12.95722],\n", + " [-84.5150413125894, 42.72302346701866, 12.91199],\n", + " [-84.51525831258903, 42.722941800352146, 12.68504],\n", + " [-84.51552757925532, 42.72285966701895, 12.41359],\n", + " [-84.51586637925476, 42.722717600352496, 12.05053],\n", + " [-84.51590597925474, 42.722696467019205, 12.005],\n", + " [-84.51605397925448, 42.722602667019316, 11.82322],\n", + " [-84.51641651258723, 42.72231186701981, 11.32429],\n", + " [-84.51647777925382, 42.722256067019885, 11.23361],\n", + " [-84.5166403125869, 42.72203446702025, 10.91543],\n", + " [-84.5166823792535, 42.721969667020346, 10.8247],\n", + " [-84.51671977925344, 42.72190326702042, 10.7339],\n", + " [-84.51683811258658, 42.72174586702067, 10.50661],\n", + " [-84.51698791258639, 42.72160386702092, 10.27938],\n", + " [-84.51704857925296, 42.72154746702097, 10.18845],\n", + " [-84.51712237925284, 42.72150040035439, 10.09757],\n", + " [-84.51736657925244, 42.72138126702123, 9.82482],\n", + " [-84.51741071258573, 42.72136626702127, 9.77955],\n", + " [-84.51745937925233, 42.72136220035458, 9.73392],\n", + " [-84.51750777925224, 42.72136306702129, 9.68881],\n", + " [-84.51755537925214, 42.721370467021245, 9.64349],\n", + " [-84.51760237925208, 42.72138080035455, 9.5978],\n", + " [-84.517646779252, 42.72139520035455, 9.55262],\n", + " [-84.51768911258529, 42.721413200354505, 9.5071],\n", + " [-84.51776271258518, 42.72145966702112, 9.41685],\n", + " [-84.51779077925181, 42.72148926702107, 9.37123],\n", + " [-84.51780957925178, 42.72152240035433, 9.32587],\n", + " [-84.51796071258485, 42.721746467020694, 9.00982],\n", + " [-84.51800637925146, 42.72184806702052, 8.87465],\n", + " [-84.51815471258453, 42.722151267020024, 8.46759],\n", + " [-84.51817871258453, 42.72218266701998, 8.42206],\n", + " [-84.51821751258444, 42.722204467019935, 8.37663],\n", + " [-84.51825831258441, 42.72222420035325, 8.33118],\n", + " [-84.518303379251, 42.72223706701993, 8.28616],\n", + " [-84.51839737925087, 42.72225566701991, 8.19549],\n", + " [-84.51844571258408, 42.72225700035324, 8.15043],\n", + " [-84.51849057925068, 42.72224306701992, 8.10508],\n", + " [-84.51857457925058, 42.722207400353284, 8.01478],\n", + " [-84.51869591258372, 42.72214880035335, 7.87968],\n", + " [-84.51873411258367, 42.72212640035343, 7.83422],\n", + " [-84.51876617925029, 42.72209966702013, 7.78914],\n", + " [-84.51895077924996, 42.72180720035391, 7.38173],\n", + " [-84.51896511258332, 42.72177360035397, 7.33724],\n", + " [-84.51896417924996, 42.72173780035399, 7.29203],\n", + " [-84.51889177925005, 42.721530000354335, 7.02108],\n", + " [-84.51887311258344, 42.721496867021074, 6.97577],\n", + " [-84.51873751258364, 42.721306600354694, 6.70431],\n", + " [-84.51859017925057, 42.721080000355016, 6.38694],\n", + " [-84.5185569792506, 42.72101306702183, 6.29693],\n", + " [-84.51852017925063, 42.72079986702215, 6.02553],\n", + " [-84.51853437925064, 42.7203686670228, 5.48084],\n", + " [-84.51853671258397, 42.72033060035619, 5.43272],\n", + " [-84.51860991258383, 42.720160000356486, 5.20674],\n", + " [-84.51869797925036, 42.720033800356646, 5.0275],\n", + " [-84.51879291258359, 42.71991086702354, 4.84883],\n", + " [-84.51883311258348, 42.719847000356935, 4.7599],\n", + " [-84.51885791258348, 42.71970406702383, 4.57793],\n", + " [-84.51884637925014, 42.719633000357305, 4.48755],\n", + " [-84.51883577925014, 42.71959780035735, 4.44201],\n", + " [-84.51881311258353, 42.71956660035738, 4.39731],\n", + " [-84.51860431258388, 42.719367867024346, 4.07978],\n", + " [-84.51856991258393, 42.719342200357744, 4.03419],\n", + " [-84.51853191258397, 42.719320000357754, 3.98904],\n", + " [-84.51850011258404, 42.71929266702449, 3.94355],\n", + " [-84.51838557925089, 42.71917646702468, 3.76211],\n", + " [-84.51837451258422, 42.71914160035806, 3.71689],\n", + " [-84.51837971258419, 42.71907026702485, 3.62668],\n", + " [-84.51847411258404, 42.71882846702522, 3.30892],\n", + " [-84.51849571258401, 42.71879620035861, 3.26347],\n", + " [-84.51864217925049, 42.71861166702553, 2.99342],\n", + " [-84.51891891258339, 42.71831586702598, 2.53954],\n", + " [-84.51900891258322, 42.718230867026136, 2.40333],\n", + " [-84.51929251258281, 42.71803260035978, 2.03932],\n", + " [-84.51948131258251, 42.71791940035996, 1.81265],\n", + " [-84.51973777924877, 42.717753267026865, 1.49467],\n", + " [-84.51988837924853, 42.717661867027005, 1.31299],\n", + " [-84.52019757924808, 42.71748680036063, 0.94985],\n", + " [-84.52067357924733, 42.71723506702767, 0.4042],\n", + " [-84.52083571258038, 42.71710086702791, 0.17717],\n", + " [-84.520864779247, 42.717071867027926, 0.13163],\n", + " [-84.52093831258026, 42.71698286702809, 0]]]}},\n", + " {'attributes': {'gnis_name': None,\n", + " 'lengthkm': 0.455,\n", + " 'permanent_identifier': '152091798',\n", + " 'reachcode': '04050004002359'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.50368877927366, 42.72321920035171, 100],\n", + " [-84.50420931260618, 42.72326600035166, 93.04296],\n", + " [-84.50468451260548, 42.72338400035147, 86.39091],\n", + " [-84.50575791260383, 42.72402186701714, 68.10472],\n", + " [-84.50672057926897, 42.724554800349665, 52.13708],\n", + " [-84.50734897926799, 42.7250254003489, 40.25742],\n", + " [-84.50819331260004, 42.72549706701483, 26.20677]]]}},\n", + " {'attributes': {'gnis_name': None,\n", + " 'lengthkm': 1.913,\n", + " 'permanent_identifier': '152091441',\n", + " 'reachcode': '04050004000960'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.50342151260742, 42.70935020037325, 100],\n", + " [-84.50409837927305, 42.709300267039964, 97.33682],\n", + " [-84.50443171260588, 42.70939160037318, 95.94473],\n", + " [-84.5046823126055, 42.70953146703965, 94.71458],\n", + " [-84.50492791260507, 42.71079260037101, 87.95462],\n", + " [-84.50523711260462, 42.710801667037686, 86.74315],\n", + " [-84.505636312604, 42.710837667037595, 85.16867],\n", + " [-84.50625871260303, 42.71087726703752, 82.72296],\n", + " [-84.50634217926955, 42.71120240037038, 80.96721],\n", + " [-84.50632851260292, 42.71155960036981, 79.07125],\n", + " [-84.50649831260267, 42.71163820036969, 78.28652],\n", + " [-84.50688591260206, 42.711603600369756, 76.75803],\n", + " [-84.50706411260177, 42.71163280036973, 76.0434],\n", + " [-84.50715111260166, 42.71185700036938, 74.80606],\n", + " [-84.50712957926834, 42.71213366703557, 73.33572],\n", + " [-84.50714697926833, 42.71241406703518, 71.84644],\n", + " [-84.50751957926775, 42.71239040036852, 70.38236],\n", + " [-84.50852811259949, 42.712194200368856, 66.29915],\n", + " [-84.50859511259938, 42.71218120036883, 66.02794],\n", + " [-84.50897277926549, 42.71228926703537, 64.44214],\n", + " [-84.50951011259798, 42.712581067034876, 61.83024],\n", + " [-84.51033671259671, 42.713028400367534, 57.81716],\n", + " [-84.51119291259533, 42.713014867034246, 54.46451],\n", + " [-84.5116181125947, 42.71296406703431, 52.77825],\n", + " [-84.51210751259396, 42.71302860036752, 50.83198],\n", + " [-84.51263577925977, 42.71307860036745, 48.74696],\n", + " [-84.51311657925902, 42.71324826703386, 46.66053],\n", + " [-84.5134997125918, 42.71347186703349, 44.74817],\n", + " [-84.51390277925782, 42.71383320036631, 42.26518],\n", + " [-84.51453037925683, 42.71497160036455, 35.74458],\n", + " [-84.5148525125897, 42.71527706703074, 33.69105],\n", + " [-84.51523011258911, 42.71539080036388, 32.09444],\n", + " [-84.5155643125886, 42.715438000363804, 30.7624],\n", + " [-84.51576977925492, 42.71560486703021, 29.56625],\n", + " [-84.51614611258765, 42.715790000363256, 27.7956],\n", + " [-84.51719231258608, 42.71598166702961, 23.57573],\n", + " [-84.51768811258529, 42.71615146702936, 21.43596],\n", + " [-84.5180085125848, 42.71655586702872, 18.95063],\n", + " [-84.51801911258474, 42.716803267028354, 17.63734],\n", + " [-84.51814977925125, 42.71699146702804, 16.51541],\n", + " [-84.51854237925062, 42.71709980036121, 14.87459],\n", + " [-84.5189471125833, 42.716944400361456, 13.08852],\n", + " [-84.51920611258294, 42.717029400361355, 11.97887],\n", + " [-84.51951071258247, 42.71706526702792, 10.7714],\n", + " [-84.51966111258224, 42.71697340036144, 10.00707],\n", + " [-84.51974837924877, 42.71669700036182, 8.5013]]]}},\n", + " {'attributes': {'gnis_name': None,\n", + " 'lengthkm': 0.178,\n", + " 'permanent_identifier': '152093510',\n", + " 'reachcode': '04050004000960'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.51974837924877, 42.71669700036182, 8.5013],\n", + " [-84.51979117924867, 42.71662646702862, 8.09128],\n", + " [-84.51982111258195, 42.716626667028606, 7.9741],\n", + " [-84.51985997924857, 42.71665826702855, 7.74769],\n", + " [-84.51990637924848, 42.71666866702856, 7.55786],\n", + " [-84.51995417924843, 42.71667680036188, 7.36583],\n", + " [-84.52000257924834, 42.716679800361874, 7.1757],\n", + " [-84.5200481792483, 42.71667646702855, 6.99632],\n", + " [-84.52006411258157, 42.716605000361994, 6.61204],\n", + " [-84.52007871258155, 42.716570800362035, 6.42179],\n", + " [-84.5201151792482, 42.71650386702879, 6.03905],\n", + " [-84.52013877924816, 42.716434200362244, 5.65804],\n", + " [-84.52017231258145, 42.71640926702895, 5.47168],\n", + " [-84.5202553125813, 42.716372067029, 5.09151],\n", + " [-84.52039491258108, 42.71634380036238, 4.52483],\n", + " [-84.52049037924758, 42.716333267029086, 4.14696],\n", + " [-84.52053897924753, 42.71633840036242, 3.95477],\n", + " [-84.52062577924738, 42.71637046702904, 3.57476],\n", + " [-84.52066651258065, 42.71639000036231, 3.38459],\n", + " [-84.52069851258062, 42.716416867028954, 3.19482],\n", + " [-84.52072431258057, 42.7164474670289, 3.00361],\n", + " [-84.52073871258057, 42.71648160036216, 2.81394],\n", + " [-84.52074631258051, 42.71651660036213, 2.62587],\n", + " [-84.52076251258052, 42.716550200362065, 2.43666],\n", + " [-84.5208105792471, 42.71661240036195, 2.05677],\n", + " [-84.5208287125804, 42.71664540036193, 1.86784],\n", + " [-84.52083517924706, 42.71668106702856, 1.67691],\n", + " [-84.52083737924704, 42.71678866702837, 1.10595],\n", + " [-84.52084811258038, 42.716823867028324, 0.91452],\n", + " [-84.52093831258026, 42.71698286702809, 0]]]}},\n", + " {'attributes': {'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 0.826,\n", + " 'permanent_identifier': '152093411',\n", + " 'reachcode': '04050004000125'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.52093831258026, 42.71698286702809, 100],\n", + " [-84.52103417924673, 42.71696940036139, 99.03444],\n", + " [-84.5211253792466, 42.716943800361435, 98.06898],\n", + " [-84.52116797924657, 42.71692606702817, 97.58503],\n", + " [-84.52132817924632, 42.716844400361595, 95.65842],\n", + " [-84.5213703792462, 42.71682606702831, 95.17388],\n", + " [-84.52171891257905, 42.716697200361864, 91.31661],\n", + " [-84.52185251257879, 42.716653600361894, 89.87119],\n", + " [-84.52212657924508, 42.71657780036202, 86.97524],\n", + " [-84.52226577924483, 42.71654446702877, 85.52723],\n", + " [-84.52254617924439, 42.716483667028854, 82.63554],\n", + " [-84.52273637924412, 42.71645280036222, 80.70866],\n", + " [-84.5230755125769, 42.7164292670289, 77.33836],\n", + " [-84.52351331257626, 42.71642106702893, 73.00524],\n", + " [-84.52360937924277, 42.71643160036223, 72.04429],\n", + " [-84.52370471257592, 42.71644560036225, 71.08253],\n", + " [-84.52375311257589, 42.716449200362206, 70.60122],\n", + " [-84.52394011257559, 42.716490067028815, 68.67153],\n", + " [-84.52446231257477, 42.71657660036203, 63.37602],\n", + " [-84.5245103125747, 42.71658246702867, 62.89462],\n", + " [-84.52455777924126, 42.71657520036206, 62.41497],\n", + " [-84.52460357924122, 42.71656240036208, 61.93039],\n", + " [-84.52491131257409, 42.716459400362226, 58.58693],\n", + " [-84.52500151257391, 42.716434267028944, 57.63294],\n", + " [-84.52508957924044, 42.71640560036229, 56.68055],\n", + " [-84.5251323792404, 42.71638786702897, 56.19487],\n", + " [-84.52516651257366, 42.716362600362345, 55.71646],\n", + " [-84.52522577924026, 42.71630506702911, 54.74737],\n", + " [-84.52526291257351, 42.7162824003625, 54.27052],\n", + " [-84.5253413125734, 42.71623986702923, 53.30767],\n", + " [-84.52547277923986, 42.716182467029284, 51.7962],\n", + " [-84.52561457923963, 42.716209800362606, 50.34609],\n", + " [-84.5256629125729, 42.71621580036259, 49.86114],\n", + " [-84.52571097923948, 42.716213000362586, 49.38407],\n", + " [-84.52575711257276, 42.71620060036264, 48.89827],\n", + " [-84.52580017923935, 42.71618500036266, 48.42357],\n", + " [-84.5259387125725, 42.7161546003627, 46.99353],\n", + " [-84.52603357923897, 42.71613886702937, 46.03146],\n", + " [-84.5261265125722, 42.71611900036277, 45.07413],\n", + " [-84.5261753125721, 42.716117067029415, 44.59059],\n", + " [-84.52627131257196, 42.716125600362716, 43.63386],\n", + " [-84.5263631125718, 42.71614940036267, 42.67112],\n", + " [-84.5264107792384, 42.71615380036269, 42.19581],\n", + " [-84.52650537923824, 42.71616940036267, 41.23671],\n", + " [-84.52655151257153, 42.71618080036262, 40.75533],\n", + " [-84.52659471257147, 42.71619620036262, 40.28062],\n", + " [-84.52668637923796, 42.71621786702923, 39.32824],\n", + " [-84.52682811257108, 42.716240467029195, 37.8935],\n", + " [-84.52687717923766, 42.71624006702922, 37.40799],\n", + " [-84.52692411257095, 42.71623066702921, 36.92681],\n", + " [-84.52696917923754, 42.71621640036261, 36.4416],\n", + " [-84.5270099125708, 42.71619626702926, 35.95649],\n", + " [-84.5270567792374, 42.71617866702934, 35.43616],\n", + " [-84.52718251257056, 42.7162336670292, 33.9899],\n", + " [-84.52722737923716, 42.71624766702922, 33.50791],\n", + " [-84.52741197923683, 42.71629306702914, 31.58262],\n", + " [-84.52746011257011, 42.71630000036248, 31.09738],\n", + " [-84.52750877923671, 42.71630100036248, 30.61567],\n", + " [-84.52797431256931, 42.71622940036258, 25.91051],\n", + " [-84.52806291256917, 42.71614340036268, 24.46186],\n", + " [-84.52810417923575, 42.71607840036279, 23.4993],\n", + " [-84.52820091256893, 42.715954267029645, 21.57909],\n", + " [-84.52827231256884, 42.715745867029966, 18.69645],\n", + " [-84.52829197923546, 42.71567580036344, 17.73689],\n", + " [-84.52831977923546, 42.7156068670302, 16.77242],\n", + " [-84.52834117923538, 42.71557446703025, 16.28907],\n", + " [-84.5284257792353, 42.715477067030406, 14.73769],\n", + " [-84.52839311256866, 42.71535086703062, 13.01472],\n", + " [-84.52838611256868, 42.71531526703063, 12.53231],\n", + " [-84.52838417923533, 42.71527926703072, 12.04916],\n", + " [-84.52838397923534, 42.71520726703085, 11.08361],\n", + " [-84.52841597923532, 42.714957267031195, 7.71609],\n", + " [-84.52842037923529, 42.71488580036464, 6.75671],\n", + " [-84.52839651256863, 42.714743200364865, 4.82986],\n", + " [-84.52838017923534, 42.71467226703163, 3.86499],\n", + " [-84.52838317923533, 42.714384067032086, 0]]]}},\n", + " {'attributes': {'gnis_name': None,\n", + " 'lengthkm': 0.116,\n", + " 'permanent_identifier': '152091797',\n", + " 'reachcode': '04050004002359'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.50850391259951, 42.725713200347855, 20.54223],\n", + " [-84.50876877926578, 42.726060867013985, 13.37016],\n", + " [-84.50938251259817, 42.72651486701329, 1.83965]]]}},\n", + " {'attributes': {'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 7.032,\n", + " 'permanent_identifier': '152093413',\n", + " 'reachcode': '04050004000126'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.44491257936488, 42.72313040035186, 100],\n", + " [-84.44505891269802, 42.723123267018536, 99.86338],\n", + " [-84.44515571269784, 42.723113600351894, 99.77237],\n", + " [-84.44529737936432, 42.72308640035192, 99.636],\n", + " [-84.44585151269678, 42.7229454670188, 99.08994],\n", + " [-84.44598151269656, 42.72289566701886, 98.95347],\n", + " [-84.44610551269636, 42.72283820035233, 98.81705],\n", + " [-84.44614957936301, 42.72282560035234, 98.77302],\n", + " [-84.44629517936278, 42.72281420035233, 98.63661],\n", + " [-84.44662631269557, 42.722751800352455, 98.31822],\n", + " [-84.44672037936209, 42.722732800352446, 98.22736],\n", + " [-84.4468089126953, 42.722702400352546, 98.13638],\n", + " [-84.44694671269508, 42.72266580035256, 97.99994],\n", + " [-84.44698897936166, 42.72264786701925, 97.95451],\n", + " [-84.44725391269463, 42.722490200352865, 97.6374],\n", + " [-84.4473337126945, 42.722448800352936, 97.54651],\n", + " [-84.44745711269428, 42.72239146701969, 97.41065],\n", + " [-84.44754317936082, 42.72235806701974, 97.32005],\n", + " [-84.44772031269389, 42.72229826701982, 97.13857],\n", + " [-84.44784631269368, 42.72224440035325, 97.0029],\n", + " [-84.4479327793602, 42.72221180035331, 96.91243],\n", + " [-84.44806757935999, 42.72216966702001, 96.77604],\n", + " [-84.44820891269313, 42.72214446702003, 96.64058],\n", + " [-84.4482533793597, 42.72213106702009, 96.59583],\n", + " [-84.44829431269301, 42.72211140035341, 96.55032],\n", + " [-84.4483849793595, 42.72208740035347, 96.46058],\n", + " [-84.44862357935915, 42.72205286702018, 96.23406],\n", + " [-84.44871411269236, 42.72202620035358, 96.14324],\n", + " [-84.44897037935863, 42.721923600353705, 95.8716],\n", + " [-84.44906117935847, 42.72189866702041, 95.78134],\n", + " [-84.44915517935834, 42.721880400353825, 95.69078],\n", + " [-84.44944277935787, 42.721842400353864, 95.41858],\n", + " [-84.44958857935762, 42.72183766702051, 95.28261],\n", + " [-84.44983217935726, 42.72184466702049, 95.05549],\n", + " [-84.44997357935705, 42.72187166702048, 94.91942],\n", + " [-84.45020731269, 42.72192166702041, 94.6927],\n", + " [-84.45025537935663, 42.721926267020365, 94.64754],\n", + " [-84.45035017935646, 42.721942867020346, 94.55677],\n", + " [-84.45066477935598, 42.72203766702023, 94.24017],\n", + " [-84.45093857935558, 42.72211346702011, 93.96772],\n", + " [-84.45170897935435, 42.72233480035311, 93.19749],\n", + " [-84.45179817935423, 42.72236286701974, 93.10715],\n", + " [-84.4518797126874, 42.72240240035296, 93.01625],\n", + " [-84.45198971268724, 42.72247266701953, 92.88069],\n", + " [-84.45206011268715, 42.72252180035281, 92.79041],\n", + " [-84.4520945126871, 42.722547067019434, 92.74518],\n", + " [-84.4522557793535, 42.72268160035253, 92.51839],\n", + " [-84.45237437935333, 42.72287820035223, 92.24665],\n", + " [-84.45238771268663, 42.72291286701886, 92.20114],\n", + " [-84.4523987126866, 42.72294780035213, 92.15585],\n", + " [-84.45241997935324, 42.723054067018666, 92.0202],\n", + " [-84.45242397935323, 42.72312606701854, 91.9292],\n", + " [-84.45238731268665, 42.72326740035163, 91.74748],\n", + " [-84.45236777935332, 42.72330046701825, 91.70193],\n", + " [-84.45231911268672, 42.72336260035149, 91.61131],\n", + " [-84.45229831268676, 42.723395000351445, 91.56604],\n", + " [-84.45225717935352, 42.72349820035129, 91.4302],\n", + " [-84.4522517793535, 42.72353386701792, 91.38488],\n", + " [-84.45226157935349, 42.723569200351164, 91.33934],\n", + " [-84.45227837935346, 42.723602600351114, 91.29435],\n", + " [-84.45230771268677, 42.723631267017765, 91.24899],\n", + " [-84.45242937935325, 42.72369140035096, 91.11256],\n", + " [-84.4524723793532, 42.72370786701765, 91.06742],\n", + " [-84.45266151268623, 42.7237422670176, 90.88595],\n", + " [-84.45295191268576, 42.72376446701753, 90.61396],\n", + " [-84.45333997935182, 42.72375800035087, 90.25234],\n", + " [-84.45377631268451, 42.7237288670176, 89.84419],\n", + " [-84.45413017935061, 42.72363800035106, 89.49513],\n", + " [-84.45427657935039, 42.72363266701774, 89.35857],\n", + " [-84.45432457935033, 42.72362660035111, 89.3132],\n", + " [-84.45437317935023, 42.723624600351116, 89.26786],\n", + " [-84.45447071268342, 42.72362466701776, 89.17699],\n", + " [-84.45451877935, 42.72363026701777, 89.13166],\n", + " [-84.45484997934949, 42.723686800351004, 88.81496],\n", + " [-84.45489711268277, 42.723696467017646, 88.76938],\n", + " [-84.45494157934934, 42.723710867017644, 88.72414],\n", + " [-84.45502477934923, 42.72374760035092, 88.63381],\n", + " [-84.45506457934914, 42.723768600350866, 88.58823],\n", + " [-84.45518197934899, 42.72383266701746, 88.45218],\n", + " [-84.45537297934868, 42.723944667017236, 88.22488],\n", + " [-84.45545397934853, 42.72398406701717, 88.1345],\n", + " [-84.45553691268174, 42.724021067017134, 88.04421],\n", + " [-84.45567091268157, 42.72406500035038, 87.9076],\n", + " [-84.45571797934815, 42.72407380035037, 87.86237],\n", + " [-84.45594937934777, 42.72412820035032, 87.63611],\n", + " [-84.45604377934762, 42.72414526701692, 87.54557],\n", + " [-84.45628431268057, 42.72417566701688, 87.31822],\n", + " [-84.45642897934704, 42.72419020035022, 87.1822],\n", + " [-84.45657417934683, 42.72419900035021, 87.04648],\n", + " [-84.45667131267999, 42.72420166701687, 86.95593],\n", + " [-84.45671957934655, 42.724206600350215, 86.91053],\n", + " [-84.4568657126797, 42.72420640035017, 86.77439],\n", + " [-84.45691417934626, 42.72420286701686, 86.72902],\n", + " [-84.457105179346, 42.72417646701689, 86.54799],\n", + " [-84.45734191267894, 42.724134200350306, 86.32108],\n", + " [-84.45757671267859, 42.72408506701703, 86.09371],\n", + " [-84.45762111267851, 42.7240704003504, 86.04839],\n", + " [-84.45766751267843, 42.724059067017095, 86.00286],\n", + " [-84.45776237934496, 42.72404446701711, 85.91258],\n", + " [-84.45785951267817, 42.724046800350436, 85.82204],\n", + " [-84.4580049793446, 42.7240560670171, 85.68602],\n", + " [-84.4580965126778, 42.724080467017075, 85.59535],\n", + " [-84.4582757126775, 42.724137067016954, 85.41375],\n", + " [-84.45835811267739, 42.72417540035025, 85.323],\n", + " [-84.45851211267711, 42.7242622003501, 85.14245],\n", + " [-84.45855531267705, 42.72427886701672, 85.09703],\n", + " [-84.45873257934346, 42.72433886701663, 84.91534],\n", + " [-84.45891431267648, 42.724390667016564, 84.73384],\n", + " [-84.4589999126764, 42.72442446701649, 84.64339],\n", + " [-84.4591591126761, 42.72450766701638, 84.46163],\n", + " [-84.45935057934253, 42.72461846701623, 84.23493],\n", + " [-84.45945117934235, 42.72469646701609, 84.09897],\n", + " [-84.45950637934226, 42.724755467015996, 84.00844],\n", + " [-84.45974637934188, 42.72506660034884, 83.55638],\n", + " [-84.4597669126752, 42.72509920034878, 83.51098],\n", + " [-84.45985637934172, 42.72534086701512, 83.19463],\n", + " [-84.46001231267479, 42.72552260034814, 82.92302],\n", + " [-84.46006811267472, 42.725581467014706, 82.83231],\n", + " [-84.4602203126745, 42.7256684670146, 82.65294],\n", + " [-84.46030471267437, 42.72570486701454, 82.56186],\n", + " [-84.46044731267415, 42.7257294670145, 82.42544],\n", + " [-84.46049597934069, 42.72573006701447, 82.38009],\n", + " [-84.46054437934066, 42.725725800347846, 82.33468],\n", + " [-84.46059191267392, 42.72571726701449, 82.28911],\n", + " [-84.46062831267386, 42.7257352670145, 82.24829],\n", + " [-84.46065717934044, 42.725764467014415, 82.20265],\n", + " [-84.46067971267377, 42.7257962670144, 82.15734],\n", + " [-84.4606923793404, 42.7259030670142, 82.02195],\n", + " [-84.46068831267377, 42.72597386701409, 81.93246],\n", + " [-84.4606571126738, 42.726041400347356, 81.84236],\n", + " [-84.46061851267388, 42.72610586701393, 81.75337],\n", + " [-84.46059517934054, 42.72613686701385, 81.70859],\n", + " [-84.46056871267393, 42.72616720034716, 81.66304],\n", + " [-84.46025517934112, 42.726389600346806, 81.25784],\n", + " [-84.46010497934134, 42.72653046701322, 81.03151],\n", + " [-84.45999177934152, 42.726647200346406, 80.85026],\n", + " [-84.45994397934157, 42.72670960034628, 80.75975],\n", + " [-84.45992511267497, 42.726742600346256, 80.71453],\n", + " [-84.45990697934161, 42.726812800346124, 80.62428],\n", + " [-84.45990957934163, 42.726848867012734, 80.57867],\n", + " [-84.4599795793415, 42.72705806701242, 80.30656],\n", + " [-84.46007257934139, 42.72726146701211, 80.03549],\n", + " [-84.46012997934127, 42.72737606701196, 79.88121],\n", + " [-84.46014357934126, 42.727410667011895, 79.83571],\n", + " [-84.46031157934101, 42.72762966701151, 79.51794],\n", + " [-84.46049057934073, 42.72780020034463, 79.24558],\n", + " [-84.46059851267387, 42.72791940034443, 79.06456],\n", + " [-84.46078291267361, 42.728087000344146, 78.79198],\n", + " [-84.4608201793402, 42.72811006701079, 78.74666],\n", + " [-84.46090737934009, 42.728142000344064, 78.65597],\n", + " [-84.46123457933959, 42.72821006701065, 78.33928],\n", + " [-84.46132991267274, 42.728224667010636, 78.24858],\n", + " [-84.46170697933883, 42.728293267010486, 77.88681],\n", + " [-84.46193017933848, 42.728364067010375, 77.66048],\n", + " [-84.4620177126717, 42.728395067010354, 77.57003],\n", + " [-84.4621999126714, 42.72844606701028, 77.38849],\n", + " [-84.46237651267114, 42.72850560034351, 77.20762],\n", + " [-84.46256271267083, 42.728548467010114, 77.02592],\n", + " [-84.46269931267062, 42.728586400343374, 76.88995],\n", + " [-84.46302397933681, 42.728664200343246, 76.57195],\n", + " [-84.4631585793366, 42.72870540034319, 76.4362],\n", + " [-84.4633791126696, 42.72878000034308, 76.2102],\n", + " [-84.46351311266938, 42.72882206700967, 76.07454],\n", + " [-84.46379131266895, 42.728887600342944, 75.8025],\n", + " [-84.46388171266881, 42.728914067009555, 75.7119],\n", + " [-84.46407231266852, 42.728943200342826, 75.53057],\n", + " [-84.464168979335, 42.728951200342806, 75.43996],\n", + " [-84.46430997933481, 42.72897940034278, 75.30387],\n", + " [-84.46435557933472, 42.72899180034278, 75.2586],\n", + " [-84.46453137933446, 42.729054067009315, 75.07694],\n", + " [-84.46523117933339, 42.729309267008944, 74.34975],\n", + " [-84.46573211266593, 42.72944766700874, 73.85146],\n", + " [-84.46619697933187, 42.729555667008526, 73.39746],\n", + " [-84.4663861126649, 42.7295914003418, 73.21559],\n", + " [-84.46662917933122, 42.72960706700849, 72.98831],\n", + " [-84.46672577933106, 42.729597400341845, 72.8975],\n", + " [-84.46706071266385, 42.72955160034189, 72.58018],\n", + " [-84.4672029793303, 42.72952646700861, 72.44391],\n", + " [-84.46738491266336, 42.72947606700865, 72.26288],\n", + " [-84.46742817932994, 42.72945940034202, 72.21741],\n", + " [-84.4676963126629, 42.72937460034217, 71.94565],\n", + " [-84.46774197932945, 42.72936266700884, 71.90052],\n", + " [-84.46783017932933, 42.72933240034223, 71.80991],\n", + " [-84.46795777932914, 42.729280200342316, 71.67399],\n", + " [-84.46799591266239, 42.729257667008994, 71.62847],\n", + " [-84.46820977932873, 42.72911026700922, 71.35582],\n", + " [-84.46837891266182, 42.72898120034279, 71.12914],\n", + " [-84.46882431266113, 42.728762267009756, 70.63055],\n", + " [-84.4690233126608, 42.72865960034329, 70.40434],\n", + " [-84.46918597932722, 42.7285802003434, 70.22264],\n", + " [-84.4696423793265, 42.7283712670104, 69.72227],\n", + " [-84.46991571265943, 42.72829346701053, 69.44935],\n", + " [-84.47001211265928, 42.728282600343846, 69.35851],\n", + " [-84.47025471265891, 42.72826980034387, 69.13194],\n", + " [-84.47040071265866, 42.72827340034388, 68.99586],\n", + " [-84.470835912658, 42.72830600034382, 68.58837],\n", + " [-84.47122591265742, 42.728308267010505, 68.22506],\n", + " [-84.47146817932367, 42.728290067010505, 67.99821],\n", + " [-84.47213797932267, 42.72819180034401, 67.36204],\n", + " [-84.47279511265498, 42.72805606701087, 66.72635],\n", + " [-84.47335417932078, 42.72792946701105, 66.18157],\n", + " [-84.47373857932018, 42.727879667011166, 65.818],\n", + " [-84.47388011265326, 42.72785200034451, 65.68161],\n", + " [-84.4742171793194, 42.72781160034458, 65.36349],\n", + " [-84.47435911265251, 42.72778540034466, 65.2272],\n", + " [-84.47496557931828, 42.727646600344826, 64.63568],\n", + " [-84.47510731265135, 42.72761946701155, 64.49927],\n", + " [-84.47538131265094, 42.72758186701162, 64.23965],\n", + " [-84.47547631265081, 42.727566200344995, 64.14897],\n", + " [-84.47571751265042, 42.727538867011674, 63.92164],\n", + " [-84.47581437931694, 42.72753106701168, 63.83087],\n", + " [-84.47620257931635, 42.72750580034506, 63.46784],\n", + " [-84.4768333126487, 42.727458467011786, 62.87724],\n", + " [-84.4769793126485, 42.727455800345126, 62.7412],\n", + " [-84.47741831264779, 42.72746046701178, 62.3322],\n", + " [-84.4774657793144, 42.727468667011806, 62.28679],\n", + " [-84.47779317931389, 42.72753820034501, 61.96941],\n", + " [-84.47812837931338, 42.72758780034496, 61.65093],\n", + " [-84.47822131264655, 42.727608667011566, 61.56044],\n", + " [-84.47840997931291, 42.72764386701152, 61.37915],\n", + " [-84.47855037931271, 42.727673600344815, 61.24308],\n", + " [-84.479002779312, 42.72780766701129, 60.78891],\n", + " [-84.47938797931141, 42.727934400344395, 60.39601],\n", + " [-84.47991911264393, 42.728114800344144, 59.85131],\n", + " [-84.48013871264357, 42.72819240034403, 59.62448],\n", + " [-84.48091551264235, 42.72848920034352, 58.80956],\n", + " [-84.48133291264173, 42.728672467009915, 58.35707],\n", + " [-84.48280771263944, 42.72943886700875, 56.67659],\n", + " [-84.4828885126393, 42.72947886700865, 56.58594],\n", + " [-84.48297271263914, 42.72951446700864, 56.49554],\n", + " [-84.48301237930576, 42.72953546700859, 56.45005],\n", + " [-84.48326791263872, 42.72963906700841, 56.17844],\n", + " [-84.48368491263807, 42.72982380034148, 55.72533],\n", + " [-84.48372851263798, 42.72983966700809, 55.68004],\n", + " [-84.48386437930446, 42.72987940034136, 55.54389],\n", + " [-84.48425917930382, 42.730020400341175, 55.13529],\n", + " [-84.48464471263657, 42.73017260034095, 54.72797],\n", + " [-84.48468557930318, 42.73019180034089, 54.68284],\n", + " [-84.48476377930308, 42.730234400340805, 54.59228],\n", + " [-84.48502097930265, 42.73039900034058, 54.2751],\n", + " [-84.48513537930245, 42.730465867007126, 54.13913],\n", + " [-84.48565037930166, 42.730793467006606, 53.50566],\n", + " [-84.48568917930163, 42.73081546700661, 53.46008],\n", + " [-84.48579911263477, 42.73088600033981, 53.32436],\n", + " [-84.48586777930132, 42.73093620033973, 53.2343],\n", + " [-84.4861917126342, 42.73115226700605, 52.82749],\n", + " [-84.48641137930048, 42.73129406700588, 52.55558],\n", + " [-84.48671151263335, 42.73147746700556, 52.19254],\n", + " [-84.48678797929989, 42.731522200338816, 52.10163],\n", + " [-84.48694497929966, 42.73160680033868, 51.92052],\n", + " [-84.48698297929963, 42.73162946700535, 51.875],\n", + " [-84.48701717929953, 42.73165466700527, 51.82997],\n", + " [-84.48732937929907, 42.73197446700482, 51.33231],\n", + " [-84.48736517929899, 42.73204040033801, 51.24262],\n", + " [-84.48739317929898, 42.73210840033795, 51.15287],\n", + " [-84.48741377929895, 42.73214046700451, 51.10806],\n", + " [-84.4874403792989, 42.732246400337715, 50.97201],\n", + " [-84.4874461792989, 42.732317200337604, 50.88244],\n", + " [-84.48745397929889, 42.73260066700385, 50.5244],\n", + " [-84.4874499126322, 42.73267200033706, 50.43424],\n", + " [-84.48746691263221, 42.73270526700367, 50.38935],\n", + " [-84.48758797929867, 42.732899467003335, 50.11942],\n", + " [-84.4876369126319, 42.73296160033658, 50.02868],\n", + " [-84.48766537929856, 42.73299020033653, 49.98388],\n", + " [-84.48769711263185, 42.73301726700316, 49.93869],\n", + " [-84.48776557929841, 42.73306746700308, 49.84877],\n", + " [-84.48779537929835, 42.73309560033641, 49.80368],\n", + " [-84.48783177929829, 42.73311940033636, 49.75837],\n", + " [-84.4879099792982, 42.73316146700296, 49.66822],\n", + " [-84.48799251263137, 42.73319966700291, 49.57746],\n", + " [-84.4882093792977, 42.7332790003361, 49.35198],\n", + " [-84.48829657929758, 42.733310200336064, 49.2617],\n", + " [-84.48861337929708, 42.733404000335895, 48.94373],\n", + " [-84.48897877929653, 42.73350046700244, 48.58223],\n", + " [-84.48916471262953, 42.73353826700236, 48.40258],\n", + " [-84.48926151262941, 42.73354666700237, 48.31179],\n", + " [-84.48955151262896, 42.73356346700234, 48.04084],\n", + " [-84.48969791262874, 42.73356720033564, 47.90439],\n", + " [-84.48979451262858, 42.733565467002336, 47.81438],\n", + " [-84.49013191262804, 42.733545400335686, 47.49909],\n", + " [-84.49047177929418, 42.7335318003357, 47.18205],\n", + " [-84.49085731262693, 42.73349626700241, 46.82015],\n", + " [-84.49100251262672, 42.7334860003358, 46.68428],\n", + " [-84.49114797929315, 42.7334784003358, 46.54844],\n", + " [-84.49148891262593, 42.733466800335805, 46.23054],\n", + " [-84.49192397929193, 42.733430000335886, 45.82264],\n", + " [-84.49206697929174, 42.73340700033589, 45.68631],\n", + " [-84.49230291262467, 42.733363000336, 45.45963],\n", + " [-84.49248511262442, 42.73331380033608, 45.2789],\n", + " [-84.49295671262365, 42.733225200336165, 44.8256],\n", + " [-84.4929987126236, 42.733206800336234, 44.78009],\n", + " [-84.49305577929016, 42.73316046700296, 44.70104],\n", + " [-84.49311577929006, 42.73298700033655, 44.47497],\n", + " [-84.49312337929007, 42.73295146700326, 44.42954],\n", + " [-84.49310031262343, 42.73284526700343, 44.29372],\n", + " [-84.49303691262355, 42.732672067003705, 44.06717],\n", + " [-84.49284911262384, 42.73242066700408, 43.70469],\n", + " [-84.49281771262389, 42.73239326700417, 43.65938],\n", + " [-84.49279091262395, 42.73236326700419, 43.61401],\n", + " [-84.49255571262427, 42.732090600337926, 43.2059],\n", + " [-84.49248031262442, 42.73199826700477, 43.06978],\n", + " [-84.49239491262455, 42.731869600338314, 42.88887],\n", + " [-84.49232291262467, 42.73173686700517, 42.70833],\n", + " [-84.49219891262487, 42.731543067005475, 42.43771],\n", + " [-84.49212337929163, 42.73141220033898, 42.25809],\n", + " [-84.492101112625, 42.73138020033906, 42.21267],\n", + " [-84.49204877929174, 42.731243467005925, 42.03325],\n", + " [-84.4920035792918, 42.73110440033946, 41.85266],\n", + " [-84.4919847126252, 42.73103386700626, 41.76187],\n", + " [-84.49193871262526, 42.73082166700658, 41.4905],\n", + " [-84.49193257929193, 42.73075080034005, 41.40082],\n", + " [-84.49193611262524, 42.73071520034006, 41.35575],\n", + " [-84.49193097929191, 42.730644067006835, 41.26579],\n", + " [-84.49193191262526, 42.73053680034036, 41.13033],\n", + " [-84.49194491262523, 42.73035800034063, 40.90422],\n", + " [-84.4919521126252, 42.73032226700735, 40.8586],\n", + " [-84.49196851262519, 42.730288400340726, 40.81318],\n", + " [-84.49199111262516, 42.73025646700745, 40.76769],\n", + " [-84.49201871262511, 42.730226667007514, 40.72211],\n", + " [-84.49212051262498, 42.73014980034094, 40.58641],\n", + " [-84.49227071262476, 42.73005820034109, 40.40488],\n", + " [-84.49234957929127, 42.73001586700781, 40.31402],\n", + " [-84.49247331262444, 42.72995826700793, 40.17773],\n", + " [-84.49265257929079, 42.72990206700803, 39.99628],\n", + " [-84.49274397929065, 42.729877400341366, 39.90562],\n", + " [-84.49297771262366, 42.72982680034147, 39.67871],\n", + " [-84.49331817928976, 42.72980506700816, 39.36038],\n", + " [-84.49336651262303, 42.72979906700817, 39.31472],\n", + " [-84.49380391262235, 42.729791600341514, 38.90716],\n", + " [-84.4941065126219, 42.72976260034159, 38.62292],\n", + " [-84.49423877928837, 42.72971686700828, 38.48685],\n", + " [-84.49428591262159, 42.7297078670083, 38.44149],\n", + " [-84.49467017928765, 42.72967226700837, 38.08073],\n", + " [-84.49476577928755, 42.72966626700838, 37.99135],\n", + " [-84.49510357928699, 42.729663867008355, 37.67667],\n", + " [-84.49519951262016, 42.729658600341736, 37.58706],\n", + " [-84.49553877928633, 42.72966286700836, 37.27098],\n", + " [-84.49563511261954, 42.72966006700841, 37.18118],\n", + " [-84.49660257928468, 42.72966800034169, 36.27991],\n", + " [-84.49737811261679, 42.72968986700835, 35.55696],\n", + " [-84.49747451261663, 42.72968946700837, 35.46716],\n", + " [-84.49766891261635, 42.729700800341675, 35.2855],\n", + " [-84.49771711261627, 42.729706667008315, 35.24],\n", + " [-84.49780937928278, 42.72972680034161, 35.15037],\n", + " [-84.49798917928251, 42.72977780034154, 34.97093],\n", + " [-84.49803011261577, 42.72979680034149, 34.92587],\n", + " [-84.49810411261569, 42.729842667008086, 34.83584],\n", + " [-84.49823977928213, 42.729945600341296, 34.65455],\n", + " [-84.49832377928203, 42.72998066700791, 34.56464],\n", + " [-84.4984189126152, 42.72999620034119, 34.47387],\n", + " [-84.49856377928165, 42.730007067007875, 34.33823],\n", + " [-84.49870937928142, 42.729998400341174, 34.20216],\n", + " [-84.49890097928113, 42.729975467007876, 34.02135],\n", + " [-84.49936297928036, 42.729859467008055, 33.56674],\n", + " [-84.4995455792801, 42.72980906700815, 33.38513],\n", + " [-84.49977717927976, 42.729752667008256, 33.15794],\n", + " [-84.50004671261269, 42.729670600341706, 32.88631],\n", + " [-84.50008897927927, 42.729653267008416, 32.84127],\n", + " [-84.50013097927922, 42.729634800341785, 32.79572],\n", + " [-84.50024097927906, 42.72956506700854, 32.66061],\n", + " [-84.50034371261222, 42.729490400342, 32.52627],\n", + " [-84.50043997927872, 42.72941086700877, 32.39162],\n", + " [-84.50046211261201, 42.729379000342135, 32.34641],\n", + " [-84.50056897927851, 42.72925926700901, 32.16538],\n", + " [-84.50062591261178, 42.72920180034242, 32.07549],\n", + " [-84.50089277927799, 42.72899440034274, 31.71439],\n", + " [-84.50096297927792, 42.72894486700949, 31.6239],\n", + " [-84.50108671261103, 42.72888780034293, 31.48796],\n", + " [-84.50121937927753, 42.72884280034299, 31.35194],\n", + " [-84.5014059792772, 42.728801267009715, 31.17037],\n", + " [-84.50159677927695, 42.72877386700975, 30.9893],\n", + " [-84.5016911126101, 42.728756600343104, 30.89876],\n", + " [-84.50173931261003, 42.72875200034315, 30.85349],\n", + " [-84.50188331260983, 42.72876946700978, 30.71755],\n", + " [-84.5020289126096, 42.72877560034311, 30.58169],\n", + " [-84.50207611260953, 42.72878186700973, 30.53702],\n", + " [-84.50230637927581, 42.72883140034298, 30.31358],\n", + " [-84.50249251260885, 42.72886720034296, 30.1344],\n", + " [-84.50262837927534, 42.72890266700955, 30.00014],\n", + " [-84.5027101792752, 42.72894066700951, 29.91009],\n", + " [-84.5027475126085, 42.72896300034279, 29.86532],\n", + " [-84.50282877927503, 42.72900166700941, 29.77523],\n", + " [-84.50294431260818, 42.7290658670093, 29.64049],\n", + " [-84.503048912608, 42.72913980034252, 29.50554],\n", + " [-84.50309411260793, 42.72920280034242, 29.41553],\n", + " [-84.50312117927456, 42.72923246700907, 29.37037],\n", + " [-84.50316071260784, 42.72933606700889, 29.23446],\n", + " [-84.50316357927449, 42.72937186700881, 29.18917],\n", + " [-84.50319237927442, 42.72951286700862, 29.00911],\n", + " [-84.50320477927443, 42.72965640034175, 28.82748],\n", + " [-84.50318851260778, 42.72972740034163, 28.73655],\n", + " [-84.50313417927453, 42.72990266700799, 28.50951],\n", + " [-84.50315551260786, 42.72993360034127, 28.46568],\n", + " [-84.50319497927444, 42.729954400341285, 28.4205],\n", + " [-84.50337677927416, 42.730005667007845, 28.23919],\n", + " [-84.50342411260743, 42.73001520034114, 28.19349],\n", + " [-84.50356837927387, 42.73000540034121, 28.05853],\n", + " [-84.50370397927367, 42.72997106700791, 27.92499],\n", + " [-84.50374837927359, 42.729957000341244, 27.87997],\n", + " [-84.50378597927352, 42.72993480034131, 27.83511],\n", + " [-84.50385331260674, 42.72988420034136, 27.74557],\n", + " [-84.50388317927337, 42.729856267008074, 27.70065],\n", + " [-84.50398257927321, 42.72973266700825, 27.51917],\n", + " [-84.50401097927318, 42.72970326700835, 27.47358],\n", + " [-84.50407651260639, 42.72965146700841, 27.3841],\n", + " [-84.50411311260638, 42.72962820034178, 27.3391],\n", + " [-84.50417731260626, 42.729574067008514, 27.24827],\n", + " [-84.50432571260603, 42.729481400342024, 27.06715],\n", + " [-84.50441857927257, 42.729459800342056, 26.97645],\n", + " [-84.50456317927234, 42.729445800342035, 26.84059],\n", + " [-84.5046117126056, 42.72944440034206, 26.79535],\n", + " [-84.50475771260534, 42.72944466700875, 26.65934],\n", + " [-84.50480391260527, 42.72945646700873, 26.6138],\n", + " [-84.50493417927174, 42.729504067008634, 26.47838],\n", + " [-84.50497491260501, 42.72952366700861, 26.43308],\n", + " [-84.50504991260487, 42.729569800341835, 26.34211],\n", + " [-84.50512331260478, 42.72961700034176, 26.25141],\n", + " [-84.50518851260466, 42.729670067008385, 26.16097],\n", + " [-84.50521331260467, 42.72970126700835, 26.11529],\n", + " [-84.50530357927119, 42.729868600341376, 25.88787],\n", + " [-84.50533057927112, 42.72989600034134, 25.84509],\n", + " [-84.50537777927104, 42.72990540034135, 25.79955],\n", + " [-84.50542617927096, 42.72990980034132, 25.75412],\n", + " [-84.50562077927066, 42.72992086700799, 25.57231],\n", + " [-84.50568917927058, 42.72991900034134, 25.50855],\n", + " [-84.5057297792705, 42.729899000341334, 25.46307],\n", + " [-84.50588177927028, 42.729809000341504, 25.28151],\n", + " [-84.50591571260355, 42.72978320034156, 25.23612],\n", + " [-84.50594137927015, 42.729752600341556, 25.19068],\n", + " [-84.50598291260343, 42.729687400341675, 25.0997],\n", + " [-84.5059919792701, 42.729652667008395, 25.05504],\n", + " [-84.50599111260345, 42.72950940034195, 24.87411],\n", + " [-84.50600211260343, 42.729474467008686, 24.82883],\n", + " [-84.50611071260323, 42.729200800342426, 24.46873],\n", + " [-84.50614117926989, 42.72913280034254, 24.37829],\n", + " [-84.5061525126032, 42.72909806700926, 24.33318],\n", + " [-84.5061829792698, 42.729030000342675, 24.24266],\n", + " [-84.50628911260299, 42.72882866700968, 23.96986],\n", + " [-84.50633251260291, 42.728764600343084, 23.87942],\n", + " [-84.50658511260252, 42.728499600343525, 23.47033],\n", + " [-84.50661551260248, 42.72847166701024, 23.42509],\n", + " [-84.50667857926902, 42.72841746701033, 23.33489],\n", + " [-84.50700497926852, 42.72820166701064, 22.92659],\n", + " [-84.50716151260161, 42.728116600344094, 22.74547],\n", + " [-84.50720337926822, 42.72809860034414, 22.70033],\n", + " [-84.50756477926768, 42.727918600344424, 22.29412],\n", + " [-84.5076835126008, 42.72785626701119, 22.15836],\n", + " [-84.50779871260062, 42.7277902003446, 22.02243],\n", + " [-84.50817251260003, 42.72755860034499, 21.56769],\n", + " [-84.50853431259947, 42.72731806701205, 21.11397],\n", + " [-84.50922017926507, 42.726761267012876, 20.1639],\n", + " [-84.50947937926469, 42.726588067013154, 19.83811]]]}},\n", + " {'attributes': {'gnis_name': None,\n", + " 'lengthkm': 0.035,\n", + " 'permanent_identifier': '152093660',\n", + " 'reachcode': '04050004002359'},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.50819331260004, 42.72549706701483, 26.20677],\n", + " [-84.50850391259951, 42.725713200347855, 20.54223]]]}}]}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#JSON representing all flowlines within buffer of the input coordinates (point to HydroLink)\n", + "hydrolink.flowlines_json" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'gnis_name': None,\n", + " 'lengthkm': 0.011,\n", + " 'permanent_identifier': '152093560',\n", + " 'reachcode': '04050004002359',\n", + " 'flowline name similarity message': 'no GNIS name',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0,\n", + " 'meters from flowline': 590.9925501612136,\n", + " 'nhdhr flowline measure': 1.83965},\n", + " {'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 1.746,\n", + " 'permanent_identifier': '152093412',\n", + " 'reachcode': '04050004000126',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0.8823529411764706,\n", + " 'flowline name similarity message': 'most likely match, based on fuzzy match',\n", + " 'meters from flowline': 595.535732278204,\n", + " 'nhdhr flowline measure': 19.83811},\n", + " {'gnis_name': None,\n", + " 'lengthkm': 0.455,\n", + " 'permanent_identifier': '152091798',\n", + " 'reachcode': '04050004002359',\n", + " 'flowline name similarity message': 'no GNIS name',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0,\n", + " 'meters from flowline': 585.7335037669081,\n", + " 'nhdhr flowline measure': 100.0},\n", + " {'gnis_name': None,\n", + " 'lengthkm': 1.913,\n", + " 'permanent_identifier': '152091441',\n", + " 'reachcode': '04050004000960',\n", + " 'flowline name similarity message': 'no GNIS name',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0,\n", + " 'meters from flowline': 1824.4988883614678,\n", + " 'nhdhr flowline measure': 73.10646858684098},\n", + " {'gnis_name': None,\n", + " 'lengthkm': 0.178,\n", + " 'permanent_identifier': '152093510',\n", + " 'reachcode': '04050004000960',\n", + " 'flowline name similarity message': 'no GNIS name',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0,\n", + " 'meters from flowline': 1913.0677192272312,\n", + " 'nhdhr flowline measure': 8.5013},\n", + " {'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 0.826,\n", + " 'permanent_identifier': '152093411',\n", + " 'reachcode': '04050004000125',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0.8823529411764706,\n", + " 'flowline name similarity message': 'most likely match, based on fuzzy match',\n", + " 'meters from flowline': 1963.969165665174,\n", + " 'nhdhr flowline measure': 100.0},\n", + " {'gnis_name': None,\n", + " 'lengthkm': 0.116,\n", + " 'permanent_identifier': '152091797',\n", + " 'reachcode': '04050004002359',\n", + " 'flowline name similarity message': 'no GNIS name',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0,\n", + " 'meters from flowline': 566.7249346614303,\n", + " 'nhdhr flowline measure': 20.54223},\n", + " {'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 7.032,\n", + " 'permanent_identifier': '152093413',\n", + " 'reachcode': '04050004000126',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0.8823529411764706,\n", + " 'flowline name similarity message': 'most likely match, based on fuzzy match',\n", + " 'meters from flowline': 52.94060499992746,\n", + " 'nhdhr flowline measure': 29.511920388736257},\n", + " {'gnis_name': None,\n", + " 'lengthkm': 0.035,\n", + " 'permanent_identifier': '152093660',\n", + " 'reachcode': '04050004002359',\n", + " 'flowline name similarity message': 'no GNIS name',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0,\n", + " 'meters from flowline': 559.1053858081182,\n", + " 'nhdhr flowline measure': 26.20677}]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#HydroLink data for all flowlines within buffer, these data are evaluated to find closest flowline \n", + "#with a matching name (using a fuzzy match approach)\n", + "hydrolink.flowlines_data" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'nhdhr flowline gnis name': 'Red Cedar River',\n", + " 'nhdhr flowline length km': 7.032,\n", + " 'nhdhr flowline permanent identifier': '152093413',\n", + " 'nhdhr flowline reachcode': '04050004000126',\n", + " 'flowline name similarity message': 'most likely match, based on fuzzy match',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0.8823529411764706,\n", + " 'meters from flowline': 52.94060499992746,\n", + " 'nhdhr flowline measure': 29.511920388736257,\n", + " 'closest flowline order': 1}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#HydroLink Data for the selected flowline.\n", + "#The name_match approach uses the closest flowline with a name match (meeting a specified similarity cutoff for names)\n", + "#If no flowline names (gnis_name) match the source water name, then the closest flowline is returned\n", + "hydrolink.hydrolink_flowline" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>source id</th>\n", + " <th>source lat nad83</th>\n", + " <th>source lon nad83</th>\n", + " <th>source buffer meters</th>\n", + " <th>closest conluence meters</th>\n", + " <th>closest flowline order</th>\n", + " <th>total count flowlines in buffer</th>\n", + " <th>source water name</th>\n", + " <th>cleaned source water name</th>\n", + " <th>flowline name similarity</th>\n", + " <th>...</th>\n", + " <th>nhdhr flowline gnis name</th>\n", + " <th>nhdhr flowline length km</th>\n", + " <th>nhdhr flowline permanent identifier</th>\n", + " <th>nhdhr flowline reachcode</th>\n", + " <th>meters from flowline</th>\n", + " <th>nhdhr flowline measure</th>\n", + " <th>nhdhr waterbody permanent identifier</th>\n", + " <th>nhdhr waterbody gnis name</th>\n", + " <th>nhdhr waterbody reachcode</th>\n", + " <th>hydrolink message</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <td>0</td>\n", + " <td>1</td>\n", + " <td>42.7284</td>\n", + " <td>-84.5026</td>\n", + " <td>2000</td>\n", + " <td>595.535732</td>\n", + " <td>1</td>\n", + " <td>9</td>\n", + " <td>The Red Cedar River</td>\n", + " <td>the red cedar river</td>\n", + " <td>0.882353</td>\n", + " <td>...</td>\n", + " <td>Red Cedar River</td>\n", + " <td>7.032</td>\n", + " <td>152093413</td>\n", + " <td>4050004000126</td>\n", + " <td>52.940605</td>\n", + " <td>29.51192</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "<p>1 rows × 21 columns</p>\n", + "</div>" + ], + "text/plain": [ + " source id source lat nad83 source lon nad83 source buffer meters \\\n", + "0 1 42.7284 -84.5026 2000 \n", + "\n", + " closest conluence meters closest flowline order \\\n", + "0 595.535732 1 \n", + "\n", + " total count flowlines in buffer source water name \\\n", + "0 9 The Red Cedar River \n", + "\n", + " cleaned source water name flowline name similarity ... \\\n", + "0 the red cedar river 0.882353 ... \n", + "\n", + " nhdhr flowline gnis name nhdhr flowline length km \\\n", + "0 Red Cedar River 7.032 \n", + "\n", + " nhdhr flowline permanent identifier nhdhr flowline reachcode \\\n", + "0 152093413 4050004000126 \n", + "\n", + " meters from flowline nhdhr flowline measure \\\n", + "0 52.940605 29.51192 \n", + "\n", + " nhdhr waterbody permanent identifier nhdhr waterbody gnis name \\\n", + "0 NaN NaN \n", + "\n", + " nhdhr waterbody reachcode hydrolink message \n", + "0 NaN NaN \n", + "\n", + "[1 rows x 21 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "df = pd.read_csv('nhdhr_hydrolink_output.csv')\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/example-using-single-point-nhd-medium-resolution.ipynb b/examples/example-using-single-point-nhd-medium-resolution.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ba126bb04f09e3f7a80e54fb9d67ce2cc5b1a7f6 --- /dev/null +++ b/examples/example-using-single-point-nhd-medium-resolution.ipynb @@ -0,0 +1,569 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This example shows how to use hydrolinking methods to address a point to the NHDPlusV2.1 Medium Resolution.\n", + "\n", + "For this example we use a point near the Red Cedar River in East Lansing, MI." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### First set required parameters.\n", + "Note: The input parameters are the same for NHDPlusV2.1 and NHD High Resolution hydrolinking methods.\n", + "\n", + "Required parameters include:\n", + "\n", + " *input_identifier: str, user supplied identifier\n", + " *input_lat: float, latitude of the point to be hydrolinked\n", + " *input_lon: float, longitude of the point to be hydrolinked\n", + " \n", + "Other optional parameters include:\n", + "\n", + " *input_crs: int, coordinate reference system, default is 4269 (NAD83)\n", + " *water_name: str, name of the water feature that the point is intended to be linked to\n", + " *buffer_m = int, search distance in meters, default is 1000, max is 2000\n", + " *method = str, link data to 'closest' or 'name_match' (closest having a matching water name), default 'name_match'\n", + " *hydro_type = str, link to 'waterbody' or 'flowline' NHD features, default 'flowline'" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#identifier -> number or string\n", + "input_identifier = 1\n", + "#latitude\n", + "input_lat = 42.7284\n", + "#longitude\n", + "input_lon = -84.5026\n", + "#coordinate system\n", + "input_crs = 4269 \n", + "#In this case we want the point to link to the Red Cedar River so water_name is a stream name\n", + "water_name = 'The Red Cedar River'\n", + "#buffer\n", + "buffer_m = 2000\n", + "\n", + "\n", + "method = 'name_match'\n", + "hydro_type = 'flowline'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First the module for National Hydrography Dataset Plus Version 2.1 is imported. This HydroLink module is called nhdplusv2." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#Import hydrolink module for nhd_mr\n", + "from hydrolink import nhd_mr\n", + "import warnings; warnings.simplefilter('ignore')" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "The next cell iniates the HydroLink object for HydroLinking a point.\n", + "\n", + "Note: It is recommended to use crs 4269, although this code attempts to accomidate other common coordinate systems by using some simple logic not all crs have been tested. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Initiate a Python object, this tries reprojecting data to nad83 and checks if data within bounding box of U.S.\n", + "hydrolink = nhd_mr.MedResPoint(input_identifier, input_lat, input_lon, water_name=water_name, input_crs=input_crs, buffer_m=buffer_m)\n", + "\n", + "# Runs the HydroLink process using name_match method and flowline hydro_type. \n", + "#This writes data out to a CSV in working directory named 'nhdplusv2_hydrolink_output.csv'\n", + "hydrolink.hydrolink_method(method=method, hydro_type=hydro_type)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'displayFieldName': 'GNIS_NAME',\n", + " 'hasM': True,\n", + " 'fieldAliases': {'GNIS_NAME': 'GNIS_Name',\n", + " 'LENGTHKM': 'LengthKm',\n", + " 'REACHCODE': 'ReachCode',\n", + " 'COMID': 'ComID',\n", + " 'TERMINALFLAG': 'TerminalFlag'},\n", + " 'geometryType': 'esriGeometryPolyline',\n", + " 'spatialReference': {'wkid': 4269, 'latestWkid': 4269},\n", + " 'fields': [{'name': 'GNIS_NAME',\n", + " 'type': 'esriFieldTypeString',\n", + " 'alias': 'GNIS_Name',\n", + " 'length': 65},\n", + " {'name': 'LENGTHKM', 'type': 'esriFieldTypeDouble', 'alias': 'LengthKm'},\n", + " {'name': 'REACHCODE',\n", + " 'type': 'esriFieldTypeString',\n", + " 'alias': 'ReachCode',\n", + " 'length': 14},\n", + " {'name': 'COMID', 'type': 'esriFieldTypeInteger', 'alias': 'ComID'},\n", + " {'name': 'TERMINALFLAG',\n", + " 'type': 'esriFieldTypeInteger',\n", + " 'alias': 'TerminalFlag'}],\n", + " 'features': [{'attributes': {'GNIS_NAME': 'Red Cedar River',\n", + " 'LENGTHKM': 8.42166733289811,\n", + " 'REACHCODE': '04050004000126',\n", + " 'COMID': 12242482,\n", + " 'TERMINALFLAG': 0},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.4466485128023, 42.72263866711311, 100],\n", + " [-84.44881837930994, 42.721860200917234, 97.65709999999672],\n", + " [-84.4500587115906, 42.721859599766, 96.45260000000417],\n", + " [-84.45114411235538, 42.72211039968431, 95.34819999999308],\n", + " [-84.45164038017462, 42.722315866781464, 94.79559999999765],\n", + " [-84.45216798030759, 42.72268140008653, 94.09279999999853],\n", + " [-84.45223011314366, 42.722887067211865, 93.81540000000678],\n", + " [-84.45223071316714, 42.723412866703896, 93.12330000000657],\n", + " [-84.45247911341495, 42.72364140075007, 92.73780000000261],\n", + " [-84.45282018025928, 42.72373266684864, 92.38550000000396],\n", + " [-84.45424651316101, 42.72366326707219, 90.99749999999767],\n", + " [-84.45607651194425, 42.724142201248036, 89.11199999999371],\n", + " [-84.45762671260444, 42.72407266731076, 87.60390000000189],\n", + " [-84.45852617923012, 42.72432360015729, 86.6701999999932],\n", + " [-84.4591777127096, 42.724574667260086, 85.95639999999548],\n", + " [-84.4594259791063, 42.72482599993359, 85.54709999999614],\n", + " [-84.4594883131649, 42.7250316672086, 85.26970000000438],\n", + " [-84.45976791277805, 42.72539726698882, 84.71719999999914],\n", + " [-84.4604503797194, 42.72580840110518, 83.86160000000382],\n", + " [-84.4604507803413, 42.7260826671534, 83.5005999999994],\n", + " [-84.46017217958351, 42.72644866721087, 82.94809999999416],\n", + " [-84.46001737922386, 42.72667739990113, 82.61160000000382],\n", + " [-84.4600491786237, 42.72722606695973, 81.88880000000063],\n", + " [-84.46039071166472, 42.72761446691081, 81.27939999999944],\n", + " [-84.46082517899784, 42.72788860027797, 80.72430000000168],\n", + " [-84.46374118039478, 42.72884686684619, 77.6247000000003],\n", + " [-84.4656027118204, 42.72944006672608, 75.65589999999793],\n", + " [-84.46631597931511, 42.7295766673141, 74.94040000000678],\n", + " [-84.46721531301897, 42.72953040120165, 74.06510000000708],\n", + " [-84.46808311319988, 42.72918680032205, 73.10880000000179],\n", + " [-84.46972551160712, 42.728248267152246, 71.09159999999974],\n", + " [-84.47015937890131, 42.72815660124826, 70.6533999999956],\n", + " [-84.47053157865562, 42.72817906706075, 70.29080000000249],\n", + " [-84.47105877996553, 42.72824726683776, 69.77109999999811],\n", + " [-84.47267117996375, 42.728109001192166, 68.19500000000698],\n", + " [-84.47577111331763, 42.72739780001159, 65.04290000000037],\n", + " [-84.4769185131139, 42.72739700028122, 63.928799999994226],\n", + " [-84.47818997918519, 42.727510266993875, 62.68529999999737],\n", + " [-84.47874851281993, 42.727624067313904, 62.12270000000717],\n", + " [-84.47995837871161, 42.72798899989651, 60.85349999999744],\n", + " [-84.48073411321779, 42.72830826703101, 59.99099999999453],\n", + " [-84.48299957912562, 42.72940386707112, 57.360799999994924],\n", + " [-84.4852963126729, 42.73061360087148, 54.62069999999949],\n", + " [-84.48719011330387, 42.73189220029633, 52.12810000000172],\n", + " [-84.48737637889685, 42.73214346702749, 51.75109999999404],\n", + " [-84.48746997879827, 42.73246346693877, 51.32030000000668],\n", + " [-84.48759451307984, 42.732737667277426, 50.93959999999788],\n", + " [-84.48802917893617, 42.73305740020754, 50.343599999992875],\n", + " [-84.48889811183467, 42.7333082010326, 49.43769999999495],\n", + " [-84.48945657989245, 42.73342199995294, 48.87519999999495],\n", + " [-84.49221637878432, 42.73332806672276, 46.1929999999993],\n", + " [-84.49283657907179, 42.73321320008478, 45.572100000004866],\n", + " [-84.49296038036822, 42.733075867217174, 45.355100000000675],\n", + " [-84.49292858006125, 42.73261866723001, 44.75250000000233],\n", + " [-84.49280397930661, 42.73232146694995, 44.34299999999348],\n", + " [-84.49264877922779, 42.732230200300314, 44.15029999999388],\n", + " [-84.49212031315686, 42.731476266798495, 43.03320000000531],\n", + " [-84.49205777967012, 42.73129340030471, 42.78489999999874],\n", + " [-84.4919647124451, 42.731224866715074, 42.65720000000147],\n", + " [-84.49187071192178, 42.73063060116892, 41.869699999995646],\n", + " [-84.49199411258749, 42.73031040111222, 41.43159999999625],\n", + " [-84.4922109128526, 42.73008146670558, 41.06399999999849],\n", + " [-84.49276871259727, 42.72985240015833, 40.44419999999809],\n", + " [-84.49431871295779, 42.729530867179825, 38.880799999999],\n", + " [-84.49689271334506, 42.729551267216046, 36.38150000000314],\n", + " [-84.4978539786736, 42.72959619981369, 35.44629999999597],\n", + " [-84.49844371257421, 42.72982419970183, 34.79989999999816],\n", + " [-84.49996311271231, 42.72970840082586, 33.31680000000051],\n", + " [-84.50036517966299, 42.72953600129288, 32.865200000000186],\n", + " [-84.50086217876607, 42.729170666799796, 32.18399999999383],\n", + " [-84.5014521130485, 42.72880566669784, 31.43640000000596],\n", + " [-84.50228957980201, 42.72871519997995, 30.61460000000079],\n", + " [-84.50287837855434, 42.728967400790914, 29.95350000000326],\n", + " [-84.50324991263277, 42.729242067292525, 29.442800000004354],\n", + " [-84.50331071322988, 42.72983660018963, 28.65799999999581],\n", + " [-84.50355831308788, 42.72997406694379, 28.357199999998556],\n", + " [-84.50393057932186, 42.72995166714746, 27.9945000000007],\n", + " [-84.50476877913592, 42.729495400328794, 26.98309999999765],\n", + " [-84.50523411260603, 42.72942726730374, 26.522500000006403],\n", + " [-84.50579177935118, 42.72970226699228, 25.871199999994133],\n", + " [-84.50607091187595, 42.72970260024079, 25.60009999999602],\n", + " [-84.50619511202206, 42.72958840124926, 25.407399999996414],\n", + " [-84.50672417922763, 42.72867446716416, 24.099400000006426],\n", + " [-84.50715937966123, 42.72817200096262, 23.31459999999788],\n", + " [-84.50812191167371, 42.72760146693175, 22.11569999999483],\n", + " [-84.50899151300723, 42.72693946713074, 20.90230000000156],\n", + " [-84.50982991315222, 42.72641460032082, 19.834600000001956],\n", + " [-84.510606178649, 42.725843867321295, 18.77049999999872],\n", + " [-84.51085477926986, 42.72556980084394, 18.33640000000014],\n", + " [-84.51237651242943, 42.72445106693804, 16.250400000004447],\n", + " [-84.51402171178066, 42.723538400186406, 14.251600000003236],\n", + " [-84.51560431201935, 42.722945666723476, 12.528099999995902],\n", + " [-84.51616297864683, 42.722626067165166, 11.841700000004494],\n", + " [-84.51678417969711, 42.722100866871536, 10.924199999994016],\n", + " [-84.51721917890595, 42.721621267000785, 10.164600000003702],\n", + " [-84.517374379903, 42.72150720096333, 9.951900000000023],\n", + " [-84.51771571265967, 42.72148466677661, 9.619099999996251],\n", + " [-84.51787058036193, 42.72157626667572, 9.426399999996647],\n", + " [-84.51796311307619, 42.72178206681702, 9.14100000000326],\n", + " [-84.51811771216671, 42.722033800169804, 8.77719999999681],\n", + " [-84.51830357892557, 42.72210259997984, 8.575299999996787],\n", + " [-84.51873771210992, 42.72212579987213, 8.152600000001257],\n", + " [-84.5189859794426, 42.721988866810435, 7.851599999994505],\n", + " [-84.51901791189287, 42.721508866779196, 7.219100000002072],\n", + " [-84.51864691324126, 42.72091406727992, 6.3571999999985565],\n", + " [-84.51861697875007, 42.72029666674323, 5.544099999999162],\n", + " [-84.51874171161917, 42.7199538007055, 5.0767999999952735],\n", + " [-84.51874391260631, 42.7187649997902, 3.5121000000071945],\n", + " [-84.51927231234833, 42.71801100102014, 2.394799999994575],\n", + " [-84.51998637940993, 42.717463066940496, 1.3942999999999302],\n", + " [-84.52085551180532, 42.7169838001858, 0.3405999999959022],\n", + " [-84.52110377914246, 42.71680106720647, 0]]]}},\n", + " {'attributes': {'GNIS_NAME': 'Red Cedar River',\n", + " 'LENGTHKM': 0.790011225885614,\n", + " 'REACHCODE': '04050004000125',\n", + " 'COMID': 12241734,\n", + " 'TERMINALFLAG': 0},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.52110377914246, 42.71680106720647, 100],\n", + " [-84.52128997918506, 42.71677846728174, 98.04949999999371],\n", + " [-84.52218957881017, 42.7165278670185, 88.11019999999553],\n", + " [-84.52414317860996, 42.71641546681839, 67.85580000000482],\n", + " [-84.5263449127941, 42.71614320089028, 44.78029999999853],\n", + " [-84.52773997893033, 42.71621319993831, 30.327000000004773],\n", + " [-84.52808131259198, 42.716122000036194, 26.574600000007194],\n", + " [-84.52823637973793, 42.716030667327296, 24.523600000000442],\n", + " [-84.52845391303248, 42.715756599739194, 20.074099999997998],\n", + " [-84.52851758030371, 42.71479639987246, 6.605999999999767],\n", + " [-84.52861091255387, 42.714636399772864, 4.1656999999977415],\n", + " [-84.52898311321981, 42.71452240010988, 0]]]}},\n", + " {'attributes': {'GNIS_NAME': None,\n", + " 'LENGTHKM': 1.98386608634494,\n", + " 'REACHCODE': '04050004000960',\n", + " 'COMID': 12241744,\n", + " 'TERMINALFLAG': 0},\n", + " 'geometry': {'hasM': True,\n", + " 'paths': [[[-84.50301331278834, 42.70907720030778, 100],\n", + " [-84.50422157968146, 42.70942146714436, 94.6594999999943],\n", + " [-84.50468611272633, 42.7096276667031, 92.42429999999877],\n", + " [-84.50487117904439, 42.710130867296975, 89.51080000000366],\n", + " [-84.50493238031002, 42.71051959999991, 87.32399999999325],\n", + " [-84.50505598035286, 42.710702667008924, 86.1811000000016],\n", + " [-84.50527291174306, 42.71077146684307, 85.20750000000407],\n", + " [-84.50626477906839, 42.71084120130026, 81.09940000000643],\n", + " [-84.50645051197165, 42.710978599911336, 80.01499999999942],\n", + " [-84.50644957858023, 42.71141299974881, 77.58759999999893],\n", + " [-84.50654237900574, 42.711481666776464, 77.0457000000024],\n", + " [-84.50700731181185, 42.711573600268444, 75.06110000000626],\n", + " [-84.50719311208873, 42.71171106722462, 73.97629999999481],\n", + " [-84.50719191278498, 42.712259667301, 70.91079999999783],\n", + " [-84.50737777864552, 42.71232846672766, 70.05340000000433],\n", + " [-84.5083393125923, 42.71214666725767, 65.9609000000055],\n", + " [-84.50864918006464, 42.71216986690556, 64.67669999999634],\n", + " [-84.50942371262971, 42.712445067194736, 61.132299999997485],\n", + " [-84.51007391209363, 42.71288020001662, 57.513099999996484],\n", + " [-84.51038377865879, 42.712994866739066, 56.08379999999306],\n", + " [-84.51196477893635, 42.71304220072817, 49.55999999999767],\n", + " [-84.51261557939964, 42.71320300033067, 46.73029999999562],\n", + " [-84.51351391324341, 42.713524066931534, 42.61490000000049],\n", + " [-84.51404031322085, 42.71395900011126, 39.35649999999441],\n", + " [-84.51431851325255, 42.71439366687709, 36.6704000000027],\n", + " [-84.51431837939471, 42.71448520133272, 36.1588999999949],\n", + " [-84.51441117892188, 42.71455380027837, 35.61740000000282],\n", + " [-84.51453471337885, 42.71482826723265, 34.00130000000354],\n", + " [-84.51484377861375, 42.715285800327486, 31.144700000004377],\n", + " [-84.51537037893539, 42.71551500124263, 28.62399999999616],\n", + " [-84.51722977866832, 42.716019866930885, 20.455400000006193],\n", + " [-84.51775617955293, 42.716363400231174, 17.55800000000454],\n", + " [-84.51794177860283, 42.71654646716665, 16.28049999999348],\n", + " [-84.51797231320394, 42.71679800131263, 14.869399999995949],\n", + " [-84.51822011338398, 42.716958267118954, 13.510800000003655],\n", + " [-84.5187161791023, 42.71695879976872, 11.465700000000652],\n", + " [-84.51946051304718, 42.716868066947455, 8.355400000000373],\n", + " [-84.52042251324607, 42.716411800265035, 3.640499999994063],\n", + " [-84.52063937906152, 42.71645779967625, 2.7102000000013504],\n", + " [-84.52110377914246, 42.71680106720647, 0]]]}}]}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#JSON representing all flowlines within buffer of the input coordinates (point to HydroLink)\n", + "hydrolink.flowlines_json" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 8.42166733289811,\n", + " 'reachcode': '04050004000126',\n", + " 'comid': 12242482,\n", + " 'terminalflag': 0,\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0.8823529411764706,\n", + " 'flowline name similarity message': 'most likely match, based on fuzzy match',\n", + " 'meters from flowline': 44.33266059218964,\n", + " 'nhdplusv2 flowline measure': 28.850441143579545},\n", + " {'gnis_name': 'Red Cedar River',\n", + " 'lengthkm': 0.790011225885614,\n", + " 'reachcode': '04050004000125',\n", + " 'comid': 12241734,\n", + " 'terminalflag': 0,\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0.8823529411764706,\n", + " 'flowline name similarity message': 'most likely match, based on fuzzy match',\n", + " 'meters from flowline': 1987.4230429025345,\n", + " 'nhdplusv2 flowline measure': 100.0},\n", + " {'gnis_name': None,\n", + " 'lengthkm': 1.98386608634494,\n", + " 'reachcode': '04050004000960',\n", + " 'comid': 12241744,\n", + " 'terminalflag': 0,\n", + " 'flowline name similarity message': 'no GNIS name',\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0,\n", + " 'meters from flowline': 1837.7531629439993,\n", + " 'nhdplusv2 flowline measure': 71.18721718739678}]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#HydroLink data for all flowlines within buffer, these data are evaluated to find closest flowline \n", + "#with a matching name (using a fuzzy match approach)\n", + "hydrolink.flowlines_data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are a few options for writing data to CSV files. Both are set up with intentions of batch hydrolinking (multiple points -> see batch example notebook). \n", + "\n", + "Both methods accept an optional parameter for 'outfile_name'. \n", + " * default for write_reach_options() is 'mr_hydrolink_output.csv'\n", + " * default for write_best() is 'mr__hydrolink_output.csv'" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'nhdplusv2 flowline gnis name': 'Red Cedar River',\n", + " 'nhdplusv2 flowline length km': 8.42166733289811,\n", + " 'nhdplusv2 flowline reachcode': '04050004000126',\n", + " 'nhdplusv2 comid': 12242482,\n", + " 'nhdplusv2 terminal flag': 0,\n", + " 'cleaned source water name': 'the red cedar river',\n", + " 'flowline name similarity': 0.8823529411764706,\n", + " 'flowline name similarity message': 'most likely match, based on fuzzy match',\n", + " 'meters from flowline': 44.33266059218964,\n", + " 'nhdplusv2 flowline measure': 28.850441143579545,\n", + " 'closest flowline order': 1}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#HydroLink Data for the selected flowline.\n", + "#The name_match approach uses the closest flowline with a name match (meeting a specified similarity cutoff for names)\n", + "#If no flowline names (gnis_name) match the source water name, then the closest flowline is returned\n", + "hydrolink.hydrolink_flowline" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>source id</th>\n", + " <th>source lat nad83</th>\n", + " <th>source lon nad83</th>\n", + " <th>source buffer meters</th>\n", + " <th>closest conluence meters</th>\n", + " <th>closest flowline order</th>\n", + " <th>total count flowlines in buffer</th>\n", + " <th>source water name</th>\n", + " <th>cleaned source water name</th>\n", + " <th>flowline name similarity</th>\n", + " <th>...</th>\n", + " <th>nhdplusv2 flowline reachcode</th>\n", + " <th>meters from flowline</th>\n", + " <th>nhdplusv2 flowline measure</th>\n", + " <th>nhdplusv2 terminal flag</th>\n", + " <th>nhdplusv2 waterbody permanent identifier</th>\n", + " <th>nhdplusv2 waterbody gnis name</th>\n", + " <th>nhdplusv2 waterbody reachcode</th>\n", + " <th>nhdplusv2 waterbody ftype</th>\n", + " <th>nhdplusv2 waterbody comid</th>\n", + " <th>hydrolink message</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <td>0</td>\n", + " <td>1</td>\n", + " <td>42.7284</td>\n", + " <td>-84.5026</td>\n", + " <td>2000</td>\n", + " <td>1987.423043</td>\n", + " <td>1</td>\n", + " <td>3</td>\n", + " <td>The Red Cedar River</td>\n", + " <td>the red cedar river</td>\n", + " <td>0.882353</td>\n", + " <td>...</td>\n", + " <td>4050004000126</td>\n", + " <td>44.332661</td>\n", + " <td>28.850441</td>\n", + " <td>0</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " <td>NaN</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "<p>1 rows × 24 columns</p>\n", + "</div>" + ], + "text/plain": [ + " source id source lat nad83 source lon nad83 source buffer meters \\\n", + "0 1 42.7284 -84.5026 2000 \n", + "\n", + " closest conluence meters closest flowline order \\\n", + "0 1987.423043 1 \n", + "\n", + " total count flowlines in buffer source water name \\\n", + "0 3 The Red Cedar River \n", + "\n", + " cleaned source water name flowline name similarity ... \\\n", + "0 the red cedar river 0.882353 ... \n", + "\n", + " nhdplusv2 flowline reachcode meters from flowline \\\n", + "0 4050004000126 44.332661 \n", + "\n", + " nhdplusv2 flowline measure nhdplusv2 terminal flag \\\n", + "0 28.850441 0 \n", + "\n", + " nhdplusv2 waterbody permanent identifier nhdplusv2 waterbody gnis name \\\n", + "0 NaN NaN \n", + "\n", + " nhdplusv2 waterbody reachcode nhdplusv2 waterbody ftype \\\n", + "0 NaN NaN \n", + "\n", + " nhdplusv2 waterbody comid hydrolink message \n", + "0 NaN NaN \n", + "\n", + "[1 rows x 24 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#View final data for returned reach\n", + "import pandas as pd\n", + "df = pd.read_csv('nhdplusv2_hydrolink_output.csv')\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/hydrolink/__init__.py b/hydrolink/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..47af56b586fd3238c6ffd77fe8546d3a5cae1c8f --- /dev/null +++ b/hydrolink/__init__.py @@ -0,0 +1,8 @@ +"""Top-level package for HydroLink.""" + +# Contact Information +__author__ = """Daniel J Wieferich""" +__email__ = 'dwieferich@usgs.gov' + +# provide version, PEP - three components ("major.minor.micro") +__version__ = '0.1.1' diff --git a/hydrolink/hydrolinker.py b/hydrolink/hydrolinker.py new file mode 100644 index 0000000000000000000000000000000000000000..5222395927eb9e9417105c1110b6f35dc55c8022 --- /dev/null +++ b/hydrolink/hydrolinker.py @@ -0,0 +1,93 @@ +"""Command line tool for HydroLinking data. + +Authors +---------- +Name: Daniel Wieferich +Contact: dwieferich@usgs.gov + +Name: Brandon Serna +Contact: bserna@usgs.gov + +""" +import click +from hydrolink import nhd_hr +from hydrolink import nhd_mr +import geopandas as gpd +import pandas as pd +import warnings +warnings.simplefilter('ignore') + + +@click.command() +@click.option('--input_file', required=True, help='Enter file name, including extension (only accepts .csv and .shp)') +@click.option('--latitude_field', required=True, show_default=True, default='y', help='Enter field name for latitude, note this is case sensitive') +@click.option('--longitude_field', required=True, show_default=True, default='x', help='Enter field name for longitude, note this is case sensitive') +@click.option('--stream_name_field', required=True, show_default=True, default='stream', help='Enter field name for stream name, if none type None, note this is case sensitive') +@click.option('--identifier_field', required=True, show_default=True, default='id', help='Enter field name for identifier, note this is case sensitive') +@click.option('--crs', required=True, show_default=True, default=4269, help='Enter crs number, recommended to use NAD83 represented by 4269') +@click.option('--buffer', required=True, show_default=True, default=1000, help='Enter buffer distance in meters, max is 2000') +@click.option('--method', required=True, show_default=True, default='name_match', help='Enter method to use, options include name_match and closest') +@click.option('--nhd_version', required=True, show_default=True, default='nhdhr', help='Version of NHD to use, options include nhdhr and nhdplusv2') +@click.option('--hydro_type', required=True, show_default=True, default='flowline', help='Options flowline or waterbody') +def handle_data(input_file, latitude_field, longitude_field, stream_name_field, identifier_field, crs, buffer, method, nhd_version, hydro_type): + """Hydrolink point data to the nhd high resolution. + + HydroLinker accepts a CSV file of multiple points of interest, HydroLinks each to + the specified version of NHD and writes HydroLink data (addresses to NHD) along with + measures of certainty to a csv file. + + """ + click.echo('Thank you, processing now...') + + in_data = {"file": input_file, + "lat": latitude_field, + "lon": longitude_field, + "stream_name": stream_name_field, + "id": identifier_field} + + if in_data['file'].endswith('.csv'): + click.echo('reading csv file') + try: + df = pd.read_csv(in_data['file'], encoding='iso-8859-1') + except KeyError: + click.echo('csv file did not properly import, verify file name and rerun') + + # If input file is not a CSV check to see if it is a shapefile + elif in_data['file'].endswith('.shp'): + click.echo('reading shapefile') + try: + df = gpd.GeoDataFrame.from_file(in_data['file']) + except KeyError: + click.echo('shapefile did not properly import, verify file name and rerun') + + # If input file is not a CSV or shapefile tell the user that the file type is not excepted + else: + click.echo('File type not currently accepted. Please try .csv or .shp') + + if in_data['lat'] in df and in_data['lon'] in df and in_data['id'] in df and in_data['stream_name'] in df: + df = df.rename(columns={in_data['id']: 'id', + in_data['lat']: 'lat', + in_data['lon']: 'lon', + in_data['stream_name']: 'stream' + }) + df['crs'] = int(crs) + + else: + click.echo('Verify field names and rerun') + + for row in df.itertuples(): + if nhd_version == 'nhdhr': + hydrolink = nhd_hr.HighResPoint(row.id, float(row.lat), float(row.lon), input_crs=int(row.crs), water_name=str(row.stream), buffer_m=buffer) + elif nhd_version == 'nhdplusv2': + hydrolink = nhd_mr.MedResPoint(row.id, float(row.lat), float(row.lon), input_crs=int(row.crs), water_name=str(row.stream), buffer_m=buffer) + hydrolink.hydrolink_method(method=method, hydro_type=hydro_type) + + # in_file = in_data['file'][:-4] #remove .csv or .shp + # output_file = f'{in_file}_output.csv' + # hydrolink.write_best(outfile_name= output_file) + + # click.echo('Output exported to %s' % output_file) + + +if __name__ == '__main__': + handle_data() diff --git a/hydrolink/nhd_hr.py b/hydrolink/nhd_hr.py new file mode 100644 index 0000000000000000000000000000000000000000..68101e5c87c0e4f14df2fa18111530ebf550d09a --- /dev/null +++ b/hydrolink/nhd_hr.py @@ -0,0 +1,471 @@ +"""Allows for HydroLinking of data to the National Hydrography Dataset High Resolution (NHDHR). + +Module that HydroLinks data to the National Hydrography Dataset High Resolution (NHDHR). Classes +and methods are designed to handle one feature at a time. This module is designed to handle errors +by returning a message to objects to help facilitate HydroLinking of multiple points in a single program. +Currently only HydroLinks point data (not lines or polygons) to flowlines or waterbodies. The terms +"HydroLinking" and "addressing" are used synonymously throughout this code and both refer to making +a relationship between a feature and a stream network, similar to addresses assigned to road networks. + +Author +---------- +Name: Daniel Wieferich +Contact: dwieferich@usgs.gov +""" + +# Import packages +import requests +import csv +import os.path +from hydrolink import utils +from shapely.geometry import Point +############################################################################################ +############################################################################################ + + +class HighResPoint: + """Class specific for HydroLinking point data to the NHDHR.""" + + def __init__(self, source_identifier, input_lat, input_lon, input_crs=4269, water_name=None, buffer_m=1000): + """Initiate attributes for HydroLinking point data to the NHDHR. + + During initiation of an object the buffer is verified to be less than 2000 meters. Initiation + also converts supplied coordinates to NAD83 (crs=4269), and validates that coordinates are within + the bounds of the United States bounding box. If any of these conditions caused a failed initiation + of an object an error message is created and the HydroLinking process does not run. + + Parameters + ---------- + source_identifier: str + User supplied identifier + input_lat: float + Latitude of the point to be HydroLinked + input_lon: float + Longitude of the point to be HydroLinked + input_crs: int + Coordinate reference system, default is 4269 which is NAD83 + water_name: str + User supplied name of the waterbody that a point occurs on. Optional + buffer_m: int + Distance in meters. Used as buffer to search for canidate NHD features + for HydroLinking + + Notes + ---------- + Coordinate system recommendations + Tested NAD83 (CRS 4269), WGS84 (CRS 4326), Albers (CRS 5070). These are recommended. + Source name recommendations + To be most effective the water_name variable should contain no abbreviations and + only contain official names from USGS Geospatial Names Information System. + Buffer recommendations + Larger buffers will take much longer to run so it is to test need for large buffer + and using the smallest buffer possible. + Expected speed note + A set of 10 points were hydrolinked (4/13/2020) with average speed of 1 point per 0.74 seconds. This + includes request of data, name matching, and writing to csv. + + """ + self.source_id = str(source_identifier) + if water_name and str(water_name) != 'nan': + self.water_name = str(water_name) + else: + self.water_name = None + self.buffer_m = int(buffer_m) + self.status = 1 # where 0 is failed, 1 is worked properly + self.message = '' + self.flowline_query = None + self.waterbody_query = None + self.hydrolink_waterbody = None + + # If buffer is greater than 2000 do not run and set error message + if buffer_m > 2000: + self.message = ('Maximum buffer is 2000 meters, reduce buffer.') + self.error_handling() + + # If buffer is less than or equal to 2000 then run + else: + # Try converting to NAD83 (crs==4269) coordinate system if different coordinate system provided + # If fails do not run and set error message + try: + self.init_lon = float(input_lon) + self.init_lat = float(input_lat) + self.input_point = Point(self.init_lon, self.init_lat) + + if int(input_crs) != 4269: + self.init_lon, self.init_lat = utils.crs_to_nad83(self.input_point, input_crs) + + # Test to make sure coordinates are within U.S. including Puerto Rico and Virgian Islands. + # This is based on a general bounding box and intended to pick up common issues like missing values, 0 values and positive lon values + if (float(self.init_lat) > 17.5 and float(self.init_lat) < 71.5) and (float(self.init_lon) < -64.0 and float(self.init_lon) > -178.5): + pass + else: + self.message = f'Coordinates for id: {self.source_id} are outside of the bounding box of the United States.' + self.error_handling() + + except: + self.message = f'Issues handling provided coordinate system or coordinates for {self.source_id}. Consider using a common crs like 4269 (NAD83) or 4326 (WGS84).' + self.error_handling() + + def hydrolink_method(self, method='name_match', hydro_type='flowline', outfile_name='nhdhr_hydrolink_output.csv', similarity_cutoff=0.6): + """Build HydroLinking pipeline based on specified method and hydro_type. + + Builds commonly used HydroLink pipelines for users. + These pipelines write data to the outfile specified in "outfile_name". + + Parameters + ---------- + method: {'name_match', 'closest'}, default 'name_match' + Method for HydroLinking data. Supported methods are + + - ``'name_match'``: This default method HydroLinks data to the closest NHD feature with a name similarity + that meets the specified similarity_cutoff. If no flowlines meet similarity cutoff the method HydroLinks + data to the closest NHD feature. + - ``'closest'``: This method HydroLinks data to the closest NHD feature. + + hydro_type: {'waterbody', 'flowline'}, default 'flowline' + Type of features to HydroLink. Feature types as defined by NHDHR. + + - ``'flowline'``: This default feature type specifies NHD feature type of flowline. + Flowline features represent water types such as streams, rivers, canals/ditches. + Waterbodies also have line representations as flowline type. + - ``'waterbody'``: This feature type specifies NHD feature type of waterbody. + Waterbody features represent water types such as lakes, ponds, estuaries, reservoirs, + marshes, swamps. + + outfile_name: str + Name and directory of csv output file. default is 'nhdhr_hydrolink_output.csv'. + similarity_cutoff: float + Values between 0 and 1.0, range of similarity between 0 representing no match to 1.0 being perfect match. + + """ + if hydro_type in ['waterbody', 'flowline'] and method in ['name_match', 'closest'] and 0.6 <= similarity_cutoff <= 1.0: + if self.status == 1: + self.build_nhd_query(query=['hem_flowline', 'hem_waterbody']) + if hydro_type == 'waterbody': + self.is_in_waterbody() + self.query_flowlines() + self.hydrolink_flowlines() + if method == 'name_match': + self.select_closest_flowline_w_name_match(similarity_cutoff=similarity_cutoff) + elif method == 'closest': + self.select_closest_flowline() + self.write_hydrolink(outfile_name=outfile_name) + else: + self.write_hydrolink(outfile_name=outfile_name) + + def build_nhd_query(self, query=['hem_flowline', 'hem_waterbody']): + """Build queries to return required data for HydroLink process. + + Parameters + ---------- + query: list, default ['hem_flowline', 'hem_waterbody'] + Specifies MapServer instance to use for HydroLink. Uses user specified information from object. + Supported queries include + + - ``'hem_flowline'``: Default query that returns data for flowline features within a buffer of + a given location. This query uses the following Hydro Event Management (HEM) MapServer layer + https://hydromaintenance.nationalmap.gov/arcgis/rest/services/HEM/NHDHigh/MapServer/1. + - ``'hem_waterbody'``: Query that returns data for the waterbody feature that intersects a given location. + This query uses the following Hydro Event Management (HEM) MapServer layer + https://hydromaintenance.nationalmap.gov/arcgis/rest/services/HEM/NHDHigh/MapServer/2. + - ``'hem_waterbody_flowline'``: Query that returns data for flowline features within a waterbody. + This query uses the following Hydro Event Management (HEM) MapServer layer + https://hydromaintenance.nationalmap.gov/arcgis/rest/services/HEM/NHDHigh/MapServer/1. + + Note(s) + ---------- + Recommended queries for NHDHR are currently those supported by Hydro Event Management (HEM). In this MapServer + all flowlines are in one layer and this layer provides a recent version of NHDHR data. + + The National Map Mapservers (including HighResPlus) can also be used but these services split flowlines + into in-network and non-network and will require multiple service calls (not handled in current code). + + """ + # hem flowlines within a buffer of coordinates + if 'hem_flowline' in query: + q = f"where=ftype%20NOT%20IN%20(420,428,566)&geometryType=esriGeometryPoint&inSR=4269&geometry={self.init_lon},{self.init_lat}&distance={self.buffer_m}&units=esriSRUnit_Meter&outSR=4269&f=JSON&outFields=gnis_name,lengthkm,permanent_identifier,reachcode&returnM=True" + base_url = 'https://hydromaintenance.nationalmap.gov/arcgis/rest/services/HEM/NHDHigh/MapServer/1/query?' + self.flowline_query = f"{base_url}{q}" + + # hem waterbody returns information about hem waterbodies that the point is within + if 'hem_waterbody' in query: + q = f"geometryType=esriGeometryPoint&spatialRel=esriSpatialRelWithin&inSR=4269&geometry={self.init_lon},{self.init_lat}&f=JSON&outFields=permanent_identifier,gnis_name,ftype,reachcode&returnGeometry=False" + base_url = 'https://hydromaintenance.nationalmap.gov/arcgis/rest/services/HEM/NHDHigh/MapServer/2/query?' + self.waterbody_query = f"{base_url}{q}" + + # returns flowlines associated with a waterbody (e.g. reservoir, lake...) + if 'hem_waterbody_flowline' in query: + q = f"where=WBAREA_PERMANENT_IDENTIFIER%20IN%20(%27{self.hydrolink_waterbody['nhdhr waterbody permanent identifier']}%27)&outSR=4269&f=JSON&outFields=gnis_name,lengthkm,permanent_identifier,reachcode&returnM=True" + base_url = 'https://hydromaintenance.nationalmap.gov/arcgis/rest/services/HEM/NHDHigh/MapServer/1/query?' + self.flowline_query = f"{base_url}{q}" + + # The National Map (including HighResPlus) are included for future reference, not currenlty supported + # if service == 'TNM_HR': + # base_url = 'https://hydro.nationalmap.gov/arcgis/rest/services/nhd/MapServer/5/query?' + # base_url_off_network = 'https://hydro.nationalmap.gov/arcgis/rest/services/nhd/MapServer/5/query?' + # if service == 'TNM_HRPlus': + # base_url = 'https://hydro.nationalmap.gov/arcgis/rest/services/NHDPlus_HR/MapServer/2/query?' + + def is_in_waterbody(self): + """Check to see if point location falls within waterbody feature. + + Check to see if point location falls within waterbody feature. If it does it collects HydroLink + data for the waterbody and also resets query of flowlines (self.flowline_query) to only return + flowlines that intersect with the waterbody. + + Parameters + ---------- + self.waterbody_query: str + Query built in build_nhd_query + + Returns + ---------- + self.hydrolink_waterbody: dictionary + Attributes of HydroLink (address) to waterbody + self.build_nhd_query: list + Reset self.build_nhd_query to query only flowlines within waterbody + + """ + # if status == 0 or if we do not have waterbody query set then skip to avoid wasted processing time + if self.status == 1 and self.waterbody_query is not None: + try: + results = requests.get(self.waterbody_query).json() + self.waterbody_json = results + if len(results['features']) > 0: + self.hydrolink_waterbody = {'nhdhr waterbody permanent identifier': results['features'][0]['attributes']['permanent_identifier'], + 'nhdhr waterbody gnis name': results['features'][0]['attributes']["gnis_name"], + 'nhdhr waterbody reachcode': results['features'][0]['attributes']['reachcode'] + } + self.build_nhd_query(query=['hem_waterbody_flowline']) + # add name match here? + except: + self.message = f'is_in_waterbody failed for: {self.source_id}. possibly service call issue' + self.error_handling() + + def query_flowlines(self): + """Query flowlines using query built in build_nhd_query. + + Query flowlines using query built in build_nhd_query. Handles failed requests and + instances where no flowlines are returned. + + Parameters + ---------- + self.flowline_query: str + Query built in build_nhd_query + + Returns + ---------- + self.flowlines_json: dictionary + JSON returned from request of flowline_query. JSON contains data about flowlines. + + """ + if self.status == 1: # if status == 0 we don't want to waste time processing + try: + self.flowlines_json = requests.get(self.flowline_query).json() + if 'features' in self.flowlines_json.keys() and len(self.flowlines_json['features']) == 0: + self.message = f'No flowlines selected in query_flowlines for id: {self.source_id}. Try increasing buffer.' + self.error_handling() + + except: + self.message = f'query_flowlines failed for id: {self.source_id}. Request failed.' + self.error_handling() + + def hydrolink_flowlines(self): + """Evaluate flowlines in self.flowlines_json to understand certainty for HydroLink selection. + + Evaluate flowlines in self.flowlines_json to understand certainty for HydroLink selection. + Evaluations include calculating distance to each flowline, calculating distance to closest + confluence, calculate name similarity for water names. Handles instances where no flowlines + are available and failed evaluations. + + Parameters + ---------- + self.flowlines_json: dictionary + JSON returned from request of flowline_query. JSON contains data about flowlines. + self.water_name: str + User supplied name of the waterbody that a point occurs on. Optional + self.input_point: shapely point + Input location for hydrolinking. For formatting see shapely.geometry Point method + + Returns + ---------- + self.closest_confluence_meters: float + Distance from input point to closest confluence in meters, see utils.closest_confluence + self.flowline_data: dictionary + Contains information about a flowline + + """ + # if status == 0 we don't want to waste time processing + if self.status == 1: + if 'features' in self.flowlines_json.keys() and len(self.flowlines_json['features']) > 0: + try: + flowlines_data = [] + all_flowline_terminal_node_points = [] + for flowline_data in self.flowlines_json['features']: + flowline_attributes, terminal_node_points, flowline_geo = utils.build_flowline_details(flowline_data, self.input_point, 'nhdhr', self.water_name) + flowlines_data.append(flowline_attributes) + all_flowline_terminal_node_points = all_flowline_terminal_node_points + terminal_node_points + + self.closest_confluence_meters = utils.closest_confluence(all_flowline_terminal_node_points, self.input_point, flowline_geo) + self.flowlines_data = flowlines_data + + except: + self.message = f'hydrolink_flowlines failed for id: {self.source_id}.' + self.error_handling() + else: + self.message = f'no flowlines retrieved for id: {self.source_id}' + self.error_handling() + + def select_closest_flowline(self, similarity_cutoff=0.6): + """Select closest flowline. + + Selects closest flowline from flowlines_data, including all evaluation information + for the flowline. Although name similarity is not considered for selection it is used + to document if a name matched flowline is available. Requires output from hydrolink_flowlines. + + """ + if self.status == 1: + df = utils.df_for_selection(self.flowlines_data) + df = df.rename(columns={"lengthkm": "nhdhr flowline length km", + "reachcode": "nhdhr flowline reachcode", + "gnis_name": "nhdhr flowline gnis name", + "permanent_identifier": "nhdhr flowline permanent identifier" + }) + self.total_count_flowlines = df.shape[0] + self.name_match_in_buffer = df.loc[df['flowline name similarity'] >= similarity_cutoff].shape[0] + + df = df.nsmallest(1, 'meters from flowline', keep='all') + if df.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}. Use name_match method.' + self.error_handling() + + else: + self.hydrolink_flowline = ((df.to_dict('records'))[0]) + + def select_closest_flowline_w_name_match(self, similarity_cutoff=0.6): + """Select closest flowline with matching water name. + + HydroLink data to the closest NHD feature with a name similarity that meets the specified + similarity_cutoff. If no flowlines meet similarity cutoff the method HydroLinks data to the + closest NHD feature. Requires output from hydrolink_flowlines. + """ + if self.status == 1: + df = utils.df_for_selection(self.flowlines_data) + df = df.rename(columns={"lengthkm": "nhdhr flowline length km", + "reachcode": "nhdhr flowline reachcode", + "gnis_name": "nhdhr flowline gnis name", + "permanent_identifier": "nhdhr flowline permanent identifier" + }) + self.total_count_flowlines = df.shape[0] + df_1 = df.loc[df['flowline name similarity'] == 1.0] + df_similarity = df.loc[df['flowline name similarity'] >= similarity_cutoff] + # only 1 flowline has extact matching name + if df_1.shape[0] == 1: + self.hydrolink_flowline = ((df.to_dict('records'))[0]) + # more than 1 flowline has exact matching name, grab closest of matching name flowlines + elif df_1.shape[0] > 1: + df_1 = df_1.nsmallest(1, 'meters from flowline', keep='all') + if df_1.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}.' + self.error_handling() + else: + self.hydrolink_flowline = ((df_1.to_dict('records'))[0]) + # only one flowline has matching name meeting similarity cutoff + elif df_1.shape[0] == 0 and df_similarity.shape[0] == 1: + self.hydrolink_flowline = ((df_similarity.to_dict('records'))[0]) + # select closest flowline meeting name match similarity cutoff + elif df_1.shape[0] == 0 and df_similarity.shape[0] > 1: + df_similarity = df_similarity.nsmallest(1, 'meters from flowline', keep='all') + if df_similarity.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}.' + self.error_handling() + else: + self.hydrolink_flowline = ((df_similarity.to_dict('records'))[0]) + # no flowlines with name match, select closest + else: + df = df.nsmallest(1, 'meters from flowline', keep='all') + if df.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}.' + self.error_handling() + else: + self.hydrolink_flowline = ((df.to_dict('records'))[0]) + + def error_handling(self): + """Handle errors throughout HydroLink.""" + self.status = 0 + print(self.message) + + def write_hydrolink(self, outfile_name='nhdhr_hydrolink_output.csv'): + """Write HydroLink data output to CSV.""" + file_exists = os.path.isfile(outfile_name) + if self.status == 1: + source_data = {'source id': self.source_id, + 'source water name': self.water_name, + 'source lat nad83': self.init_lat, + 'source lon nad83': self.init_lon, + 'closest conluence meters': self.closest_confluence_meters, + 'source buffer meters': self.buffer_m, + 'total count flowlines in buffer': self.total_count_flowlines, + 'hydrolink message': self.message} + source_data.update(self.hydrolink_flowline) + if self.hydrolink_waterbody is not None: + source_data.update(self.hydrolink_waterbody) + elif self.status == 0: + source_data = {'source id': self.source_id, + 'source water name': self.water_name, + 'source lat nad83': self.init_lat, + 'source lon nad83': self.init_lon, + 'source buffer meters': self.buffer_m, + 'hydrolink message': self.message} + field_names = ['source id', 'source lat nad83', 'source lon nad83', 'source buffer meters', + 'closest conluence meters', 'closest flowline order', 'total count flowlines in buffer', + 'source water name', 'cleaned source water name', 'flowline name similarity', + 'flowline name similarity message', 'nhdhr flowline gnis name', + 'nhdhr flowline length km', 'nhdhr flowline permanent identifier', + 'nhdhr flowline reachcode', 'meters from flowline', 'nhdhr flowline measure', + 'nhdhr waterbody permanent identifier', 'nhdhr waterbody gnis name', + 'nhdhr waterbody reachcode', 'hydrolink message' + ] + with open(outfile_name, 'a', newline='') as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=field_names, delimiter=',') + if not file_exists: + writer.writeheader() + writer.writerow(source_data) + + # def write_flowline_options(self, outfile_name='hr_hydrolink_reach_output.csv'): + # """Write HydroLink data output to CSV.""" + # if self.status ==1: + # file_exists = os.path.isfile(outfile_name) + # with open(outfile_name, 'a', newline='') as csv_file: + # for r in self.reach_df.to_dict(orient='records'): + # r.update({'source identifier': self.source_id}) + # writer = csv.DictWriter(csv_file, r.keys(), delimiter=',') + # if not file_exists: + # writer.writeheader() + # file_exists = True + # writer.writerow(r) + + +# def get_ftype(fcode): +# """Lookup NHD feature type based on fcode.""" +# # create dictionary with fcode:ftype pairs +# ftypes = { +# '436': 'Reservoir', +# '390': 'LakePond', +# '493': 'Estuary', +# '466': 'SwampMarsh', +# '361': 'Playa', +# '378': 'Ice Mass', +# '566': 'Coastline', +# '334': 'Connector', +# '336': 'CanalDitch', +# '558': 'ArtificialPath', +# '428': 'Pipeline', +# '460': 'StreamRiver', +# '420': 'Underground Conduit' +# } +# ftype = ftypes[str(fcode)] +# return ftype + +############################################################################################ +############################################################################################ diff --git a/hydrolink/nhd_mr.py b/hydrolink/nhd_mr.py new file mode 100644 index 0000000000000000000000000000000000000000..a8e518a8b8dbb74ad561819408affa541e049d0d --- /dev/null +++ b/hydrolink/nhd_mr.py @@ -0,0 +1,470 @@ +"""Allows for HydroLinking of data to the National Hydrography Dataset Plus Version 2.1 (NHDPlusV2.1). + +Module that HydroLinks data to the National Hydrography Dataset Plus Version 2.1 (NHDPlusV2.1). Classes +and methods are designed to handle one feature at a time. This module is designed to handle errors +by returning a message to objects to help facilitate HydroLinking of multiple points in a single program. +Currently only HydroLinks point data (not lines or polygons) to flowlines or waterbodies. The terms +"HydroLink" and "addressing" are used synonymously throughout this code and both refer to making +a relationship between a feature and a stream network, similar to addresses assigned to road networks. + +Author +---------- +Name: Daniel Wieferich +Contact: dwieferich@usgs.gov +""" + +# Import packages +import requests +import csv +import os.path +from hydrolink import utils +from shapely.geometry import Point + +############################################################################################ +############################################################################################ + + +class MedResPoint: + """Class specific for HydroLinking point data to the NHDPlusV2.1.""" + + def __init__(self, source_identifier, input_lat, input_lon, input_crs=4269, water_name=None, buffer_m=1000): + """Initiate attributes for HydroLinking point data to the NHDHR. + + During initiation of an object the buffer is verified to be less than 2000 meters. Initiation + also converts supplied coordinates to NAD83 (crs=4269), and validates that coordinates are within + the bounds of the United States bounding box. If any of these conditions caused a failed initiation + of an object an error message is created and the HydroLinking process does not run. + + Parameters + ---------- + source_identifier: str + User supplied identifier + input_lat: float + Latitude of the point to be HydroLinked + input_lon: float + Longitude of the point to be HydroLinked + input_crs: int + Coordinate reference system, default is 4269 which is NAD83 + water_name: str + User supplied name of the waterbody that a point occurs on. Optional + buffer_m: int + Distance in meters. Used as buffer to search for canidate NHD features + for HydroLinking + + Notes + ---------- + Coordinate system recommendations + Tested NAD83 (CRS 4269), WGS84 (CRS 4326), Albers (CRS 5070). These are recommended. + Source name recommendations + To be most effective the water_name variable should contain no abbreviations and + only contain official names from USGS Geospatial Names Information System. + Buffer recommendations + Larger buffers will take much longer to run so it is to test need for large buffer + and using the smallest buffer possible. + Expected speed note + A set of 10 points were hydrolinked (4/13/2020) with average speed of 1 point per 0.74 seconds. This + includes request of data, name matching, and writing to csv. + + """ + self.source_id = str(source_identifier) + if water_name and str(water_name) != 'nan': + self.water_name = str(water_name) + else: + self.water_name = None + self.buffer_m = int(buffer_m) + self.status = 1 # where 0 is failed, 1 is worked properly + self.message = '' + self.flowline_query = None + self.waterbody_query = None + self.hydrolink_waterbody = None + + # If buffer is greater than 2000 do not run and set error message + if buffer_m > 2000: + self.message = ('Maximum buffer is 2000 meters, reduce buffer.') + self.error_handling() + + # If buffer is less than or equal to 2000 then run + else: + # Try converting to NAD83 (crs==4269) coordinate system if different coordinate system provided + # If fails do not run and set error message + try: + self.init_lon = float(input_lon) + self.init_lat = float(input_lat) + self.input_point = Point(self.init_lon, self.init_lat) + + if int(input_crs) != 4269: + self.init_lon, self.init_lat = utils.crs_to_nad83(self.input_point, input_crs) + + # Test to make sure coordinates are within U.S. including Puerto Rico and Virgian Islands. + # This is based on a general bounding box and intended to pick up common issues like missing values, 0 values and positive lon values + if (float(self.init_lat) > 17.5 and float(self.init_lat) < 71.5) and (float(self.init_lon) < -64.0 and float(self.init_lon) > -178.5): + pass + else: + self.message = f'Coordinates for id: {self.source_id} are outside of the bounding box of the United States.' + self.error_handling() + + except: + self.message = f'Issues handling provided coordinate system or coordinates for {self.source_id}. Consider using a common crs like 4269 (NAD83) or 4326 (WGS84).' + self.error_handling() + + def hydrolink_method(self, method='name_match', hydro_type='flowline', outfile_name='nhdplusv2_hydrolink_output.csv', similarity_cutoff=0.6): + """Build HydroLinking pipeline based on specified method and hydro_type. + + Builds commonly used HydroLink pipelines for users. + These pipelines write data to the outfile specified in "outfile_name". + + Parameters + ---------- + method: {'name_match', 'closest'}, default 'name_match' + Method for HydroLinking data. Supported methods are + + - ``'name_match'``: This default method HydroLinks data to the closest NHD feature with a name similarity + that meets the specified similarity_cutoff. If no flowlines meet similarity cutoff the method HydroLinks + data to the closest NHD feature. + - ``'closest'``: This method HydroLinks data to the closest NHD feature. + + hydro_type: {'waterbody', 'flowline'}, default 'flowline' + Type of features to HydroLink. Feature types as defined by NHDPlusV2.1. + + - ``'flowline'``: This default feature type specifies NHD feature type of flowline. + Flowline features represent water types such as streams, rivers, canals/ditches. + Waterbodies also have line representations as flowline type. + - ``'waterbody'``: This feature type specifies NHD feature type of waterbody. + Waterbody features represent water types such as lakes, ponds, estuaries, reservoirs, + marshes, swamps. + + outfile_name: str + Name and directory of csv output file. default is 'nhdplusv2_hydrolink_output.csv'. + similarity_cutoff: float + Values between 0 and 1.0, range of similarity between 0 representing no match to 1.0 being perfect match. + + """ + if hydro_type in ['waterbody', 'flowline'] and method in ['name_match', 'closest'] and 0.6 <= similarity_cutoff <= 1.0: + if self.status == 1: + self.build_nhd_query(query=['network_flow', 'waterbody']) + if hydro_type == 'waterbody': + self.is_in_waterbody() + self.query_flowlines() + self.hydrolink_flowlines() + if method == 'name_match': + self.select_closest_flowline_w_name_match(similarity_cutoff=similarity_cutoff) + elif method == 'closest': + self.select_closest_flowline() + self.write_hydrolink(outfile_name=outfile_name) + else: + self.write_hydrolink(outfile_name=outfile_name) + + def build_nhd_query(self, query=['network_flow', 'waterbody']): + """Build queries to return required data for HydroLink process. + + Parameters + ---------- + query: list, default ['network_flow', 'waterbody'] + Specifies MapServer instance to use for HydroLink. Uses user specified information from object. + Supported queries include + + - ``'network_flow'``: Default query that returns data for flowline features within a buffer of + a given location. This query uses the WatersGeo MapServer layer from EPA. + - ``'nonnetwork_flow'``: Default query that returns data for flowline features within a buffer of + a given location. This query uses the WatersGeo MapServer layer from EPA. + - ``'waterbody'``: Query that returns data for the waterbody feature that intersects a given location. + This query uses the WatersGeo MapServer layer from EPA. + - ``'waterbody_flowline'``: Query that returns data for flowline features within a waterbody. + This query uses the WatersGeo MapServer layer from EPA. + + Note(s) + ---------- + Currently nonnetwork streams are only used if no streams are returned with network flowlines. + It would be best to query both network and nonnetwork and combine before hydrolinking but that + doubles the requests and would increase processing time and service loads... + + """ + if 'network_flow' in query: + q = f"where=FTYPE%20NOT%20IN%20(420,428,566)&geometryType=esriGeometryPoint&inSR=4269&geometry={self.init_lon},{self.init_lat}&distance={self.buffer_m}&units=esriSRUnit_Meter&outSR=4269&f=JSON&outFields=GNIS_NAME,LENGTHKM,REACHCODE,COMID,TERMINALFLAG&returnM=True" + base_url = 'https://watersgeo.epa.gov/arcgis/rest/services/NHDPlus/NHDPlus/MapServer/2/query?' + self.flowline_query = f"{base_url}{q}" + if 'nonnetwork_flow' in query: + q = f"where=FTYPE%20NOT%20IN%20(420,428,566)&geometryType=esriGeometryPoint&inSR=4269&geometry={self.init_lon},{self.init_lat}&distance={self.buffer_m}&units=esriSRUnit_Meter&outSR=4269&f=JSON&outFields=GNIS_NAME,LENGTHKM,REACHCODE,COMID&returnM=True" + base_url = 'https://watersgeo.epa.gov/arcgis/rest/services/NHDPlus/NHDPlus/MapServer/3/query?' + self.nonnetwork_flowline_query = f"{base_url}{q}" + if 'waterbody' in query: + q = f"geometryType=esriGeometryPoint&spatialRel=esriSpatialRelWithin&inSR=4269&geometry={self.init_lon},{self.init_lat}&f=JSON&outFields=PERMANENT_IDENTIFIER,COMID,GNIS_NAME,FTYPE,REACHCODE&returnGeometry=False" + base_url = 'https://watersgeo.epa.gov/arcgis/rest/services/NHDPlus/NHDPlus/MapServer/4/query?' + self.waterbody_query = f"{base_url}{q}" + if 'waterbody_flowline' in query: + q = f"where=WBAREA_PERMANENT_IDENTIFIER%20IN%20(%27{self.hydrolink_waterbody['nhdplusv2 waterbody permanent identifier']}%27)&outSR=4269&f=JSON&outFields=GNIS_NAME,LENGTHKM,REACHCODE,COMID,TERMINALFLAG&returnM=True" + base_url = 'https://watersgeo.epa.gov/arcgis/rest/services/NHDPlus/NHDPlus/MapServer/2/query?' + self.flowline_query = f"{base_url}{q}" + + def is_in_waterbody(self): + """Check to see if point location falls within waterbody feature. + + Check to see if point location falls within waterbody feature. If it does it collects HydroLink + data for the waterbody and also resets query of flowlines (self.flowline_query) to only return + flowlines that intersect with the waterbody. + + Parameters + ---------- + self.waterbody_query: str + Query built in build_nhd_query + + Returns + ---------- + self.hydrolink_waterbody: dictionary + Attributes of HydroLink (address) to waterbody + self.build_nhd_query: list + Reset self.build_nhd_query to query only flowlines within waterbody + + """ + # if status == 0 or if we do not have waterbody query set then skip to avoid wasted processing time + if self.status == 1 and self.waterbody_query is not None: + try: + results = requests.get(self.waterbody_query).json() + self.waterbody_json = results + if len(results['features']) > 0: + self.hydrolink_waterbody = {'nhdplusv2 waterbody permanent identifier': results['features'][0]['attributes']['PERMANENT_IDENTIFIER'], + 'nhdplusv2 waterbody gnis name': results['features'][0]['attributes']["GNIS_NAME"], + 'nhdplusv2 waterbody reachcode': results['features'][0]['attributes']['REACHCODE'], + 'nhdplusv2 waterbody ftype': results['features'][0]['attributes']['FTYPE'], + 'nhdplusv2 waterbody comid': results['features'][0]['attributes']['COMID'] + } + self.build_nhd_query(query=['waterbody_flowline']) + # add name match here? + except: + self.message = f'is_in_waterbody failed for: {self.source_id}. possibly service call issue' + self.error_handling() + + def query_flowlines(self): + """Query flowlines using query built in build_nhd_query. + + Query flowlines using query built in build_nhd_query. Handles failed requests and + instances where no flowlines are returned. + + Parameters + ---------- + self.flowline_query: str + Query built in build_nhd_query + + Returns + ---------- + self.flowlines_json: dictionary + JSON returned from request of flowline_query. JSON contains data about flowlines. + + """ + if self.status == 1: # if status == 0 we don't want to waste time processing + try: + self.flowlines_json = requests.get(self.flowline_query).json() + if 'features' in self.flowlines_json.keys() and len(self.flowlines_json['features']) == 0: + self.build_nhd_query(query=['nonnetwork_flow']) + self.flowlines_json = requests.get(self.nonnetwork_flowline_query).json() + if 'features' in self.flowlines_json.keys() and len(self.flowlines_json['features']) == 0: + self.message = f'No flowlines selected in query_flowlines for id: {self.source_id}. Try increasing buffer.' + self.error_handling() + except: + self.message = f'query_flowlines failed for id: {self.source_id}. Request failed.' + self.error_handling() + + def hydrolink_flowlines(self): + """Evaluate flowlines in self.flowlines_json to understand certainty for HydroLink selection. + + Evaluate flowlines in self.flowlines_json to understand certainty for HydroLink selection. + Evaluations include calculating distance to each flowline, calculating distance to closest + confluence, calculate name similarity for water names. Handles instances where no flowlines + are available and failed evaluations. + + Parameters + ---------- + self.flowlines_json: dictionary + JSON returned from request of flowline_query. JSON contains data about flowlines. + self.water_name: str + User supplied name of the waterbody that a point occurs on. Optional + self.input_point: shapely point + Input location for hydrolinking. For formatting see shapely.geometry Point method + + Returns + ---------- + self.closest_confluence_meters: float + Distance from input point to closest confluence in meters, see utils.closest_confluence + self.flowline_data: dictionary + Contains information about a flowline + + """ + # if status == 0 we don't want to waste time processing + if self.status == 1: + if 'features' in self.flowlines_json.keys() and len(self.flowlines_json['features']) > 0: + try: + flowlines_data = [] + all_flowline_terminal_node_points = [] + for flowline_data in self.flowlines_json['features']: + flowline_attributes, terminal_node_points, flowline_geo = utils.build_flowline_details(flowline_data, self.input_point, 'nhdplusv2', self.water_name) + flowlines_data.append(flowline_attributes) + all_flowline_terminal_node_points = all_flowline_terminal_node_points + terminal_node_points + + self.closest_confluence_meters = utils.closest_confluence(all_flowline_terminal_node_points, self.input_point, flowline_geo) + self.flowlines_data = flowlines_data + + except: + self.message = f'hydrolink_flowlines failed for id: {self.source_id}.' + self.error_handling() + else: + self.message = f'no flowlines retrieved for id: {self.source_id}' + self.error_handling() + + def select_closest_flowline(self, similarity_cutoff=0.6): + """Select closest flowline. + + Selects closest flowline from flowlines_data, including all evaluation information + for the flowline. Although name similarity is not considered for selection it is used + to document if a name matched flowline is available. Requires output from hydrolink_flowlines. + + """ + if self.status == 1: + df = utils.df_for_selection(self.flowlines_data) + df = df.rename(columns={"lengthkm": "nhdplusv2 flowline length km", + "reachcode": "nhdplusv2 flowline reachcode", + "gnis_name": "nhdplusv2 flowline gnis name", + "comid": "nhdplusv2 comid", + "terminalflag": "nhdplusv2 terminal flag", + "permanent_identifier": "nhdplusv2 flowline permanent identifier" + }) + self.total_count_flowlines = df.shape[0] + self.name_match_in_buffer = df.loc[df['flowline name similarity'] >= similarity_cutoff].shape[0] + + df = df.nsmallest(1, 'meters from flowline', keep='all') + if df.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}. Use name_match method.' + self.error_handling() + + else: + self.hydrolink_flowline = ((df.to_dict('records'))[0]) + + def select_closest_flowline_w_name_match(self, similarity_cutoff=0.6): + """Select closest flowline with matching water name. + + HydroLink data to the closest NHD feature with a name similarity that meets the specified + similarity_cutoff. If no flowlines meet similarity cutoff the method HydroLinks data to the + closest NHD feature. Requires output from hydrolink_flowlines. + """ + if self.status == 1: + df = utils.df_for_selection(self.flowlines_data) + df = df.rename(columns={"lengthkm": "nhdplusv2 flowline length km", + "reachcode": "nhdplusv2 flowline reachcode", + "gnis_name": "nhdplusv2 flowline gnis name", + "comid": "nhdplusv2 comid", + "terminalflag": "nhdplusv2 terminal flag", + "permanent_identifier": "nhdplusv2 flowline permanent identifier" + }) + self.total_count_flowlines = df.shape[0] + df_1 = df.loc[df['flowline name similarity'] == 1.0] + df_similarity = df.loc[df['flowline name similarity'] >= similarity_cutoff] + # only 1 flowline has extact matching name + if df_1.shape[0] == 1: + self.hydrolink_flowline = ((df.to_dict('records'))[0]) + # more than 1 flowline has exact matching name, grab closest of matching name flowlines + elif df_1.shape[0] > 1: + df_1 = df_1.nsmallest(1, 'meters from flowline', keep='all') + if df_1.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}.' + self.error_handling() + else: + self.hydrolink_flowline = ((df_1.to_dict('records'))[0]) + # only one flowline has matching name meeting similarity cutoff + elif df_1.shape[0] == 0 and df_similarity.shape[0] == 1: + self.hydrolink_flowline = ((df_similarity.to_dict('records'))[0]) + # select closest flowline meeting name match similarity cutoff + elif df_1.shape[0] == 0 and df_similarity.shape[0] > 1: + df_similarity = df_similarity.nsmallest(1, 'meters from flowline', keep='all') + if df_similarity.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}.' + self.error_handling() + else: + self.hydrolink_flowline = ((df_similarity.to_dict('records'))[0]) + # no flowlines with name match, select closest + else: + df = df.nsmallest(1, 'meters from flowline', keep='all') + if df.shape[0] > 1: + self.message = f'multiple flowlines with same snap distance for id: {self.source_id}.' + self.error_handling() + else: + self.hydrolink_flowline = ((df.to_dict('records'))[0]) + + def error_handling(self): + """Handle errors throughout HydroLink.""" + self.status = 0 + print(self.message) + + def write_hydrolink(self, outfile_name='nhdplusv2_hydrolink_output.csv'): + """Write HydroLink data output to CSV.""" + file_exists = os.path.isfile(outfile_name) + if self.status == 1: + source_data = {'source id': self.source_id, + 'source water name': self.water_name, + 'source lat nad83': self.init_lat, + 'source lon nad83': self.init_lon, + 'closest conluence meters': self.closest_confluence_meters, + 'source buffer meters': self.buffer_m, + 'total count flowlines in buffer': self.total_count_flowlines, + 'hydrolink message': self.message} + source_data.update(self.hydrolink_flowline) + if self.hydrolink_waterbody is not None: + source_data.update(self.hydrolink_waterbody) + elif self.status == 0: + source_data = {'source id': self.source_id, + 'source water name': self.water_name, + 'source lat nad83': self.init_lat, + 'source lon nad83': self.init_lon, + 'source buffer meters': self.buffer_m, + 'hydrolink message': self.message} + field_names = ['source id', 'source lat nad83', 'source lon nad83', 'source buffer meters', + 'closest conluence meters', 'closest flowline order', 'total count flowlines in buffer', + 'source water name', 'cleaned source water name', 'flowline name similarity', + 'flowline name similarity message', 'nhdplusv2 flowline gnis name', 'nhdplusv2 comid', + 'nhdplusv2 flowline length km', 'nhdplusv2 flowline reachcode', + 'meters from flowline', 'nhdplusv2 flowline measure', + 'nhdplusv2 terminal flag', 'nhdplusv2 waterbody permanent identifier', + 'nhdplusv2 waterbody gnis name', 'nhdplusv2 waterbody reachcode', + 'nhdplusv2 waterbody ftype', 'nhdplusv2 waterbody comid', 'hydrolink message' + ] + with open(outfile_name, 'a', newline='') as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=field_names, delimiter=',') + if not file_exists: + writer.writeheader() + writer.writerow(source_data) + + # def get_hl_measure(self): + # ''' + # Description + # ------------ + # Use HEM SOE extension HEMPointEvents to pass the snap location on best reach to return reach measure. + # Documentation of HEMPointEvents is found https://edits.nationalmap.gov/hem-soe-docs/soe-reference/hem-point-events.html + # ''' + # if self.status == 1: + # if 'snap_xy' in self.best_reach.keys(): + # get_mr_hl = 'https://ofmpub.epa.gov/waters10/PointIndexing.Service' + # x = self.best_reach['snap_xy'].x + # y = self.best_reach['snap_xy'].y + # xy_mr2 = f"POINT({x} {y})" + + # payload_mr2 = { + # 'optNHDPlusDataset': '2.1', + # 'pGeometry': xy_mr2, + # 'pGeometryMod': 'SRID=4269', + # 'pOutputPathFlag': 'TRUE', + # 'pPointIndexingMaxDist': '0.1', #Kilometers + # 'pPointIndexingMethod':'DISTANCE', + # 'pResolution': '3', + # 'pPointIndexingFcodeDeny' : [56600], + # 'pReturnFlowlineGeomFlag':'FALSE', + # 'f':'json'} + + # try: + # self.mr_hl = requests.post(get_mr_hl,params=payload_mr2,verify=False).json() + # meas_mr2 = self.mr_hl['output']['ary_flowlines'][0]['fmeasure'] + # self.best_reach.update({'mr_meas':meas_mr2}) + # except: + # self.message='get_hl_measure failed' + # self.best_reach.update({"meas": None, 'message': self.message}) + # else: + # self.message = f'no snap point to run point indexing on id: {self.id}' + # message = {'message': self.message} + # self.best_reach.update(message) diff --git a/hydrolink/utils.py b/hydrolink/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..672f6517ab0c305629d932b920c65049522781e1 --- /dev/null +++ b/hydrolink/utils.py @@ -0,0 +1,357 @@ +"""Utility functions used across hydrolink modules. + +Author +---------- +Daniel Wieferich: dwieferich@usgs.gov +""" + +# Import packages +import geopandas as gpd +import pandas as pd +from shapely.geometry import Point, LineString +import shapely.wkt +import re +import difflib + +############################################################################################ +############################################################################################ + + +def crs_to_nad83(input_point, crs): + """Reproject point data to NAD83, aka crs 4269. + + Parameters + ---------- + input_point: shapely point + See shapely.geometry Point method + input_crs: int + EPSG defined coordinate reference system, default is 4269 which is NAD83 + + Returns + ---------- + lon_nad83: float + longitude in crs 4269 (NAD83) + lat_nad83: float + latitude in crs 4269 (NAD83) + + """ + # create geoseries of shapely point + pt_gdf = gpd.GeoSeries(input_point) + # set crs of geoseries, using user provided crs + epsg_crs = f'epsg:{str(int(crs))}' + pt_gdf.crs = epsg_crs + # reproject point + pt_gdf = pt_gdf.to_crs('epsg:4269') + + lon_nad83 = float(pt_gdf[0].x) + lat_nad83 = float(pt_gdf[0].y) + + return lon_nad83, lat_nad83 + + +def build_flowline_details(flowline_data, input_point, nhd_version='nhdhr', source_water_name=''): + """Brings together functions to get hydrolink details for a flowline. + + Parameters + ---------- + flowline_data: dictionary + Contains information about a flowline, built in nhd_hr.hydrolink_flowlines or nhd_mr.hydrolink_flowlines + input_point: shapely point + Input location for hydrolinking. For formatting see shapely.geometry Point method + nhd_version: {'nhdhr', 'nhdplusv2'}, default 'nhdhr' + Version of National Hydrography Dataset. Supported versions include + + - ``'nhdhr'``: National Hydrography Dataset High Resolution + - ``'nhdplusv2'``: National Hydrography Dataset Plus Version 2.1 Medium Resolution + + source_water_name: str, optional, default '' + If available name of water body (e.g. name of lake, river, estuary...) + + Returns + ---------- + flowline_attributes: dictionary + Hydrolink calculated attributes updated to input flowline_data + terminal_node_points: list + List of shapely points expressed in wkt representing terminal nodes of flowline + Example [POINT (-70.63598606746581 41.7689812018329)', 'POINT (-70.63435706746833 41.77051200183053)'] + flowline_geo: list + List of shapely points representing shapely linestring of the flowline + + """ + flowline_attributes = flowline_data['attributes'] + # get all keys lower case hr lower, mr was upper + flowline_attributes = {key.lower(): v for key, v in flowline_attributes.items()} + flowline_geo = flowline_data['geometry']['paths'][0] + + # measure distance from point to flowline, return point location on flowline (flowline_point) + flowline_snap_point, snap_distance_meters = point_to_line_meters(flowline_geo, input_point) + # list of nhd measures within flowline_geo (m values) + node_measures = [x[2] for x in flowline_geo] + + # check to see if there are more than 2 occurences of a terminal node (this indicates a confluence) + terminal_node_points = list(set([Point(x[0], x[1]).wkt for x in flowline_geo if x[2] == max(node_measures) or x[2] == min(node_measures)])) + + nhd_measure = nhd_flowline_measure(flowline_geo, node_measures, flowline_snap_point) + + name_similarity = gnis_name_similarity(flowline_attributes['gnis_name'], source_water_name) + flowline_attributes.update(name_similarity) + + label_nhd_version = f'{nhd_version} flowline measure' + flowline_attributes.update({'meters from flowline': snap_distance_meters, + label_nhd_version: nhd_measure + }) + + return flowline_attributes, terminal_node_points, flowline_geo + + +def nhd_flowline_measure(flowline_geo, node_measures, flowline_snap_point): + """Measure along flowline where the flowline_snap_point is located (the address). + + Parameters + ---------- + flowline_geo: list + List of shapely points representing shapely linestring of the flowline, see build_flowline_details + node_measures: list + nhd measures within flowline_geo (m values) + flowline_snap_point: shapely point + shapely point that marks snap location (closest location on line to a coordinate), see point_to_line_meters + + Returns + ---------- + nhd_measure: + Measure along NHD flowline where 0 is most downstream node and 100 is most upstream node within the reachcode + Note a reachcode can span multiple flowlines + + """ + flowline_line = LineString(flowline_geo) + + # Below calculates measure along nhd flowline + # Make sure the max node value is actually the max measure for the flowline + max_node_point = [Point(x[0], x[1]) for x in flowline_geo if x[2] == max(node_measures)][0] + min_node_point = [Point(x[0], x[1]) for x in flowline_geo if x[2] == min(node_measures)][0] + + length_line_to_point = flowline_line.project(flowline_snap_point) + length_line_total = max(flowline_line.project(min_node_point), flowline_line.project(max_node_point)) + # total span of flowline measures + flowline_total_meas = float(max(node_measures)) - float(min(node_measures)) + + if flowline_line.project(min_node_point) > flowline_line.project(max_node_point): + nhd_measure = (float(max(node_measures)) - ((float(flowline_total_meas) * float(length_line_to_point)) / float(length_line_total))) + elif length_line_total != 0: + nhd_measure = (((float(flowline_total_meas) * float(length_line_to_point)) / float(length_line_total)) + float(min(node_measures))) + else: + nhd_measure = None + + return nhd_measure + + +def closest_confluence(terminal_node_points, input_point, flowline_geo): + """Calculate distance from input point coordinates to closest confluence. + + This function accepts a list of terminal nodes. A confluence is considered where + a terminal node is shared in three or more flowlines. The smaller this number + the less certain a hydrolink will be. + + Parameters + ---------- + terminal_node_points: list + complete list of terminal node points from a subset of flowlines including duplicate values + input_point: shapely point + location from which distance calculations are made. For formatting see shapely.geometry Point method + flowline_geo: list + List of shapely points representing shapely linestring of the flowline, see build_flowline_details + + Returns + ---------- + closest_confluence_meters: float + distance from input point to closest confluence in meters + + """ + confluence_points = sorted(set([i for i in terminal_node_points if terminal_node_points.count(i) > 2])) + closest_confluence_meters = None + if len(confluence_points) > 0: + for point_wkt in confluence_points: + p = shapely.wkt.loads(point_wkt) + confluence_snap_distance_meters = build_distance_line(p, input_point, crs='epsg:4269') + if closest_confluence_meters is None or closest_confluence_meters > confluence_snap_distance_meters: + closest_confluence_meters = confluence_snap_distance_meters + + return closest_confluence_meters + + +def point_to_line_meters(flowline_geo, input_point): + """Calculate distance in meters from input point coordinates to closest point along a line. + + Parameters + ---------- + input_point: shapely point + Location from which distance calculations are made. For formatting see shapely.geometry Point method + flowline_geo: list + List of shapely points representing shapely linestring of the flowline, see build_flowline_details + + Returns + ---------- + flowline_snap_point: shapely point + Location along flowline that is closest in distance to input_point. + + """ + flowline_geo_line = LineString(flowline_geo) + snap_meas = flowline_geo_line.project(input_point) + # for flowline find x,y on line closest to input x,y + snap_xy = flowline_geo_line.interpolate(snap_meas) + # shapely point that marks snap location (closest location on line to a coordinate) + flowline_snap_point = Point(snap_xy.x, snap_xy.y) + snap_distance_meters = build_distance_line(flowline_snap_point, input_point, crs='epsg:4269') + + return flowline_snap_point, snap_distance_meters + + +def build_distance_line(point_1, point_2, crs='epsg:4269'): + """Build line from point1 to point2 an measure distance in meters. + + Parameters + ---------- + point_1: shapely point + Location from which distance calculations are made + point_2: shapely point + Location to which distance calculations are made + crs: str, default = 'epsg:4269' + The value can be anything accepted by pyproj.CRS.from_user_input(), such as an + authority string (eg “EPSG:4326â€) or a WKT string. + + Returns + ---------- + line_length_meters: float + Distance in meters + + """ + line_geom = LineString([point_1, point_2]) + line_geoseries = gpd.GeoSeries(line_geom) + line_geoseries.crs = crs + line_geoseries = line_geoseries.to_crs('epsg:5070') + line_length_meters = line_geoseries.length[0] + return line_length_meters + + +def gnis_name_similarity(gnis_name, source_water_name): + """Similarity comparison of two names using difflib. + + Measures similarity between two names using difflib + Returned dictionary has a measure of similarity called 'flowline name similarity' + + Parameters + ---------- + gnis_name: str + name of water feature as defined by USGS Geographic Names Information System + source_water_name: str + name of water feature as defined by user + + Returns + ---------- + name_similarity: dictionary + dictionary capturing measures of similarity + 'flowline name similarity' is a float and values range from 0 (no match) to 1 (exact match) + 'flowline name similarity message' is a text representation of the similarity measure + + """ + name_similarity = {} + if gnis_name is None and (source_water_name is None or source_water_name.isspace() or source_water_name.lower() == 'none'): + name_similarity.update({'flowline name similarity message': 'no source water name provided and no GNIS_NAME', + 'flowline name similarity': 0 + }) + elif source_water_name is None or source_water_name.isspace() or source_water_name.lower() == 'none': + name_similarity.update({'flowline name similarity message': 'no source water name provided', + 'flowline name similarity': 0 + }) + elif gnis_name is None: + name_similarity.update({'flowline name similarity message': 'no GNIS name', + 'cleaned source water name': source_water_name.lower(), + 'flowline name similarity': 0 + }) + elif gnis_name.lower() == source_water_name.lower(): + name_similarity.update({'flowline name similarity message': 'exact water name match', + 'cleaned source water name': source_water_name.lower(), + 'flowline name similarity': 1.0 + }) + else: + cleaned_water_name = clean_water_name(source_water_name) + gnis = gnis_name.lower() + if 'tributary' not in cleaned_water_name and 'branch' not in cleaned_water_name: + match_ratio = difflib.SequenceMatcher(lambda x: x == " ", gnis, cleaned_water_name).ratio() + name_similarity.update({'cleaned source water name': cleaned_water_name, + 'flowline name similarity': match_ratio + }) + if match_ratio >= 0.75: + name_similarity.update({'flowline name similarity message': 'most likely match, based on fuzzy match'}) + elif 0.75 > match_ratio >= 0.6: + name_similarity.update({'flowline name similarity message': 'likely match, based on fuzzy match'}) + elif match_ratio < 0.6: + name_similarity.update({'flowline name similarity message': 'likely not a match, based on fuzzy match'}) + else: + name_similarity.update( + {'cleaned source water name': cleaned_water_name, + 'flowline name similarity message': 'tributary or branch in source water name, fuzzy match not conducted.', + 'flowline name similarity': 0 + } + ) + + return name_similarity + + +def clean_water_name(name): + """Quick and dirty approach to clean up unstandardized water names. + + Replaces common abbreviations, and deals with unnneeded spaces. + This needs improvement but need to be careful not to replace unwanted strings. + This step is implemented with the assumption that GNIS_NAME never contains abbreviations... something to verify. + If you have a better way to do this let me know!!!! + + Parameters + ---------- + name: str + name of water feature as defined by user + + Returns + ---------- + water_name_cleaned: str + resulting waterbody name with common abbreviations (hopefully) spelled out + + """ + name_lower = f' {name.lower()} ' + name_lower = re.sub(r"[\(\[].*?[\)\]]", "", name_lower) + name_lower = name_lower.replace(' st. ', ' stream ') + name_lower = name_lower.replace(' st ', ' stream ') + name_lower = name_lower.replace(' str ', ' stream ') + name_lower = name_lower.replace(' str. ', ' stream ') + name_lower = name_lower.replace(' rv. ', ' river ') + name_lower = name_lower.replace(' rv ', ' river ') + name_lower = name_lower.replace(' unt ', ' unnamed tributary ') + name_lower = name_lower.replace(' trib. ', ' tributary ') + name_lower = name_lower.replace(' trib) ', ' tributary ') + name_lower = name_lower.replace(' trib ', ' tributary ') + name_lower = name_lower.replace(' ck ', ' creek ') + name_lower = name_lower.replace(' ck. ', ' creek ') + name_lower = name_lower.replace(' br ', ' branch ') + name_lower = name_lower.replace(' br. ', ' branch ') + water_name_cleaned = name_lower.strip() + return water_name_cleaned + + +def df_for_selection(flowlines_data): + """Organizes flowline data into pandas dataframe for ease in selection of information. + + Parameters + ---------- + flowlines_data: dictionary + data for all flowlines + + Returns + ---------- + df: pandas dataframe + + """ + df = (pd.DataFrame(flowlines_data)).sort_values(by=['meters from flowline']) + df = df.reset_index(drop=True) + df['closest flowline order'] = df.index + 1 + + return df diff --git a/hydrolinker.py b/hydrolinker.py new file mode 100644 index 0000000000000000000000000000000000000000..5abb364a5db9975c41066ed5f2c4d3a5dc06d3e4 --- /dev/null +++ b/hydrolinker.py @@ -0,0 +1,99 @@ +"""Command line tool for HydroLinking data. + +Authors +---------- +Name: Daniel Wieferich +Contact: dwieferich@usgs.gov + +Name: Brandon Serna +Contact: bserna@usgs.gov + +""" +import click +from hydrolink import nhd_hr +from hydrolink import nhd_mr +import geopandas as gpd +import pandas as pd +import warnings +warnings.simplefilter('ignore') + + +@click.command() +@click.option('--input_file', required=True, show_default=True, default='tests/test-data.csv', help='Enter file name, including extension (only accepts .csv and .shp)') +@click.option('--latitude_field', required=True, show_default=True, default='y', help='Enter field name for latitude, note this is case sensitive') +@click.option('--longitude_field', required=True, show_default=True, default='x', help='Enter field name for longitude, note this is case sensitive') +@click.option('--stream_name_field', required=True, show_default=True, default='stream', help='Enter field name for stream name, if none type None, note this is case sensitive') +@click.option('--identifier_field', required=True, show_default=True, default='id', help='Enter field name for identifier, note this is case sensitive') +@click.option('--crs', required=True, show_default=True, default=4269, help='Enter crs number, recommended to use NAD83 represented by 4269') +@click.option('--buffer', required=True, show_default=True, default=1000, help='Enter buffer distance in meters, max is 2000') +@click.option('--method', required=True, show_default=True, default='name_match', help='Enter method to use, options include name_match and closest') +@click.option('--nhd_version', required=True, show_default=True, default='nhdhr', help='Version of NHD to use, options include nhdhr and nhdplusv2') +@click.option('--hydro_type', required=True, show_default=True, default='flowline', help='Options flowline or waterbody') +def handle_data(input_file, latitude_field, longitude_field, stream_name_field, identifier_field, crs, buffer, method, nhd_version, hydro_type): + """Hydrolink point data to the nhd high resolution. + + First set appropriate Python environment. + Run HydroLinker. Examples included below. + + python hydrolinker.py >>> runs with defaults + + python hydrolinker.py --input_file=file_name.csv >>> runs with specified file name and default other values + + Returns + >>> writes hydrolinked (hydro addressed information to csv file) + + """ + click.echo('Thank you, processing now...') + + in_data = {"file": input_file, + "lat": latitude_field, + "lon": longitude_field, + "stream_name": stream_name_field, + "id": identifier_field} + + if in_data['file'].endswith('.csv'): + click.echo('reading csv file') + try: + df = pd.read_csv(in_data['file'], encoding='iso-8859-1') + except KeyError: + click.echo('csv file did not properly import, verify file name and rerun') + + # If input file is not a CSV check to see if it is a shapefile + elif in_data['file'].endswith('.shp'): + click.echo('reading shapefile') + try: + df = gpd.GeoDataFrame.from_file(in_data['file']) + except KeyError: + click.echo('shapefile did not properly import, verify file name and rerun') + + # If input file is not a CSV or shapefile tell the user that the file type is not excepted + else: + click.echo('File type not currently accepted. Please try .csv or .shp') + + if in_data['lat'] in df and in_data['lon'] in df and in_data['id'] in df and in_data['stream_name'] in df: + df = df.rename(columns={in_data['id']: 'id', + in_data['lat']: 'lat', + in_data['lon']: 'lon', + in_data['stream_name']: 'stream' + }) + df['crs'] = int(crs) + + else: + click.echo('Verify field names and rerun') + + for row in df.itertuples(): + if nhd_version == 'nhdhr': + hydrolink = nhd_hr.HighResPoint(row.id, float(row.lat), float(row.lon), input_crs=int(row.crs), water_name=str(row.stream), buffer_m=buffer) + elif nhd_version == 'nhdplusv2': + hydrolink = nhd_mr.HighResPoint(row.id, float(row.lat), float(row.lon), input_crs=int(row.crs), water_name=str(row.stream), buffer_m=buffer) + hydrolink.hydrolink_method(method=method, hydro_type=hydro_type) + + # in_file = in_data['file'][:-4] #remove .csv or .shp + # output_file = f'{in_file}_output.csv' + # hydrolink.write_best(outfile_name= output_file) + + # click.echo('Output exported to %s' % output_file) + + +if __name__ == '__main__': + handle_data() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..1690c455218939b135b4de6f3eaf45eac6905c8e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# This file may be used to create an environment using: +requests==2.28.2 +geopandas==0.12.2 diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..854b58bb0f4fa5e658270919257366f2d3f50582 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,5 @@ +requests==2.28.2 +geopandas==0.12.2 +pytest==7.2.2 +black==23.1.0 +validators==0.20.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..e3b8e8947aa0e317c7ab3af44c3ff024b08ebc9d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[bumpversion] +current_version = 0.1.1 +commit = True +tag = True + +[bumpversion:file:setup.py] + +[bumpversion:file:hydrolink/__init__.py] + +[bumpversion:file:code.json] + +[bumpversion:file:README.md] +search = Version-{current_version} +replace = Version-{new_version} + +[flake8] +ignore = E203, E266, E501, W503 +max-line-length = 88 +max-complexity = 18 +select = B,C,E,F,W,T4 +exclude = + tests, + docs + +[aliases] +test = pytest + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..38891f26d445caa6501f367610ef5950da2144d6 --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +"""The setup script.""" +from setuptools import find_packages +from setuptools import setup + +with open('README.rst') as readme_file: + readme = readme_file.read() + +with open("requirements.txt") as f: + requirements = f.read().splitlines() + +setup_requirements = ['pytest-runner', ] + +test_requirements = ['pytest>=3', ] + +setup( + author="Daniel Wieferich", + author_email='dwieferich@usgs.gov', + python_requires='>=3.8', + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: unlicense', + 'Natural Language :: English', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9' + ], + description="Python package with methods to hydro address information to National Hydrography Datasets.", + install_requires=requirements, + license="unlicense", + long_description=readme, + include_package_data=True, + keywords='hydrolink', + name='hydrolink', + packages=find_packages(include=['hydrolink', 'hydrolink.*']), + setup_requires=setup_requirements, + test_suite='tests', + tests_require=test_requirements, + url='https://code.usgs.gov/sas/bioscience/hlt/hydrolink', + version='0.1.1', + zip_safe=False, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/flowlines_json.json b/tests/flowlines_json.json new file mode 100644 index 0000000000000000000000000000000000000000..3d61cff9aa043175d7186c221df3c74218f33261 --- /dev/null +++ b/tests/flowlines_json.json @@ -0,0 +1 @@ +{"displayFieldName": "gnis_name", "hasM": true, "fieldAliases": {"gnis_name": "GNIS_NAME", "lengthkm": "LENGTHKM", "permanent_identifier": "PERMANENT_IDENTIFIER", "reachcode": "REACHCODE"}, "geometryType": "esriGeometryPolyline", "spatialReference": {"wkid": 4269, "latestWkid": 4269}, "fields": [{"name": "gnis_name", "type": "esriFieldTypeString", "alias": "GNIS_NAME", "length": 65}, {"name": "lengthkm", "type": "esriFieldTypeDouble", "alias": "LENGTHKM"}, {"name": "permanent_identifier", "type": "esriFieldTypeString", "alias": "PERMANENT_IDENTIFIER", "length": 40}, {"name": "reachcode", "type": "esriFieldTypeString", "alias": "REACHCODE", "length": 14}], "features": [{"attributes": {"gnis_name": null, "lengthkm": 0.011, "permanent_identifier": "152093560", "reachcode": "04050004002359", "meters from flowline": 590.9925501612136, "nhdhr measure": 0.0}, "geometry": {"hasM": true, "paths": [[[-84.50938251259817, 42.72651486701329, 1.83965], [-84.50947937926469, 42.726588067013154, 0]]]}}, {"attributes": {"gnis_name": "Red Cedar River", "lengthkm": 1.746, "permanent_identifier": "152093412", "reachcode": "04050004000126", "meters from flowline": 595.535732278204, "nhdhr measure": 0.0}, "geometry": {"hasM": true, "paths": [[[-84.50947937926469, 42.726588067013154, 19.83811], [-84.50969557926436, 42.726443267013394, 19.56608], [-84.50972357926429, 42.726413867013434, 19.52071], [-84.50988497926409, 42.72628020034699, 19.29466], [-84.51016877926361, 42.72608306701392, 18.93152], [-84.5103387125967, 42.72595520034747, 18.70539], [-84.51109851259548, 42.72539966701498, 17.70881], [-84.51119997926202, 42.72532266701512, 17.57321], [-84.51126371259528, 42.72526900034853, 17.4831], [-84.51182737926104, 42.724827067015894, 16.71682], [-84.51217951259383, 42.724579400349626, 16.26357], [-84.51224357926037, 42.72452600034967, 16.17352], [-84.5123805792602, 42.72442440034985, 15.99255], [-84.51264891259308, 42.72421666701683, 15.63019], [-84.51305857925911, 42.72396320035057, 15.13208], [-84.5130931125924, 42.7239378003506, 15.08665], [-84.51312011259239, 42.72390786701732, 15.04125], [-84.51315377925897, 42.72388166701734, 14.99566], [-84.51323257925884, 42.723840200350764, 14.90549], [-84.51368357925816, 42.72363020035107, 14.40864], [-84.51383757925794, 42.723542000351244, 14.22701], [-84.51394217925775, 42.72346700035132, 14.09112], [-84.51397437925772, 42.72344006701803, 14.04577], [-84.5140471792576, 42.72339240035143, 13.95509], [-84.51420771259069, 42.72331126701823, 13.77381], [-84.51433157925715, 42.72325426701832, 13.6378], [-84.51454211259016, 42.72316386701846, 13.41086], [-84.51476257925651, 42.72308840035191, 13.18444], [-84.51499617925612, 42.723036667018675, 12.95722], [-84.5150413125894, 42.72302346701866, 12.91199], [-84.51525831258903, 42.722941800352146, 12.68504], [-84.51552757925532, 42.72285966701895, 12.41359], [-84.51586637925476, 42.722717600352496, 12.05053], [-84.51590597925474, 42.722696467019205, 12.005], [-84.51605397925448, 42.722602667019316, 11.82322], [-84.51641651258723, 42.72231186701981, 11.32429], [-84.51647777925382, 42.722256067019885, 11.23361], [-84.5166403125869, 42.72203446702025, 10.91543], [-84.5166823792535, 42.721969667020346, 10.8247], [-84.51671977925344, 42.72190326702042, 10.7339], [-84.51683811258658, 42.72174586702067, 10.50661], [-84.51698791258639, 42.72160386702092, 10.27938], [-84.51704857925296, 42.72154746702097, 10.18845], [-84.51712237925284, 42.72150040035439, 10.09757], [-84.51736657925244, 42.72138126702123, 9.82482], [-84.51741071258573, 42.72136626702127, 9.77955], [-84.51745937925233, 42.72136220035458, 9.73392], [-84.51750777925224, 42.72136306702129, 9.68881], [-84.51755537925214, 42.721370467021245, 9.64349], [-84.51760237925208, 42.72138080035455, 9.5978], [-84.517646779252, 42.72139520035455, 9.55262], [-84.51768911258529, 42.721413200354505, 9.5071], [-84.51776271258518, 42.72145966702112, 9.41685], [-84.51779077925181, 42.72148926702107, 9.37123], [-84.51780957925178, 42.72152240035433, 9.32587], [-84.51796071258485, 42.721746467020694, 9.00982], [-84.51800637925146, 42.72184806702052, 8.87465], [-84.51815471258453, 42.722151267020024, 8.46759], [-84.51817871258453, 42.72218266701998, 8.42206], [-84.51821751258444, 42.722204467019935, 8.37663], [-84.51825831258441, 42.72222420035325, 8.33118], [-84.518303379251, 42.72223706701993, 8.28616], [-84.51839737925087, 42.72225566701991, 8.19549], [-84.51844571258408, 42.72225700035324, 8.15043], [-84.51849057925068, 42.72224306701992, 8.10508], [-84.51857457925058, 42.722207400353284, 8.01478], [-84.51869591258372, 42.72214880035335, 7.87968], [-84.51873411258367, 42.72212640035343, 7.83422], [-84.51876617925029, 42.72209966702013, 7.78914], [-84.51895077924996, 42.72180720035391, 7.38173], [-84.51896511258332, 42.72177360035397, 7.33724], [-84.51896417924996, 42.72173780035399, 7.29203], [-84.51889177925005, 42.721530000354335, 7.02108], [-84.51887311258344, 42.721496867021074, 6.97577], [-84.51873751258364, 42.721306600354694, 6.70431], [-84.51859017925057, 42.721080000355016, 6.38694], [-84.5185569792506, 42.72101306702183, 6.29693], [-84.51852017925063, 42.72079986702215, 6.02553], [-84.51853437925064, 42.7203686670228, 5.48084], [-84.51853671258397, 42.72033060035619, 5.43272], [-84.51860991258383, 42.720160000356486, 5.20674], [-84.51869797925036, 42.720033800356646, 5.0275], [-84.51879291258359, 42.71991086702354, 4.84883], [-84.51883311258348, 42.719847000356935, 4.7599], [-84.51885791258348, 42.71970406702383, 4.57793], [-84.51884637925014, 42.719633000357305, 4.48755], [-84.51883577925014, 42.71959780035735, 4.44201], [-84.51881311258353, 42.71956660035738, 4.39731], [-84.51860431258388, 42.719367867024346, 4.07978], [-84.51856991258393, 42.719342200357744, 4.03419], [-84.51853191258397, 42.719320000357754, 3.98904], [-84.51850011258404, 42.71929266702449, 3.94355], [-84.51838557925089, 42.71917646702468, 3.76211], [-84.51837451258422, 42.71914160035806, 3.71689], [-84.51837971258419, 42.71907026702485, 3.62668], [-84.51847411258404, 42.71882846702522, 3.30892], [-84.51849571258401, 42.71879620035861, 3.26347], [-84.51864217925049, 42.71861166702553, 2.99342], [-84.51891891258339, 42.71831586702598, 2.53954], [-84.51900891258322, 42.718230867026136, 2.40333], [-84.51929251258281, 42.71803260035978, 2.03932], [-84.51948131258251, 42.71791940035996, 1.81265], [-84.51973777924877, 42.717753267026865, 1.49467], [-84.51988837924853, 42.717661867027005, 1.31299], [-84.52019757924808, 42.71748680036063, 0.94985], [-84.52067357924733, 42.71723506702767, 0.4042], [-84.52083571258038, 42.71710086702791, 0.17717], [-84.520864779247, 42.717071867027926, 0.13163], [-84.52093831258026, 42.71698286702809, 0]]]}}, {"attributes": {"gnis_name": "Red Cedar River", "lengthkm": 7.032, "permanent_identifier": "152093413", "reachcode": "04050004000126", "meters from flowline": 52.94060499992746, "nhdhr measure": 90.32618961126374}, "geometry": {"hasM": true, "paths": [[[-84.44491257936488, 42.72313040035186, 100], [-84.44505891269802, 42.723123267018536, 99.86338], [-84.44515571269784, 42.723113600351894, 99.77237], [-84.44529737936432, 42.72308640035192, 99.636], [-84.44585151269678, 42.7229454670188, 99.08994], [-84.44598151269656, 42.72289566701886, 98.95347], [-84.44610551269636, 42.72283820035233, 98.81705], [-84.44614957936301, 42.72282560035234, 98.77302], [-84.44629517936278, 42.72281420035233, 98.63661], [-84.44662631269557, 42.722751800352455, 98.31822], [-84.44672037936209, 42.722732800352446, 98.22736], [-84.4468089126953, 42.722702400352546, 98.13638], [-84.44694671269508, 42.72266580035256, 97.99994], [-84.44698897936166, 42.72264786701925, 97.95451], [-84.44725391269463, 42.722490200352865, 97.6374], [-84.4473337126945, 42.722448800352936, 97.54651], [-84.44745711269428, 42.72239146701969, 97.41065], [-84.44754317936082, 42.72235806701974, 97.32005], [-84.44772031269389, 42.72229826701982, 97.13857], [-84.44784631269368, 42.72224440035325, 97.0029], [-84.4479327793602, 42.72221180035331, 96.91243], [-84.44806757935999, 42.72216966702001, 96.77604], [-84.44820891269313, 42.72214446702003, 96.64058], [-84.4482533793597, 42.72213106702009, 96.59583], [-84.44829431269301, 42.72211140035341, 96.55032], [-84.4483849793595, 42.72208740035347, 96.46058], [-84.44862357935915, 42.72205286702018, 96.23406], [-84.44871411269236, 42.72202620035358, 96.14324], [-84.44897037935863, 42.721923600353705, 95.8716], [-84.44906117935847, 42.72189866702041, 95.78134], [-84.44915517935834, 42.721880400353825, 95.69078], [-84.44944277935787, 42.721842400353864, 95.41858], [-84.44958857935762, 42.72183766702051, 95.28261], [-84.44983217935726, 42.72184466702049, 95.05549], [-84.44997357935705, 42.72187166702048, 94.91942], [-84.45020731269, 42.72192166702041, 94.6927], [-84.45025537935663, 42.721926267020365, 94.64754], [-84.45035017935646, 42.721942867020346, 94.55677], [-84.45066477935598, 42.72203766702023, 94.24017], [-84.45093857935558, 42.72211346702011, 93.96772], [-84.45170897935435, 42.72233480035311, 93.19749], [-84.45179817935423, 42.72236286701974, 93.10715], [-84.4518797126874, 42.72240240035296, 93.01625], [-84.45198971268724, 42.72247266701953, 92.88069], [-84.45206011268715, 42.72252180035281, 92.79041], [-84.4520945126871, 42.722547067019434, 92.74518], [-84.4522557793535, 42.72268160035253, 92.51839], [-84.45237437935333, 42.72287820035223, 92.24665], [-84.45238771268663, 42.72291286701886, 92.20114], [-84.4523987126866, 42.72294780035213, 92.15585], [-84.45241997935324, 42.723054067018666, 92.0202], [-84.45242397935323, 42.72312606701854, 91.9292], [-84.45238731268665, 42.72326740035163, 91.74748], [-84.45236777935332, 42.72330046701825, 91.70193], [-84.45231911268672, 42.72336260035149, 91.61131], [-84.45229831268676, 42.723395000351445, 91.56604], [-84.45225717935352, 42.72349820035129, 91.4302], [-84.4522517793535, 42.72353386701792, 91.38488], [-84.45226157935349, 42.723569200351164, 91.33934], [-84.45227837935346, 42.723602600351114, 91.29435], [-84.45230771268677, 42.723631267017765, 91.24899], [-84.45242937935325, 42.72369140035096, 91.11256], [-84.4524723793532, 42.72370786701765, 91.06742], [-84.45266151268623, 42.7237422670176, 90.88595], [-84.45295191268576, 42.72376446701753, 90.61396], [-84.45333997935182, 42.72375800035087, 90.25234], [-84.45377631268451, 42.7237288670176, 89.84419], [-84.45413017935061, 42.72363800035106, 89.49513], [-84.45427657935039, 42.72363266701774, 89.35857], [-84.45432457935033, 42.72362660035111, 89.3132], [-84.45437317935023, 42.723624600351116, 89.26786], [-84.45447071268342, 42.72362466701776, 89.17699], [-84.45451877935, 42.72363026701777, 89.13166], [-84.45484997934949, 42.723686800351004, 88.81496], [-84.45489711268277, 42.723696467017646, 88.76938], [-84.45494157934934, 42.723710867017644, 88.72414], [-84.45502477934923, 42.72374760035092, 88.63381], [-84.45506457934914, 42.723768600350866, 88.58823], [-84.45518197934899, 42.72383266701746, 88.45218], [-84.45537297934868, 42.723944667017236, 88.22488], [-84.45545397934853, 42.72398406701717, 88.1345], [-84.45553691268174, 42.724021067017134, 88.04421], [-84.45567091268157, 42.72406500035038, 87.9076], [-84.45571797934815, 42.72407380035037, 87.86237], [-84.45594937934777, 42.72412820035032, 87.63611], [-84.45604377934762, 42.72414526701692, 87.54557], [-84.45628431268057, 42.72417566701688, 87.31822], [-84.45642897934704, 42.72419020035022, 87.1822], [-84.45657417934683, 42.72419900035021, 87.04648], [-84.45667131267999, 42.72420166701687, 86.95593], [-84.45671957934655, 42.724206600350215, 86.91053], [-84.4568657126797, 42.72420640035017, 86.77439], [-84.45691417934626, 42.72420286701686, 86.72902], [-84.457105179346, 42.72417646701689, 86.54799], [-84.45734191267894, 42.724134200350306, 86.32108], [-84.45757671267859, 42.72408506701703, 86.09371], [-84.45762111267851, 42.7240704003504, 86.04839], [-84.45766751267843, 42.724059067017095, 86.00286], [-84.45776237934496, 42.72404446701711, 85.91258], [-84.45785951267817, 42.724046800350436, 85.82204], [-84.4580049793446, 42.7240560670171, 85.68602], [-84.4580965126778, 42.724080467017075, 85.59535], [-84.4582757126775, 42.724137067016954, 85.41375], [-84.45835811267739, 42.72417540035025, 85.323], [-84.45851211267711, 42.7242622003501, 85.14245], [-84.45855531267705, 42.72427886701672, 85.09703], [-84.45873257934346, 42.72433886701663, 84.91534], [-84.45891431267648, 42.724390667016564, 84.73384], [-84.4589999126764, 42.72442446701649, 84.64339], [-84.4591591126761, 42.72450766701638, 84.46163], [-84.45935057934253, 42.72461846701623, 84.23493], [-84.45945117934235, 42.72469646701609, 84.09897], [-84.45950637934226, 42.724755467015996, 84.00844], [-84.45974637934188, 42.72506660034884, 83.55638], [-84.4597669126752, 42.72509920034878, 83.51098], [-84.45985637934172, 42.72534086701512, 83.19463], [-84.46001231267479, 42.72552260034814, 82.92302], [-84.46006811267472, 42.725581467014706, 82.83231], [-84.4602203126745, 42.7256684670146, 82.65294], [-84.46030471267437, 42.72570486701454, 82.56186], [-84.46044731267415, 42.7257294670145, 82.42544], [-84.46049597934069, 42.72573006701447, 82.38009], [-84.46054437934066, 42.725725800347846, 82.33468], [-84.46059191267392, 42.72571726701449, 82.28911], [-84.46062831267386, 42.7257352670145, 82.24829], [-84.46065717934044, 42.725764467014415, 82.20265], [-84.46067971267377, 42.7257962670144, 82.15734], [-84.4606923793404, 42.7259030670142, 82.02195], [-84.46068831267377, 42.72597386701409, 81.93246], [-84.4606571126738, 42.726041400347356, 81.84236], [-84.46061851267388, 42.72610586701393, 81.75337], [-84.46059517934054, 42.72613686701385, 81.70859], [-84.46056871267393, 42.72616720034716, 81.66304], [-84.46025517934112, 42.726389600346806, 81.25784], [-84.46010497934134, 42.72653046701322, 81.03151], [-84.45999177934152, 42.726647200346406, 80.85026], [-84.45994397934157, 42.72670960034628, 80.75975], [-84.45992511267497, 42.726742600346256, 80.71453], [-84.45990697934161, 42.726812800346124, 80.62428], [-84.45990957934163, 42.726848867012734, 80.57867], [-84.4599795793415, 42.72705806701242, 80.30656], [-84.46007257934139, 42.72726146701211, 80.03549], [-84.46012997934127, 42.72737606701196, 79.88121], [-84.46014357934126, 42.727410667011895, 79.83571], [-84.46031157934101, 42.72762966701151, 79.51794], [-84.46049057934073, 42.72780020034463, 79.24558], [-84.46059851267387, 42.72791940034443, 79.06456], [-84.46078291267361, 42.728087000344146, 78.79198], [-84.4608201793402, 42.72811006701079, 78.74666], [-84.46090737934009, 42.728142000344064, 78.65597], [-84.46123457933959, 42.72821006701065, 78.33928], [-84.46132991267274, 42.728224667010636, 78.24858], [-84.46170697933883, 42.728293267010486, 77.88681], [-84.46193017933848, 42.728364067010375, 77.66048], [-84.4620177126717, 42.728395067010354, 77.57003], [-84.4621999126714, 42.72844606701028, 77.38849], [-84.46237651267114, 42.72850560034351, 77.20762], [-84.46256271267083, 42.728548467010114, 77.02592], [-84.46269931267062, 42.728586400343374, 76.88995], [-84.46302397933681, 42.728664200343246, 76.57195], [-84.4631585793366, 42.72870540034319, 76.4362], [-84.4633791126696, 42.72878000034308, 76.2102], [-84.46351311266938, 42.72882206700967, 76.07454], [-84.46379131266895, 42.728887600342944, 75.8025], [-84.46388171266881, 42.728914067009555, 75.7119], [-84.46407231266852, 42.728943200342826, 75.53057], [-84.464168979335, 42.728951200342806, 75.43996], [-84.46430997933481, 42.72897940034278, 75.30387], [-84.46435557933472, 42.72899180034278, 75.2586], [-84.46453137933446, 42.729054067009315, 75.07694], [-84.46523117933339, 42.729309267008944, 74.34975], [-84.46573211266593, 42.72944766700874, 73.85146], [-84.46619697933187, 42.729555667008526, 73.39746], [-84.4663861126649, 42.7295914003418, 73.21559], [-84.46662917933122, 42.72960706700849, 72.98831], [-84.46672577933106, 42.729597400341845, 72.8975], [-84.46706071266385, 42.72955160034189, 72.58018], [-84.4672029793303, 42.72952646700861, 72.44391], [-84.46738491266336, 42.72947606700865, 72.26288], [-84.46742817932994, 42.72945940034202, 72.21741], [-84.4676963126629, 42.72937460034217, 71.94565], [-84.46774197932945, 42.72936266700884, 71.90052], [-84.46783017932933, 42.72933240034223, 71.80991], [-84.46795777932914, 42.729280200342316, 71.67399], [-84.46799591266239, 42.729257667008994, 71.62847], [-84.46820977932873, 42.72911026700922, 71.35582], [-84.46837891266182, 42.72898120034279, 71.12914], [-84.46882431266113, 42.728762267009756, 70.63055], [-84.4690233126608, 42.72865960034329, 70.40434], [-84.46918597932722, 42.7285802003434, 70.22264], [-84.4696423793265, 42.7283712670104, 69.72227], [-84.46991571265943, 42.72829346701053, 69.44935], [-84.47001211265928, 42.728282600343846, 69.35851], [-84.47025471265891, 42.72826980034387, 69.13194], [-84.47040071265866, 42.72827340034388, 68.99586], [-84.470835912658, 42.72830600034382, 68.58837], [-84.47122591265742, 42.728308267010505, 68.22506], [-84.47146817932367, 42.728290067010505, 67.99821], [-84.47213797932267, 42.72819180034401, 67.36204], [-84.47279511265498, 42.72805606701087, 66.72635], [-84.47335417932078, 42.72792946701105, 66.18157], [-84.47373857932018, 42.727879667011166, 65.818], [-84.47388011265326, 42.72785200034451, 65.68161], [-84.4742171793194, 42.72781160034458, 65.36349], [-84.47435911265251, 42.72778540034466, 65.2272], [-84.47496557931828, 42.727646600344826, 64.63568], [-84.47510731265135, 42.72761946701155, 64.49927], [-84.47538131265094, 42.72758186701162, 64.23965], [-84.47547631265081, 42.727566200344995, 64.14897], [-84.47571751265042, 42.727538867011674, 63.92164], [-84.47581437931694, 42.72753106701168, 63.83087], [-84.47620257931635, 42.72750580034506, 63.46784], [-84.4768333126487, 42.727458467011786, 62.87724], [-84.4769793126485, 42.727455800345126, 62.7412], [-84.47741831264779, 42.72746046701178, 62.3322], [-84.4774657793144, 42.727468667011806, 62.28679], [-84.47779317931389, 42.72753820034501, 61.96941], [-84.47812837931338, 42.72758780034496, 61.65093], [-84.47822131264655, 42.727608667011566, 61.56044], [-84.47840997931291, 42.72764386701152, 61.37915], [-84.47855037931271, 42.727673600344815, 61.24308], [-84.479002779312, 42.72780766701129, 60.78891], [-84.47938797931141, 42.727934400344395, 60.39601], [-84.47991911264393, 42.728114800344144, 59.85131], [-84.48013871264357, 42.72819240034403, 59.62448], [-84.48091551264235, 42.72848920034352, 58.80956], [-84.48133291264173, 42.728672467009915, 58.35707], [-84.48280771263944, 42.72943886700875, 56.67659], [-84.4828885126393, 42.72947886700865, 56.58594], [-84.48297271263914, 42.72951446700864, 56.49554], [-84.48301237930576, 42.72953546700859, 56.45005], [-84.48326791263872, 42.72963906700841, 56.17844], [-84.48368491263807, 42.72982380034148, 55.72533], [-84.48372851263798, 42.72983966700809, 55.68004], [-84.48386437930446, 42.72987940034136, 55.54389], [-84.48425917930382, 42.730020400341175, 55.13529], [-84.48464471263657, 42.73017260034095, 54.72797], [-84.48468557930318, 42.73019180034089, 54.68284], [-84.48476377930308, 42.730234400340805, 54.59228], [-84.48502097930265, 42.73039900034058, 54.2751], [-84.48513537930245, 42.730465867007126, 54.13913], [-84.48565037930166, 42.730793467006606, 53.50566], [-84.48568917930163, 42.73081546700661, 53.46008], [-84.48579911263477, 42.73088600033981, 53.32436], [-84.48586777930132, 42.73093620033973, 53.2343], [-84.4861917126342, 42.73115226700605, 52.82749], [-84.48641137930048, 42.73129406700588, 52.55558], [-84.48671151263335, 42.73147746700556, 52.19254], [-84.48678797929989, 42.731522200338816, 52.10163], [-84.48694497929966, 42.73160680033868, 51.92052], [-84.48698297929963, 42.73162946700535, 51.875], [-84.48701717929953, 42.73165466700527, 51.82997], [-84.48732937929907, 42.73197446700482, 51.33231], [-84.48736517929899, 42.73204040033801, 51.24262], [-84.48739317929898, 42.73210840033795, 51.15287], [-84.48741377929895, 42.73214046700451, 51.10806], [-84.4874403792989, 42.732246400337715, 50.97201], [-84.4874461792989, 42.732317200337604, 50.88244], [-84.48745397929889, 42.73260066700385, 50.5244], [-84.4874499126322, 42.73267200033706, 50.43424], [-84.48746691263221, 42.73270526700367, 50.38935], [-84.48758797929867, 42.732899467003335, 50.11942], [-84.4876369126319, 42.73296160033658, 50.02868], [-84.48766537929856, 42.73299020033653, 49.98388], [-84.48769711263185, 42.73301726700316, 49.93869], [-84.48776557929841, 42.73306746700308, 49.84877], [-84.48779537929835, 42.73309560033641, 49.80368], [-84.48783177929829, 42.73311940033636, 49.75837], [-84.4879099792982, 42.73316146700296, 49.66822], [-84.48799251263137, 42.73319966700291, 49.57746], [-84.4882093792977, 42.7332790003361, 49.35198], [-84.48829657929758, 42.733310200336064, 49.2617], [-84.48861337929708, 42.733404000335895, 48.94373], [-84.48897877929653, 42.73350046700244, 48.58223], [-84.48916471262953, 42.73353826700236, 48.40258], [-84.48926151262941, 42.73354666700237, 48.31179], [-84.48955151262896, 42.73356346700234, 48.04084], [-84.48969791262874, 42.73356720033564, 47.90439], [-84.48979451262858, 42.733565467002336, 47.81438], [-84.49013191262804, 42.733545400335686, 47.49909], [-84.49047177929418, 42.7335318003357, 47.18205], [-84.49085731262693, 42.73349626700241, 46.82015], [-84.49100251262672, 42.7334860003358, 46.68428], [-84.49114797929315, 42.7334784003358, 46.54844], [-84.49148891262593, 42.733466800335805, 46.23054], [-84.49192397929193, 42.733430000335886, 45.82264], [-84.49206697929174, 42.73340700033589, 45.68631], [-84.49230291262467, 42.733363000336, 45.45963], [-84.49248511262442, 42.73331380033608, 45.2789], [-84.49295671262365, 42.733225200336165, 44.8256], [-84.4929987126236, 42.733206800336234, 44.78009], [-84.49305577929016, 42.73316046700296, 44.70104], [-84.49311577929006, 42.73298700033655, 44.47497], [-84.49312337929007, 42.73295146700326, 44.42954], [-84.49310031262343, 42.73284526700343, 44.29372], [-84.49303691262355, 42.732672067003705, 44.06717], [-84.49284911262384, 42.73242066700408, 43.70469], [-84.49281771262389, 42.73239326700417, 43.65938], [-84.49279091262395, 42.73236326700419, 43.61401], [-84.49255571262427, 42.732090600337926, 43.2059], [-84.49248031262442, 42.73199826700477, 43.06978], [-84.49239491262455, 42.731869600338314, 42.88887], [-84.49232291262467, 42.73173686700517, 42.70833], [-84.49219891262487, 42.731543067005475, 42.43771], [-84.49212337929163, 42.73141220033898, 42.25809], [-84.492101112625, 42.73138020033906, 42.21267], [-84.49204877929174, 42.731243467005925, 42.03325], [-84.4920035792918, 42.73110440033946, 41.85266], [-84.4919847126252, 42.73103386700626, 41.76187], [-84.49193871262526, 42.73082166700658, 41.4905], [-84.49193257929193, 42.73075080034005, 41.40082], [-84.49193611262524, 42.73071520034006, 41.35575], [-84.49193097929191, 42.730644067006835, 41.26579], [-84.49193191262526, 42.73053680034036, 41.13033], [-84.49194491262523, 42.73035800034063, 40.90422], [-84.4919521126252, 42.73032226700735, 40.8586], [-84.49196851262519, 42.730288400340726, 40.81318], [-84.49199111262516, 42.73025646700745, 40.76769], [-84.49201871262511, 42.730226667007514, 40.72211], [-84.49212051262498, 42.73014980034094, 40.58641], [-84.49227071262476, 42.73005820034109, 40.40488], [-84.49234957929127, 42.73001586700781, 40.31402], [-84.49247331262444, 42.72995826700793, 40.17773], [-84.49265257929079, 42.72990206700803, 39.99628], [-84.49274397929065, 42.729877400341366, 39.90562], [-84.49297771262366, 42.72982680034147, 39.67871], [-84.49331817928976, 42.72980506700816, 39.36038], [-84.49336651262303, 42.72979906700817, 39.31472], [-84.49380391262235, 42.729791600341514, 38.90716], [-84.4941065126219, 42.72976260034159, 38.62292], [-84.49423877928837, 42.72971686700828, 38.48685], [-84.49428591262159, 42.7297078670083, 38.44149], [-84.49467017928765, 42.72967226700837, 38.08073], [-84.49476577928755, 42.72966626700838, 37.99135], [-84.49510357928699, 42.729663867008355, 37.67667], [-84.49519951262016, 42.729658600341736, 37.58706], [-84.49553877928633, 42.72966286700836, 37.27098], [-84.49563511261954, 42.72966006700841, 37.18118], [-84.49660257928468, 42.72966800034169, 36.27991], [-84.49737811261679, 42.72968986700835, 35.55696], [-84.49747451261663, 42.72968946700837, 35.46716], [-84.49766891261635, 42.729700800341675, 35.2855], [-84.49771711261627, 42.729706667008315, 35.24], [-84.49780937928278, 42.72972680034161, 35.15037], [-84.49798917928251, 42.72977780034154, 34.97093], [-84.49803011261577, 42.72979680034149, 34.92587], [-84.49810411261569, 42.729842667008086, 34.83584], [-84.49823977928213, 42.729945600341296, 34.65455], [-84.49832377928203, 42.72998066700791, 34.56464], [-84.4984189126152, 42.72999620034119, 34.47387], [-84.49856377928165, 42.730007067007875, 34.33823], [-84.49870937928142, 42.729998400341174, 34.20216], [-84.49890097928113, 42.729975467007876, 34.02135], [-84.49936297928036, 42.729859467008055, 33.56674], [-84.4995455792801, 42.72980906700815, 33.38513], [-84.49977717927976, 42.729752667008256, 33.15794], [-84.50004671261269, 42.729670600341706, 32.88631], [-84.50008897927927, 42.729653267008416, 32.84127], [-84.50013097927922, 42.729634800341785, 32.79572], [-84.50024097927906, 42.72956506700854, 32.66061], [-84.50034371261222, 42.729490400342, 32.52627], [-84.50043997927872, 42.72941086700877, 32.39162], [-84.50046211261201, 42.729379000342135, 32.34641], [-84.50056897927851, 42.72925926700901, 32.16538], [-84.50062591261178, 42.72920180034242, 32.07549], [-84.50089277927799, 42.72899440034274, 31.71439], [-84.50096297927792, 42.72894486700949, 31.6239], [-84.50108671261103, 42.72888780034293, 31.48796], [-84.50121937927753, 42.72884280034299, 31.35194], [-84.5014059792772, 42.728801267009715, 31.17037], [-84.50159677927695, 42.72877386700975, 30.9893], [-84.5016911126101, 42.728756600343104, 30.89876], [-84.50173931261003, 42.72875200034315, 30.85349], [-84.50188331260983, 42.72876946700978, 30.71755], [-84.5020289126096, 42.72877560034311, 30.58169], [-84.50207611260953, 42.72878186700973, 30.53702], [-84.50230637927581, 42.72883140034298, 30.31358], [-84.50249251260885, 42.72886720034296, 30.1344], [-84.50262837927534, 42.72890266700955, 30.00014], [-84.5027101792752, 42.72894066700951, 29.91009], [-84.5027475126085, 42.72896300034279, 29.86532], [-84.50282877927503, 42.72900166700941, 29.77523], [-84.50294431260818, 42.7290658670093, 29.64049], [-84.503048912608, 42.72913980034252, 29.50554], [-84.50309411260793, 42.72920280034242, 29.41553], [-84.50312117927456, 42.72923246700907, 29.37037], [-84.50316071260784, 42.72933606700889, 29.23446], [-84.50316357927449, 42.72937186700881, 29.18917], [-84.50319237927442, 42.72951286700862, 29.00911], [-84.50320477927443, 42.72965640034175, 28.82748], [-84.50318851260778, 42.72972740034163, 28.73655], [-84.50313417927453, 42.72990266700799, 28.50951], [-84.50315551260786, 42.72993360034127, 28.46568], [-84.50319497927444, 42.729954400341285, 28.4205], [-84.50337677927416, 42.730005667007845, 28.23919], [-84.50342411260743, 42.73001520034114, 28.19349], [-84.50356837927387, 42.73000540034121, 28.05853], [-84.50370397927367, 42.72997106700791, 27.92499], [-84.50374837927359, 42.729957000341244, 27.87997], [-84.50378597927352, 42.72993480034131, 27.83511], [-84.50385331260674, 42.72988420034136, 27.74557], [-84.50388317927337, 42.729856267008074, 27.70065], [-84.50398257927321, 42.72973266700825, 27.51917], [-84.50401097927318, 42.72970326700835, 27.47358], [-84.50407651260639, 42.72965146700841, 27.3841], [-84.50411311260638, 42.72962820034178, 27.3391], [-84.50417731260626, 42.729574067008514, 27.24827], [-84.50432571260603, 42.729481400342024, 27.06715], [-84.50441857927257, 42.729459800342056, 26.97645], [-84.50456317927234, 42.729445800342035, 26.84059], [-84.5046117126056, 42.72944440034206, 26.79535], [-84.50475771260534, 42.72944466700875, 26.65934], [-84.50480391260527, 42.72945646700873, 26.6138], [-84.50493417927174, 42.729504067008634, 26.47838], [-84.50497491260501, 42.72952366700861, 26.43308], [-84.50504991260487, 42.729569800341835, 26.34211], [-84.50512331260478, 42.72961700034176, 26.25141], [-84.50518851260466, 42.729670067008385, 26.16097], [-84.50521331260467, 42.72970126700835, 26.11529], [-84.50530357927119, 42.729868600341376, 25.88787], [-84.50533057927112, 42.72989600034134, 25.84509], [-84.50537777927104, 42.72990540034135, 25.79955], [-84.50542617927096, 42.72990980034132, 25.75412], [-84.50562077927066, 42.72992086700799, 25.57231], [-84.50568917927058, 42.72991900034134, 25.50855], [-84.5057297792705, 42.729899000341334, 25.46307], [-84.50588177927028, 42.729809000341504, 25.28151], [-84.50591571260355, 42.72978320034156, 25.23612], [-84.50594137927015, 42.729752600341556, 25.19068], [-84.50598291260343, 42.729687400341675, 25.0997], [-84.5059919792701, 42.729652667008395, 25.05504], [-84.50599111260345, 42.72950940034195, 24.87411], [-84.50600211260343, 42.729474467008686, 24.82883], [-84.50611071260323, 42.729200800342426, 24.46873], [-84.50614117926989, 42.72913280034254, 24.37829], [-84.5061525126032, 42.72909806700926, 24.33318], [-84.5061829792698, 42.729030000342675, 24.24266], [-84.50628911260299, 42.72882866700968, 23.96986], [-84.50633251260291, 42.728764600343084, 23.87942], [-84.50658511260252, 42.728499600343525, 23.47033], [-84.50661551260248, 42.72847166701024, 23.42509], [-84.50667857926902, 42.72841746701033, 23.33489], [-84.50700497926852, 42.72820166701064, 22.92659], [-84.50716151260161, 42.728116600344094, 22.74547], [-84.50720337926822, 42.72809860034414, 22.70033], [-84.50756477926768, 42.727918600344424, 22.29412], [-84.5076835126008, 42.72785626701119, 22.15836], [-84.50779871260062, 42.7277902003446, 22.02243], [-84.50817251260003, 42.72755860034499, 21.56769], [-84.50853431259947, 42.72731806701205, 21.11397], [-84.50922017926507, 42.726761267012876, 20.1639], [-84.50947937926469, 42.726588067013154, 19.83811]]]}}, {"attributes": {"gnis_name": null, "lengthkm": 0.035, "permanent_identifier": "152093660", "reachcode": "04050004002359", "meters from flowline": 559.1053858081182, "nhdhr measure": 20.54223}, "geometry": {"hasM": true, "paths": [[[-84.50819331260004, 42.72549706701483, 26.20677], [-84.50850391259951, 42.725713200347855, 20.54223]]]}}, {"attributes": {"gnis_name": null, "lengthkm": 0.455, "permanent_identifier": "152091798", "reachcode": "04050004002359", "meters from flowline": 585.7335037669081, "nhdhr measure": 26.20677}, "geometry": {"hasM": true, "paths": [[[-84.50368877927366, 42.72321920035171, 100], [-84.50420931260618, 42.72326600035166, 93.04296], [-84.50468451260548, 42.72338400035147, 86.39091], [-84.50575791260383, 42.72402186701714, 68.10472], [-84.50672057926897, 42.724554800349665, 52.13708], [-84.50734897926799, 42.7250254003489, 40.25742], [-84.50819331260004, 42.72549706701483, 26.20677]]]}}, {"attributes": {"gnis_name": null, "lengthkm": 0.116, "permanent_identifier": "152091797", "reachcode": "04050004002359", "meters from flowline": 566.7249346614303, "nhdhr measure": 1.83965}, "geometry": {"hasM": true, "paths": [[[-84.50850391259951, 42.725713200347855, 20.54223], [-84.50876877926578, 42.726060867013985, 13.37016], [-84.50938251259817, 42.72651486701329, 1.83965]]]}}]} \ No newline at end of file diff --git a/tests/test-data.csv b/tests/test-data.csv new file mode 100644 index 0000000000000000000000000000000000000000..bdd0f5c924ca51ba8223c7e03fa9e8122a580c31 --- /dev/null +++ b/tests/test-data.csv @@ -0,0 +1,12 @@ +id,stream,x,y +1,,-105.1857,39.7308 +2,clear ck,-105.517566,39.740534 +3,Michigan,-105.8521,39.3814 +4,at work,-105.12741,39.7146 +11,,-105.193,39.735 +123,Grand River,-84.54752,42.72484 +124,Red Cedar River,-84.52808,42.71433 +125,The Red Cedar River,-84.48215,42.72898 +222,Amazon River,-60.724,-3.31 +223,Canada,-102.5521,58.06159 +224,Fife Lake,-85.35371,44.5636 diff --git a/tests/test_nhd_hr.py b/tests/test_nhd_hr.py new file mode 100644 index 0000000000000000000000000000000000000000..7730eb4dc6edeb28905c981e6efbda41ed15e9b2 --- /dev/null +++ b/tests/test_nhd_hr.py @@ -0,0 +1,111 @@ +# !/usr/bin/env python + +"""Tests for `utils` package.""" + +import pytest +from hydrolink import nhd_hr +import requests +import validators + +# Create test point that can be used for tests +ident, good_lat, good_lon = 2, 42.7284, -84.5026 +test_point = nhd_hr.HighResPoint(ident, good_lat, good_lon) + + +def test_build_nhd_query(): + """Validate structure of url.""" + # test hem_flowline and hem_waterbody + test_point.build_nhd_query(query=['hem_flowline', 'hem_waterbody']) + assert validators.url(test_point.flowline_query) + assert validators.url(test_point.waterbody_query) + + # test hem_waterbody_flowline + test_point.hydrolink_waterbody = {} + test_point.hydrolink_waterbody['nhdhr waterbody permanent identifier'] = '88894713' + test_point.build_nhd_query(query=['hem_waterbody_flowline']) + assert validators.url(test_point.flowline_query) + + +def test_input_buffer(): + """Tests to ensure buffer logic is working properly.""" + good_buffers = [0, 10, 2000] + for buffer in good_buffers: + # Good buffer, good lat, lon should have status=1 + buffer_test = nhd_hr.HighResPoint(ident, good_lat, good_lon, buffer_m=buffer) + assert buffer_test.status == 1 and buffer_test.message == '' + + # buffer greater than 2000 should set status to 0 (fail) + bad_buffers = [2001, 3000] + for buffer in bad_buffers: + buffer_test = nhd_hr.HighResPoint(ident, good_lat, good_lon, buffer_m=buffer) + m = 'Maximum buffer is 2000 meters, reduce buffer.' + assert buffer_test.status == 0 and buffer_test.message == m + + +def test_input_coordinates(): + """Make sure code handles lat or lon falling outside U.S. bounds.""" + bad_lat = 17.4 + bad_lon = -63.9 + + good_buffers = [0, 10, 2000] + for buffer in good_buffers: + # Good buffer, good lat, lon should have status=1 + us_bounds_test = nhd_hr.HighResPoint(ident, good_lat, good_lon, buffer_m=buffer) + assert us_bounds_test.status == 1 and us_bounds_test.message == '' + m = f'Coordinates for id: {ident} are outside of the bounding box of the United States.' + us_bounds_test = nhd_hr.HighResPoint(ident, bad_lat, good_lon, buffer_m=buffer) + assert us_bounds_test.status == 0 and us_bounds_test.message == m + # bad lon should set status to 0 (fail) + us_bounds_test = nhd_hr.HighResPoint(ident, good_lat, bad_lon, buffer_m=buffer) + assert us_bounds_test.status == 0 and us_bounds_test.message == m + + +@pytest.mark.skip(reason="this tests similarities of addressing to NDH services, not always required") +def test_addressing(): + """Test against hem point events SOE. + + Use HEM SOE extension HEMPointEvents to pass the snap location on best reach to return reach measure. + Assert that returned measure is the same as what I am getting when not using this service. + Documentation of HEMPointEvents is found https://edits.nationalmap.gov/hem-soe-docs/soe-reference/hem-point-events.html + + """ + list_reachcodes = [{'gnis_name': None, + 'lengthkm': 0.035, + 'permanent_identifier': '152093660', + 'reachcode': '04050004002359', + 'meters from flowline': 559.1053858081182, + 'nhdhr measure': 20.54223 + }, + {'gnis_name': 'Red Cedar River', + 'lengthkm': 7.032, + 'permanent_identifier': '152093413', + 'reachcode': '04050004000126', + 'meters from flowline': 52.94060499992746, + 'nhdhr measure': 90.32618961126374 + }] + + lon = -84.5026 + lat = 42.7284 + + # find closest flowline for each reachcode, only test those + for reach in list_reachcodes: + reachcode = reach['reachcode'] + # min_meters = reach['meters from flowline'] + nhdhr_meas = reach['nhdhr measure'] + + hem_get_hr_xy = 'https://edits.nationalmap.gov/arcgis/rest/services/HEM/NHDHigh/MapServer/exts/Vwe_HEM_Soe/HEMPointEvents' + xy = '{"x":' + str(lon) + ',"y":' + str(lat) + ', "spatialReference": {"wkid":4269}}' + + payload = {"point": xy, + "reachcode": reachcode, + "searchToleranceMeters": 1500, + "outWKID": 4269, + "f": "json" + } + + hr_xy = requests.post(hem_get_hr_xy, params=payload, verify=False).json() + + if hr_xy['resultStatus'] == 'success' and hr_xy['features']: + # hl_reach_meas = hr_xy['features'][0]['attributes'] + meas = hr_xy['features'][0]['attributes']['MEASURE'] + assert nhdhr_meas == meas diff --git a/tests/test_nhd_mr.py b/tests/test_nhd_mr.py new file mode 100644 index 0000000000000000000000000000000000000000..7d2b03a510b5352faa7539eccbae8089582fd656 --- /dev/null +++ b/tests/test_nhd_mr.py @@ -0,0 +1,61 @@ +# !/usr/bin/env python + +"""Tests for `nhd_mr` package.""" + +import pytest +from hydrolink import nhd_mr +import validators + +# Create test point that can be used for tests +ident, good_lat, good_lon = 2, 42.7284, -84.5026 +test_point = nhd_mr.MedResPoint(ident, good_lat, good_lon) + + +def test_build_nhd_query(): + """Validate structure of url.""" + # test hem_flowline and waterbody + test_point.build_nhd_query(query=['network_flow', 'waterbody','nonnetwork_flow']) + assert validators.url(test_point.flowline_query) + assert validators.url(test_point.nonnetwork_flowline_query) + assert validators.url(test_point.waterbody_query) + + # test hem_waterbody_flowline + test_point.hydrolink_waterbody = {} + test_point.hydrolink_waterbody['nhdplusv2 waterbody permanent identifier'] = '88894713' + test_point.build_nhd_query(query=['waterbody_flowline']) + assert validators.url(test_point.flowline_query) + + +def test_input_buffer(): + """Tests to ensure buffer logic is working properly.""" + + good_buffers = [0, 10, 2000] + for buffer in good_buffers: + # Good buffer, good lat, lon should have status=1 + buffer_test = nhd_mr.MedResPoint(ident, good_lat, good_lon, buffer_m=buffer) + assert buffer_test.status == 1 and buffer_test.message == '' + + # buffer greater than 2000 should set status to 0 (fail) + bad_buffers = [2001, 3000] + for buffer in bad_buffers: + buffer_test = nhd_mr.MedResPoint(ident, good_lat, good_lon, buffer_m=buffer) + m = 'Maximum buffer is 2000 meters, reduce buffer.' + assert buffer_test.status == 0 and buffer_test.message == m + + +def test_input_coordinates(): + """Make sure code handles lat or lon falling outside U.S. bounds.""" + bad_lat = 17.4 + bad_lon = -63.9 + + good_buffers = [0, 10, 2000] + for buffer in good_buffers: + # Good buffer, good lat, lon should have status=1 + us_bounds_test = nhd_mr.MedResPoint(ident, good_lat, good_lon, buffer_m=buffer) + assert us_bounds_test.status == 1 and us_bounds_test.message == '' + m = f'Coordinates for id: {ident} are outside of the bounding box of the United States.' + us_bounds_test = nhd_mr.MedResPoint(ident, bad_lat, good_lon, buffer_m=buffer) + assert us_bounds_test.status == 0 and us_bounds_test.message == m + # bad lon should set status to 0 (fail) + us_bounds_test = nhd_mr.MedResPoint(ident, good_lat, bad_lon, buffer_m=buffer) + assert us_bounds_test.status == 0 and us_bounds_test.message == m diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..74aeac3d79b704ed457804784d79f68e608ae319 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +"""Tests for `utils` package.""" + +import pytest +from shapely.geometry import Point +from hydrolink import utils +import json + + +def test_crs_to_nad83(): + """Placeholder for test if conversions are occuring correctly. + + test if conversions are occuring correctly + 4326 to 4269 + 3857 to 4269 + 5070 to 4269 + """ + pass + + +def test_clean_water_name(): + """Test function utils.clean_water_name. Ensure abbreviations are being correctly addressed.""" + assert utils.clean_water_name('Grand stream (north)') == 'grand stream' + assert utils.clean_water_name('(north)Grand stream ') == 'grand stream' + assert utils.clean_water_name('Grand st.') == 'grand stream' + assert utils.clean_water_name('Grand St') == 'grand stream' + assert utils.clean_water_name('grand str ') == 'grand stream' + assert utils.clean_water_name('grand str.') == 'grand stream' + assert utils.clean_water_name('trib. Grand Rv.') == 'tributary grand river' + assert utils.clean_water_name('trib Grand Rv ') == 'tributary grand river' + assert utils.clean_water_name('Grand Rv. unt') == 'grand river unnamed tributary' + assert utils.clean_water_name('Grand River trib)') == 'grand river tributary' + assert utils.clean_water_name('trib. Grand River') == 'tributary grand river' + assert utils.clean_water_name('Grand River') == 'grand river' + assert utils.clean_water_name('br. Grand River') == 'branch grand river' + assert utils.clean_water_name('Grand River br ') == 'grand river branch' + assert utils.clean_water_name('Grand Ck.') == 'grand creek' + assert utils.clean_water_name('Grand ck') == 'grand creek' + assert utils.clean_water_name('unt Grand Rv.') == 'unnamed tributary grand river' + + +def test_gnis_name_similarity(): + """Test function utils.gnis_name_similarity. Ensure logic and match ratio correctly assigned.""" + gnis_name = 'Red Cedar River' + test_scenarios = [{'source_water_name': 'red cedar river', + 'flowline name similarity message': 'exact water name match', + 'match_ratio': 1.0 + }, + {'source_water_name': 'Red Cedar River', + 'flowline name similarity message': 'exact water name match', + 'match_ratio': 1.0 + }, + {'source_water_name': ' ', + 'flowline name similarity message': 'no source water name provided', + 'match_ratio': 0 + }, + {'source_water_name': 'red cedar rv.', + 'flowline name similarity message': 'most likely match, based on fuzzy match', + 'match_ratio': 1.0 + }, + {'source_water_name': 'red Cedar', + 'flowline name similarity message': 'most likely match, based on fuzzy match', + 'match_ratio': 0.75 + }] + for test in test_scenarios: + name = test['source_water_name'] + message = test['flowline name similarity message'] + ratio = test['match_ratio'] + name_similarity = utils.gnis_name_similarity(gnis_name, name) + assert message == name_similarity['flowline name similarity message'] + if test['match_ratio'] is not None: + assert ratio == name_similarity['flowline name similarity'] + else: + assert 'flowline name similarity' not in name_similarity + + +def test_build_distance_line(): + """Test build_distance_line function. + + Verify build_distance_line function. + Ensure distance between two points is being measured correctly. + Line lengths were measured in arcmap. Accounts for 1% rounding error. + """ + # Input CRS which all data are transformed too + crs = 'epsg:4269' + + # using shapely create 2 points to allow for measuring of connecting line + point1 = Point(-72.522365, 41.485054) + point2 = Point(-72.529494, 41.464437) + + # test if the build_distance_line measurement is within 1% rounding error of arcmap measurement + arcmap_length_m = 2381.955938 + arcmap_length_m_lt1 = arcmap_length_m - (arcmap_length_m * 0.01) + arcmap_length_m_gt1 = arcmap_length_m + (arcmap_length_m * 0.01) + len = utils.build_distance_line(point1, point2, crs=crs) + assert arcmap_length_m_lt1 <= len <= arcmap_length_m_gt1 + + +def test_build_flowline_details(): + """Test build flowline details. + + Currently tests closest_confluence. + Use known data in JSON format to test function. JSON was built with 1500m buffer + and verified using services in ArcGIS Pro. + Could build this out a bit more. + """ + lon = -84.5026 + lat = 42.7284 + input_point = Point(lon, lat) + + # imports flowlines json stored from live test. this is the input + with open('tests/flowlines_json.json') as f: + flowlines_json = json.load(f) + + flowlines_data = [] + all_flowline_terminal_node_points = [] + for flowline_data in flowlines_json['features']: + source_water_name = flowline_data['attributes']['gnis_name'] + flowline_attributes, terminal_node_points, flowline_geo = utils.build_flowline_details(flowline_data, input_point, 'nhdhr', source_water_name) + flowlines_data.append(flowline_attributes) + all_flowline_terminal_node_points = all_flowline_terminal_node_points + terminal_node_points + + closest_confluence_meters = utils.closest_confluence(all_flowline_terminal_node_points, input_point, flowline_geo) + assert closest_confluence_meters == 595.535732278204 + # assert flowlines_data == flowlines_data_test