From b22e31b3550cddc1f524f9c29b0c5ff45c9964e4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:26:03 +0000 Subject: [PATCH 1/2] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/commitizen-tools/commitizen: v3.6.0 → 3.10.0](https://github.com/commitizen-tools/commitizen/compare/v3.6.0...3.10.0) - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) - https://github.com/charliermarsh/ruff-pre-commit → https://github.com/astral-sh/ruff-pre-commit - [github.com/astral-sh/ruff-pre-commit: v0.0.285 → v0.0.291](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.285...v0.0.291) - [github.com/pre-commit/mirrors-prettier: v3.0.2 → v3.0.3](https://github.com/pre-commit/mirrors-prettier/compare/v3.0.2...v3.0.3) - [github.com/igorshubovych/markdownlint-cli: v0.35.0 → v0.37.0](https://github.com/igorshubovych/markdownlint-cli/compare/v0.35.0...v0.37.0) --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d244b92..efb95aa3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,35 +1,35 @@ repos: # Versioning: Commit messages & changelog - repo: https://github.com/commitizen-tools/commitizen - rev: v3.6.0 + rev: 3.10.0 hooks: - id: commitizen stages: [commit-msg] # Autoformat: Python code - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black # Lint / autoformat: Python code - - repo: https://github.com/charliermarsh/ruff-pre-commit + - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: "v0.0.285" + rev: "v0.0.291" hooks: - id: ruff args: [--exit-non-zero-on-fix] # Autoformat: YAML, JSON, Markdown, etc. - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.2 + rev: v3.0.3 hooks: - id: prettier args: [--ignore-unknown, --no-error-on-unmatched-pattern, "!chart/**"] # Lint: Markdown - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.35.0 + rev: v0.37.0 hooks: - id: markdownlint args: [--fix, --ignore, CHANGELOG.md] From ca6fb9e218e4f141a42fe72efe0438c7e33dc73b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:29:14 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- LICENSE.md | 2 +- README.md | 37 +- docs/about/About.md | 14 +- docs/about/CSVDump.md | 9 +- docs/about/Contribution.md | 4 +- docs/about/FAQ.md | 44 +- docs/about/Troubleshooting.md | 65 +- docs/about/Version-Control.md | 1 + docs/about/_Sidebar.md | 1 - docs/about/basemapper.md | 27 +- docs/about/configuring.md | 1 + docs/about/convert.md | 52 +- docs/about/externaldata.md | 22 +- docs/about/json2osm.md | 18 +- docs/about/make_data_extract.md | 34 +- docs/about/odk2csv.md | 14 +- docs/about/odk2geojson.md | 18 +- docs/about/odk2osm.md | 14 +- docs/about/odk_client.md | 40 +- docs/about/odk_merge.md | 13 +- docs/about/osm2favorites.md | 9 +- docs/about/osmfile.md | 2 - docs/about/programs.md | 7 +- docs/about/xlsforms.md | 24 +- docs/api/CSVDump.md | 7 +- docs/api/OdkCentral.md | 30 +- docs/api/basemapper.md | 13 +- docs/api/convert.md | 6 +- docs/api/filter_data.md | 6 +- docs/api/json2osm.md | 6 +- docs/api/odk2csv.md | 6 +- docs/api/odk2geojson.md | 6 +- docs/api/odk2osm.md | 6 +- docs/api/osmfile.md | 6 +- docs/api/sqlite.md | 6 +- docs/api/yamlfile.md | 7 +- docs/css/extra.css | 7 +- docs/index.md | 1 - osm_fieldwork/CSVDump.py | 182 +- osm_fieldwork/ODKDump.py | 22 +- osm_fieldwork/ODKForm.py | 51 +- osm_fieldwork/ODKInstance.py | 49 +- osm_fieldwork/OdkCentral.py | 585 +- osm_fieldwork/basemapper.py | 150 +- osm_fieldwork/convert.py | 146 +- osm_fieldwork/data_models/buildings.yaml | 30 +- osm_fieldwork/data_models/camping.yaml | 6 +- osm_fieldwork/data_models/category.yaml | 36 +- osm_fieldwork/data_models/cemeteries.yaml | 10 +- osm_fieldwork/data_models/education.yaml | 24 +- osm_fieldwork/data_models/emergency.yaml | 12 +- osm_fieldwork/data_models/health.yaml | 16 +- osm_fieldwork/data_models/highways.yaml | 18 +- osm_fieldwork/data_models/landusage.yaml | 10 +- osm_fieldwork/data_models/nature.yaml | 12 +- osm_fieldwork/data_models/places.yaml | 12 +- osm_fieldwork/data_models/religious.yaml | 12 +- osm_fieldwork/data_models/toilets.yaml | 19 +- osm_fieldwork/data_models/validate.py | 38 +- osm_fieldwork/data_models/wastedisposal.yaml | 28 +- osm_fieldwork/data_models/waterpoints.yaml | 12 +- osm_fieldwork/data_models/waterways.yaml | 11 +- osm_fieldwork/filter_data.py | 155 +- osm_fieldwork/json2osm.py | 203 +- osm_fieldwork/make_data_extract.py | 139 +- osm_fieldwork/models.yaml | 471 +- osm_fieldwork/odk2csv.py | 33 +- osm_fieldwork/odk2geojson.py | 49 +- osm_fieldwork/odk2osm.py | 34 +- osm_fieldwork/odk_client.py | 132 +- osm_fieldwork/odk_merge.py | 309 +- osm_fieldwork/osm2favorities.py | 81 +- osm_fieldwork/osmfile.py | 189 +- osm_fieldwork/sqlite.py | 141 +- osm_fieldwork/xforms.yaml | 1 - osm_fieldwork/xlsforms/__init__.py | 1 - osm_fieldwork/yamlfile.py | 79 +- tests/test_conflation.py | 14 +- tests/test_convert.py | 48 +- tests/test_csv.py | 11 +- tests/test_osm.py | 10 +- tests/test_uriparser.py | 49 +- tests/test_yaml.py | 32 +- tests/testdata/testcamps.json | 12770 ++++++++--------- 84 files changed, 8075 insertions(+), 8952 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 54eb4360..2e54fe6d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -385,7 +385,7 @@ notice like this when it starts in an interactive mode: This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. +under certain conditions; type`show c' for details. The hypothetical commands `show w` and `show c` should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". diff --git a/README.md b/README.md index 7df1f8ff..9c286f8a 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,7 @@ talk from SOTM-US 2022 titled [OSM For Firefighting](https://www.youtube.com/watch?v=qgk9al1rluE). Much of the tech and usage is explained in these [tech briefs](https://www.senecass.com/projects/Mapping/tech/). Currently -these are now part of the backend for the [Field Mapping Tasking Manager]( -https://github.com/hotosm/fmtm/wiki) project at +these are now part of the backend for the [Field Mapping Tasking Manager](https://github.com/hotosm/fmtm/wiki) project at [HOT](https://www.hotosm.org). ## Installation @@ -45,25 +44,30 @@ The config file is uswd to store the credentials to access an ODK Central server. You must have an account on the Central server of course for this to work. That file looks like this: - url=https://foo.org - user=foo@bar.org - passwd=arfood + url=https://foo.org + user=foo@bar.org + passwd=arfood #### Environment Variables **LOG_LEVEL** + > If present, will change the log level. Defaults to DEBUG. **ODK_CENTRAL_URL** + > The URL for an ODKCentral server to connect to. **ODK_CENTRAL_USER** + > The user for ODKCentral. **ODK_CENTRAL_PASSWD** + > The password for ODKCentral. **ODK_CENTRAL_SECURE** + > If set to False, will allow insecure connections to the ODKCentral API. Else defaults to True. ## Using the Container Image @@ -101,7 +105,7 @@ one is suitable for OSM,and is in OSM XML format. The other No converted data should ever be uploaded to OSM without validating the conversion in JOSM. To do efficient conversion from ODK to OSM, it's best to use the XLSForm library as templates, as everything is -designed to work together. +designed to work together. ## basemapper.py @@ -124,7 +128,7 @@ This program makes data extracts from [OpenStreetMap](https://www.openstreetmap.org) data. Multiple input sources are supported, a local postgresql database, the HOT maintained [Underpass](https://galaxy.hotosm.org/) database, or -[Overpass](https://overpass-turbo.eu). +[Overpass](https://overpass-turbo.eu). ## odk2csv.py @@ -156,16 +160,16 @@ Collect. # Best Practices To ensure the quality of your converted data, here are some best -practices to follow: +practices to follow: - Always validate your conversion in JOSM before uploading to OpenStreetMap. - Use the XLSForm library as templates to ensure that your ODK Collect -data is compatible with the conversion process. + data is compatible with the conversion process. - If you're having trouble with the conversion process, try using the -utility programs included with Osm-Fieldwork to troubleshoot common -issues. + utility programs included with Osm-Fieldwork to troubleshoot common + issues. By following these best practices and using the utility programs included with Osm-Fieldwork, you can effectively process data collection @@ -195,25 +199,26 @@ user interface. With XLSForms, you can easily design and test forms on your computer, then deploy them to mobile devices for data collection using ODK Collect or other data collection tools. XLSForms use a simple and structured format, making it easy for you to share and -collaborate on form designs with your team or other organizations. +collaborate on form designs with your team or other organizations. ### Using the XLSForm Library with Osm-Fieldwork + The XLSForms in the XForms directory of the XLSForm Library have been designed to support the HOT data models and have an efficient mapper data flow. These forms also allow for editing of existing OSM data and support the data models, specifying the preferred tag values for each data item with the goal of both tag completeness and tag correctness. -### Here are some examples of how to use the XLSForm Library with Osm-Fieldwork: +### Here are some examples of how to use the XLSForm Library with Osm-Fieldwork - Download an XLSForm from the XForms directory: - wget https://github.com/hotosm/xlsform/raw/master/XForms/buildings.xls + wget - Convert the XForm to OSM XML using CSVDump: - Use the resulting OSM XML file with JOSM or other OSM editors to -validate and edit the data before uploading it to OpenStreetMap. + validate and edit the data before uploading it to OpenStreetMap. ### Conclusion @@ -222,7 +227,7 @@ in humanitarian data collection, as it provides a collection of pre-designed forms that are optimized for efficient mapper data flow and tag completeness/correctness. By using the XLSForm Library with Osm-Fieldwork, you can streamline your data collection process and ensure -the quality of your data. +the quality of your data. Osm-Fieldwork is a powerful tool for processing data collection from OpenDataKit into OpenStreetMap format. By following the best practices diff --git a/docs/about/About.md b/docs/about/About.md index 85784578..cb57f260 100644 --- a/docs/about/About.md +++ b/docs/about/About.md @@ -1,8 +1,11 @@ + ## OSM-Fieldwork Project + Osm_Fieldwork is a project that aims to simplify the process of processing data collected using OpenDataKit into OpenStreetMap format. It consists of several utility programs that automate different parts of the data flow. These include creating satellite imagery basemaps and data extracts from OpenStreetMap so they can be used with ODK Collect. It is maintained by the [Humanitarian OpenStreetMap Team (HOT)](https://www.hotosm.org/) and designed to work with [ODK Collect](https://docs.getodk.org/collect-intro/), an Android app for data collection, and [ODK Central](https://docs.getodk.org/central-intro/), a web-based platform for managing and visualizing data. ## osm_fieldwork + This program converts the data collected from ODK Collect into the proper OpenStreetMap tagging schema. The conversion is controlled by a YAML file, which makes it easy to modify for other projects. The output is an OSM XML formatted file for JOSM. However, it is important to note that no converted data should ever be uploaded to OSM without first validating the conversion in JOSM. To do high-quality conversion from ODK to OSM, it's best to use the XLSForm library as template, as everything is designed to work together. Osm_Fieldwork includes the following utilities: @@ -17,18 +20,21 @@ Osm_Fieldwork includes the following utilities: Osm_Fieldwork also includes support modules, such as convert.py for processing YAML config files and osmfile.py for writing OSM XML output files. ## Installation + To install osm-fieldwork, you can use pip. Here are two options: 1. Directly from the main branch: - `pip install git+https://github.com/hotosm/osm-fieldwork.git` - + `pip install git+https://github.com/hotosm/osm-fieldwork.git` + -OR- 2. Latest on PyPi: - `pip install Osm-Fieldwork` + `pip install Osm-Fieldwork` > Note: installation requires GDAL >3.4 installed on your system. + ## Usage + Each utility has its own command-line interface, with various options and arguments. You can find detailed instructions on how to use each utility by running it with the -h or --help option. For example, to extract OSM data within a boundary polygon from Overpass Turbo, run: @@ -43,7 +49,9 @@ To convert a CSV file from ODK Central to OSM XML format, run: This will generate two output files - one OSM XML of public data, and the other a GeoJson file with all the data. ## Contributing + Osm_Fieldwork is an open-source project, and contributions are always welcome! If you want to contribute, please read the [Contribution Guidelines](https://github.com/hotosm/osm-fieldwork/wiki/Contribution) and [Code of Conduct](https://github.com/hotosm/osm-fieldwork/wiki/Code-of-Conduct) first. ## License + Osm_Fieldwork is released under the [AGPLv3](https://www.gnu.org/licenses/agpl-3.0.en.html). diff --git a/docs/about/CSVDump.md b/docs/about/CSVDump.md index 442297fa..49f1301e 100644 --- a/docs/about/CSVDump.md +++ b/docs/about/CSVDump.md @@ -10,7 +10,7 @@ ODK Central data into a compatible format. -v, --verbose - verbose output -i CSVFILE, --infile CSVFILE - Specifies the path and filename of the input CSV file downloaded from ODK Central. This option is required for the program to run. -### Examples: +### Examples To convert a CSV file named "survey_data.csv" located in the current working directory, the following command can be used: @@ -22,7 +22,7 @@ command can be used: [path]/CSVDump.py -i survey_data.csv -v -### Input Format: +### Input Format CSVDump.py expects an input file in CSV format downloaded from ODK Central. The CSV file should have a header row with column names that @@ -30,7 +30,7 @@ correspond to the survey questions. Each row in the CSV file should contain a response to the survey questions, with each column representing a different question. -### Output Format: +### Output Format The output of CSVDump.py is an OSM XML file that can be used with OpenStreetMap data tools and services. The converted OSM XML file will @@ -39,11 +39,10 @@ metadata associated with the survey. The format of the OSM XML file generated by CSVDump.py is compatible with other OpenStreetMap data tools and services. -### Limitations: +### Limitations - CSVDump.py only supports CSV files downloaded from ODK Central. Other CSV files may not be compatible with the tool. - The tool only supports simple data types such as strings, numbers, and dates. Complex data types such as arrays and nested structures are not supported. - diff --git a/docs/about/Contribution.md b/docs/about/Contribution.md index 8aa801d2..2c5b547c 100644 --- a/docs/about/Contribution.md +++ b/docs/about/Contribution.md @@ -18,11 +18,11 @@ The [issue queue](https://github.com/hotosm/osm_fieldwork/issues) is the best wa Create pull requests (PRs) for changes that you think are needed. We would really appreciate your help! -## Useful Resources for Contribution +## Useful Resources for Contribution + - [Troubleshooting](https://github.com/hotosm/osm-fieldwork/wiki/troubleshooting) - [FAQs](https://github.com/hotosm/osm-fieldwork/wiki/FAQ) - ## :handshake: Thank you Thank you very much in advance for your contributions!! Please ensure you refer to our [Code of Conduct](https://github.com/hotosm/osm-fieldwork/wiki/Code-of-Conduct). diff --git a/docs/about/FAQ.md b/docs/about/FAQ.md index aad4bc17..8b61f155 100644 --- a/docs/about/FAQ.md +++ b/docs/about/FAQ.md @@ -1,6 +1,5 @@ # ❓ Frequently Asked Questions ❓ - **Q:** What is OSM Fieldwork? **A:** OSM Fieldwork is a project to support field data collection using @@ -12,6 +11,7 @@ basemaps for [ODK Collect](https://docs.getodk.org/collect-intro/) and [Osmand](https://osmand.net/). In addition there is a library of [XLSForms](https://xlsform.org/en/) focused on humanitarian data collection. +
**Q:** How do I install OSM Fieldwork ? @@ -19,17 +19,19 @@ collection. **A:** To install osm-fieldwork, you can use pip. Here are two options: 1. Directly from the main branch: - `pip install git+https://github.com/hotosm/osm-fieldwork.git` - + `pip install git+https://github.com/hotosm/osm-fieldwork.git` + -OR- 2. Latest on PyPi: - `pip install Osm-Fieldwork` +`pip install Osm-Fieldwork` +
**Q:** Where can I find the source code and the XLSForm library ? **A:** Check the osm-fieldwork git repo [here](https://github.com/hotosm/osm-fieldwork) +
**Q:** What language is Osm Fieldwork written in ? @@ -40,6 +42,7 @@ collection. [xmltodict](https://pypi.org/project/xmltodict/), [psycopg2](https://pypi.org/project/psycopg/), and [pandas](https://pypi.org/project/pandas/) +
**Q:** What is the XLSForm library ? @@ -51,12 +54,14 @@ other [humanitarian NGOs](https://en.wikipedia.org/wiki/Non-governmental_organization). These are designed for efficient data collection and conversion to OSM XML format to allow for easy and high quality contributions to the map. +
**Q:** Who can contribute to osm-fieldwork? **A:** It is an open-source project, and contributions from developers and technical writers are always welcome. +
**Q:** What kind of contributions can I make ? @@ -68,40 +73,45 @@ and technical writers are always welcome. - Documentation: If you have experience in technical writing, you can contribute by writing documentation, tutorials, or other educational materials. - Testing: If you have experience in software testing, you can contribute by testing the application and reporting bugs or suggesting improvements. +
**Q:** How can I report a bug or suggest a new feature for OSM - Fieldwork ? +Fieldwork ? **A:** You can report bugs or suggest new features by opening an issue - on the [OSM Fieldwork - repository](https://github.com/hotosm/osm-fieldwork/issues) on - GitHub. Be sure to provide as much detail as possible, including - steps to reproduce the bug and any relevant error messages. - For more details visit [Contributions Page](https://github.com/hotosm/osm-fieldwork/wiki/Contribution). +on the [OSM Fieldwork +repository](https://github.com/hotosm/osm-fieldwork/issues) on +GitHub. Be sure to provide as much detail as possible, including +steps to reproduce the bug and any relevant error messages. +For more details visit [Contributions Page](https://github.com/hotosm/osm-fieldwork/wiki/Contribution). +
**Q:** Do I need to have prior experience with XLSForms or python to contribute to OSM Fieldwork ? **A:** While prior experience with the various data formats usd by OSM - Fieldwork is helpful, it is not required to contribute to OSM - Fieldwork. You can start by reviewing the documentation, exploring - the codebase, and contributing to issues labeled as **good first issue**. +Fieldwork is helpful, it is not required to contribute to OSM +Fieldwork. You can start by reviewing the documentation, exploring +the codebase, and contributing to issues labeled as **good first issue**. +
**Q:** How can I get help or support for OSM Fieldwork ? **A:** If you need help or support with XLSForms, you can reach out to the - ODK community on the [ODK Forum](https://forum.getodk.org/). For - questions on OSM Fieldwork you can open an issue on the OSM - Fieldwork repository. +ODK community on the [ODK Forum](https://forum.getodk.org/). For +questions on OSM Fieldwork you can open an issue on the OSM +Fieldwork repository. +
**Q:** What are the benefits of contributing to OSM Fieldwork? **A:** Contributing to OSM Fieldwork allows you to help improve a widely used tools for data collection. +
**Q:** What is the license for OSM Fieldwork ? @@ -110,6 +120,7 @@ used tools for data collection. [AGPLv3](https://www.fsf.org/bulletin/2021/fall/the-fundamentals-of-the-agplv3), because it encourages us to all work together. The XLSForms themselves are under the [CC 4.0](https://creativecommons.org/licenses/by/4.0/) +
**Q:** How can I test my changes to OSM Fieldwork ? @@ -119,6 +130,7 @@ ensure that your changes do not introduce new bugs or break existing functionality. You can run the tests locally on your computer using the command-line interface or by setting up a continuous integration environment on a platform like Travis CI. +
**Q:** Facing addititional problems? diff --git a/docs/about/Troubleshooting.md b/docs/about/Troubleshooting.md index af64201f..70cd5f13 100644 --- a/docs/about/Troubleshooting.md +++ b/docs/about/Troubleshooting.md @@ -14,71 +14,70 @@ Here are some steps to troubleshoot and resolve the issue: If you are using a self-signed certificate, make sure to add it to your system's trusted certificate store. For Ubuntu/Debian users, -follow the steps below: +follow the steps below: In a terminal: sudo apt update && sudo apt install ca-certificates sudo cp cert.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates - If running OSM Fieldwork within the [Field Mapping Tasking Manager](https://github.com/hotosm/fmtm/wiki), -this is handled for you. +this is handled for you. **Q:** Can I disable SSL verification (not recommended) -**A:** If you have tried the above step and still cannot connect to - ODK Central, you can disable SSL verification for the - certificate. However, this is not recommended as it will connect - to ODK Central insecurely. To do this, add the environment - variable **ODK_CENTRAL_SECURE=False** to your system. +**A:** If you have tried the above step and still cannot connect to +ODK Central, you can disable SSL verification for the +certificate. However, this is not recommended as it will connect +to ODK Central insecurely. To do this, add the environment +variable **ODK_CENTRAL_SECURE=False** to your system. ### Additional Troubleshooting Steps -If you are still unable to connect to the ODKCentral server over HTTP: +If you are still unable to connect to the ODKCentral server over HTTP: - Verify that the ODK Central API URL is correct - Make sure that you have entered the correct ODK Central API URL in - your OSM Fieldwork configuration file. You can check the URL by - logging into ODK Central and navigating to the "Site - Configuration" page. + Make sure that you have entered the correct ODK Central API URL in + your OSM Fieldwork configuration file. You can check the URL by + logging into ODK Central and navigating to the "Site + Configuration" page. - Check that the ODK Central server is running - Make sure that the ODK Central server is running and - accessible. You can check the server status by navigating to the - ODK Central API URL in your web browser. + Make sure that the ODK Central server is running and + accessible. You can check the server status by navigating to the + ODK Central API URL in your web browser. - Check that the ODK Central server is reachable from your network - Make sure that your network is not blocking the connection to the - ODK Central server. You can try pinging the server from your - computer to see if there is a network issue. + Make sure that your network is not blocking the connection to the + ODK Central server. You can try pinging the server from your + computer to see if there is a network issue. - Check that your firewall is not blocking the connection - Make sure that your firewall is not blocking the connection to the - ODK Central server. You can try temporarily disabling your - firewall to see if this resolves the issue. + Make sure that your firewall is not blocking the connection to the + ODK Central server. You can try temporarily disabling your + firewall to see if this resolves the issue. - Check the ODK Central server logs - Check the ODK Central server logs to see if there are any error - messages related to the connection. This can help identify the - root cause of the issue. + Check the ODK Central server logs to see if there are any error + messages related to the connection. This can help identify the + root cause of the issue. - Try using a different web browser - If you are having trouble connecting to ODK Central through a web - browser, try using a different browser to see if the issue - persists. It is possible that the issue is related to the browser - or its settings. + If you are having trouble connecting to ODK Central through a web + browser, try using a different browser to see if the issue + persists. It is possible that the issue is related to the browser + or its settings. - Update OSM Fieldwork and ODK Central to the latest version - Make sure that you are using the latest version of OSM Fieldwork - and ODK Central. Check the OSM Fieldwork and ODK Central release - notes to see if any updates address the issue you are - experiencing. + Make sure that you are using the latest version of OSM Fieldwork + and ODK Central. Check the OSM Fieldwork and ODK Central release + notes to see if any updates address the issue you are + experiencing. diff --git a/docs/about/Version-Control.md b/docs/about/Version-Control.md index 6d94ed21..63a51626 100644 --- a/docs/about/Version-Control.md +++ b/docs/about/Version-Control.md @@ -43,6 +43,7 @@ git push ``` This will: + - Update the SemVer version number in locations specific in `pyproject.toml`, throughout the codebase. - If a `feat` commit is included, the version is bumped by a minor increment (0.x.0), if only `fix` is included a patch will be used (0.0.x). - Automatically update CHANGELOG.md with all changes since the last version. diff --git a/docs/about/_Sidebar.md b/docs/about/_Sidebar.md index f9785363..86249e0b 100644 --- a/docs/about/_Sidebar.md +++ b/docs/about/_Sidebar.md @@ -41,4 +41,3 @@ [External Data](https://github.com/hotosm/osm-fieldwork/wiki/externaldata) [XLS Forms](https://github.com/hotosm/osm-fieldwork/wiki/xlsforms) - diff --git a/docs/about/basemapper.md b/docs/about/basemapper.md index 14c8dab2..44539ced 100644 --- a/docs/about/basemapper.md +++ b/docs/about/basemapper.md @@ -10,7 +10,7 @@ primary formats: - sqlitedb, supported only by Osmand Both of these use formats use underlying -[sqlite3](https://www.sqlite.org/index.html), with similar database +[sqlite3](https://www.sqlite.org/index.html), with similar database schemas. The schema are a simple XYZ that stores a png or jpeg image. When the entire planet is chopped into squares, there is a relation between which map tile contains the GPS coordinates you @@ -21,7 +21,7 @@ Basemapper does not store anything in memory, all processing is done as a stream so large areas can be downloaded. Time to go buy a really large hard drive. You can also use this map tile cache for any program that supports a TMS data source like -[JOSM](https://josm.openstreetmap.de/). Luckily once downloaded, +[JOSM](https://josm.openstreetmap.de/). Luckily once downloaded, you don't have to update the map tile cache very often, but it's also easy to do so when you need to. When I expect to be working offline, I commonly download a larger area, and then in the field produce the @@ -50,17 +50,17 @@ detail took me a while to figure out, it isn't documented anywhere. ### mbtiles - CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob); - CREATE INDEX tiles_idx on tiles (zoom_level, tile_column, tile_row); - CREATE TABLE metadata (name text, value text); - CREATE UNIQUE INDEX metadata_idx ON metadata (name); + CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob); + CREATE INDEX tiles_idx on tiles (zoom_level, tile_column, tile_row); + CREATE TABLE metadata (name text, value text); + CREATE UNIQUE INDEX metadata_idx ON metadata (name); ### sqlitedb - CREATE TABLE tiles (x int, y int, z int, s int, image blob, PRIMARY KEY (x,y,z,s)); - CREATE INDEX IND on tiles (x,y,z,s); - CREATE TABLE info (maxzoom Int, minzoom Int); - CREATE TABLE android_metadata (en_US); + CREATE TABLE tiles (x int, y int, z int, s int, image blob, PRIMARY KEY (x,y,z,s)); + CREATE INDEX IND on tiles (x,y,z,s); + CREATE TABLE info (maxzoom Int, minzoom Int); + CREATE TABLE android_metadata (en_US); # Usage @@ -68,7 +68,7 @@ The **basemapper.py** script is run from the command line when running standalone, or the class can be imported into python programs. The [Field Mapping Tasking Manager](https://github.com/hotosm/fmtm/wiki) uses this as part of a -(FastAPI])https://fastapi.tiangolo.com/) backend for the website. +(FastAPI])) backend for the website. The first time you run basemapper.py, it'll start downloading map tiles, which may take a long time. Often the upstream source is @@ -99,11 +99,10 @@ used to select the output format. The boundary file, if specified, must be in If in BBOX string format, it must be comma separated: "minX,minY,maxX,maxY". - ## Imagery Sources - ESRI - Environmental Systems Research Institute -- Bing - Microsoft Bing imagery +- Bing - Microsoft Bing imagery - Topo - USGS topographical maps (US only) - OAM - OpenAerialMap @@ -117,7 +116,7 @@ lets JOSM or QGIS use them when working offline. ### **Example 1:** Generate a basemap for Osmand using -[ERSI](https://www.esri.com/en-us/home) imagery, for an area +[ERSI](https://www.esri.com/en-us/home) imagery, for an area specified by a geojson bounding box, and supporting zoom levels 12 through 19. diff --git a/docs/about/configuring.md b/docs/about/configuring.md index 2b6f9e96..fbaae9aa 100644 --- a/docs/about/configuring.md +++ b/docs/about/configuring.md @@ -26,6 +26,7 @@ to the organizers. Anything in the _convert_ section is the real control of the conversion process. ### Here is an example of a configuration file with explanations of + its different sections and options expained in detail: #ignore section diff --git a/docs/about/convert.md b/docs/about/convert.md index dc2c659b..0b129b8b 100644 --- a/docs/about/convert.md +++ b/docs/about/convert.md @@ -25,28 +25,19 @@ appropriate tag and value for OSM. The final one is where a singe survey question creates multiple tahg and value pairs, deliminated by a comma. Each of the pairs is handled as a separate tag and value in OSM. - - convert: - - latitude: lat - - longitude: lon - - altitude: ele - - cemetery_services: - - cemetery: amenity=grave_yard - - cremation: amenity=crematorium - - amenity: - - coffee: amenity=cafe,cuisine=coffee_shop - ... +convert: - latitude: lat - longitude: lon - altitude: ele - cemetery_services: - cemetery: amenity=grave_yard - cremation: amenity=crematorium - amenity: - coffee: amenity=cafe,cuisine=coffee_shop +... ### private Not all collected data is suitable for OSM. This may include data that -has no equivalant tag in OSM, or personal data. +has no equivalant tag in OSM, or personal data. - private: - - income - - age - - gender - - education + private: + - income + - age + - gender + - education ### ignore @@ -54,16 +45,15 @@ ODK supports many tags useful only internally. These go into the ignore section of the config file. Any tag in this section gets removed from from all output files. An example would be this: - ignore: - - attachmentsexpected - - attachmentspresent - - reviewstate - - edits - - gps_type - - accuracy - - deviceid - ... - + ignore: + - attachmentsexpected + - attachmentspresent + - reviewstate + - edits + - gps_type + - accuracy + - deviceid + ... ### multiple @@ -73,7 +63,7 @@ assumes one answer per question, this specifies the questions with multiple answers since they have to be processed seperately. The normal conversion process is applied to these too. - multiple: - - healthcare - - amenity_type - - specialty + multiple: + - healthcare + - amenity_type + - specialty diff --git a/docs/about/externaldata.md b/docs/about/externaldata.md index 578b78bb..5a65524b 100644 --- a/docs/about/externaldata.md +++ b/docs/about/externaldata.md @@ -65,7 +65,7 @@ same name as the filename. It is prefered to have the name using the actual OSM tag instead of the file. If you get this error, you need to rename the GeoJson file. - Duplicate type: 'choice', Duplicate URI: 'None', Duplicate context: 'survey'. + Duplicate type: 'choice', Duplicate URI: 'None', Duplicate context: 'survey'. ## Naming Conflicts @@ -78,7 +78,7 @@ the survey sheet. The name column in the survey sheet can then be just _healthcare_, which will translate directly into its OSM tag equivalent. For this example note that the GeoJson file must not be named healthcare.geojson, because it'll conflict with -__healthcare_. You can also avoid this by having the calculation in +\__healthcare_. You can also avoid this by having the calculation in the same row as the survey question and avoiding the variable. If you do that, add a trigger for the geojson file, and it'll populate the default value for the question. @@ -90,7 +90,7 @@ is defined, and anything outside of that will cause an error. Therefore, it's important to adhere to validated data models to avoid introducing errors or inconsistencies into the dataset. If the SQL query returns columns that aren't compatible with the XLSForm, -XPATH errors will occur in ODK Collect. +XPATH errors will occur in ODK Collect. Something else to consider is the frequency of the tags and values. Since almost anything can be added to OSM, there are a lot of @@ -133,21 +133,21 @@ OSM data to set the survey question default value. When working with external data, the _map value_ in the _appearance_ column of the survey sheet is often used. However, this can slow down the debugging process. To make it more efficient, you can turn off the -map values and use the select menu instead. That works especially well +map values and use the select menu instead. That works especially well if you have a small data file for testing, because then it's easy to cycle between them. To use the placement map, here's an example. -| type | name | label | appearance | -| --------------------------------------- | -------- | ------------------ | ---------- | +| type | name | label | appearance | +| ------------------------------------------- | -------- | ------------------ | ---------- | | select_one_from_file **camp_sites.geojson** | existing | Existing Campsites | map | And an example where the values in the data file are an inline select menu instead. -| type | name | label | appearance | -| --------------------------------------- | -------- | ------------------ | ---------- | +| type | name | label | appearance | +| ------------------------------------------- | -------- | ------------------ | ---------- | | select_one_from_file **camp_sites.geojson** | existing | Existing Campsites | minimal | ### Display calculated values @@ -177,9 +177,9 @@ Collect, you can also reference it in the same row. This saves potential naming conflicts, although is why I use an **x** prefix for gobal values. -| type | name | label | calculation | trigger | -| --------- | --------- | ---------------- | --------------------------------------------------------- | ----------- | -| text | name | Business Name | instance(“camp_sites”)/root/item[id=${existing}]/name | ${existing} | +| type | name | label | calculation | trigger | +| ---- | ---- | ------------- | ----------------------------------------------------- | ----------- | +| text | name | Business Name | instance(“camp_sites”)/root/item[id=${existing}]/name | ${existing} | ### Error Dialog diff --git a/docs/about/json2osm.md b/docs/about/json2osm.md index 630dc0b3..4f34eebb 100644 --- a/docs/about/json2osm.md +++ b/docs/about/json2osm.md @@ -2,13 +2,13 @@ convert JSON from ODK Central to OSM XML - usage: json2osm [-h] [-v] [-y YAML] [-x XLSFILE] -i INFILE + usage: json2osm [-h] [-v] [-y YAML] [-x XLSFILE] -i INFILE - options: - -h, --help show this help message and exit - -v, --verbose verbose output - -y YAML, --yaml YAML Alternate YAML file - -x XLSFILE, --xlsfile XLSFILE - Source XLSFile - -i INFILE, --infile INFILE - The input file downloaded from ODK Central + options: + -h, --help show this help message and exit + -v, --verbose verbose output + -y YAML, --yaml YAML Alternate YAML file + -x XLSFILE, --xlsfile XLSFILE + Source XLSFile + -i INFILE, --infile INFILE + The input file downloaded from ODK Central diff --git a/docs/about/make_data_extract.md b/docs/about/make_data_extract.md index dcdb4654..720b445b 100644 --- a/docs/about/make_data_extract.md +++ b/docs/about/make_data_extract.md @@ -18,31 +18,30 @@ ways, but needed to be automated to be used for FMTM. --geojson (-g) GEOJSON Name of the GeoJson output file --boundary (-b) BOUNDARY Boundary polygon to limit the data size --category (-c) CATEGORY Which category to extract - --uri (-u) URI Database URI - --xlsfile (-x) XLSFILE An XLSForm in the library - --list (-l) List List all XLSForms in the library + --uri (-u) URI Database URI + --xlsfile (-x) XLSFILE An XLSForm in the library + --list (-l) List List all XLSForms in the library ## Examples -Make_data_extract uses a Postgres database to extract OSM data. By +Make*data_extract uses a Postgres database to extract OSM data. By default, the program uses **localhost** as the database host. If you -use **underpass* as the data base name, this will remotely access the +use \**underpass*as the data base name, this will remotely access the [Humanitarian OpenStreetMap Team(HOT)](https://www.hotosm.org) maintained OSM database that covers the entire planet, and is updated every minute. The name of the database can be specified using the -*--uri*_ option. The program extracts the buildings category of OSM +*--uri** option. The program extracts the buildings category of OSM data by default. The size of the extracted data can be limited using -the _--boundary_ option. The program outputs the data in GeoJSON +the *--boundary* option. The program outputs the data in GeoJSON format. -For raw OSM data, the existing country data is downloaded from [GeoFabrik]( -https://download.geofabrik.de/index.html), and imported using a +For raw OSM data, the existing country data is downloaded from [GeoFabrik](https://download.geofabrik.de/index.html), and imported using a modified schema for osm2pgsql. First create the database and install two postgres extensions: - # createdb nigeria - psql -d nigeria -c "CREATE EXTENSION postgis" - psql -d nigeria -c "CREATE EXTENSION hstore" + # createdb nigeria + psql -d nigeria -c "CREATE EXTENSION postgis" + psql -d nigeria -c "CREATE EXTENSION hstore" And then import the OSM data. @@ -54,8 +53,7 @@ part of the [Underpass project](https://hotosm.github.io/underpass/index.html). It uses a more compressed and efficient data schema. - -### Example: +### Example ./make_data_extract.py -u colorado --boundary mycounty.geojson -g mycounty_buildings.geojson @@ -65,7 +63,7 @@ extracted data to the boundary specified in the `mycounty.geojson` file. The program outputs the data in GeoJSON format to a file named `mycounty_buildings.geojson`. -### Boundary: +### Boundary The `--boundary` option can be used to specify a polygon boundary to limit the size of the extracted data. The boundary has to be in @@ -81,13 +79,13 @@ limits the size of the extracted data to the boundary specified in the `mycounty.geojson` file. The program outputs the data in GeoJSON format to a file named `mycounty_healtcare.geojson`. -### Category: +### Category The `--category` option can be used to specify which category of OSM data to extract. The program supports any category in the [xlsform library](https://github.com/hotosm/osm-fieldwork/tree/main/osm_fieldwork/xlsforms) -### Example: +### Example ./make_data_extract.py -u underpass --boundary mycounty.geojson --category amenities -g mycounty_amenities.geojson @@ -96,7 +94,7 @@ of OSM data within the boundary specified in the `mycounty.geojson` file. The program outputs the data in GeoJSON format to a file named `mycounty_amenities.geojson`. -### Output File Format: +### Output File Format The program outputs the extracted OSM data in GeoJSON format. The name of the output file can be specified using the `--geojson option`. If diff --git a/docs/about/odk2csv.md b/docs/about/odk2csv.md index d5f5a753..78090502 100644 --- a/docs/about/odk2csv.md +++ b/docs/about/odk2csv.md @@ -2,11 +2,11 @@ Convert ODK XML instance file to CSV format - usage: odk2csv [-h] [-v [VERBOSE]] -i INSTANCE + usage: odk2csv [-h] [-v [VERBOSE]] -i INSTANCE - options: - -h, --help show this help message and exit - -v [VERBOSE], --verbose [VERBOSE] - verbose output - -i INSTANCE, --instance INSTANCE - The instance file(s) from ODK Collect + options: + -h, --help show this help message and exit + -v [VERBOSE], --verbose [VERBOSE] + verbose output + -i INSTANCE, --instance INSTANCE + The instance file(s) from ODK Collect diff --git a/docs/about/odk2geojson.md b/docs/about/odk2geojson.md index 6a050c4a..f3aed30a 100644 --- a/docs/about/odk2geojson.md +++ b/docs/about/odk2geojson.md @@ -2,13 +2,13 @@ Convert ODK XML instance file to GeoJson - usage: odk2geojson [-h] [-v [VERBOSE]] -i INSTANCE [-o OUTFILE] + usage: odk2geojson [-h] [-v [VERBOSE]] -i INSTANCE [-o OUTFILE] - options: - -h, --help show this help message and exit - -v [VERBOSE], --verbose [VERBOSE] - verbose output - -i INSTANCE, --instance INSTANCE - The instance file(s) from ODK Collect - -o OUTFILE, --outfile OUTFILE - The output file for JOSM + options: + -h, --help show this help message and exit + -v [VERBOSE], --verbose [VERBOSE] + verbose output + -i INSTANCE, --instance INSTANCE + The instance file(s) from ODK Collect + -o OUTFILE, --outfile OUTFILE + The output file for JOSM diff --git a/docs/about/odk2osm.md b/docs/about/odk2osm.md index 3e46f591..e8e6f538 100644 --- a/docs/about/odk2osm.md +++ b/docs/about/odk2osm.md @@ -1,11 +1,11 @@ # odk2osm - Convert ODK XML instance file to OSM XML format + Convert ODK XML instance file to OSM XML format - usage: odk2osm [-h] [-v [VERBOSE]] -i INSTANCE + usage: odk2osm [-h] [-v [VERBOSE]] -i INSTANCE - options: - -h, --help show this help message and exit - -v [VERBOSE], --verbose [VERBOSE] verbose output - -i INSTANCE, --instance INSTANCE - The instance file(s) from ODK Collect + options: + -h, --help show this help message and exit + -v [VERBOSE], --verbose [VERBOSE] verbose output + -i INSTANCE, --instance INSTANCE + The instance file(s) from ODK Collect diff --git a/docs/about/odk_client.md b/docs/about/odk_client.md index 3956c60d..84c73eba 100644 --- a/docs/about/odk_client.md +++ b/docs/about/odk_client.md @@ -1,9 +1,10 @@ # ODK Client -odk_client.py is a command line utility for interacting with the ODK Central server. It +odk_client.py is a command line utility for interacting with the ODK Central server. It exposes many of the REST API calls supported by the server and allows users to perform various tasks, such as uploading and downloading attachments and submissions. ## Usage + `[-h] [-v] [-s {projects,users,delete}] [-p {forms,app-users,assignments,delete}] [-i ID] [-f FORM] [-u UUID]` `[-x {attachments,csv,submissions,upload,download,create,assignments,delete,publish}] [-a {create,delete,update,qrcode,access}] [-d DATA] [-t TIMESTAMP]` @@ -12,7 +13,7 @@ exposes many of the REST API calls supported by the server and allows users to p ### command line client for ODK Central -### Options: +### Options -h, --help show this help message and exit -v, --verbose verbose output @@ -33,48 +34,47 @@ exposes many of the REST API calls supported by the server and allows users to p -b {qrcodes,update}, --bulk {qrcodes,update} Bulk operations - ## Server requests -Server requests allow users to access global data about projects and users. +Server requests allow users to access global data about projects and users. ### Usage + The following server-specific commands are supported by ODK Client: - `--server projects` This command returns a list of project IDs and their corresponding project names. - ### Example usage: +### Example usage python odk_client.py --server projects - - `--server users` This command returns a list of user IDs and their corresponding usernames. - - ### Example usage: - python odk_client.py --server users +### Example usage + python odk_client.py --server users ## Project Requests -Project requests allow users to access data for a specific project, such as XForms, attachments, and app users. +Project requests allow users to access data for a specific project, such as XForms, attachments, and app users. Projects contain all the Xforms and attachments for that project. To access the data for a project, it is necessary to supply the project ID. That can be retrieved using the above server command. In this example, 1 is used. ### Usage + The following are the project-specific commands supported by ODK Client: - `--id --project forms` This command returns a list of all the XForms contained in the specified project. Replace `""` with the actual ID of the project you want to retrieve the forms for. - ### Example usage: +### Example usage python odk_client.py --id 1 --project forms @@ -82,16 +82,15 @@ The following are the project-specific commands supported by ODK Client: This command returns a list of all the app users who have access to the specified project. Replace `""` with the actual ID of the project you want to retrieve the list of app users for. - ### Example usage: +### Example usage python odk_client.py --id 1 --project app-users Note: Replace "1" with the actual ID of the project you want to access. - ## XForm Requests -XForm requests allow users to access data for a specific XForm within a project, such as attachments, submissions, and CSV data. +XForm requests allow users to access data for a specific XForm within a project, such as attachments, submissions, and CSV data. An XForm has several components. The primary one is the XForm description itself. In addition to that, there may be additional attachments, usually a CSV file of external data to be used by the @@ -103,13 +102,14 @@ ID and the XForm ID. The XForm ID can be retrieved using the above project command. ### Usage + The following are the XForm-specific commands supported by ODK Client: - `--id --form --xform attachments` This command returns a list of all the attachments for the specified XForm. Replace "``" with the actual ID of the project that contains the XForm, and "``" with the actual ID of the XForm you want to retrieve the attachments for. - ### Example usage: +### Example usage python odk_client.py --id 1 --form 1 --xform attachments @@ -117,7 +117,7 @@ The following are the XForm-specific commands supported by ODK Client: This command downloads the specified attachments for the specified XForm. Replace "``" with the actual ID of the project that contains the XForm, "``" with the actual ID of the XForm you want to download the attachments for, and "``,``,`etc...`" with the actual names of the attachments you want to download. - ### Example usage: +### Example usage python odk_client.py --id 1 --form 1 --xform download file1.csv,file2.pdf @@ -125,7 +125,7 @@ The following are the XForm-specific commands supported by ODK Client: This command returns a list of all the submissions for the specified XForm. Replace "``" with the actual ID of the project that contains the XForm, and "``" with the actual ID of the XForm you want to retrieve the submissions for. - ### Example usage: +### Example usage python odk_client.py --id 1 --form 1 --xform submissions @@ -133,7 +133,7 @@ The following are the XForm-specific commands supported by ODK Client: This command returns the data for the submissions for the specified XForm in CSV format. Replace "``" with the actual ID of the project that contains the XForm, and "``" with the actual ID of the XForm you want to retrieve the submission data for. - ### Example usage: +### Example usage python odk_client.py --id 1 --form 1 --xform csv @@ -141,7 +141,7 @@ The following are the XForm-specific commands supported by ODK Client: This command uploads the specified attachments for the specified XForm. Replace "``" with the actual ID of the project that contains the XForm, "``" with the actual ID of the XForm you want to upload the attachments for, and "`,,...`" with the actual names of the attachments you want to upload. - ### Example usage: +### Example usage python odk_client.py --id 1 --form 1 --xform upload file1.csv,file2.pdf @@ -155,7 +155,7 @@ towns. ./osm_fieldwork/odk_client.py --id 4 --form waterpoints --xform create osm_fieldwork/xlsforms/waterpoints.xml osm_fieldwork/xlsforms/towns.csv osm_fieldwork/xlsforms/municipality.csv -### To create a new XForm and upload two attachments, follow these steps: +### To create a new XForm and upload two attachments, follow these steps - Create a new XForm using the ODK XLSForm syntax. You can use any tool that supports this syntax, such as ODK Build or Excel. Save the XLSForm file as "`waterpoints.xml`". diff --git a/docs/about/odk_merge.md b/docs/about/odk_merge.md index 9af06db8..2af39dfc 100644 --- a/docs/about/odk_merge.md +++ b/docs/about/odk_merge.md @@ -42,21 +42,26 @@ existing data has the new tags added. ## Examples -### Example 1: Merge ODK data into an existing OSM file: +### Example 1: Merge ODK data into an existing OSM file odk_merge.py -f /path/to/existing.osm -c /path/to/odk_data.csv -o /path/to/output.osm This command will merge the data collected through ODK into the existing.osm file, adding new tags or updating existing ones based on the data in the odk_data.csv file. The resulting merged data will be written to the output.osm file. -### Example 2: Merge ODK data into an existing OSM file within a specific boundary: +### Example 2: Merge ODK data into an existing OSM file within a specific boundary + odk_merge.py -f /path/to/existing.osm -c /path/to/odk_data.csv -o /path/to/output.osm -b /path/to/boundary.geojson + This command is similar to the previous one, but it limits the merged data to the area defined by the boundary.geojson file. -### Example 3: Merge ODK data into an existing OSM database: +### Example 3: Merge ODK data into an existing OSM database + odk_merge.py -f pg:osm -c /path/to/odk_data.csv -o /path/to/output.osm + This command merges the ODK data into an existing OSM database instead of a file. The pg:osm argument specifies the name of the OSM database. -### Example 4: Merge ODK data into an existing OSM database within a specific boundary: +### Example 4: Merge ODK data into an existing OSM database within a specific boundary odk_merge.py -f pg:osm -c /path/to/odk_data.csv -o /path/to/output.osm -b /path/to/boundary.geojson + This command is similar to the previous one, but it limits the merged data to the area defined by the boundary.geojson file. diff --git a/docs/about/osm2favorites.md b/docs/about/osm2favorites.md index a800653c..0c609087 100644 --- a/docs/about/osm2favorites.md +++ b/docs/about/osm2favorites.md @@ -8,8 +8,9 @@ makes them all readily available for navigation. For some features this program also adds Osmand styling to change the displayed icons and colors. -## options: - -h, --help show this help message and exit - -v, --verbose verbose output - -i INFILE, --infile INFILE +## options + + -h, --help show this help message and exit + -v, --verbose verbose output + -i INFILE, --infile INFILE The data extract diff --git a/docs/about/osmfile.md b/docs/about/osmfile.md index 20404c59..32c4da2d 100644 --- a/docs/about/osmfile.md +++ b/docs/about/osmfile.md @@ -41,7 +41,6 @@ argument: writer.write(relation_xml) writer.close() - This would create XML code for the node, way, and relation using createNode(), createWay(), and createRelation() respectively. These methods return a string of XML code which is then written to the @@ -103,7 +102,6 @@ the object, and the role of the object in the relation. For example: writer.write_relation(relation) - This would write the following XML code to the output file: diff --git a/docs/about/programs.md b/docs/about/programs.md index 2810f08e..2a2b6ddb 100644 --- a/docs/about/programs.md +++ b/docs/about/programs.md @@ -2,13 +2,12 @@ OSM Fieldwork contains a few standalone utility programs for converting data from ODK Collect and the ODK Central server, and a few support -modules. You can install from the source tree using: - - pip install . +modules. You can install from the source tree using: +pip install . or you can install the package from PyPi.org: - pip install osm-fieldwork + pip install osm-fieldwork ## make_data_extract.py diff --git a/docs/about/xlsforms.md b/docs/about/xlsforms.md index a6592000..1146c155 100644 --- a/docs/about/xlsforms.md +++ b/docs/about/xlsforms.md @@ -127,11 +127,11 @@ are required to exist in each sheet, the rest are optional. ### [Survey Sheet](https://xlsform.org/en/#the-survey-worksheet) - - This sheet contains all the questions used for collecting data, - and refers to the actual values for each question which are on the - _choices_ sheet. +- This sheet contains all the questions used for collecting data, + and refers to the actual values for each question which are on the + _choices_ sheet. - These are the mandatory column headers in the survey sheet: +These are the mandatory column headers in the survey sheet: - **Type** - The type of question, the most common ones are **text**, **select_one**, **select_multiple.**, and **select_from_file** The @@ -175,6 +175,7 @@ the mapper to enter an interger, text, or select one or multiple items from a menu. ### [Choices Sheet](https://xlsform.org/en/#the-choices-worksheet) + The choices sheet is used to define the values used for the **select_one** and **select_multiple** questions on the **survey** sheet. @@ -198,7 +199,6 @@ form. The version is used by the server and the mobile apps to track changes in the data format, so it should always be updated after changes are made. - - **form_title** - This is what is displayed in ODK Central - **form_id** - This is a unique ID to identify this XForm. - **version** - This is mandatory, and needs to change after major @@ -241,12 +241,12 @@ ones. multiple questions are on the same screen. - The screen can be scrolled if there are more input fields than fit. -| type | name | label | appearance | -| ------------------ | ------- | --------------- | ------------| -| begin_group | agroup | Amenity Details | field-list | -| select_one text | name | Amenity Name | minimal | -| select_one amenity | amenity | Type of Amenity | minimal | -| end_group | | | | +| type | name | label | appearance | +| ------------------ | ------- | --------------- | ---------- | +| begin_group | agroup | Amenity Details | field-list | +| select_one text | name | Amenity Name | minimal | +| select_one amenity | amenity | Type of Amenity | minimal | +| end_group | | | | # Conditionals @@ -486,5 +486,5 @@ the OMK app anymore. Since mobile data collection often involves gathering many of the same types of data, setting defaults helps reduce the number of user actions needed to collect data. -When collecting multiples of the same type of data,good defaults can record data +When collecting multiples of the same type of data,good defaults can record data even when only the location has changed. diff --git a/docs/api/CSVDump.md b/docs/api/CSVDump.md index ce7c6c43..ca9ee7c7 100644 --- a/docs/api/CSVDump.md +++ b/docs/api/CSVDump.md @@ -1,7 +1,6 @@ # CSVDump.py ::: osm_fieldwork.CSVDump.CSVDump - options: - show_source: false - heading_level: 3 - +options: +show_source: false +heading_level: 3 diff --git a/docs/api/OdkCentral.md b/docs/api/OdkCentral.md index af2b31d4..3f504dab 100644 --- a/docs/api/OdkCentral.md +++ b/docs/api/OdkCentral.md @@ -1,26 +1,26 @@ # OdkCentral ::: osm_fieldwork.OdkCentral.downloadThread - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 ::: osm_fieldwork.OdkCentral.OdkCentral - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 ::: osm_fieldwork.OdkCentral.OdkProject - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 ::: osm_fieldwork.OdkCentral.OdkForm - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 ::: osm_fieldwork.OdkCentral.OdkAppUser - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/basemapper.md b/docs/api/basemapper.md index f3036aa7..dc63f7ae 100644 --- a/docs/api/basemapper.md +++ b/docs/api/basemapper.md @@ -1,12 +1,11 @@ # basemapper.py ::: osm_fieldwork.basemapper.dlthread - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 ::: osm_fieldwork.basemapper.BaseMapper - options: - show_source: false - heading_level: 3 - +options: +show_source: false +heading_level: 3 diff --git a/docs/api/convert.md b/docs/api/convert.md index ba52e33b..ef729e19 100644 --- a/docs/api/convert.md +++ b/docs/api/convert.md @@ -1,6 +1,6 @@ # convert.py ::: osm_fieldwork.convert.Convert - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/filter_data.md b/docs/api/filter_data.md index d535a2fa..a23c8f4c 100644 --- a/docs/api/filter_data.md +++ b/docs/api/filter_data.md @@ -1,6 +1,6 @@ # filter_data.py ::: osm_fieldwork.filter_data.FilterData - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/json2osm.md b/docs/api/json2osm.md index c3448d93..2fa72f68 100644 --- a/docs/api/json2osm.md +++ b/docs/api/json2osm.md @@ -1,6 +1,6 @@ # json2osm.py ::: osm_fieldwork.json2osm.JsonDump - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/odk2csv.md b/docs/api/odk2csv.md index 8bd8c76e..88f5775d 100644 --- a/docs/api/odk2csv.md +++ b/docs/api/odk2csv.md @@ -1,6 +1,6 @@ # odk2csv.py ::: osm_fieldwork.odk2csv - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/odk2geojson.md b/docs/api/odk2geojson.md index 3f7bfd85..12100b57 100644 --- a/docs/api/odk2geojson.md +++ b/docs/api/odk2geojson.md @@ -1,6 +1,6 @@ # odk2geojson.py ::: osm_fieldwork.odk2geojson - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/odk2osm.md b/docs/api/odk2osm.md index 79551edc..ba208419 100644 --- a/docs/api/odk2osm.md +++ b/docs/api/odk2osm.md @@ -1,6 +1,6 @@ # odk2osm.py ::: osm_fieldwork.odk2osm - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/osmfile.md b/docs/api/osmfile.md index aba7d22a..848ebf85 100644 --- a/docs/api/osmfile.md +++ b/docs/api/osmfile.md @@ -1,6 +1,6 @@ # osmfile.py ::: osm_fieldwork.osmfile.OsmFile - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/sqlite.md b/docs/api/sqlite.md index aba7d22a..848ebf85 100644 --- a/docs/api/sqlite.md +++ b/docs/api/sqlite.md @@ -1,6 +1,6 @@ # osmfile.py ::: osm_fieldwork.osmfile.OsmFile - options: - show_source: false - heading_level: 3 +options: +show_source: false +heading_level: 3 diff --git a/docs/api/yamlfile.md b/docs/api/yamlfile.md index f6fc4fb1..8a862989 100644 --- a/docs/api/yamlfile.md +++ b/docs/api/yamlfile.md @@ -1,7 +1,6 @@ # yamlfile.py ::: osm_fieldwork.yamlfile.YamlFile - options: - show_source: false - heading_level: 3 - +options: +show_source: false +heading_level: 3 diff --git a/docs/css/extra.css b/docs/css/extra.css index 94fa1539..6ff6b31b 100644 --- a/docs/css/extra.css +++ b/docs/css/extra.css @@ -1,6 +1,5 @@ :root { - --md-primary-fg-color: #d73f3f; - --md-primary-fg-color--light: #e27575; - --md-primary-fg-color--dark: #c22929; + --md-primary-fg-color: #d73f3f; + --md-primary-fg-color--light: #e27575; + --md-primary-fg-color--dark: #c22929; } - \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index e4579fce..126b78ba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -135,7 +135,6 @@ This is just a short overview. - ### json2osm - - ### odk2csv.py, odk2geojson.py, odk2osm.py These programs ER used when working offline for extended periods. This diff --git a/osm_fieldwork/CSVDump.py b/osm_fieldwork/CSVDump.py index e2cb8590..9a7ec624 100755 --- a/osm_fieldwork/CSVDump.py +++ b/osm_fieldwork/CSVDump.py @@ -20,91 +20,98 @@ import argparse import csv -import os import logging +import os +import re import sys -from sys import argv + +import pandas as pd +from geojson import Feature, FeatureCollection, Point, dump + from osm_fieldwork.convert import Convert from osm_fieldwork.osmfile import OsmFile from osm_fieldwork.xlsforms import xlsforms_path -from geojson import Point, Feature, FeatureCollection, dump -import pandas as pd -import re - # set log level for urlib log = logging.getLogger(__name__) + class CSVDump(Convert): - """A class to parse the CSV files from ODK Central""" + """A class to parse the CSV files from ODK Central.""" - def __init__(self, - yaml: str = None, - ): + def __init__( + self, + yaml: str = None, + ): self.fields = dict() self.nodesets = dict() self.data = list() self.osm = None self.json = None self.features = list() - path = xlsforms_path.replace("xlsforms", "") + xlsforms_path.replace("xlsforms", "") if yaml: - file = f"{path}{yaml}" + pass else: - file = f"{path}/xforms.yaml" + pass self.config = super().__init__(yaml) self.saved = dict() self.defaults = dict() - def lastSaved(self, - keyword: str, - ): + def lastSaved( + self, + keyword: str, + ): if keyword is not None and len(keyword) > 0: return self.saved[keyword] return None - def updateSaved(self, - keyword: str, - value: str, - ): + def updateSaved( + self, + keyword: str, + value: str, + ): if keyword is not None and value is not None and len(value) > 0: self.saved[keyword] = value - def parseXLS(self, - xlsfile: str, - ): - """Parse the source XLSFile if available to look for details we need""" + def parseXLS( + self, + xlsfile: str, + ): + """Parse the source XLSFile if available to look for details we need.""" if xlsfile is not None and len(xlsfile) > 0: entries = pd.read_excel(xlsfile, sheet_name=[0]) # There will only be a single sheet - names = entries[0]['name'] - defaults = entries[0]['default'] + names = entries[0]["name"] + defaults = entries[0]["default"] total = len(names) i = 0 while i < total: entry = defaults[i] - if str(entry) != 'nan': + if str(entry) != "nan": pat = re.compile("..last-saved.*") if pat.match(entry): - name = entry.split('#')[1][:-1] + name = entry.split("#")[1][:-1] self.saved[name] = None else: self.defaults[names[i]] = entry i += 1 return True - def createOSM(self, - filespec: str, - ): - """Create an OSM XML output files""" + def createOSM( + self, + filespec: str, + ): + """Create an OSM XML output files.""" log.debug("Creating OSM XML file: %s" % filespec) self.osm = OsmFile(filespec) - #self.osm.header() + # self.osm.header() - def writeOSM(self, - feature: dict, - ): - """Write a feature to an OSM XML output file""" + def writeOSM( + self, + feature: dict, + ): + """Write a feature to an OSM XML output file.""" out = "" if "id" in feature["tags"]: feature["id"] = feature["tags"]["id"] @@ -117,27 +124,29 @@ def writeOSM(self, self.osm.write(out) def finishOSM(self): - """Write the OSM XML file footer and close it""" + """Write the OSM XML file footer and close it.""" self.osm.footer() - def createGeoJson(self, - file: str = "tmp.geojson", - ): - """Create a GeoJson output file""" + def createGeoJson( + self, + file: str = "tmp.geojson", + ): + """Create a GeoJson output file.""" log.debug("Creating GeoJson file: %s" % file) self.json = open(file, "w") - def writeGeoJson(self, - feature: dict, - ): - """Write a feature to a GeoJson output file""" + def writeGeoJson( + self, + feature: dict, + ): + """Write a feature to a GeoJson output file.""" # These get written later when finishing , since we have to create a FeatureCollection if "lat" not in feature["attrs"] or "lon" not in feature["attrs"]: return None self.features.append(feature) def finishGeoJson(self): - """Write the GeoJson FeatureCollection to the output file and close it""" + """Write the GeoJson FeatureCollection to the output file and close it.""" features = list() for item in self.features: poi = Point((float(item["attrs"]["lon"]), float(item["attrs"]["lat"]))) @@ -149,18 +158,18 @@ def finishGeoJson(self): collection = FeatureCollection(features) dump(collection, self.json) - def parse(self, - filespec: str, - data: str = None, - ): - """Parse the CSV file from ODK Central and convert it to a data structure""" + def parse( + self, + filespec: str, + data: str = None, + ): + """Parse the CSV file from ODK Central and convert it to a data structure.""" all_tags = list() if not data: f = open(filespec, newline="") reader = csv.DictReader(f, delimiter=",") else: reader = csv.DictReader(data, delimiter=",") - last_saved = dict() for row in reader: tags = dict() # log.info(f"ROW: {row}") @@ -170,10 +179,7 @@ def parse(self, base = self.basename(keyword).lower() # There's many extraneous fields in the input file which we don't need. - if (base is None - or base in self.ignore - or value is None - ): + if base is None or base in self.ignore or value is None: continue # if base in self.multiple: # epdb.st() @@ -188,49 +194,51 @@ def parse(self, # location, there is not always a value if the accuracy is way # off. In this case use the warmup value, which is where we are # standing anyway. - if base == 'latitude' and len(value) == 0: - if 'warmup-Latitude' in row: - value = row['warmup-Latitude'] - if base == 'longitude' and len(value) == 0: - value = row['warmup-Longitude'] + if base == "latitude" and len(value) == 0: + if "warmup-Latitude" in row: + value = row["warmup-Latitude"] + if base == "longitude" and len(value) == 0: + value = row["warmup-Longitude"] items = self.convertEntry(base, value) # log.info(f"ROW: {base} {value}") if len(items) > 0: if base in self.saved: - if str(value) == 'nan' or len(value) == 0: + if str(value) == "nan" or len(value) == 0: # log.debug(f"FIXME: {base} {value}") val = self.saved[base] if val and len(value) == 0: - log.warning(f"Using last saved value for \"{base}\"! Now \"{val}\"" ) + log.warning(f'Using last saved value for "{base}"! Now "{val}"') value = val else: self.saved[base] = value - log.debug(f"Updating last saved value for \"{base}\" with \"{value}\"") + log.debug(f'Updating last saved value for "{base}" with "{value}"') # Handle nested dict in list if isinstance(items, list): items = items[0] for k, v in items.items(): - tags[k] = v + tags[k] = v else: tags[base] = value # log.debug(f"\tFIXME1: {tags}") all_tags.append(tags) return all_tags - def basename(self, - line: str, - ): - """Extract the basename of a path after the last -""" + def basename( + self, + line: str, + ): + """Extract the basename of a path after the last -.""" tmp = line.split("-") if len(tmp) == 0: return line base = tmp[len(tmp) - 1] return base - def createEntry(self, - entry: dict, - ): - """Create the feature data structure""" + def createEntry( + self, + entry: dict, + ): + """Create the feature data structure.""" # print(line) feature = dict() attrs = dict() @@ -240,9 +248,9 @@ def createEntry(self, # log.debug("Creating entry") # First convert the tag to the approved OSM equivalent - if 'lat' in entry and 'lon' in entry: - attrs["lat"] = entry['lat'] - attrs["lon"] = entry['lon'] + if "lat" in entry and "lon" in entry: + attrs["lat"] = entry["lat"] + attrs["lon"] = entry["lon"] for key, value in entry.items(): attributes = ( "id", @@ -264,7 +272,7 @@ def createEntry(self, attrs["lon"] = geometry[1] continue - if len(attrs['lat']) == 0: + if len(attrs["lat"]) == 0: continue if key is not None and len(key) > 0 and key in attributes: attrs[key] = value @@ -304,19 +312,14 @@ def createEntry(self, def main(): - """ - """ - parser = argparse.ArgumentParser( - description="convert CSV from ODK Central to OSM XML" - ) + """ """ + parser = argparse.ArgumentParser(description="convert CSV from ODK Central to OSM XML") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") parser.add_argument("-y", "--yaml", help="Alternate YAML file") parser.add_argument("-x", "--xlsfile", help="Source XLSFile") - parser.add_argument( - "-i", "--infile", required=True, help="The input file downloaded from ODK Central" - ) + parser.add_argument("-i", "--infile", required=True, help="The input file downloaded from ODK Central") args = parser.parse_args() - + # if verbose, dump to the terminal. if args.verbose is not None: root = logging.getLogger() @@ -324,9 +327,7 @@ def main(): ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) root.addHandler(ch) @@ -363,6 +364,7 @@ def main(): log.info("Wrote OSM XML file: %r" % osmoutfile) log.info("Wrote GeoJson file: %r" % jsonoutfile) + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/ODKDump.py b/osm_fieldwork/ODKDump.py index 939a710d..c74e7901 100755 --- a/osm_fieldwork/ODKDump.py +++ b/osm_fieldwork/ODKDump.py @@ -17,16 +17,16 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -import sys -from osm_fieldwork.ODKForm import ODKForm -from osm_fieldwork.ODKInstance import ODKInstance import argparse -from datetime import datetime -from osm_fieldwork.osmfile import OsmFile -from osm_fieldwork.convert import Convert import logging import re +import sys +from datetime import datetime +from osm_fieldwork.convert import Convert +from osm_fieldwork.ODKForm import ODKForm +from osm_fieldwork.ODKInstance import ODKInstance +from osm_fieldwork.osmfile import OsmFile if __name__ != "__main__": print("This is not a loadable python module!") @@ -34,9 +34,7 @@ parser = argparse.ArgumentParser() parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") -parser.add_argument( - "-x", "--xform", required=True, help="input xform file in XML format" -) +parser.add_argument("-x", "--xform", required=True, help="input xform file in XML format") parser.add_argument("-i", "--infile", required=True, help="input data in XML format") parser.add_argument("-o", "--outdir", help="Output Directory (defaults to $PWD)") args = parser.parse_args() @@ -48,9 +46,7 @@ ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) root.addHandler(ch) @@ -93,7 +89,7 @@ continue elif reg.match(x): groups[x] = data[x] - for key, value in data[x].items(): + for key, _value in data[x].items(): if odkform.getNodeType(x, key) == "geotrace": ln = "LINESTRING(" ln += groups[x][key] + ")" diff --git a/osm_fieldwork/ODKForm.py b/osm_fieldwork/ODKForm.py index 6ab29842..b737e454 100755 --- a/osm_fieldwork/ODKForm.py +++ b/osm_fieldwork/ODKForm.py @@ -17,8 +17,8 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -import logging import argparse +import logging import os import sys @@ -30,20 +30,19 @@ class ODKForm(object): """Support for parsing an XLS Form, currently a work in progress...""" def __init__(self): - """ - Returns: - (ODKForm): An instance of this object + """Returns: + (ODKForm): An instance of this object. """ self.fields = dict() self.nodesets = dict() self.groups = dict() self.ignore = ("label", "@appearance", "hint", "upload") - def parseSelect(self, - select: dict, - ): - """ - Parse a select statement in XML + def parseSelect( + self, + select: dict, + ): + """Parse a select statement in XML. Args: select (dict): The select in XML: @@ -63,11 +62,11 @@ def parseSelect(self, print("\tQQQQQ %r" % (newsel)) return newsel - def parseItems(self, - items: list, - ): - """ - Parse the items in a select list + def parseItems( + self, + items: list, + ): + """Parse the items in a select list. Args: items (list): The select items list in XML: @@ -107,18 +106,18 @@ def parseItems(self, # return group, subgroup, newitems return newitems - def parseGroup(self, - group: dict(), - ): - """ - Convert the XML of a group into a data structure. + def parseGroup( + self, + group: dict(), + ): + """Convert the XML of a group into a data structure. Args: group (dict): The group data """ print("\tparseGroup %r" % (type(group))) if type(group) == list: - for val in group: + for _val in group: for k in group: print("\nZZZZ1 %r" % (k)) else: # it's a list @@ -137,13 +136,9 @@ def parseGroup(self, if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" - parser = argparse.ArgumentParser( - description="convert CSV from ODK Central to OSM XML" - ) + parser = argparse.ArgumentParser(description="convert CSV from ODK Central to OSM XML") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") - parser.add_argument( - "-i", "--infile", help="The input file downloaded from ODK Central" - ) + parser.add_argument("-i", "--infile", help="The input file downloaded from ODK Central") args = parser.parse_args() # if verbose, dump to the terminal as well as the logfile. @@ -152,9 +147,7 @@ def parseGroup(self, ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) diff --git a/osm_fieldwork/ODKInstance.py b/osm_fieldwork/ODKInstance.py index e6b783ac..f3eef3a6 100755 --- a/osm_fieldwork/ODKInstance.py +++ b/osm_fieldwork/ODKInstance.py @@ -17,28 +17,30 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -import sys -import logging -import xmltodict import argparse +import logging import os import re +import sys # from shapely.geometry import Point, LineString, Polygon from collections import OrderedDict +import xmltodict + # Logging log = logging.getLogger(__name__) + class ODKInstance(object): - def __init__(self, - filespec: str = None, - data: str = None, - ): - """ - This class imports a ODK Instance file, which is in XML into a data + def __init__( + self, + filespec: str = None, + data: str = None, + ): + """This class imports a ODK Instance file, which is in XML into a data structure. - + Args: filespec (str): The filespec to the ODK XML Instance file data (str): The XML data @@ -53,15 +55,15 @@ def __init__(self, elif data: self.data = self.parse(data) - def parse(self, - filespec: str, - data: str = None, - ): - """ - Import an ODK XML Instance file ito a data structure. The input is + def parse( + self, + filespec: str, + data: str = None, + ): + """Import an ODK XML Instance file ito a data structure. The input is either a filespec to the Instance file copied off your phone, or the XML that has been read in elsewhere. - + Args: filespec (str): The filespec to the ODK XML Instance file data (str): The XML data @@ -79,16 +81,17 @@ def parse(self, xml = data doc = xmltodict.parse(xml) import json + json.dumps(doc) tags = dict() data = doc["data"] for i, j in data.items(): - if j is None or i == 'meta': + if j is None or i == "meta": continue print(f"tag: {i} == {j}") pat = re.compile("[0-9.]* [0-9.-]* [0-9.]* [0-9.]*") if pat.match(str(j)): - if i == 'warmup': + if i == "warmup": continue gps = j.split(" ") tags["lat"] = gps[0] @@ -119,12 +122,12 @@ def parse(self, rows.append(tags) return rows + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" parser = argparse.ArgumentParser() parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") - parser.add_argument("-i", "--infile", required=True, help="instance data in XML format" - ) + parser.add_argument("-i", "--infile", required=True, help="instance data in XML format") args = parser.parse_args() os.path.basename(args.infile) @@ -135,9 +138,7 @@ def parse(self, ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) diff --git a/osm_fieldwork/OdkCentral.py b/osm_fieldwork/OdkCentral.py index 3759664f..d5bb5f3d 100755 --- a/osm_fieldwork/OdkCentral.py +++ b/osm_fieldwork/OdkCentral.py @@ -23,25 +23,20 @@ # Author: Reetta Valimaki +import concurrent.futures +import json import logging -import sys import os -import requests -from requests.auth import HTTPBasicAuth -import json +import sys import zlib -from datetime import datetime from base64 import b64encode +from datetime import datetime + +import requests import segno -import zlib -import re -from shapely.geometry import Point -from geojson import Feature, FeatureCollection, dump -from cpuinfo import get_cpu_info -from time import sleep from codetiming import Timer -import concurrent.futures - +from cpuinfo import get_cpu_info +from requests.auth import HTTPBasicAuth # Set log level for urllib log_level = os.getenv("LOG_LEVEL", default="INFO") @@ -49,14 +44,9 @@ log = logging.getLogger(__name__) -def downloadThread( - project_id: int, - xforms: list, - odk_credentials: dict, - filters: dict = None -): - """ - Download a list of submissions from ODK Central + +def downloadThread(project_id: int, xforms: list, odk_credentials: dict, filters: dict = None): + """Download a list of submissions from ODK Central. Args: project_id (int): The ID of the project on ODK Central @@ -71,11 +61,7 @@ def downloadThread( data = list() # log.debug(f"downloadThread() called! {len(xforms)} xforms") for task in xforms: - form = OdkForm( - odk_credentials["url"], - odk_credentials["user"], - odk_credentials["passwd"] - ) + form = OdkForm(odk_credentials["url"], odk_credentials["user"], odk_credentials["passwd"]) # submissions = form.getSubmissions(project_id, task, 0, False, True) subs = form.listSubmissions(project_id, task, filters) if type(subs) == dict: @@ -88,14 +74,15 @@ def downloadThread( timer.stop() return data + class OdkCentral(object): - def __init__(self, - url: str = None, - user: str = None, - passwd: str = None, - ): - """ - A Class for accessing an ODK Central server via it's REST API + def __init__( + self, + url: str = None, + user: str = None, + passwd: str = None, + ): + """A Class for accessing an ODK Central server via it's REST API. Args: url (str): The URL of the ODK Central @@ -166,15 +153,15 @@ def __init__(self, self.users = list() # The number of threads is based on the CPU cores info = get_cpu_info() - self.cores = info['count'] + self.cores = info["count"] - def authenticate(self, - url: str = None, - user: str = None, - passwd: str = None, - ): - """ - Setup authenticate to an ODK Central server. + def authenticate( + self, + url: str = None, + user: str = None, + passwd: str = None, + ): + """Setup authenticate to an ODK Central server. Args: url (str): The URL of the ODK Central @@ -197,31 +184,30 @@ def authenticate(self, return self.session.get(self.url, auth=self.auth, verify=self.verify) def listProjects(self): - """ - Fetch a list of projects from an ODK Central server, and + """Fetch a list of projects from an ODK Central server, and store it as an indexed list. Returns: (list): A list of projects on a ODK Central server """ log.info("Getting a list of projects from %s" % self.url) - url = f'{self.base}projects' + url = f"{self.base}projects" result = self.session.get(url, auth=self.auth, verify=self.verify) projects = result.json() for project in projects: if isinstance(project, dict): if project.get("id") is not None: - self.projects[project['id']] = project + self.projects[project["id"]] = project else: log.info("No projects returned. Is this a first run?") return projects - def createProject(self, - name: str, - ): - """ - Create a new project on an ODK Central server if it doesn't - already exist + def createProject( + self, + name: str, + ): + """Create a new project on an ODK Central server if it doesn't + already exist. Args: name (str): The name for the new project @@ -238,9 +224,7 @@ def createProject(self, url = f"{self.base}projects" log.debug(f"POSTing project {name} to {url} with verify={self.verify}") try: - result = self.session.post( - url, auth=self.auth, json={"name": name}, verify=self.verify, timeout=4 - ) + result = self.session.post(url, auth=self.auth, json={"name": name}, verify=self.verify, timeout=4) result.raise_for_status() except requests.exceptions.RequestException as e: log.error(e) @@ -251,11 +235,11 @@ def createProject(self, self.listProjects() return json_response - def deleteProject(self, - project_id: int, - ): - """ - Delete a project on an ODK Central server + def deleteProject( + self, + project_id: int, + ): + """Delete a project on an ODK Central server. Args: project_id (int): The ID of the project on ODK Central @@ -269,12 +253,12 @@ def deleteProject(self, self.listProjects() return self.findProject(project_id=project_id) - def findProject(self, - name: str = None, - project_id: int = None, - ): - """ - Get the project data from Central + def findProject( + self, + name: str = None, + project_id: int = None, + ): + """Get the project data from Central. Args: name (str): The name of the project @@ -288,24 +272,24 @@ def findProject(self, if self.projects: if name: log.debug(f"Finding project by name: {name}") - for key, value in self.projects.items(): + for _key, value in self.projects.items(): if name == value["name"]: log.info(f"ODK project found: {name}") return value if project_id: log.debug(f"Finding project by id: {project_id}") - for key, value in self.projects.items(): + for _key, value in self.projects.items(): if project_id == value["id"]: log.info(f"ODK project found: {project_id}") return value return None - def findAppUser(self, - user_id: int, - name: str = None, - ): - """ - Get the data for an app user + def findAppUser( + self, + user_id: int, + name: str = None, + ): + """Get the data for an app user. Args: user_id (int): The user ID of the app-user on ODK Central @@ -316,14 +300,14 @@ def findAppUser(self, """ if self.appusers: if name is not None: - result = [d for d in self.appusers if d['displayName']==name] + result = [d for d in self.appusers if d["displayName"] == name] if result: return result[0] else: log.debug(f"No user found with name: {name}") return None if user_id is not None: - result = [d for d in self.appusers if d['id']==user_id] + result = [d for d in self.appusers if d["id"] == user_id] if result: return result[0] else: @@ -332,8 +316,7 @@ def findAppUser(self, return None def listUsers(self): - """ - Fetch a list of users on the ODK Central server + """Fetch a list of users on the ODK Central server. Returns: (list): A list of users on ODK Central, not app-users @@ -345,7 +328,7 @@ def listUsers(self): return self.users def dump(self): - """Dump internal data structures, for debugging purposes only""" + """Dump internal data structures, for debugging purposes only.""" # print("URL: %s" % self.url) # print("User: %s" % self.user) # print("Passwd: %s" % self.passwd) @@ -363,17 +346,18 @@ def dump(self): class OdkProject(OdkCentral): - """Class to manipulate a project on an ODK Central server""" - def __init__(self, - url: str = None, - user: str = None, - passwd: str = None, - ): - """ - Args: + """Class to manipulate a project on an ODK Central server.""" + + def __init__( + self, + url: str = None, + user: str = None, + passwd: str = None, + ): + """Args: url (str): The URL of the ODK Central user (str): The user's account name on ODK Central - passwd (str): The user's account password on ODK Central + passwd (str): The user's account password on ODK Central. Returns: (OdkProject): An instance of this object @@ -385,24 +369,20 @@ def __init__(self, self.appusers = None self.id = None - def getData(self, - keyword: str, - ): - """ - Args: - keyword (str): The keyword to search for + def getData( + self, + keyword: str, + ): + """Args: + keyword (str): The keyword to search for. Returns: (json): The data for the keyword """ return self.data[keyword] - def listForms(self, - project_id: int, - metadata: bool = False - ): - """ - Fetch a list of forms in a project on an ODK Central server. + def listForms(self, project_id: int, metadata: bool = False): + """Fetch a list of forms in a project on an ODK Central server. Args: project_id (int): The ID of the project on ODK Central @@ -417,13 +397,8 @@ def listForms(self, self.forms = result.json() return self.forms - def getAllSubmissions(self, - project_id: int, - xforms: list = None, - filters: dict = None - ): - """ - Fetch a list of submissions in a project on an ODK Central server. + def getAllSubmissions(self, project_id: int, xforms: list = None, filters: dict = None): + """Fetch a list of submissions in a project on an ODK Central server. Args: project_id (int): The ID of the project on ODK Central @@ -436,11 +411,11 @@ def getAllSubmissions(self, timer.start() if not xforms: xforms_data = self.listForms(project_id) - xforms = [d['xmlFormId'] for d in xforms_data] + xforms = [d["xmlFormId"] for d in xforms_data] - chunk = round(len(xforms) / self.cores) if round(len(xforms) / self.cores) > 0 else 1 + chunk = round(len(xforms) / self.cores) if round(len(xforms) / self.cores) > 0 else 1 last_slice = len(xforms) if len(xforms) % chunk == 0 else len(xforms) - 1 - cycle = range(0, (last_slice + chunk) +1, chunk) + cycle = range(0, (last_slice + chunk) + 1, chunk) future = None result = None previous = 0 @@ -454,11 +429,7 @@ def getAllSubmissions(self, # previous = current # newdata += result - odk_credentials = { - "url": self.url, - "user": self.user, - "passwd": self.passwd - } + odk_credentials = {"url": self.url, "user": self.user, "passwd": self.passwd} with concurrent.futures.ThreadPoolExecutor(max_workers=self.cores) as executor: futures = list() @@ -469,19 +440,18 @@ def getAllSubmissions(self, previous = current futures.append(result) for future in concurrent.futures.as_completed(futures): - log.debug(f"Waiting for thread to complete..") + log.debug("Waiting for thread to complete..") data = future.result(timeout=10) if len(data) > 0: newdata += data timer.stop() return newdata - - def listAppUsers(self, - projectId: int, - ): - """ - Fetch a list of app users for a project from an ODK Central server. + def listAppUsers( + self, + projectId: int, + ): + """Fetch a list of app users for a project from an ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -494,11 +464,11 @@ def listAppUsers(self, self.appusers = result.json() return self.appusers - def listAssignments(self, - projectId: int, - ): - """ - List the Role & Actor assignments for users on a project + def listAssignments( + self, + projectId: int, + ): + """List the Role & Actor assignments for users on a project. Args: projectId (int): The ID of the project on ODK Central @@ -510,11 +480,11 @@ def listAssignments(self, result = self.session.get(url, auth=self.auth, verify=self.verify) return result.json() - def getDetails(self, - projectId: int, - ): - """ - Get all the details for a project on an ODK Central server + def getDetails( + self, + projectId: int, + ): + """Get all the details for a project on an ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -527,11 +497,11 @@ def getDetails(self, self.data = result.json() return self.data - def getFullDetails(self, - projectId: int, - ): - """ - Get extended details for a project on an ODK Central server + def getFullDetails( + self, + projectId: int, + ): + """Get extended details for a project on an ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -545,14 +515,12 @@ def getFullDetails(self, return result.json() def dump(self): - """Dump internal data structures, for debugging purposes only""" + """Dump internal data structures, for debugging purposes only.""" super().dump() if self.forms: print("There are %d forms in this project" % len(self.forms)) for data in self.forms: - print( - "\t %s(%s): %s" % (data["xmlFormId"], data["version"], data["name"]) - ) + print("\t %s(%s): %s" % (data["xmlFormId"], data["version"], data["name"])) if self.data: print("Project ID: %s" % self.data["id"]) print("There are %d submissions in this project" % len(self.submissions)) @@ -564,18 +532,18 @@ def dump(self): class OdkForm(OdkCentral): - """Class to manipulate a from on an ODK Central server""" - - def __init__(self, - url:str = None, - user: str = None, - passwd: str = None, - ): - """ - Args: + """Class to manipulate a from on an ODK Central server.""" + + def __init__( + self, + url: str = None, + user: str = None, + passwd: str = None, + ): + """Args: url (str): The URL of the ODK Central user (str): The user's account name on ODK Central - passwd (str): The user's account password on ODK Central + passwd (str): The user's account password on ODK Central. Returns: (OdkForm): An instance of this object @@ -613,12 +581,12 @@ def __init__(self, # else: # log.warning("Execute OdkForm.getDetails() to get this data.") - def getDetails(self, - projectId: int, - xform: str, - ): - """ - Get all the details for a form on an ODK Central server + def getDetails( + self, + projectId: int, + xform: str, + ): + """Get all the details for a form on an ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -632,12 +600,12 @@ def getDetails(self, self.data = result.json() return result - def getFullDetails(self, - projectId: int, - xform: str, - ): - """ - Get the full details for a form on an ODK Central server + def getFullDetails( + self, + projectId: int, + xform: str, + ): + """Get the full details for a form on an ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -651,12 +619,12 @@ def getFullDetails(self, result = self.session.get(url, auth=self.auth, verify=self.verify) return result.json() - def listSubmissionBasicInfo(self, - projectId: int, - xform: str, - ): - """ - Fetch a list of submission instances basic information for a given form. + def listSubmissionBasicInfo( + self, + projectId: int, + xform: str, + ): + """Fetch a list of submission instances basic information for a given form. Args: projectId (int): The ID of the project on ODK Central @@ -669,14 +637,8 @@ def listSubmissionBasicInfo(self, result = self.session.get(url, auth=self.auth, verify=self.verify) return result.json() - - def listSubmissions(self, - projectId: int, - xform: str, - filters: dict = None - ): - """ - Fetch a list of submission instances for a given form. + def listSubmissions(self, projectId: int, xform: str, filters: dict = None): + """Fetch a list of submission instances for a given form. Args: projectId (int): The ID of the project on ODK Central @@ -689,16 +651,16 @@ def listSubmissions(self, result = self.session.get(url, auth=self.auth, params=filters, verify=self.verify) if result.ok: self.submissions = result.json() - return self.submissions['value'] + return self.submissions["value"] else: return list() - def listAssignments(self, - projectId: int, - xform: str, - ): - """ - List the Role & Actor assignments for users on a project + def listAssignments( + self, + projectId: int, + xform: str, + ): + """List the Role & Actor assignments for users on a project. Fetch a list of submission instances basic information for a given form. @@ -713,15 +675,15 @@ def listAssignments(self, result = self.session.get(url, auth=self.auth, verify=self.verify) return result.json() - def getSubmissions(self, - projectId: int, - xform: str, - submission_id: int, - disk: bool = False, - json: bool = True, - ): - """ - Fetch a CSV or JSON file of the submissions without media to a survey form. + def getSubmissions( + self, + projectId: int, + xform: str, + submission_id: int, + disk: bool = False, + json: bool = True, + ): + """Fetch a CSV or JSON file of the submissions without media to a survey form. Args: projectId (int): The ID of the project on ODK Central @@ -736,7 +698,7 @@ def getSubmissions(self, headers = {"Content-Type": "application/json"} now = datetime.now() timestamp = f"{now.year}_{now.hour}_{now.minute}" - + if json: url = self.base + f"projects/{projectId}/forms/{xform}.svc/Submissions" filespec = f"{xform}_{timestamp}.json" @@ -762,15 +724,15 @@ def getSubmissions(self, file.close() return result.content else: - log.error(f'Submissions for {projectId}, Form {xform}' + " doesn't exist") + log.error(f"Submissions for {projectId}, Form {xform}" + " doesn't exist") return list() - def getSubmissionMedia(self, - projectId: int, - xform: str, - ): - """ - Fetch a ZIP file of the submissions with media to a survey form. + def getSubmissionMedia( + self, + projectId: int, + xform: str, + ): + """Fetch a ZIP file of the submissions with media to a survey form. Args: projectId (int): The ID of the project on ODK Central @@ -783,12 +745,12 @@ def getSubmissionMedia(self, result = self.session.get(url, auth=self.auth, verify=self.verify) return result - def addMedia(self, - media: str, - filespec: str, - ): - """ - Add a data file to this form + def addMedia( + self, + media: str, + filespec: str, + ): + """Add a data file to this form. Args: media (str): The media file @@ -797,13 +759,13 @@ def addMedia(self, # FIXME: this also needs the data self.media[filespec] = media - def addXMLForm(self, - projectId: int, - xmlFormId: int, - xform: str, - ): - """ - Add an XML file to this form + def addXMLForm( + self, + projectId: int, + xmlFormId: int, + xform: str, + ): + """Add an XML file to this form. Args: projectId (int): The ID of the project on ODK Central @@ -811,12 +773,12 @@ def addXMLForm(self, """ self.xml = xform - def listMedia(self, - projectId: int, - xform: str, - ): - """ - List all the attchements for this form + def listMedia( + self, + projectId: int, + xform: str, + ): + """List all the attchements for this form. Args: projectId (int): The ID of the project on ODK Central @@ -833,15 +795,14 @@ def listMedia(self, self.media = result.json() return self.media - - def uploadMedia(self, - projectId: int, - xform: str, - filespec: str, - convert_to_draft: bool = True, - ): - """ - Upload an attachement to the ODK Central server + def uploadMedia( + self, + projectId: int, + xform: str, + filespec: str, + convert_to_draft: bool = True, + ): + """Upload an attachement to the ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -851,9 +812,9 @@ def uploadMedia(self, """ title = os.path.basename(os.path.splitext(filespec)[0]) datafile = f"{title}.geojson" - xid = xform.split('_')[2] + xid = xform.split("_")[2] - if convert_to_draft: + if convert_to_draft: url = f"{self.base}projects/{projectId}/forms/{xid}/draft" result = self.session.post(url, auth=self.auth, verify=self.verify) if result.status_code == 200: @@ -867,9 +828,7 @@ def uploadMedia(self, file = open(filespec, "rb") media = file.read() file.close() - result = self.session.post( - url, auth=self.auth, data=media, headers=headers, verify=self.verify - ) + result = self.session.post(url, auth=self.auth, data=media, headers=headers, verify=self.verify) if result.status_code == 200: log.debug(f"Uploaded {filespec} to Central") else: @@ -878,13 +837,13 @@ def uploadMedia(self, return result - def getMedia(self, - projectId: int, - xform: str, - filename: str, - ): - """ - Fetch a specific attachment by filename from a submission to a form. + def getMedia( + self, + projectId: int, + xform: str, + filename: str, + ): + """Fetch a specific attachment by filename from a submission to a form. Args: projectId (int): The ID of the project on ODK Central @@ -907,14 +866,14 @@ def getMedia(self, self.media = result.content return self.media - def createForm(self, - projectId: int, - xform: str, - filespec: str, - draft: bool = False, - ): - """ - Create a new form on an ODK Central server + def createForm( + self, + projectId: int, + xform: str, + filespec: str, + draft: bool = False, + ): + """Create a new form on an ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -939,7 +898,7 @@ def createForm(self, file.close() log.info("Read %d bytes from %s" % (len(xml), filespec)) - result = self.session.post(url, auth=self.auth, data=xml, headers=headers, verify=self.verify) + result = self.session.post(url, auth=self.auth, data=xml, headers=headers, verify=self.verify) # epdb.st() # FIXME: should update self.forms with the new form if result.status_code != 200: @@ -951,12 +910,12 @@ def createForm(self, return result.status_code - def deleteForm(self, - projectId: int, - xform: str, - ): - """ - Delete a form from an ODK Central server + def deleteForm( + self, + projectId: int, + xform: str, + ): + """Delete a form from an ODK Central server. Args: projectId (int): The ID of the project on ODK Central @@ -973,12 +932,12 @@ def deleteForm(self, result = self.session.delete(url, auth=self.auth, verify=self.verify) return result - def publishForm(self, - projectId: int, - xform: str, - ): - """ - Publish a draft form. When creating a form that isn't a draft, it can get publised then + def publishForm( + self, + projectId: int, + xform: str, + ): + """Publish a draft form. When creating a form that isn't a draft, it can get publised then. Args: projectId (int): The ID of the project on ODK Central @@ -987,9 +946,9 @@ def publishForm(self, Returns: (int): The staus code from ODK Central """ - version = now = datetime.now().strftime("%Y-%m-%dT%TZ") + version = datetime.now().strftime("%Y-%m-%dT%TZ") if xform.find("_") > 0: - xid = xform.split('_')[2] + xid = xform.split("_")[2] else: xid = xform @@ -1003,7 +962,7 @@ def publishForm(self, return result.status_code def dump(self): - """Dump internal data structures, for debugging purposes only""" + """Dump internal data structures, for debugging purposes only.""" # super().dump() entries = len(self.media) print("Form has %d attachments" % entries) @@ -1013,13 +972,13 @@ def dump(self): class OdkAppUser(OdkCentral): - def __init__(self, - url: (str)= None, - user: str = None, - passwd: (str)= None, - ): - """ - A Class for app user data + def __init__( + self, + url: (str) = None, + user: str = None, + passwd: (str) = None, + ): + """A Class for app user data. Args: url (str): The URL of the ODK Central @@ -1034,12 +993,12 @@ def __init__(self, self.qrcode = None self.id = None - def create(self, - projectId: int, - name: str, - ): - """ - Create a new app-user for a form + def create( + self, + projectId: int, + name: str, + ): + """Create a new app-user for a form. Args: projectId (int): The ID of the project on ODK Central @@ -1049,18 +1008,16 @@ def create(self, (bool): Whether it was created or not """ url = f"{self.base}projects/{projectId}/app-users" - result = self.session.post( - url, auth=self.auth, json={"displayName": name}, verify=self.verify - ) + result = self.session.post(url, auth=self.auth, json={"displayName": name}, verify=self.verify) self.user = name return result - def delete(self, - projectId: int, - userId: int, - ): - """ - Create a new app-user for a form + def delete( + self, + projectId: int, + userId: int, + ): + """Create a new app-user for a form. Args: projectId (int): The ID of the project on ODK Central @@ -1073,14 +1030,14 @@ def delete(self, result = self.session.delete(url, auth=self.auth, verify=self.verify) return result - def updateRole(self, - projectId: int, - xform: str, - roleId: int = 2, - actorId: int = None, - ): - """ - Update the role of an app user for a form + def updateRole( + self, + projectId: int, + xform: str, + roleId: int = 2, + actorId: int = None, + ): + """Update the role of an app user for a form. Args: projectId (int): The ID of the project on ODK Central @@ -1088,7 +1045,7 @@ def updateRole(self, roleId (int): The role for the user actorId (int): The ID of the user - Returns + Returns: (bool): Whether it was update or not """ log.info("Update access to XForm %s for %s" % (xform, actorId)) @@ -1096,15 +1053,8 @@ def updateRole(self, result = self.session.post(url, auth=self.auth, verify=self.verify) return result - def grantAccess(self, - projectId: int, - roleId: int = 2, - userId: int = None, - xform: str = None, - actorId: int = None - ): - """ - Grant access to an app user for a form + def grantAccess(self, projectId: int, roleId: int = 2, userId: int = None, xform: str = None, actorId: int = None): + """Grant access to an app user for a form. Args: projectId (int): The ID of the project on ODK Central @@ -1113,20 +1063,20 @@ def grantAccess(self, xform (str): The XForm to get the details of from ODK Central actorId (int): The actor ID of the user on ODK Central - Returns + Returns: (bool): Whether access was granted or not """ url = f"{self.base}projects/{projectId}/forms/{xform}/assignments/{roleId}/{actorId}" result = self.session.post(url, auth=self.auth, verify=self.verify) return result - def createQRCode(self, - project_id: int, - token: str, - name: str, - ): - """ - Get the QR Code for an app-user + def createQRCode( + self, + project_id: int, + token: str, + name: str, + ): + """Get the QR Code for an app-user. Args: project_id (int): The ID of the project on ODK Central @@ -1136,9 +1086,7 @@ def createQRCode(self, Returns: (bytes): The new QR code """ - log.info( - 'Generating QR Code for app-user "%s" for project %s' % (name, project_id) - ) + log.info('Generating QR Code for app-user "%s" for project %s' % (name, project_id)) self.settings = { "general": { "server_url": f"{self.base}key/{token}/projects/{project_id}", @@ -1157,17 +1105,14 @@ def createQRCode(self, # This following code is only for debugging purposes, since this is easier # to use a debugger with instead of pytest. -if __name__ == '__main__': +if __name__ == "__main__": """ This main function lets this class be run standalone by a bash script for development purposes. To use it, try the odk_client program instead. """ logging.basicConfig( level=log_level, - format=( - "%(asctime)s.%(msecs)03d [%(levelname)s] " - "%(name)s | %(funcName)s:%(lineno)d | %(message)s" - ), + format=("%(asctime)s.%(msecs)03d [%(levelname)s] " "%(name)s | %(funcName)s:%(lineno)d | %(message)s"), datefmt="%y-%m-%d %H:%M:%S", stream=sys.stdout, ) diff --git a/osm_fieldwork/basemapper.py b/osm_fieldwork/basemapper.py index e2bf6d3a..98ba98b3 100755 --- a/osm_fieldwork/basemapper.py +++ b/osm_fieldwork/basemapper.py @@ -19,35 +19,34 @@ # import argparse -import os +import concurrent.futures +import json import logging +import os +import queue import sys -import json -from typing import Union +import threading import mercantile +from cpuinfo import get_cpu_info +from pySmartDL import SmartDL from shapely.geometry import shape from shapely.ops import unary_union -from pySmartDL import SmartDL -from cpuinfo import get_cpu_info -import queue -import concurrent.futures -import threading + from osm_fieldwork.sqlite import DataFile, MapTile from osm_fieldwork.xlsforms import xlsforms_path from osm_fieldwork.yamlfile import YamlFile - log = logging.getLogger(__name__) -def dlthread(dest: str, - mirrors: list, - tiles: list, - xy: bool, - ): - """ - Thread to handle downloads for Queue +def dlthread( + dest: str, + mirrors: list, + tiles: list, + xy: bool, +): + """Thread to handle downloads for Queue. Args: dest (str): The filespec of the tile cache @@ -63,10 +62,7 @@ def dlthread(dest: str, # start = datetime.now() # totaltime = 0.0 - log.info( - "Downloading %d tiles in thread %d to %s" - % (len(tiles), threading.get_ident(), dest) - ) + log.info("Downloading %d tiles in thread %d to %s" % (len(tiles), threading.get_ident(), dest)) for tile in tiles: bingkey = mercantile.quadkey(tile) filespec = f"{tile[2]}/{tile[1]}/{tile[0]}" @@ -74,12 +70,12 @@ def dlthread(dest: str, if site["source"] != "topo": filespec += "." + site["suffix"] url = site["url"] - if site["source"] == "bing": + if site["source"] == "bing": remote = url % bingkey - elif site["source"] == "google": + elif site["source"] == "google": path = f"x={tile[0]}&s=&y={tile[1]}&z={tile[2]}" remote = url % path - elif site["source"] == "custom": + elif site["source"] == "custom": if not xy: path = f"x={tile[0]}&s=&y={tile[1]}&z={tile[2]}" else: @@ -115,14 +111,14 @@ def dlthread(dest: str, class BaseMapper(object): - def __init__(self, - boundary: str, - base: str, - source: str, - xy: bool, - ): - """ - Create an mbtiles basemap for ODK Collect + def __init__( + self, + boundary: str, + base: str, + source: str, + xy: bool, + ): + """Create an mbtiles basemap for ODK Collect. Args: boundary (str): A BBOX string or GeoJSON file of the AOI. @@ -145,24 +141,24 @@ def __init__(self, path = xlsforms_path.replace("xlsforms", "imagery.yaml") self.yaml = YamlFile(path) - for entry in self.yaml.yaml['sources']: + for entry in self.yaml.yaml["sources"]: for k, v in entry.items(): src = dict() for item in v: - src['source'] = k + src["source"] = k for k1, v1 in item.items(): # print(f"\tFIXME2: {k1} - {v1}") src[k1] = v1 self.sources[k] = src - def customTMS(self, - url: str, - name: str = 'custom', - source: str = 'custom', - suffix: str = 'jpg', - ): - """ - Add a custom TMS URL to the list of sources. + def customTMS( + self, + url: str, + name: str = "custom", + source: str = "custom", + suffix: str = "jpg", + ): + """Add a custom TMS URL to the list of sources. Args: name (str): The name to display @@ -170,21 +166,20 @@ def customTMS(self, suffix (str): The suffix, png or jpg source (str): The source value to use as an index """ - self.sources['custom'] = {'name': name, 'url': url, 'suffix': suffix, 'source': source} - self.source = 'custom' + self.sources["custom"] = {"name": name, "url": url, "suffix": suffix, "source": source} + self.source = "custom" def getFormat(self): - """ - Returns: - (str): the upstream source for map tiles + """Returns: + (str): the upstream source for map tiles. """ return self.sources[self.source]["suffix"] - def getTiles(self, - zoom: int = None, - ): - """ - Get a list of tiles for the specifed zoom level + def getTiles( + self, + zoom: int = None, + ): + """Get a list of tiles for the specifed zoom level. Args: zoom (int): The Zoom level of the desired map tiles @@ -195,10 +190,7 @@ def getTiles(self, info = get_cpu_info() cores = info["count"] - self.tiles = list( - mercantile.tiles( - self.bbox[0], self.bbox[1], self.bbox[2], self.bbox[3], zoom) - ) + self.tiles = list(mercantile.tiles(self.bbox[0], self.bbox[1], self.bbox[2], self.bbox[3], zoom)) total = len(self.tiles) log.info("%d tiles for zoom level %d" % (len(self.tiles), zoom)) chunk = round(len(self.tiles) / cores) @@ -213,9 +205,7 @@ def getTiles(self, with concurrent.futures.ThreadPoolExecutor(max_workers=cores) as executor: block = 0 while block <= len(self.tiles): - executor.submit( - dlthread, self.base, mirrors, self.tiles[block : block + chunk] - ) + executor.submit(dlthread, self.base, mirrors, self.tiles[block : block + chunk]) log.debug("Dispatching Block %d:%d" % (block, block + chunk)) block += chunk executor.shutdown() @@ -223,11 +213,11 @@ def getTiles(self, return len(self.tiles) - def tileExists(self, - tile: MapTile, - ): - """ - See if a map tile already exists + def tileExists( + self, + tile: MapTile, + ): + """See if a map tile already exists. Args: tile (MapTile): The map tile to check for the existence of @@ -243,11 +233,11 @@ def tileExists(self, log.debug("%s doesn't exists" % filespec) return False - def makeBbox(self, - boundary: str, - ): - """ - Make a bounding box from a shapely geometry. + def makeBbox( + self, + boundary: str, + ): + """Make a bounding box from a shapely geometry. Args: boundary (str): A BBOX string or GeoJSON file of the AOI. @@ -259,7 +249,7 @@ def makeBbox(self, if not boundary.lower().endswith((".json", ".geojson")): # Is BBOX string try: - bbox_parts = boundary.split(',') + bbox_parts = boundary.split(",") bbox = tuple(float(x) for x in bbox_parts) if len(bbox) == 4: # BBOX valid @@ -275,8 +265,8 @@ def makeBbox(self, log.debug(f"Reading geojson file: {boundary}") with open(boundary, "r") as f: poly = json.load(f) - if 'features' in poly: - geometry = shape(poly['features'][0]['geometry']) + if "features" in poly: + geometry = shape(poly["features"][0]["geometry"]) else: geometry = shape(poly) @@ -296,10 +286,8 @@ def makeBbox(self, def main(): - """This main function lets this class be run standalone by a bash script""" - parser = argparse.ArgumentParser( - description="Create an mbtiles basemap for ODK Collect" - ) + """This main function lets this class be run standalone by a bash script.""" + parser = argparse.ArgumentParser(description="Create an mbtiles basemap for ODK Collect") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") parser.add_argument("-b", "--boundary", required=True, help="The boundary for the area you want") parser.add_argument("-t", "--tms", help="Custom TMS URL") @@ -307,7 +295,10 @@ def main(): parser.add_argument("-o", "--outfile", required=True, help="Output file name") parser.add_argument("-z", "--zooms", default="12-17", help="The Zoom levels") parser.add_argument("-d", "--outdir", help="Output directory name for tile cache") - parser.add_argument("-s", "--source", default="esri", + parser.add_argument( + "-s", + "--source", + default="esri", choices=["esri", "bing", "topo", "google", "oam"], help="Imagery source", ) @@ -318,9 +309,7 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) @@ -355,7 +344,7 @@ def main(): if args.source and not args.tms: basemap = BaseMapper(args.boundary, base, args.source, args.xy) elif args.tms: - basemap = BaseMapper(args.boundary, base, 'custom', args.xy) + basemap = BaseMapper(args.boundary, base, "custom", args.xy) basemap.customTMS(args.tms) else: log.error("You need to specify a source!") @@ -379,6 +368,7 @@ def main(): else: log.info("Only downloading tiles to %s!" % base) + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/convert.py b/osm_fieldwork/convert.py index 8c9b4db5..ed7975b0 100755 --- a/osm_fieldwork/convert.py +++ b/osm_fieldwork/convert.py @@ -18,19 +18,19 @@ # along with OSM-Fieldwork. If not, see . # -import os -from osm_fieldwork.yamlfile import YamlFile -from osm_fieldwork.xlsforms import xlsforms_path -import logging import argparse +import logging import sys +from osm_fieldwork.xlsforms import xlsforms_path +from osm_fieldwork.yamlfile import YamlFile + # Instantiate logger log = logging.getLogger(__name__) + def escape(value: str): - """ - Escape characters like embedded quotes in text fields + """Escape characters like embedded quotes in text fields. Args: value (str):The string to modify @@ -44,16 +44,16 @@ def escape(value: str): class Convert(YamlFile): - """ - A class to apply a YAML config file and convert ODK to OSM + """A class to apply a YAML config file and convert ODK to OSM. Returns: (Convert): An instance of this object """ - def __init__(self, - xform: str = None, - ): + def __init__( + self, + xform: str = None, + ): path = xlsforms_path.replace("xlsforms", "") if xform is not None: file = xform @@ -84,16 +84,16 @@ def __init__(self, self.convert[key] = vals self.ignore = self.yaml.yaml["ignore"] self.private = self.yaml.yaml["private"] - if "multiple" in self.yaml.yaml: + if "multiple" in self.yaml.yaml: self.multiple = self.yaml.yaml["multiple"] else: self.multiple = list() - def privateData(self, - keyword: str, - ): - """ - See is a keyword is in the private data category + def privateData( + self, + keyword: str, + ): + """See is a keyword is in the private data category. Args: keyword (str): The keyword to search for @@ -103,12 +103,12 @@ def privateData(self, """ return keyword.lower() in self.private - def convertData(self, - keyword: str, - ): - """ - See is a keyword is in the convert data category - + def convertData( + self, + keyword: str, + ): + """See is a keyword is in the convert data category. + Args: keyword (str): The keyword to search for @@ -117,12 +117,12 @@ def convertData(self, """ return keyword.lower() in self.convert - def ignoreData(self, - keyword: str, - ): - """ - See is a keyword is in the convert data category - + def ignoreData( + self, + keyword: str, + ): + """See is a keyword is in the convert data category. + Args: keyword (str): The keyword to search for @@ -131,12 +131,12 @@ def ignoreData(self, """ return keyword.lower() in self.ignore - def getKeyword(self, - value: str, - ): - """ - Get the keyword for a value from the yaml file - + def getKeyword( + self, + value: str, + ): + """Get the keyword for a value from the yaml file. + Args: value (str): The value to find the keyword for Returns: @@ -149,12 +149,12 @@ def getKeyword(self, key = self.yaml.getKeyword(value) return key - def getValues(self, - keyword: str = None, - ): - """ - Get the values for a primary key - + def getValues( + self, + keyword: str = None, + ): + """Get the values for a primary key. + Args: keyword (str): The keyword to get the value of @@ -167,13 +167,13 @@ def getValues(self, else: return None - def convertEntry(self, - tag: str, - value: str, - ): - """ - Convert a tag and value from the ODK represention to an OSM one - + def convertEntry( + self, + tag: str, + value: str, + ): + """Convert a tag and value from the ODK represention to an OSM one. + Args: tag (str): The tag from the ODK XML file value (str): The value from the ODK XML file @@ -188,11 +188,7 @@ def convertEntry(self, # logging.debug(f"FIXME: Ignoring {tag}") return None low = tag.lower() - if ( - low not in self.convert - and low not in self.ignore - and low not in self.private - ): + if low not in self.convert and low not in self.ignore and low not in self.private: return {tag: value} newtag = tag.lower() @@ -220,13 +216,13 @@ def convertEntry(self, all.append({k: v}) return all - def convertValue(self, - tag: str, - value: str, - ): - """ - Convert a single tag value - + def convertValue( + self, + tag: str, + value: str, + ): + """Convert a single tag value. + Args: tag (str): The tag from the ODK XML file value (str): The value from the ODK XML file @@ -264,12 +260,12 @@ def convertValue(self, all.append(entry) return all - def convertTag(self, - tag: str, - ): - """ - Convert a single tag - + def convertTag( + self, + tag: str, + ): + """Convert a single tag. + Args: tag (str): The tag from the ODK XML file @@ -296,7 +292,7 @@ def convertTag(self, return low def dump(self): - """Dump internal data structures, for debugging purposes only""" + """Dump internal data structures, for debugging purposes only.""" print("YAML file: %s" % self.filespec) print("Convert section") for key, val in self.convert.items(): @@ -311,17 +307,22 @@ def dump(self): for item in self.ignore: print(f"\tIgnoring tag {item}") + # # This script can be run standalone for debugging purposes. It's easier to debug # this way than using pytest, # def main(): - """This main function lets this class be run standalone by a bash script""" + """This main function lets this class be run standalone by a bash script.""" parser = argparse.ArgumentParser(description="Read and parse a YAML file") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") parser.add_argument("-x", "--xform", default="xform.yaml", help="Default Yaml file") - parser.add_argument("-i", "--infile", required=True, help="The CSV input file", + parser.add_argument( + "-i", + "--infile", + required=True, + help="The CSV input file", ) args = parser.parse_args() @@ -330,12 +331,10 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) - + # convert = Convert(args.xform) convert = Convert("xforms.yaml") print("-----") @@ -376,6 +375,7 @@ def main(): for i in entry: print("XX: %r" % i) + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/data_models/buildings.yaml b/osm_fieldwork/data_models/buildings.yaml index 05006a77..55165910 100644 --- a/osm_fieldwork/data_models/buildings.yaml +++ b/osm_fieldwork/data_models/buildings.yaml @@ -1,22 +1,22 @@ select: - - osm_id: id - - version + - osm_id: id + - version from: - - nodes - - ways_poly + - nodes + - ways_poly where: tags: - join_or: - { building: not null, amenity: not null, tourism: not null } keep: - - building - - building:levels - - building:material - - roof:material - - roof:shape - - roof:levels - - cusine - - amenity - - convenience - - diesel - - version + - building + - building:levels + - building:material + - roof:material + - roof:shape + - roof:levels + - cusine + - amenity + - convenience + - diesel + - version diff --git a/osm_fieldwork/data_models/camping.yaml b/osm_fieldwork/data_models/camping.yaml index 7d8160b7..ca6310fd 100644 --- a/osm_fieldwork/data_models/camping.yaml +++ b/osm_fieldwork/data_models/camping.yaml @@ -1,8 +1,8 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: tags: - join_or: diff --git a/osm_fieldwork/data_models/category.yaml b/osm_fieldwork/data_models/category.yaml index 2c4d16fc..6c4da1a7 100644 --- a/osm_fieldwork/data_models/category.yaml +++ b/osm_fieldwork/data_models/category.yaml @@ -1,58 +1,58 @@ # landuse is an official OSM tag - landuse: - - landusage.xls + - landusage.xls # healthcare is an official OSM tag - healthcare: - - health.xls + - health.xls # natural is an official OSM tag - natural: - - nature.xls + - nature.xls # historic is an official OSM tag - historic: - - historical.xls + - historical.xls # highway is an official OSM tag - highways: - - highways.xls + - highways.xls - waterpoints: - - waterpoints.xls + - waterpoints.xls - buildings: - - buildings.xls + - buildings.xls - cemeteries: - - cemeteries.xls + - cemeteries.xls - education: - - education.xls + - education.xls - places: - - places.xls + - places.xls - religious: - - religious.xls + - religious.xls - toilets: - - toilets.xls + - toilets.xls - transportation: - - transportation.xls + - transportation.xls - wastedisposal: - - wastedisposal.xls + - wastedisposal.xls - waterways: - - waterways.xls + - waterways.xls - camping: - - camping.xls + - camping.xls - amenity: - - amenities.xls + - amenities.xls - hotspring: - - hotspring.xls + - hotspring.xls diff --git a/osm_fieldwork/data_models/cemeteries.yaml b/osm_fieldwork/data_models/cemeteries.yaml index 1f64ad92..887418e3 100644 --- a/osm_fieldwork/data_models/cemeteries.yaml +++ b/osm_fieldwork/data_models/cemeteries.yaml @@ -1,8 +1,8 @@ select: - osm_id: id + osm_id: id from: - - ways_poly + - ways_poly where: - tags: - - join_or: - - { amenity: [grave_yard], landuse: [cemetery] } + tags: + - join_or: + - { amenity: [grave_yard], landuse: [cemetery] } diff --git a/osm_fieldwork/data_models/education.yaml b/osm_fieldwork/data_models/education.yaml index c73f3459..c6de922f 100644 --- a/osm_fieldwork/data_models/education.yaml +++ b/osm_fieldwork/data_models/education.yaml @@ -1,12 +1,22 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { amenity: [college, indergarten, school, music_school, language_school, university]} + tags: + - join_or: + - { + amenity: + [ + college, + indergarten, + school, + music_school, + language_school, + university, + ], + } keep: - wheelchar - religion @@ -18,5 +28,3 @@ keep: - building:lateral:system - building:lateral:material - building:levels:underground - - diff --git a/osm_fieldwork/data_models/emergency.yaml b/osm_fieldwork/data_models/emergency.yaml index fe09122b..c2ffca82 100644 --- a/osm_fieldwork/data_models/emergency.yaml +++ b/osm_fieldwork/data_models/emergency.yaml @@ -1,9 +1,9 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { emergency: not null, fire_hydrant:type: not null } + tags: + - join_or: + - { emergency: not null, fire_hydrant:type: not null } diff --git a/osm_fieldwork/data_models/health.yaml b/osm_fieldwork/data_models/health.yaml index ec12c4ef..5b484060 100644 --- a/osm_fieldwork/data_models/health.yaml +++ b/osm_fieldwork/data_models/health.yaml @@ -1,12 +1,16 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { healthcare: not null, social_facility: not null, healthcare:speciality: not null } + tags: + - join_or: + - { + healthcare: not null, + social_facility: not null, + healthcare:speciality: not null, + } keep: - healthcare:type - healthcare diff --git a/osm_fieldwork/data_models/highways.yaml b/osm_fieldwork/data_models/highways.yaml index c106464e..3e7e39d6 100644 --- a/osm_fieldwork/data_models/highways.yaml +++ b/osm_fieldwork/data_models/highways.yaml @@ -1,15 +1,15 @@ select: - - osm_id: id - - version + - osm_id: id + - version from: - - ways_line + - ways_line where: tags: - - join_or: + - join_or: - { highway: not null } keep: - - name - - smoothness - - surface - - tracktype - - width + - name + - smoothness + - surface + - tracktype + - width diff --git a/osm_fieldwork/data_models/landusage.yaml b/osm_fieldwork/data_models/landusage.yaml index 410bd8c2..70bfc6d5 100644 --- a/osm_fieldwork/data_models/landusage.yaml +++ b/osm_fieldwork/data_models/landusage.yaml @@ -1,8 +1,8 @@ select: - osm_id: id + osm_id: id from: - - ways_poly + - ways_poly where: - tags: - - join_or: - - { landuse: not null } + tags: + - join_or: + - { landuse: not null } diff --git a/osm_fieldwork/data_models/nature.yaml b/osm_fieldwork/data_models/nature.yaml index 2b5e444b..1738c48f 100644 --- a/osm_fieldwork/data_models/nature.yaml +++ b/osm_fieldwork/data_models/nature.yaml @@ -1,9 +1,9 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { natural: not null } + tags: + - join_or: + - { natural: not null } diff --git a/osm_fieldwork/data_models/places.yaml b/osm_fieldwork/data_models/places.yaml index 5ff2dc70..feed8e29 100644 --- a/osm_fieldwork/data_models/places.yaml +++ b/osm_fieldwork/data_models/places.yaml @@ -1,9 +1,9 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { place: not null } + tags: + - join_or: + - { place: not null } diff --git a/osm_fieldwork/data_models/religious.yaml b/osm_fieldwork/data_models/religious.yaml index adcc062b..f74ef66f 100644 --- a/osm_fieldwork/data_models/religious.yaml +++ b/osm_fieldwork/data_models/religious.yaml @@ -1,9 +1,9 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { religion: not null } + tags: + - join_or: + - { religion: not null } diff --git a/osm_fieldwork/data_models/toilets.yaml b/osm_fieldwork/data_models/toilets.yaml index 65cc3d14..7982be00 100644 --- a/osm_fieldwork/data_models/toilets.yaml +++ b/osm_fieldwork/data_models/toilets.yaml @@ -1,9 +1,16 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { amenity: not null, toilets:access: not null, toilets:disposal: not null, toilets:position: not null, toilets:unisex: not null, toilets:wheelchair: not null } + tags: + - join_or: + - { + amenity: not null, + toilets:access: not null, + toilets:disposal: not null, + toilets:position: not null, + toilets:unisex: not null, + toilets:wheelchair: not null, + } diff --git a/osm_fieldwork/data_models/validate.py b/osm_fieldwork/data_models/validate.py index 737224e0..de3c7866 100755 --- a/osm_fieldwork/data_models/validate.py +++ b/osm_fieldwork/data_models/validate.py @@ -20,11 +20,12 @@ import argparse import logging +import sqlite3 import sys -import os + import pandas as pd -import sqlite3 import requests + # from requests.auth import HTTPBasicAuth # @@ -35,7 +36,8 @@ # # Instantiate logger -logging.basicConfig(stream = sys.stdout,level=logging.INFO) +logging.basicConfig(stream=sys.stdout, level=logging.INFO) + class ValidateModel(object): def __init__(self, taginfo=None): @@ -49,19 +51,19 @@ def __init__(self, taginfo=None): self.threshold = dict() self.url = "https://taginfo.openstreetmap.org/taginfo/apidoc#api_4_key_values?" self.session = requests.Session() - self.headers = {'Content-Type': 'application/json;charset=UTF-8'} + self.headers = {"Content-Type": "application/json;charset=UTF-8"} self.threshold = 100 def parse(self): - models = 'Impact Areas - Data Models V1.1.xlsx' + models = "Impact Areas - Data Models V1.1.xlsx" data = pd.read_excel(models, sheet_name="Overview - all Tags", usecols=["key", "value"]) - + entries = data.to_dict() - total = len(entries['key']) + total = len(entries["key"]) index = 1 while index < total: - key = entries['key'][index] - value = entries['value'][index] + key = entries["key"][index] + value = entries["value"][index] if value == "": index += 1 continue @@ -77,7 +79,7 @@ def validateTaginfo(self, csv=None): threshold = self.threshold for key in self.tags.keys(): for value in self.tags[key]: - if value[:3] == 'yes' or value[:2] == 'no' or value[0] == '<': + if value[:3] == "yes" or value[:2] == "no" or value[0] == "<": continue threshold = self.threshold sql = f"SELECT value,count_all FROM tags where key='{key}'" @@ -85,25 +87,22 @@ def validateTaginfo(self, csv=None): result = self.cursor.execute(sql) data = result.fetchall() if len(data) == 0: - logging.warning(f"\'{key}\' does not exist in taginfo!") + logging.warning(f"'{key}' does not exist in taginfo!") else: for val in data: if val[0] == value: - if value[:3] == 'yes' or value[:2] == 'no' or value[0] == '<': + if value[:3] == "yes" or value[:2] == "no" or value[0] == "<": continue # logging.info(f"\"{value}\" exists in the taginfo for \"{key}\"!") if val[1] < threshold: - logging.warning(f"\"{value}\" doesn't pass the threshold for \"{key}\"! Only {val[1]} occurances") + logging.warning(f'"{value}" doesn\'t pass the threshold for "{key}"! Only {val[1]} occurances') if csv: csvfile.write(f"{key},{value},{val[1]}\n") break - if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Validate data_models using taginfo database" - ) + parser = argparse.ArgumentParser(description="Validate data_models using taginfo database") parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") parser.add_argument("-t", "--taginfo", help="Taginfo database") parser.add_argument("-c", "--csv", help="Output CSV") @@ -116,9 +115,7 @@ def validateTaginfo(self, csv=None): ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) root.addHandler(ch) @@ -126,4 +123,3 @@ def validateTaginfo(self, csv=None): tags = model.parse() # import epdb; epdb.st() model.validateTaginfo(args.csv) - diff --git a/osm_fieldwork/data_models/wastedisposal.yaml b/osm_fieldwork/data_models/wastedisposal.yaml index 2d7457dd..a003712d 100644 --- a/osm_fieldwork/data_models/wastedisposal.yaml +++ b/osm_fieldwork/data_models/wastedisposal.yaml @@ -1,9 +1,25 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: - tags: - - join_or: - - { amenity: [waste_disposal, waste_dump_site, waste_transfer_station, waste_basket, recycling, landfill], waste: not null, recycling_type: not null, recycling: not null, recycling:aluminum: not null, recycling:glass: not null, recycling:hazardous_waste: not null } + tags: + - join_or: + - { + amenity: + [ + waste_disposal, + waste_dump_site, + waste_transfer_station, + waste_basket, + recycling, + landfill, + ], + waste: not null, + recycling_type: not null, + recycling: not null, + recycling:aluminum: not null, + recycling:glass: not null, + recycling:hazardous_waste: not null, + } diff --git a/osm_fieldwork/data_models/waterpoints.yaml b/osm_fieldwork/data_models/waterpoints.yaml index db5206bd..59c7f59c 100644 --- a/osm_fieldwork/data_models/waterpoints.yaml +++ b/osm_fieldwork/data_models/waterpoints.yaml @@ -1,9 +1,13 @@ select: - osm_id: id + osm_id: id from: - - nodes - - ways_poly + - nodes + - ways_poly where: tags: - join_or: - - { drinking_water: not null, amenity: [drinking_water, water_point], man_made: [water_tap] } + - { + drinking_water: not null, + amenity: [drinking_water, water_point], + man_made: [water_tap], + } diff --git a/osm_fieldwork/data_models/waterways.yaml b/osm_fieldwork/data_models/waterways.yaml index 3a6e2aa9..b4865fc7 100644 --- a/osm_fieldwork/data_models/waterways.yaml +++ b/osm_fieldwork/data_models/waterways.yaml @@ -1,13 +1,12 @@ select: - - osm_id: id - - version + - osm_id: id + - version from: - - ways_line + - ways_line where: tags: - join_or: - { waterway: not null } keep: - - name - - intermittent - + - name + - intermittent diff --git a/osm_fieldwork/filter_data.py b/osm_fieldwork/filter_data.py index d7f5fd43..ff6a48af 100755 --- a/osm_fieldwork/filter_data.py +++ b/osm_fieldwork/filter_data.py @@ -20,16 +20,18 @@ import argparse import logging -import sys import os +import sys + +import geojson import pandas as pd from geojson import Feature, FeatureCollection -import geojson -from osm_fieldwork.xlsforms import xlsforms_path from osm_rawdata.config import QueryConfig # Find the other files for this project import osm_fieldwork as of +from osm_fieldwork.xlsforms import xlsforms_path + rootdir = of.__path__[0] # Instantiate logger @@ -37,14 +39,13 @@ class FilterData(object): - def __init__(self, - filespec: str = None, - config: QueryConfig = None, - ): - """ - - Args: - filespec (str): The optional data file to read + def __init__( + self, + filespec: str = None, + config: QueryConfig = None, + ): + """Args: + filespec (str): The optional data file to read. Returns: (FilterData): An instance of this object @@ -54,12 +55,12 @@ def __init__(self, if filespec and config: self.parse(filespec, config) - def parse(self, - filespec: str, - config: QueryConfig, - ): - """ - Read in the XLSForm and extract the data we want + def parse( + self, + filespec: str, + config: QueryConfig, + ): + """Read in the XLSForm and extract the data we want. Args: filespec (str): The filespec to the XLSForm file @@ -71,27 +72,26 @@ def parse(self, if config: self.qc = config excel_object = pd.ExcelFile(filespec) - entries = excel_object.parse(sheet_name=[0, 1, 2], index_col=0, - usercols=[0, 1, 2]) + entries = excel_object.parse(sheet_name=[0, 1, 2], index_col=0, usercols=[0, 1, 2]) entries = pd.read_excel(filespec, sheet_name=[0, 1, 2]) - title = entries[2]['form_title'].to_list()[0] + title = entries[2]["form_title"].to_list()[0] extract = "" - for entry in entries[0]['type']: - if str(entry) == 'nan': + for entry in entries[0]["type"]: + if str(entry) == "nan": continue if entry[:20] == "select_one_from_file": extract = entry[21:] - log.info(f"Got data extract filename: \"{extract}\", title: \"{title}\"") + log.info(f'Got data extract filename: "{extract}", title: "{title}"') else: extract = "none" - total = len(entries[1]['list_name']) + total = len(entries[1]["list_name"]) index = 1 while index < total: - key = entries[1]['list_name'][index] - if key == 'model' or str(key) == "nan": + key = entries[1]["list_name"][index] + if key == "model" or str(key) == "nan": index += 1 continue - value = entries[1]['name'][index] + value = entries[1]["name"][index] if value == "" or str(value) == "null": index += 1 continue @@ -103,38 +103,39 @@ def parse(self, # The yaml config file for the query has a list of columns # to keep in addition to this default set. These wind up # in the SELECT - keep = ("name", - "name:en", - "id", - "operator", - "addr:street", - "addr:housenumber", - "osm_id", - "title", - "tags", - "label", - "landuse", - "opening_hours", - "tourism", - ) + keep = ( + "name", + "name:en", + "id", + "operator", + "addr:street", + "addr:housenumber", + "osm_id", + "title", + "tags", + "label", + "landuse", + "opening_hours", + "tourism", + ) self.keep = list(keep) - if 'keep' in config['keep']: - self.keep.extend(config['keep']) + if "keep" in config["keep"]: + self.keep.extend(config["keep"]) return title, extract - def cleanData(self, - data, - ): - """ - Filter out any data not in the data_model + def cleanData( + self, + data, + ): + """Filter out any data not in the data_model. Args: data (bytes): The input data or filespec to the input data file Returns: (FeatureCollection): The modifed data - + """ log.debug("Cleaning data...") if type(data) == str: @@ -150,25 +151,25 @@ def cleanData(self, "timestamp", "version", "changeset", - ) - keep = ('osm_id', 'id', 'version') + ) + keep = ("osm_id", "id", "version") collection = list() - for feature in indata['features']: - log.debug(f'FIXME0: {feature}') + for feature in indata["features"]: + log.debug(f"FIXME0: {feature}") properties = dict() - for key, value in feature['properties'].items(): + for key, value in feature["properties"].items(): log.debug(f"FIXME1: {key} = {value}") - if key in self.qc['keep']: - if key == 'tags': + if key in self.qc["keep"]: + if key == "tags": for k, v in value.items(): if k[:4] == "name": - properties['title'] = value[k] - properties['label'] = value[k] + properties["title"] = value[k] + properties["label"] = value[k] else: properties[k] = v else: - if key == 'osm_id': - properties['id'] = value + if key == "osm_id": + properties["id"] = value else: properties[key] = value else: @@ -177,9 +178,9 @@ def cleanData(self, properties[key] = value continue if key in self.tags: - if key == "name" or key == 'name:en': - properties['title'] = self.tags[key] - properties['label'] = self.tags[key] + if key == "name" or key == "name:en": + properties["title"] = self.tags[key] + properties["label"] = self.tags[key] if value in self.tags[key]: properties[key] = value else: @@ -191,20 +192,19 @@ def cleanData(self, continue log.warning(f"Tag {key} not in the data model!") continue - newfeature = Feature(geometry=feature['geometry'], properties=properties) + newfeature = Feature(geometry=feature["geometry"], properties=properties) collection.append(newfeature) if type(data) == str: geojson.dump(FeatureCollection(collection), outfile) return FeatureCollection(collection) + def main(): - """This main function lets this class be run standalone by a bash script""" - parser = argparse.ArgumentParser( - description="Convert ODK XML instance file to CSV format" - ) + """This main function lets this class be run standalone by a bash script.""" + parser = argparse.ArgumentParser(description="Convert ODK XML instance file to CSV format") parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") parser.add_argument("-i", "--infile", required=True, help="The data extract for ODK Collect") - parser.add_argument("-x", "--xform", required=True, help="The XForm for ODK Collect") + parser.add_argument("-x", "--xform", required=True, help="The XForm for ODK Collect") parser.add_argument("-o", "--outfile", default="models.yaml", help="The Yaml file of all tags and values") args = parser.parse_args() @@ -213,34 +213,35 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) - xls = FilterData() - path = xlsforms_path.replace("xlsforms", "data_models") + FilterData() + xlsforms_path.replace("xlsforms", "data_models") models = FilterData() data = models.parse(args.xform) if args.infile: - cleaned = models.cleanData(args.infile) + models.cleanData(args.infile) else: if os.path.exists(args.outfile): os.remove(args.outfile) outfile = open(args.outfile, "w") current = None tab = " " - outfile.write(""" + outfile.write( + """ # Do not edit this file, it is generated by the filter_data.py # script from the XForm spreadsheet. - """) + """ + ) for key, value in data.items(): if key != current: outfile.write(f"\n{key}:\n") for val in value: outfile.write(f"{tab}- {val}\n") + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/json2osm.py b/osm_fieldwork/json2osm.py index 1ce2a619..83707c48 100755 --- a/osm_fieldwork/json2osm.py +++ b/osm_fieldwork/json2osm.py @@ -19,35 +19,34 @@ # import argparse -import csv -import os +import json import logging + +# import pandas as pd +import re import sys -import json -import geojson -import shapely -from sys import argv -from osm_fieldwork.convert import Convert, escape -from osm_fieldwork.osmfile import OsmFile -from osm_fieldwork.xlsforms import xlsforms_path -from geojson import Point, Feature, FeatureCollection, dump from pathlib import Path -#import pandas as pd -import math -import re + import flatdict +import geojson +import shapely +from geojson import Feature, FeatureCollection, Point, dump +from osm_fieldwork.convert import Convert +from osm_fieldwork.osmfile import OsmFile # set log level for urlib log = logging.getLogger(__name__) + class JsonDump(Convert): - """A class to parse the JSON files from ODK Central or odk2geojson""" - def __init__(self, - yaml: str = None, - ): - """ - A class to convert the JSON file from ODK Central, or the GeoJson + """A class to parse the JSON files from ODK Central or odk2geojson.""" + + def __init__( + self, + yaml: str = None, + ): + """A class to convert the JSON file from ODK Central, or the GeoJson file created by the odk2geojson utility. Args: @@ -86,11 +85,11 @@ def __init__(self, # i += 1 # return True - def createOSM(self, - filespec: str = "tmp.osm", - ): - """ - Create an OSM XML output files + def createOSM( + self, + filespec: str = "tmp.osm", + ): + """Create an OSM XML output files. Args: filespec (str): The filespec for the output OSM XML file @@ -102,11 +101,11 @@ def createOSM(self, self.osm = OsmFile(filespec) return self.osm - def writeOSM(self, - feature: dict, - ): - """ - Write a feature to an OSM XML output file + def writeOSM( + self, + feature: dict, + ): + """Write a feature to an OSM XML output file. Args: feature (dict): The feature to write to the OSM XML output file @@ -116,12 +115,12 @@ def writeOSM(self, feature["id"] = feature["tags"]["id"] if "lat" not in feature["attrs"] or "lon" not in feature["attrs"]: return None - if 'user' in feature['tags'] and 'user' not in feature["attrs"]: - feature["attrs"]['user'] = feature['tags']['user'] - del feature['tags']['user'] - if 'uid' in feature['tags'] and 'uid' not in ["attrs"]: - feature["attrs"]['uid'] = feature['tags']['uid'] - del feature['tags']['uid'] + if "user" in feature["tags"] and "user" not in feature["attrs"]: + feature["attrs"]["user"] = feature["tags"]["user"] + del feature["tags"]["user"] + if "uid" in feature["tags"] and "uid" not in ["attrs"]: + feature["attrs"]["uid"] = feature["tags"]["uid"] + del feature["tags"]["uid"] if "refs" not in feature: out += self.osm.createNode(feature, True) else: @@ -129,17 +128,16 @@ def writeOSM(self, self.osm.write(out) def finishOSM(self): - """ - Write the OSM XML file footer and close it. The destructor in the + """Write the OSM XML file footer and close it. The destructor in the OsmFile class should do this, but this is the manual way. """ self.osm.footer() - def createGeoJson(self, - file="tmp.geojson", - ): - """ - Create a GeoJson output file + def createGeoJson( + self, + file="tmp.geojson", + ): + """Create a GeoJson output file. Args: file (str): The filespec of the output GeoJson file @@ -147,11 +145,11 @@ def createGeoJson(self, log.debug("Creating GeoJson file: %s" % file) self.json = open(file, "w") - def writeGeoJson(self, - feature: dict, - ): - """ - Write a feature to a GeoJson output file + def writeGeoJson( + self, + feature: dict, + ): + """Write a feature to a GeoJson output file. Args: feature (dict): The feature to write to the GeoJson output file @@ -162,12 +160,10 @@ def writeGeoJson(self, self.features.append(feature) def finishGeoJson(self): - """ - Write the GeoJson FeatureCollection to the output file and close it. - """ + """Write the GeoJson FeatureCollection to the output file and close it.""" features = list() for item in self.features: - #poi = Point() + # poi = Point() poi = Point((float(item["attrs"]["lon"]), float(item["attrs"]["lat"]))) if "private" in item: props = {**item["tags"], **item["private"]} @@ -177,12 +173,12 @@ def finishGeoJson(self): collection = FeatureCollection(features) dump(collection, self.json) - def parse(self, - filespec: str = None, - data: str = None, - ): - """ - Parse the JSON file from ODK Central and convert it to a data structure. + def parse( + self, + filespec: str = None, + data: str = None, + ): + """Parse the JSON file from ODK Central and convert it to a data structure. The input is either a filespec to open, or the data itself. Args: @@ -208,43 +204,43 @@ def parse(self, reader = geojson.loads(data) elif isinstance(data, list): reader = data - + total = list() # JSON files from Central use value as the keyword, whereas # GeoJSON uses features for the same thing. - if 'value' in reader: - data = reader['value'] - elif 'features' in reader: - data = reader['features'] + if "value" in reader: + data = reader["value"] + elif "features" in reader: + data = reader["features"] else: data = reader for row in data: # log.info(f"ROW: {row}") tags = dict() - if 'geometry' in row: + if "geometry" in row: # If geom not point, convert to centroid - if row['geometry']['type'] != 'Point': + if row["geometry"]["type"] != "Point": log.debug(f"Converting {row['geometry']['type']} geometry to centroid point") geom = shapely.from_geojson(str(row)) centroid = shapely.to_geojson(geom.centroid) - row['geometry'] = centroid - tags['geometry'] = row['geometry'] + row["geometry"] = centroid + tags["geometry"] = row["geometry"] else: pat = re.compile("[-0-9.]*, [0-9.-]*, [0-9.]*") gps = re.findall(pat, str(row)) # If geopoint warmup is used, there will be two matches, we only # want the second one, which is the location. for coords in gps: - tags['geometry'] = coords - if 'properties' in row: - indata = row['properties'] # A GeoJson formatted file + tags["geometry"] = coords + if "properties" in row: + row["properties"] # A GeoJson formatted file else: - indata = row # A JOSM file from ODK Central + pass # A JOSM file from ODK Central # flatten all the groups into a single data structure flattened = flatdict.FlatDict(row) for k, v in flattened.items(): - last = k.rfind(':') + 1 + last = k.rfind(":") + 1 key = k[last:] # log.debug(f"Processing tag {key} = {v}") # names and comments may have spaces, otherwise @@ -255,15 +251,15 @@ def parse(self, for name in names: tags[name] = v continue - if key == 'comment': + if key == "comment": tags[key] = v # a JSON file from ODK Central always uses coordinates as # the keyword - if key == 'coordinates': + if key == "coordinates": if isinstance(v, list): lat = v[1] lon = v[0] - tags['geometry'] = f"{lat} {lon}" + tags["geometry"] = f"{lat} {lon}" continue tags[key] = v total.append(tags) @@ -271,11 +267,11 @@ def parse(self, log.debug(f"Finsished parsing JSON file {filespec}") return total - def createEntry(self, - entry: dict, - ): - """ - Create the feature data structure for this entry. + def createEntry( + self, + entry: dict, + ): + """Create the feature data structure for this entry. Args: entry (dict): The feature to convert to the output format @@ -293,16 +289,6 @@ def createEntry(self, # log.debug("Creating entry") # First convert the tag to the approved OSM equivalent for key, value in entry.items(): - attributes = ( - "id", - "timestamp", - "lat", - "lon", - "uid", - "user", - "version", - "action", - ) # When using existing OSM data, there's a special geometry field. # Otherwise use the GPS coordinates where you are. lat = None @@ -311,11 +297,11 @@ def createEntry(self, continue # log.debug(f"FIXME: {key} = {value} {type(value)}") if key == "xid" and value is not None: - attrs['id'] = int(value) + attrs["id"] = int(value) if key == "geometry": # The GeoJson file has the geometry field. Usually it's a list # but on occasion it's a string instead, so turn it into a list - if isinstance(value, str) and len(coords:=value.split(' ')) >= 2: + if isinstance(value, str) and len(coords := value.split(" ")) >= 2: lat = coords[0] lon = coords[1] @@ -323,7 +309,7 @@ def createEntry(self, else: geom = shapely.from_geojson(str(value)) - if geom.geom_type != 'Point': + if geom.geom_type != "Point": # Use centroid if polygon geom = geom.centroid @@ -368,8 +354,8 @@ def createEntry(self, tags.update(entry) if len(tags) > 0: - if 'geometry' in tags: - del tags['geometry'] + if "geometry" in tags: + del tags["geometry"] feature["attrs"] = attrs feature["tags"] = tags if len(refs) > 0: @@ -379,9 +365,9 @@ def createEntry(self, return feature + def json2osm(input_file, yaml_file=None): - """ - Process the JSON file from ODK Central or the GeoJSON file to OSM XML format. + """Process the JSON file from ODK Central or the GeoJSON file to OSM XML format. Args: input_file (str): The path to the input JSON or GeoJSON file. @@ -417,13 +403,13 @@ def json2osm(input_file, yaml_file=None): if len(feature) > 0: if "lat" not in feature["attrs"]: - if 'geometry' in feature['tags']: + if "geometry" in feature["tags"]: if isinstance(feature["tags"]["geometry"], str): - coords = list(feature['tags']['geometry']) + coords = list(feature["tags"]["geometry"]) # del feature['tags']['geometry'] - elif 'coordinates' in feature['tags']: - coords = feature['tags']['coordinates'] - feature['attrs'] = {'lat': coords[1], 'lon': coords[0]} + elif "coordinates" in feature["tags"]: + coords = feature["tags"]["coordinates"] + feature["attrs"] = {"lat": coords[1], "lon": coords[0]} else: log.warning(f"Bad record! {feature}") continue # Skip bad records @@ -439,15 +425,11 @@ def json2osm(input_file, yaml_file=None): def main(): """Run conversion directly from the terminal.""" - parser = argparse.ArgumentParser( - description="convert JSON from ODK Central to OSM XML" - ) + parser = argparse.ArgumentParser(description="convert JSON from ODK Central to OSM XML") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") parser.add_argument("-y", "--yaml", help="Alternate YAML file") parser.add_argument("-x", "--xlsfile", help="Source XLSFile") - parser.add_argument("-i", "--infile", required=True, - help="The input file downloaded from ODK Central" - ) + parser.add_argument("-i", "--infile", required=True, help="The input file downloaded from ODK Central") args = parser.parse_args() # if verbose, dump to the terminal. @@ -455,14 +437,13 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) - log.addHandler(ch) + log.addHandler(ch) json2osm(args.infile, args.yaml) + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/make_data_extract.py b/osm_fieldwork/make_data_extract.py index 8c6a165d..26973db2 100755 --- a/osm_fieldwork/make_data_extract.py +++ b/osm_fieldwork/make_data_extract.py @@ -19,43 +19,30 @@ # import argparse -import os import logging +import os import sys -import re -import yaml -import json -from sys import argv -from geojson import Feature, FeatureCollection, dump, Polygon -import geojson -from osm_fieldwork.filter_data import FilterData -from osm_fieldwork.xlsforms import xlsforms_path -from osm_rawdata.postgres import PostgresClient, uriParser -import requests from io import BytesIO -import zipfile -import time -import psycopg2 -from shapely.geometry import shape, Polygon -import overpy -import shapely -from shapely import wkt -import logging -from pathlib import Path -from osm_rawdata.config import QueryConfig +import geojson +import yaml +from geojson import FeatureCollection, dump +from osm_rawdata.postgres import PostgresClient +from shapely.geometry import shape + +from osm_fieldwork.filter_data import FilterData # Instantiate logger log = logging.getLogger(__name__) # Find the other files for this project import osm_fieldwork as of + rootdir = of.__path__[0] def getChoices(): - """ - Get the categories and associated XLSFiles from the config file + """Get the categories and associated XLSFiles from the config file. Returns: (list): A list of the XLSForms included in osm-fieldwork @@ -65,21 +52,21 @@ def getChoices(): file = open(f"{rootdir}/data_models/category.yaml", "r").read() contents = yaml.load(file, Loader=yaml.Loader) for entry in contents: - [[k,v]] = entry.items() + [[k, v]] = entry.items() data[k] = v[0] return data + class MakeExtract(object): - """ - Class to handle SQL queries for the categories - """ - def __init__(self, - uri: str, - config: str, - xlsfile: str, + """Class to handle SQL queries for the categories.""" + + def __init__( + self, + uri: str, + config: str, + xlsfile: str, ): - """ - Initialize the postgres handler + """Initialize the postgres handler. Args: dburi (str): The URI string for the database connection @@ -97,18 +84,18 @@ def __init__(self, # self.qc.parseJson(f"{rootdir}/data_models/{config}") # Read in the XLSFile - if '/' in xlsfile: - file = open(xlsfile, 'rb') + if "/" in xlsfile: + file = open(xlsfile, "rb") else: - file = open(f"{rootdir}/{xlsfile}", 'rb') + file = open(f"{rootdir}/{xlsfile}", "rb") self.xls = BytesIO(file.read()) - def getFeatures(self, - boundary: FeatureCollection, - polygon: bool, - ): - """ - Extract features from Postgres + def getFeatures( + self, + boundary: FeatureCollection, + polygon: bool, + ): + """Extract features from Postgres. Args: boundary (str): The filespec for the project AOI in GeoJson format @@ -120,11 +107,11 @@ def getFeatures(self, """ log.info("Extracting features from Postgres...") - if 'features' in boundary: - poly = boundary['features'][0]['geometry'] + if "features" in boundary: + poly = boundary["features"][0]["geometry"] else: poly = boundary["geometry"] - wkt = shape(poly) + shape(poly) collection = self.db.execQuery(boundary) if not collection: @@ -132,35 +119,34 @@ def getFeatures(self, return collection - def cleanFeatures(self, - collection: FeatureCollection, - ): - """ - Filter out any data not in the data_model + def cleanFeatures( + self, + collection: FeatureCollection, + ): + """Filter out any data not in the data_model. Args: collection (bytes): The input data or filespec to the input data file Returns: (FeatureCollection): The modifed data - + """ log.debug("Cleaning features") cleaned = FilterData() cleaned.parse(self.xls, self.db.qc.config) new = cleaned.cleanData(collection) - #jsonfile = open(filespec, "w") - #dump(new, jsonfile) + # jsonfile = open(filespec, "w") + # dump(new, jsonfile) return new + def main(): - """ - This program makes data extracts from OSM data, which can be used with ODK Collect - """ - choices = getChoices() - + """This program makes data extracts from OSM data, which can be used with ODK Collect.""" + getChoices() + parser = argparse.ArgumentParser( - prog='make_data_extract', + prog="make_data_extract", formatter_class=argparse.RawDescriptionHelpFormatter, description="Make GeoJson data file for ODK from OSM", epilog=""" @@ -183,24 +169,16 @@ def main(): The defaults are buildings.yaml for the config file, buildings.xls for the XLSForm, and for the database, it uses the remote Underpass database. - """ + """, ) - parser.add_argument("-v", "--verbose", nargs="?", const="0", - help="verbose output") - parser.add_argument("-p", "--polygon", action="store_true", - default=False, help="Output polygons instead of centroids") - parser.add_argument("-g", "--geojson", default='extract.geojson', - help="Name of the GeoJson output file") - parser.add_argument("-u", "--uri", default='underpass', - help="Database URI") - parser.add_argument("-b", "--boundary", required=True, - help="Boundary polygon to limit the data size") - parser.add_argument("-c", "--config", default="buildings.yaml", - help="Config file for the query") - parser.add_argument("-x", "--xlsfile", default="buildings.xls", - help="XLSForm for this data collection") - parser.add_argument("-l", "--list", action="store_true", - help="List files in the XLSForm library") + parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") + parser.add_argument("-p", "--polygon", action="store_true", default=False, help="Output polygons instead of centroids") + parser.add_argument("-g", "--geojson", default="extract.geojson", help="Name of the GeoJson output file") + parser.add_argument("-u", "--uri", default="underpass", help="Database URI") + parser.add_argument("-b", "--boundary", required=True, help="Boundary polygon to limit the data size") + parser.add_argument("-c", "--config", default="buildings.yaml", help="Config file for the query") + parser.add_argument("-x", "--xlsfile", default="buildings.xls", help="XLSForm for this data collection") + parser.add_argument("-l", "--list", action="store_true", help="List files in the XLSForm library") args = parser.parse_args() if args.list: @@ -208,20 +186,18 @@ def main(): for k, v in files.items(): print(f"Category: {k}, XLSForm: {v}") quit() - + # if verbose, dump to the terminal. if args.verbose: log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) extract = MakeExtract(args.uri, args.config, args.xlsfile) - file = open(args.boundary, 'r') + file = open(args.boundary, "r") poly = geojson.load(file) data = extract.getFeatures(poly, args.polygon) log.debug(f"Query returned {len(data['features'])} features") @@ -239,6 +215,7 @@ def main(): log.info("Wrote output data file to: %s" % args.geojson) + if __name__ == "__main__": """This is just a hook so this file can be run standalone during development.""" main() diff --git a/osm_fieldwork/models.yaml b/osm_fieldwork/models.yaml index 979713fc..d49e3cdb 100644 --- a/osm_fieldwork/models.yaml +++ b/osm_fieldwork/models.yaml @@ -1,284 +1,283 @@ +# Do not edit this file, it is generated by the filter_data.py +# script from the XForm spreadsheet. - # Do not edit this file, it is generated by the filter_data.py - # script from the XForm spreadsheet. - category: - - housing - - retail - - food - - medical - - service - - religious - - tourism - - government - - emergency - - education + - housing + - retail + - food + - medical + - service + - religious + - tourism + - government + - emergency + - education building:material: - - cement_block - - wood - - brick - - concrete - - stone - - glass - - bamboo - - iron_sheet - - metal - - nowalls + - cement_block + - wood + - brick + - concrete + - stone + - glass + - bamboo + - iron_sheet + - metal + - nowalls building:structure: - - confined_masonry - - steel_frame - - wood_frame - - bamboo_frame + - confined_masonry + - steel_frame + - wood_frame + - bamboo_frame building:floor: - - ground - - wood - - cement - - tekhel - - ceramics + - ground + - wood + - cement + - tekhel + - ceramics building:roof: - - metal - - concrete - - tiles - - wood - - bamboo - - iron_sheet - - thatch - - clay_tiles - - stone - - shingles + - metal + - concrete + - tiles + - wood + - bamboo + - iron_sheet + - thatch + - clay_tiles + - stone + - shingles building:condition: - - good - - average - - poor - - disused + - good + - average + - poor + - disused building: - - residential - - commercial - - commercial;residential - - construction - - office - - supermarket - - construction - - service - - public - - industrial - - kiosk - - apartments - - cabin - - hotel - - hut - - train_station - - historic - - pump_station - - depot + - residential + - commercial + - commercial;residential + - construction + - office + - supermarket + - construction + - service + - public + - industrial + - kiosk + - apartments + - cabin + - hotel + - hut + - train_station + - historic + - pump_station + - depot shop: - - fuel - - convenience - - clothes - - alcohol - - hardware - - general - - mobile_phone - - supermarket - - mall - - tea - - car_parts - - art - - beauty - - beverages - - bicycle - - books - - car - - cosmetics - - charcoal - - electronics - - farm - - furniture - - greengrocer - - houseware - - jewellery - - kiosk - - pastry - - department_store + - fuel + - convenience + - clothes + - alcohol + - hardware + - general + - mobile_phone + - supermarket + - mall + - tea + - car_parts + - art + - beauty + - beverages + - bicycle + - books + - car + - cosmetics + - charcoal + - electronics + - farm + - furniture + - greengrocer + - houseware + - jewellery + - kiosk + - pastry + - department_store office: - - company - - government - - insurance - - lawyer - - political_party - - charity - - ngo - - foundation - - union - - financial + - company + - government + - insurance + - lawyer + - political_party + - charity + - ngo + - foundation + - union + - financial religious: - - church - - mosque - - temple - - shrine - - cathedral - - chapel - - synagogue + - church + - mosque + - temple + - shrine + - cathedral + - chapel + - synagogue religion: - - muslim - - hindu - - christian - - buddhist - - jewish - - bahai + - muslim + - hindu + - christian + - buddhist + - jewish + - bahai capacity_persons: - - <50 - - 50-100 - - 100-250 - - 250-500 - - >500 + - <50 + - 50-100 + - 100-250 + - 250-500 + - >500 denomination: - - protestant - - pentecostal - - catholic - - baptist - - methodist - - orthodox - - Presbyterian - - lutheran - - Mormon - - evangelical + - protestant + - pentecostal + - catholic + - baptist + - methodist + - orthodox + - Presbyterian + - lutheran + - Mormon + - evangelical muslim: - - sunni - - sufi - - shia + - sunni + - sufi + - shia hindu: - - vaishnavism - - shaivism - - shaktism - - smartism + - vaishnavism + - shaivism + - shaktism + - smartism water_source: - - water_works - - manual_pump - - powered_pump - - groundwater + - water_works + - manual_pump + - powered_pump + - groundwater yes_no: - - yes - - no + - yes + - no historic: - - archaeological_site - - building - - fort - - monument - - area + - archaeological_site + - building + - fort + - monument + - area housing: - - residential - - hotel - - camp_site - - camp_pitch - - apartments - - toilet + - residential + - hotel + - camp_site + - camp_pitch + - apartments + - toilet service: - - car_repair - - electronics_repair - - pump_station - - copyshop - - beauty - - service - - tailor + - car_repair + - electronics_repair + - pump_station + - copyshop + - beauty + - service + - tailor cuisine: - - pizza - - burger - - italian - - mexican - - thai - - sandwich - - indian - - chinese - - seafood - - steak_house + - pizza + - burger + - italian + - mexican + - thai + - sandwich + - indian + - chinese + - seafood + - steak_house amenity: - - fire_station - - refuge_site - - police - - hospital - - clinic - - toilets - - coffee - - bar - - brewery - - pub - - cafe - - fast_food - - restaurant - - fbakery - - fbutcher - - courthouse - - library - - police - - post_office - - townhall - - social_facility - - clinic - - dentist - - doctors - - hospital - - pharmacy - - veterinary - - cemetery - - place_of_worship - - fuel - - convenience - - marketplace - - bureau_de_change - - atm - - parking - - toilets - - nursery - - primary - - secondary - - tertiary - - training - - university - - language_school - - music_school - - kindergarten - - childcare - - prep_school - - amenity + - fire_station + - refuge_site + - police + - hospital + - clinic + - toilets + - coffee + - bar + - brewery + - pub + - cafe + - fast_food + - restaurant + - fbakery + - fbutcher + - courthouse + - library + - police + - post_office + - townhall + - social_facility + - clinic + - dentist + - doctors + - hospital + - pharmacy + - veterinary + - cemetery + - place_of_worship + - fuel + - convenience + - marketplace + - bureau_de_change + - atm + - parking + - toilets + - nursery + - primary + - secondary + - tertiary + - training + - university + - language_school + - music_school + - kindergarten + - childcare + - prep_school + - amenity tourism: - - camp_site - - taxi - - camp pitch - - caravan_site - - artwork - - gallery - - museum - - zoo - - attraction - - guest_house - - hotel - - hostel + - camp_site + - taxi + - camp pitch + - caravan_site + - artwork + - gallery + - museum + - zoo + - attraction + - guest_house + - hotel + - hostel generator:source: - - grid - - generator - - solar - - hydro + - grid + - generator + - solar + - hydro diff --git a/osm_fieldwork/odk2csv.py b/osm_fieldwork/odk2csv.py index 57bc36b9..5bb98c00 100755 --- a/osm_fieldwork/odk2csv.py +++ b/osm_fieldwork/odk2csv.py @@ -17,33 +17,31 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -import logging import argparse -import xmltodict +import csv +import logging import os -import sys import re +import sys from collections import OrderedDict -import csv -from pathlib import Path from datetime import datetime +from pathlib import Path + +import xmltodict # Instantiate logger log = logging.getLogger(__name__) + def main(): - """ - This is a program that reads in the ODK Instance file, which is in XML, + """This is a program that reads in the ODK Instance file, which is in XML, and converts it to a CSV file so it can be viewed in an spreadsheet program. The CSV syntax is the same as what can be downloaded from ODK Central. """ - parser = argparse.ArgumentParser( - description="Convert ODK XML instance file to CSV format" - ) + parser = argparse.ArgumentParser(description="Convert ODK XML instance file to CSV format") parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") - parser.add_argument("-i", "--instance", required=True, help="The instance file(s) from ODK Collect" - ) + parser.add_argument("-i", "--instance", required=True, help="The instance file(s) from ODK Collect") # parser.add_argument("-d","--directories", help='A local directory pato to instance files.') # parser.add_argument("-o","--outfile", default='tmp.csv', help='The output file for JOSM') args = parser.parse_args() @@ -53,9 +51,7 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) @@ -89,12 +85,12 @@ def main(): tags = dict() data = doc["data"] for i, j in data.items(): - if j is None or i == 'meta': + if j is None or i == "meta": continue - #print(f"tag: {i} == {j}") + # print(f"tag: {i} == {j}") pat = re.compile("[0-9.]* [0-9.-]* [0-9.]* [0-9.]*") if pat.match(str(j)): - if i == 'warmup': + if i == "warmup": continue gps = j.split(" ") tags["lat"] = gps[0] @@ -143,5 +139,6 @@ def main(): print("Wrote: %s" % outfile) + if __name__ == "__main__": main() diff --git a/osm_fieldwork/odk2geojson.py b/osm_fieldwork/odk2geojson.py index 01ade578..7fd28d05 100755 --- a/osm_fieldwork/odk2geojson.py +++ b/osm_fieldwork/odk2geojson.py @@ -17,39 +17,31 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -import logging import argparse -import xmltodict +import logging import os -import sys import re -from collections import OrderedDict -from pathlib import Path +import sys from datetime import datetime -from osm_fieldwork.ODKInstance import ODKInstance -import geojson -import json +from pathlib import Path + +import flatdict +import xmltodict from geojson import Feature, FeatureCollection, dump from shapely.geometry import Point -from datetime import datetime -import flatdict - # Instantiate logger log = logging.getLogger(__name__) def main(): - """ - This is a program that reads in the ODK Instance file, which is in XML, + """This is a program that reads in the ODK Instance file, which is in XML, and converts it to a GeoJson file so it can be viewed in an editor. """ - parser = argparse.ArgumentParser( - description="Convert ODK XML instance file to GeoJson" - ) + parser = argparse.ArgumentParser(description="Convert ODK XML instance file to GeoJson") parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") parser.add_argument("-i", "--instance", required=True, help="The instance file(s) from ODK Collect") - parser.add_argument("-o","--outfile", default='tmp.geojson', help='The output file for JOSM') + parser.add_argument("-o", "--outfile", default="tmp.geojson", help="The output file for JOSM") args = parser.parse_args() # if verbose, dump to the termina @@ -57,9 +49,7 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) @@ -82,30 +72,28 @@ def main(): # print(xmlfiles) # These are all generated by Collect, and can be ignored - rows = list() features = list() for xml in xmlfiles: print("Processing instance file: %s" % xml) - tags = dict() - #odkxml = ODKInstance() - #data = odkxml.parse(filespec=xml) + # odkxml = ODKInstance() + # data = odkxml.parse(filespec=xml) file = open(xml, "r") # Instances are small, read the whole file data = file.read(os.path.getsize(xml)) doc = xmltodict.parse(data) - flattened = flatdict.FlatDict(doc['data']) + flattened = flatdict.FlatDict(doc["data"]) poi = Point() feature = dict() for key, value in flattened.items(): - if key[0] == '@' or value is None: + if key[0] == "@" or value is None: continue - last = key.rfind(':') + 1 + last = key.rfind(":") + 1 key = key[last:] pat = re.compile("[0-9.]* [0-9.-]* [0-9.]* [0-9.]*") gps = re.findall(pat, value) if len(gps) > 0: - tmp = gps[0].split(' ') + tmp = gps[0].split(" ") lat = float(tmp[0]) lon = float(tmp[1]) poi = Point(lon, lat) @@ -117,12 +105,13 @@ def main(): now = datetime.now() timestamp = f"_{now.year}_{now.month}-{now.day}-{now.hour}-{now.minute}" outfile = args.instance.replace("*", "") + timestamp + ".geojson" - outfile = outfile.replace(' ', '') - fout = open(outfile, 'w') + outfile = outfile.replace(" ", "") + fout = open(outfile, "w") dump(collection, fout) print(f"Wrote output file {outfile}") + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/odk2osm.py b/osm_fieldwork/odk2osm.py index 922e699e..0b6a5b01 100755 --- a/osm_fieldwork/odk2osm.py +++ b/osm_fieldwork/odk2osm.py @@ -17,32 +17,28 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -import logging import argparse -import xmltodict +import logging import os -import sys import re +import sys from collections import OrderedDict -import csv -from pathlib import Path from datetime import datetime +from pathlib import Path + +import xmltodict # Instantiate logger log = logging.getLogger(__name__) def main(): - """ - This is a program that reads in the ODK Instance file, which is in XML, + """This is a program that reads in the ODK Instance file, which is in XML, and converts it to an OSM XML file so it can be viewed in an editor. """ - parser = argparse.ArgumentParser( - description="Convert ODK XML instance file to OSM XML format" - ) + parser = argparse.ArgumentParser(description="Convert ODK XML instance file to OSM XML format") parser.add_argument("-v", "--verbose", nargs="?", const="0", help="verbose output") - parser.add_argument("-i", "--instance", required=True, help="The instance file(s) from ODK Collect" - ) + parser.add_argument("-i", "--instance", required=True, help="The instance file(s) from ODK Collect") # parser.add_argument("-o","--outfile", default='tmp.csv', help='The output file for JOSM') args = parser.parse_args() @@ -51,9 +47,7 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) @@ -87,12 +81,12 @@ def main(): tags = dict() data = doc["data"] for i, j in data.items(): - if j is None or i == 'meta': + if j is None or i == "meta": continue - #print(f"tag: {i} == {j}") + # print(f"tag: {i} == {j}") pat = re.compile("[0-9.]* [0-9.-]* [0-9.]* [0-9.]*") if pat.match(str(j)): - if i == 'warmup': + if i == "warmup": continue gps = j.split(" ") tags["lat"] = gps[0] @@ -124,12 +118,11 @@ def main(): rows.append(tags) xml = os.path.basename(xmlfiles[0]) - tmp = xml.replace(' ', '').split("_") + tmp = xml.replace(" ", "").split("_") now = datetime.now() timestamp = f"_{now.year}_{now.hour}_{now.minute}" outfile = tmp[0] + timestamp + ".csv" - # with open(outfile, "w", newline="") as csvfile: # fields = list() # for row in rows: @@ -143,6 +136,7 @@ def main(): print("Wrote: %s" % outfile) + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/odk_client.py b/osm_fieldwork/odk_client.py index cafe86a6..cf2e09cd 100755 --- a/osm_fieldwork/odk_client.py +++ b/osm_fieldwork/odk_client.py @@ -16,19 +16,18 @@ # along with OSM-Fieldwork. If not, see . # -import logging import argparse -import sys +import logging import os -import json -from osm_fieldwork.OdkCentral import OdkCentral, OdkProject, OdkForm, OdkAppUser -from osm_fieldwork.json2osm import JsonDump +import sys +from datetime import datetime from pathlib import Path from sys import argv -from geojson import Feature, FeatureCollection, dump -from datetime import datetime + from codetiming import Timer +from osm_fieldwork.json2osm import JsonDump +from osm_fieldwork.OdkCentral import OdkAppUser, OdkCentral, OdkForm, OdkProject # Set log level log_level = os.getenv("LOG_LEVEL", default="INFO") @@ -38,13 +37,13 @@ class OdkClient(object): - def __init__(self, - url:str = None, - user: str = None, - passwd: str = None, - ): - """ - A Class for higher-level client side access to ODK Central + def __init__( + self, + url: str = None, + user: str = None, + passwd: str = None, + ): + """A Class for higher-level client side access to ODK Central. Args: url (str): The URL of the ODK Central @@ -80,15 +79,15 @@ def __init__(self, def main(): - """This main function lets this class be run standalone by a bash script""" + """This main function lets this class be run standalone by a bash script.""" parser = argparse.ArgumentParser(description="command line client for ODK Central") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") # This is for server requests - parser.add_argument( - "-s", "--server", choices=["projects", "users", "delete"], help="project operations" - ) + parser.add_argument("-s", "--server", choices=["projects", "users", "delete"], help="project operations") # This is for project specific requests - parser.add_argument("-p", "--project", + parser.add_argument( + "-p", + "--project", choices=["forms", "app-users", "assignments", "delete", "submissions"], help="project operations", ) @@ -96,21 +95,23 @@ def main(): parser.add_argument("-f", "--form", help="XForm name") parser.add_argument("-u", "--uuid", help="Submission UUID, needed to download the data") # This is for form specific requests - parser.add_argument("-x", "--xform", - choices=[ - "attachments", - "csv", - "json", - "submissions", - "upload", - "download", - "create", - "assignments", - "delete", - "publish", - ], - help="XForm ID for operations with data files", - ) + parser.add_argument( + "-x", + "--xform", + choices=[ + "attachments", + "csv", + "json", + "submissions", + "upload", + "download", + "create", + "assignments", + "delete", + "publish", + ], + help="XForm ID for operations with data files", + ) parser.add_argument( "-a", "--appuser", @@ -119,9 +120,7 @@ def main(): ) parser.add_argument("-d", "--data", help="Data files for upload or download") parser.add_argument("-t", "--timestamp", help="Timestamp for submissions") - parser.add_argument( - "-b", "--bulk", choices=["qrcodes", "update"], help="Bulk operations" - ) + parser.add_argument("-b", "--bulk", choices=["qrcodes", "update"], help="Bulk operations") # logging.basicConfig( # level=log_level, @@ -161,9 +160,7 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) @@ -178,7 +175,7 @@ def main(): projects = central.listProjects() if not projects: projects = list() - elif 'message' in projects: + elif "message" in projects: log.error(f"{projects['message']}, {projects['code']} ") quit() @@ -201,9 +198,7 @@ def main(): # project.authenticate() if not args.id: print('Need to specify a project ID using "--id"!') - print( - 'You can use "odk_client.-py --server projects" to list all the projects!' - ) + print('You can use "odk_client.-py --server projects" to list all the projects!') # parser.print_help() quit() if args.project == "forms": @@ -212,7 +207,7 @@ def main(): log.error(f"{forms['message']}, {forms['code']} ") quit() if type(forms) != list: - log.error(forms['message']) + log.error(forms["message"]) quit() ordered = sorted(forms, key=lambda item: item.get("xmlFormId")) for form in ordered: @@ -233,13 +228,13 @@ def main(): if len(feature) == 0: continue if "lat" not in feature["attrs"]: - if 'geometry' in feature['tags']: - if type(feature['tags']['geometry']) == str: - coords = list(feature['tags']['geometry']) + if "geometry" in feature["tags"]: + if type(feature["tags"]["geometry"]) == str: + coords = list(feature["tags"]["geometry"]) # del feature['tags']['geometry'] - elif 'coordinates' in feature['tags']: - coords = feature['tags']['coordinates'] - feature['attrs'] = {'lat': coords[1], 'lon': coords[0]} + elif "coordinates" in feature["tags"]: + coords = feature["tags"]["coordinates"] + feature["attrs"] = {"lat": coords[1], "lon": coords[0]} else: log.warning("Bad record! %r" % feature) continue @@ -247,7 +242,7 @@ def main(): log.info(f"Wrote {outfile}.osm") # This GeoJson file has all the data values # jsonin.writeGeoJson(feature) - #log.info(f"Wrote {outfile}.geojson") + # log.info(f"Wrote {outfile}.geojson") elif args.project == "app-users": users = project.listAppUsers(args.id) @@ -266,9 +261,7 @@ def main(): # logging.info("There are %d app users on this ODK Central server" %) if args.project == "assignments": assign = project.listAssignments(args.id) - logging.info( - "There are %d assignments on this ODK Central server" % len(assign) - ) + logging.info("There are %d assignments on this ODK Central server" % len(assign)) ordered = sorted(assign, key=lambda item: item.get("id")) for role in ordered: print("\t%r" % role) @@ -307,9 +300,7 @@ def main(): if type(assign) == dict: log.error(f"{assign['message']}, {assign['code']} ") quit() - logging.info( - "There are %d assignments on this ODK Central server" % len(assign) - ) + logging.info("There are %d assignments on this ODK Central server" % len(assign)) # ordered = sorted(assign, key=lambda item: item.get('id')) for role in assign: print("\t%r" % role) @@ -318,27 +309,23 @@ def main(): submissions = form.listSubmissions(args.id, args.form) if not submissions: submissions = list() - elif 'message' in submissions: + elif "message" in submissions: log.error(f"{submissions['message']}, {submissions['code']} ") quit() - logging.info( - "There are %d submissions for XForm %s" % (len(submissions), args.form) - ) + logging.info("There are %d submissions for XForm %s" % (len(submissions), args.form)) for file in submissions: # form.submissions.append(file) - print("%s: %s" % (file['meta']['instanceID'], file["end"])) + print("%s: %s" % (file["meta"]["instanceID"], file["end"])) elif args.xform == "csv": submissions = form.getSubmissions(args.id, args.form, True, False) if type(submissions) == dict: log.error(f"{submissions['message']}, {submissions['code']} ") - logging.info( - "There are %d submissions for XForm %s" % (len(submissions), args.form) - ) + logging.info("There are %d submissions for XForm %s" % (len(submissions), args.form)) for file in submissions: form.submissions.append(file) - print("%s: %s" % (file['meta']['instanceID'], file["end"])) + print("%s: %s" % (file["meta"]["instanceID"], file["end"])) elif args.xform == "json": submissions = form.getSubmissions(args.id, args.form, False, True, True) @@ -348,21 +335,17 @@ def main(): else: if submissions is None: submissions = list() - logging.info( - "There are %d submissions for XForm %s" % (len(submissions), args.form) - ) + logging.info("There are %d submissions for XForm %s" % (len(submissions), args.form)) for file in submissions: form.submissions.append(file) - #print("%s: %s" % (file["instanceId"], file["createdAt"])) + # print("%s: %s" % (file["instanceId"], file["createdAt"])) elif args.xform == "attachments": attachments = form.listMedia(args.id, args.form) if type(attachments) == dict: log.error(f"{attachments['message']}, {attachments['code']} ") quit() - logging.info( - "There are %d attachments for XForm %s" % (len(attachments), args.form) - ) + logging.info("There are %d attachments for XForm %s" % (len(attachments), args.form)) for file in attachments: print("\t%s exists ? %s" % (file["name"], file["exists"])) @@ -441,6 +424,7 @@ def main(): result = appuser.updateRole(args.id, args.form, role, user["id"]) timer.stop() + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/odk_merge.py b/osm_fieldwork/odk_merge.py index 4825b6b3..b9933151 100755 --- a/osm_fieldwork/odk_merge.py +++ b/osm_fieldwork/odk_merge.py @@ -16,47 +16,42 @@ # along with this program. If not, see . import argparse +import concurrent.futures import logging -import sys import os -from sys import argv -from osm_fieldwork.osmfile import OsmFile -from geojson import Point, Feature, FeatureCollection, dump, Polygon +import sys + import geojson -import psycopg2 -from shapely.geometry import shape, Polygon, mapping import shapely -from shapely import wkt -import xmltodict -from progress.bar import Bar, PixelBar -from progress.spinner import PixelSpinner -from osm_fieldwork.convert import escape -from osm_fieldwork.make_data_extract import PostgresClient, uriParser from codetiming import Timer -import concurrent.futures from cpuinfo import get_cpu_info -from time import sleep -from haversine import haversine, Unit -from thefuzz import fuzz, process +from geojson import Point +from haversine import Unit, haversine +from shapely.geometry import mapping, shape +from thefuzz import fuzz + +from osm_fieldwork.convert import escape +from osm_fieldwork.make_data_extract import PostgresClient, uriParser +from osm_fieldwork.osmfile import OsmFile # Instantiate logger log = logging.getLogger(__name__) # The number of threads is based on the CPU cores info = get_cpu_info() -cores = info['count'] +cores = info["count"] class OdkMerge(object): - def __init__(self, - source: str, - boundary: str = None, - ): - """ - Initialize Input data source + def __init__( + self, + source: str, + boundary: str = None, + ): + """Initialize Input data source. Args: - source (str): + source (str): boundary: str = None Returns: @@ -75,8 +70,8 @@ def __init__(self, uri = uriParser(source[3:]) # self.source = "underpass" is not support yet # Each thread needs it's own connection to postgres to avoid problems. - for thread in range(0, cores + 1): - db = PostgresClient(dbhost=uri['dbhost'], dbname=uri['dbname'], dbuser=uri['dbuser'], dbpass=uri['dbpass']) + for _thread in range(0, cores + 1): + db = PostgresClient(dbhost=uri["dbhost"], dbname=uri["dbname"], dbuser=uri["dbuser"], dbpass=uri["dbpass"]) self.postgres.append(db) if boundary: self.clip(boundary, db) @@ -87,12 +82,12 @@ def __init__(self, if boundary: self.clip(boundary) - def clip(self, - boundary: str, - db: PostgresClient, - ): - """ - Clip a data source by a boundary + def clip( + self, + boundary: str, + db: PostgresClient, + ): + """Clip a data source by a boundary. Args: boundary (str): The filespec of the project AOI @@ -101,15 +96,14 @@ def clip(self, Returns: (bool): If the region was clipped sucessfully """ - remove = list() if not boundary: return False if type(boundary) != dict: clip = open(boundary, "r") geom = geojson.load(clip) - if 'features' in geom: - poly = geom['features'][0]['geometry'] + if "features" in geom: + poly = geom["features"][0]["geometry"] else: poly = geom["geometry"] else: @@ -120,24 +114,24 @@ def clip(self, # TODO: FMTM produces data extracts the exact size of the boundary # polygon, so we don't need to clip it. In the future though we # want this to produce a subset from a larger file. - for feature in self.data['features']: - entry = shapely.from_geojson(str(feature)) + for feature in self.data["features"]: + shapely.from_geojson(str(feature)) # if not shapely.contains(ewkt, entry): # log.debug(f"CONTAINS {entry}") # del self.data[self.data['features']] pass else: # setup the postgres VIEWs with a dummy SQL query - sql = f"SELECT COUNT(osm_id) FROM nodes" - result = db.queryLocal(sql, ewkt) + sql = "SELECT COUNT(osm_id) FROM nodes" + db.queryLocal(sql, ewkt) return True - def makeNewFeature(self, - attrs: dict = None, - tags: dict = None, - ): - """ - Create a new feature with optional data + def makeNewFeature( + self, + attrs: dict = None, + tags: dict = None, + ): + """Create a new feature with optional data. Args: attrs (dict): All of the attributes and their values @@ -148,20 +142,20 @@ def makeNewFeature(self, """ newf = dict() if attrs: - newf['attrs'] = attrs + newf["attrs"] = attrs else: - newf['attrs'] = dict() + newf["attrs"] = dict() if tags: - newf['tags'] = tags + newf["tags"] = tags else: - newf['tags'] = dict() + newf["tags"] = dict() return newf - def conflateFile(self, - feature: dict, - ): - """ - Conflate a POI against all the features in a GeoJson file + def conflateFile( + self, + feature: dict, + ): + """Conflate a POI against all the features in a GeoJson file. Args: feature (dict): The feature to conflate @@ -177,13 +171,12 @@ def conflateFile(self, match_threshold = 80 # log.debug(f"conflateFile({feature})") hits = False - data = dict() geom = Point((float(feature["attrs"]["lon"]), float(feature["attrs"]["lat"]))) wkt = shape(geom) - for existing in self.data['features']: - id = int(existing['properties']['id']) + for existing in self.data["features"]: + id = int(existing["properties"]["id"]) entry = shapely.from_geojson(str(existing)) - if entry.geom_type != 'Point': + if entry.geom_type != "Point": center = shapely.centroid(entry) else: center = entry @@ -200,38 +193,38 @@ def conflateFile(self, # if 'name' in existing['properties']: # log.debug(f"DIST2: {dist}") # log.debug(f"Got a Hit! {feature['tags']['name']}") - for key,value in feature['tags'].items(): + for key, value in feature["tags"].items(): if key in self.analyze: - if key in existing['properties']: - result = fuzz.ratio(value, existing['properties'][key]) + if key in existing["properties"]: + result = fuzz.ratio(value, existing["properties"][key]) if result > match_threshold: # log.debug(f"Matched: {result}: {feature['tags']['name']}") - existing['properties']['fixme'] = "Probably a duplicate!" + existing["properties"]["fixme"] = "Probably a duplicate!" log.debug(f"Got a dup in file!!! {existing['properties']['name'] }") hits = True break if hits: - version = int(existing['properties']['version']) + version = int(existing["properties"]["version"]) # coords = feature['geometry']['coordinates'] # lat = coords[1] # lon = coords[0] - attrs = {'id': id, 'version': version, 'lat': feature['attrs']['lat'], 'lon': feature['attrs']['lon']} - tags = existing['properties'] - tags['fixme'] = "Probably a duplicate!" + attrs = {"id": id, "version": version, "lat": feature["attrs"]["lat"], "lon": feature["attrs"]["lon"]} + tags = existing["properties"] + tags["fixme"] = "Probably a duplicate!" # Data extracts for ODK Collect - del tags['title'] - del tags['label'] - if 'building' in tags: - return {'attrs': attrs, 'tags': tags, 'refs': list()} - return {'attrs': attrs, 'tags': tags} + del tags["title"] + del tags["label"] + if "building" in tags: + return {"attrs": attrs, "tags": tags, "refs": list()} + return {"attrs": attrs, "tags": tags} return dict() - def conflateWay(self, - feature: dict, - dbindex: int, - ): - """ - Conflate a POI against all the ways in a postgres view + def conflateWay( + self, + feature: dict, + dbindex: int, + ): + """Conflate a POI against all the ways in a postgres view. Args: feature (dict): The feature to conflate @@ -245,11 +238,11 @@ def conflateWay(self, result = list() geom = Point((float(feature["attrs"]["lon"]), float(feature["attrs"]["lat"]))) wkt = shape(geom) - for key, value in feature['tags'].items(): + for key, value in feature["tags"].items(): if key in self.analyze: # Sometimes the duplicate is a polygon, really common for parking lots. cleanval = escape(value) - query = f"SELECT osm_id,tags,version,ST_AsText(ST_Centroid(geom)) FROM ways_view WHERE ST_Distance(geom::geography, ST_GeogFromText(\'SRID=4326;{wkt.wkt}\')) < {self.tolerance} AND levenshtein(tags->>'{key}', '{cleanval}') <= 1" + query = f"SELECT osm_id,tags,version,ST_AsText(ST_Centroid(geom)) FROM ways_view WHERE ST_Distance(geom::geography, ST_GeogFromText('SRID=4326;{wkt.wkt}')) < {self.tolerance} AND levenshtein(tags->>'{key}', '{cleanval}') <= 1" # log.debug(query) self.postgres[dbindex].dbcursor.execute(query) try: @@ -264,10 +257,10 @@ def conflateWay(self, log.debug(f"Got a dup in ways!!! {feature['tags']['name']}") # the result is a list from what we specify for SELECT version = int(result[0][2]) + 1 - attrs = {'id': int(result[0][0]), 'version': version} + attrs = {"id": int(result[0][0]), "version": version} tags = result[0][1] - tags[f'old_{key}'] = value - tags['fixme'] = "Probably a duplicate!" + tags[f"old_{key}"] = value + tags["fixme"] = "Probably a duplicate!" geom = mapping(shapely.from_wkt(result[0][3])) refs = list() # FIXME: iterate through the points and find the existing nodes, @@ -276,15 +269,15 @@ def conflateWay(self, # SELECT osm_id,tags,version FROM nodes WHERE ST_Contains(geom, ST_GeomFromText('Point(-105.9918636 38.5360821)')); # for i in geom['coordinates'][0]: # print(f"XXXXX: {i}") - return {'attrs': attrs, 'tags': tags, 'refs': refs} + return {"attrs": attrs, "tags": tags, "refs": refs} return dict() - def conflateNode(self, - feature: dict, - dbindex: int, - ): - """ - Conflate a POI against all the nodes in the view + def conflateNode( + self, + feature: dict, + dbindex: int, + ): + """Conflate a POI against all the nodes in the view. Args: feature (dict): The feature to conflate @@ -299,13 +292,13 @@ def conflateNode(self, wkt = shape(geom) result = list() ratio = 1 - for key,value in feature['tags'].items(): + for key, value in feature["tags"].items(): if key in self.analyze: # print("%s = %s" % (key, value)) # Use a Geography data type to get the answer in meters, which # is easier to deal with than degress of the earth. cleanval = escape(value) - query = f"SELECT osm_id,tags,version,ST_AsEWKT(geom) FROM nodes_view WHERE ST_Distance(geom::geography, ST_GeogFromText(\'SRID=4326;{wkt.wkt}\')) < {self.tolerance} AND levenshtein(tags->>'{key}', '{cleanval}') <= {ratio}" + query = f"SELECT osm_id,tags,version,ST_AsEWKT(geom) FROM nodes_view WHERE ST_Distance(geom::geography, ST_GeogFromText('SRID=4326;{wkt.wkt}')) < {self.tolerance} AND levenshtein(tags->>'{key}', '{cleanval}') <= {ratio}" # print(query) # FIXME: this currently only works with a local database, not underpass yet self.postgres[dbindex].dbcursor.execute(query) @@ -323,19 +316,19 @@ def conflateNode(self, coords = shapely.from_wkt(result[0][3][10:]) lat = coords.y lon = coords.x - attrs = {'id': int(result[0][0]), 'version': version, 'lat': lat, 'lon': lon} + attrs = {"id": int(result[0][0]), "version": version, "lat": lat, "lon": lon} tags = result[0][1] - tags[f'old_{key}'] = value - tags['fixme'] = "Probably a duplicate!" - return {'attrs': attrs, 'tags': tags} + tags[f"old_{key}"] = value + tags["fixme"] = "Probably a duplicate!" + return {"attrs": attrs, "tags": tags} return dict() - def conflateById(self, - feature: dict, - dbindex: int, - ): - """ - Conflate a feature with existing ways using the OSM ID + def conflateById( + self, + feature: dict, + dbindex: int, + ): + """Conflate a feature with existing ways using the OSM ID. Args: feature (dict): The feature to conflate @@ -345,7 +338,7 @@ def conflateById(self, (dict): The modified feature """ log.debug(f"conflateById({feature})") - id = int(feature['attrs']['id']) + id = int(feature["attrs"]["id"]) if id > 0: if self.source[:3] != "PG:": sql = f"SELECT osm_id,tags,version,ST_AsText(geom) FROM ways_view WHERE tags->>'id'='{id}'" @@ -354,12 +347,12 @@ def conflateById(self, result = self.postgres[0].dbcursor.fetchone() if result: version = int(result[0][2]) + 1 - attrs = {'id': int(result[0][0]), 'version': version} + attrs = {"id": int(result[0][0]), "version": version} tags = result[0][1] # tags[f'old_{key}'] = value - tags['fixme'] = "Probably a duplicate!" - geom = mapping(shapely.from_wkt(result[0][3])) - return {'attrs': attrs, 'tags': tags} + tags["fixme"] = "Probably a duplicate!" + mapping(shapely.from_wkt(result[0][3])) + return {"attrs": attrs, "tags": tags} else: sql = f"SELECT osm_id,tags,version,ST_AsText(geom) FROM ways_view WHERE tags->>'id'='{id}'" # log.debug(sql) @@ -367,53 +360,53 @@ def conflateById(self, result = self.postgres[dbindex].dbcursor.fetchone() if result: version = int(result[0][2]) + 1 - attrs = {'id': int(result[0][0]), 'version': version} + attrs = {"id": int(result[0][0]), "version": version} tags = result[0][1] # tags[f'old_{key}'] = value - tags['fixme'] = "Probably a duplicate!" - geom = mapping(shapely.from_wkt(result[0][3])) - return {'attrs': attrs, 'tags': tags, 'refs': refs} + tags["fixme"] = "Probably a duplicate!" + mapping(shapely.from_wkt(result[0][3])) + return {"attrs": attrs, "tags": tags, "refs": refs} else: for key, value in self.data.items(): if key == id: return value return dict() - def cleanFeature(self, - feature: dict, - ): - """ - Remove tags that are attributes instead + def cleanFeature( + self, + feature: dict, + ): + """Remove tags that are attributes instead Args: - feature (dict): The feature to clean + feature (dict): The feature to clean. Returns: (dict): The modified feature """ - # We only use the version and ID in the attributes - if 'id' in feature['tags']: - del feature['tags']['id'] - if 'version' in feature['tags']: - del feature['tags']['version'] - if 'title' in feature['tags']: - del feature['tags']['title'] - if 'label' in feature['tags']: - del feature['tags']['label'] + # We only use the version and ID in the attributes + if "id" in feature["tags"]: + del feature["tags"]["id"] + if "version" in feature["tags"]: + del feature["tags"]["version"] + if "title" in feature["tags"]: + del feature["tags"]["title"] + if "label" in feature["tags"]: + del feature["tags"]["label"] return feature def dump(self): - """Dump internal data""" + """Dump internal data.""" print(f"Data source is: {self.source}") print(f"There are {len(self.data)} existing features") # if len(self.versions) > 0: # for k, v in self.original.items(): # print(f"{k}(v{self.versions[k]}) = {v}") - def conflateData(self, - odkdata: list, - ): - """ - Conflate all the data. This the primary interfacte for conflation. + def conflateData( + self, + odkdata: list, + ): + """Conflate all the data. This the primary interfacte for conflation. Args: odkdata (list): A list of all the entries in the OSM XML input file @@ -454,20 +447,21 @@ def conflateData(self, subset = dict() i += 1 for future in concurrent.futures.as_completed(futures): - # # for future in concurrent.futures.wait(futures, return_when='ALL_COMPLETED'): - log.debug(f"Waiting for thread to complete..") + # # for future in concurrent.futures.wait(futures, return_when='ALL_COMPLETED'): + log.debug("Waiting for thread to complete..") # print(f"YYEESS!! {future.result(timeout=10)}") newdata.append(future.result(timeout=5)) timer.stop() return newdata # return alldata -def conflateThread(features: dict, - source: str, - dbindex: int, - ): - """ - Conflate a subset of the data + +def conflateThread( + features: dict, + source: str, + dbindex: int, +): + """Conflate a subset of the data. Args: feature (dict): The feature to conflate @@ -484,8 +478,8 @@ def conflateThread(features: dict, dups = 0 # This is brute force, slow but accurate. Process each feature # and look for a possible match with existing data. - for key, value in features.items(): - id = int(value['attrs']['id']) + for _key, value in features.items(): + id = int(value["attrs"]["id"]) # Each of the conflation methods take a single feature # as a parameter, and returns a possible match or a zero # length dictionary. @@ -507,12 +501,12 @@ def conflateThread(features: dict, if result and len(result) > 0: # Merge the tags and attributes together, the OSM data and ODK data. # If no match is found, the ODK data is used to create a new feature. - if 'fixme' in result['tags']: + if "fixme" in result["tags"]: dups += 1 # newf = source.cleanFeature(result) - attrs = value['attrs'] | result['attrs'] - tags = value['tags'] | result['tags'] - merged.append({'attrs': attrs, 'tags': tags}) + attrs = value["attrs"] | result["attrs"] + tags = value["tags"] | result["tags"] + merged.append({"attrs": attrs, "tags": tags}) else: merged.append(value) else: @@ -524,7 +518,7 @@ def conflateThread(features: dict, def main(): - """This main function lets this class be run standalone by a bash script""" + """This main function lets this class be run standalone by a bash script.""" parser = argparse.ArgumentParser( prog="odk_merge.py", formatter_class=argparse.RawDescriptionHelpFormatter, @@ -541,8 +535,8 @@ def main(): OR refix with "PG:[url]" to use a postgres database """, ) - parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") - parser.add_argument("-o", "--outfile", help="Output file from the conflation") + parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") + parser.add_argument("-o", "--outfile", help="Output file from the conflation") # parser.add_argument("-e", "--extract", help="OSM data extract created by Osm-Fieldwork") # parser.add_argument("-f", "--odkfile", required=True, help="OSM XML file created by Osm-Field") parser.add_argument("-b", "--boundary", help="Boundary polygon to limit the data size") @@ -562,16 +556,14 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) if args.outfile: outfile = args.outfile else: - outfile = os.path.basename(osmdata.replace('.osm', '-foo.osm')) + outfile = os.path.basename(osmdata.replace(".osm", "-foo.osm")) # This is the existing OSM data, a database or a file if args.boundary: @@ -580,9 +572,9 @@ def main(): extract = OdkMerge(source) if extract: - odkf = OsmFile(outfile) # output file - osm = odkf.loadFile(osmdata) # input file - #odkf.dump() + odkf = OsmFile(outfile) # output file + osm = odkf.loadFile(osmdata) # input file + # odkf.dump() else: log.error("No ODK data source specified!") parser.print_help() @@ -592,12 +584,12 @@ def main(): # a list of the features, and len(data) is thre number of CPU cores. data = extract.conflateData(osm) out = list() - #print(data) + # print(data) for entry in data: # if 'refs' in feature or 'building' in feature['tags']: for feature in entry: - if 'refs' in feature: - feature['refs'] = list() + if "refs" in feature: + feature["refs"] = list() out.append(odkf.createWay(feature, True)) else: out.append(odkf.createNode(feature, True)) @@ -613,6 +605,7 @@ def main(): odkf.write(out) log.info(f"Wrote {outfile}") + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/osm2favorities.py b/osm_fieldwork/osm2favorities.py index 48dad072..389b0795 100755 --- a/osm_fieldwork/osm2favorities.py +++ b/osm_fieldwork/osm2favorities.py @@ -19,53 +19,45 @@ # import argparse -import csv -import os import logging import sys -import json + import geojson -from sys import argv -from geojson import Point, Feature, FeatureCollection, dump -from pathlib import Path import gpxpy import gpxpy.gpx -from lxml import etree -from shapely.geometry import shape, Polygon import shapely - +from lxml import etree +from shapely.geometry import shape # set log level for urlib log = logging.getLogger(__name__) + def createExtension(icon): - """ - Create an extended feature with Osmand styling - """ + """Create an extended feature with Osmand styling.""" # camp_pitch.png, tourism_camp_site.png, topo_camp_pitch.png, topo_camp_site.png # trailhead.png, tourism_picnic_site.png, tourism_picnic_site.png, # tourism_attraction.png, tourism_information.png, information_board.png, # firepit.png, historic_ruins.png, amenity_drinking_water.png, # amenity_toilets.png, amenity_parking.png, special_trekking - colors = {'tourism_camp_site': '#ff5020', 'tourism_picnic_site': '#ff5020', 'special_trekking': '#a71de1'} - nsmap = {'osmand': 'https://osmand.net'} - png = etree.Element('{osmand}icon', nsmap=nsmap) + colors = {"tourism_camp_site": "#ff5020", "tourism_picnic_site": "#ff5020", "special_trekking": "#a71de1"} + nsmap = {"osmand": "https://osmand.net"} + png = etree.Element("{osmand}icon", nsmap=nsmap) png.text = icon - back = etree.Element('{osmand}background', nsmap=nsmap) + back = etree.Element("{osmand}background", nsmap=nsmap) back.text = "circle" color = None if icon in colors: - color = etree.Element('{osmand}color', nsmap=nsmap) + color = etree.Element("{osmand}color", nsmap=nsmap) color.text = colors[icon] return (png, back, color) else: return (png, back) - + + def main(): - """This main function lets this class be run standalone by a bash script""" - parser = argparse.ArgumentParser( - description="convert GeoJson to a GPX favorites file for Osmand" - ) + """This main function lets this class be run standalone by a bash script.""" + parser = argparse.ArgumentParser(description="convert GeoJson to a GPX favorites file for Osmand") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") parser.add_argument("-i", "--infile", required=True, help="The data extract") args = parser.parse_args() @@ -75,9 +67,7 @@ def main(): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) @@ -86,12 +76,12 @@ def main(): # gpxpy.gpxfield.GPXField() gpx = gpxpy.gpx.GPX() - gpx.nsmap['osmand'] = "https://osmand.net" + gpx.nsmap["osmand"] = "https://osmand.net" gpx.creator = "osm2favorites 0.1" - for feature in indata['features']: - coords = feature['geometry']['coordinates'] - if feature['geometry']['type'] == "Polygon": - wkt = shape(feature['geometry']) + for feature in indata["features"]: + coords = feature["geometry"]["coordinates"] + if feature["geometry"]["type"] == "Polygon": + wkt = shape(feature["geometry"]) center = shapely.centroid(wkt) lat = center.y lon = center.x @@ -99,19 +89,19 @@ def main(): lat = coords[1] lon = coords[0] name = "" - if 'name' in feature['properties']: - name = feature['properties']['name'] + if "name" in feature["properties"]: + name = feature["properties"]["name"] tourism = None - if 'tourism' in feature['properties']: - tourism = feature['properties']['tourism'] + if "tourism" in feature["properties"]: + tourism = feature["properties"]["tourism"] highway = None - if 'highway' in feature['properties']: - highway = feature['properties']['highway'] + if "highway" in feature["properties"]: + highway = feature["properties"]["highway"] amenity = None - if 'amenity' in feature['properties'] and not highway: - amenity = feature['properties']['amenity'] - for key, value in feature['properties'].items(): - if key == 'name': + if "amenity" in feature["properties"] and not highway: + amenity = feature["properties"]["amenity"] + for key, value in feature["properties"].items(): + if key == "name": continue description = "

" description += f"{key} = {value}
" @@ -124,14 +114,14 @@ def main(): # symbol="", # comment="", ) - extensions = '' - if tourism and tourism != 'picnic site': + extensions = "" + if tourism and tourism != "picnic site": extensions = createExtension("tourism_camp_site") - elif tourism and tourism != 'picnic site': + elif tourism and tourism != "picnic site": extensions = createExtension("tourism_picnic_site") - elif highway and highway == 'trailhead': + elif highway and highway == "trailhead": extensions = createExtension("special_trekking") - elif amenity and amenity == 'parking': + elif amenity and amenity == "parking": extensions = createExtension("amenity_parking") for ext in extensions: @@ -143,6 +133,7 @@ def main(): log.info(f"Wrote {outfile}") + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" main() diff --git a/osm_fieldwork/osmfile.py b/osm_fieldwork/osmfile.py index 58049b12..3490a493 100755 --- a/osm_fieldwork/osmfile.py +++ b/osm_fieldwork/osmfile.py @@ -16,30 +16,30 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # +import argparse import logging +import os from datetime import datetime -from osm_fieldwork.convert import Convert, escape -from osm_fieldwork.xlsforms import xlsforms_path -import xmltodict -import argparse from sys import argv -import os -import pathlib + +import xmltodict + +from osm_fieldwork.convert import Convert, escape # Intantiate logger log = logging.getLogger(__name__) class OsmFile(object): - """OSM File output""" + """OSM File output.""" - def __init__(self, - filespec: str = None, - options: dict = dict(), - outdir: str = "/tmp/", - ): - """ - This class reads and writes the OSM XML formated files + def __init__( + self, + filespec: str = None, + options: dict = None, + outdir: str = "/tmp/", + ): + """This class reads and writes the OSM XML formated files. Args: filespec (str): The input or output file @@ -49,7 +49,8 @@ def __init__(self, Returns: (OsmFile): An instance of this object """ - + if options is None: + options = dict() self.options = options # Read the config file to get our OSM credentials, if we have any # self.config = config.config(self.options) @@ -78,15 +79,12 @@ def __init__(self, self.data = dict() def __del__(self): - """ - Close the OSM XML file automatically - """ + """Close the OSM XML file automatically.""" log.debug("Closing output file") self.footer() def isclosed(self): - """ - Is the OSM XML file open or closed ? + """Is the OSM XML file open or closed ? Returns: (bool): The OSM XML file status @@ -94,9 +92,7 @@ def isclosed(self): return self.file.closed def header(self): - """ - Write the header of the OSM XML file - """ + """Write the header of the OSM XML file.""" if self.file is not None: self.file.write("\n") # self.file.write('\n') @@ -104,9 +100,7 @@ def header(self): self.file.flush() def footer(self): - """ - Write the footer of the OSM XML file - """ + """Write the footer of the OSM XML file.""" # logging.debug("FIXME: %r" % self.file) if self.file is not None: self.file.write("\n") @@ -114,12 +108,11 @@ def footer(self): if self.file is False: self.file.close() - def write(self, - data = None, - ): - """ - Write the data to the OSM XML file - """ + def write( + self, + data=None, + ): + """Write the data to the OSM XML file.""" if type(data) == list: if data is not None: for line in data: @@ -127,12 +120,12 @@ def write(self, else: self.file.write("%s\n" % data) - def createWay(self, - way: dict, - modified: bool = False, - ): - """ - This creates a string that is the OSM representation of a node + def createWay( + self, + way: dict, + modified: bool = False, + ): + """This creates a string that is the OSM representation of a node. Args: way (dict): The input way data structure @@ -217,20 +210,18 @@ def createWay(self, newkey = escape(key) osm += "\n " % (newkey, str(value)) if modified: - osm += ( - '\n ' - ) + osm += '\n ' osm += "\n" osm += " \n" return osm - def featureToNode(self, - feature: dict, - ): - """ - Convert a GeoJson feature into the data structures used here + def featureToNode( + self, + feature: dict, + ): + """Convert a GeoJson feature into the data structures used here. Args: feature (dict): The GeoJson feature to convert @@ -242,24 +233,24 @@ def featureToNode(self, ignore = ("label", "title") tags = dict() attrs = dict() - for tag, value in feature['properties'].items(): - if tag == 'id': - attrs['osm_id'] = value + for tag, value in feature["properties"].items(): + if tag == "id": + attrs["osm_id"] = value elif tag not in ignore: tags[tag] = value - coords = feature['geometry']['coordinates'] - attrs['lat'] = coords[1] - attrs['lon'] = coords[0] - osm['attrs'] = attrs - osm['tags'] = tags + coords = feature["geometry"]["coordinates"] + attrs["lat"] = coords[1] + attrs["lon"] = coords[0] + osm["attrs"] = attrs + osm["tags"] = tags return osm - def createNode(self, - node: dict, - modified: bool = False, - ): - """ - This creates a string that is the OSM representation of a node + def createNode( + self, + node: dict, + modified: bool = False, + ): + """This creates a string that is the OSM representation of a node. Args: node (dict): The input node data structure @@ -281,16 +272,16 @@ def createNode(self, if "version" not in node["attrs"]: attrs["version"] = "1" else: - attrs["version"] = int(node['attrs']["version"]) + 1 + attrs["version"] = int(node["attrs"]["version"]) + 1 attrs["lat"] = node["attrs"]["lat"] attrs["lon"] = node["attrs"]["lon"] attrs["timestamp"] = datetime.now().strftime("%Y-%m-%dT%TZ") # If the resulting file is publicly accessible without authentication, THE GDPR applies # and the identifying fields should not be included - if "uid" in node['attrs']: - attrs["uid"] = node['attrs']["uid"] - if "user" in node['attrs']: - attrs["user"] = node['attrs']["user"] + if "uid" in node["attrs"]: + attrs["uid"] = node["attrs"]["uid"] + if "user" in node["attrs"]: + attrs["user"] = node["attrs"]["user"] # Processs atrributes line = "" @@ -307,21 +298,19 @@ def createNode(self, if key not in attrs: osm += "\n " % (key, str(value)) if modified: - osm += ( - '\n ' - ) + osm += '\n ' osm += "\n \n" else: osm += "/>" return osm - def createTag(self, - field: str, - value: str, - ): - """ - Create a data structure for an OSM feature tag + def createTag( + self, + field: str, + value: str, + ): + """Create a data structure for an OSM feature tag. Args: field (str): The tag name @@ -345,11 +334,11 @@ def createTag(self, tag[newtag] = newval return tag - def loadFile(self, - osmfile: str, - ): - """ - Read a OSM XML file generated by osm_fieldwork + def loadFile( + self, + osmfile: str, + ): + """Read a OSM XML file generated by osm_fieldwork. Args: osmfile (str): The OSM XML file to load @@ -374,14 +363,14 @@ def loadFile(self, attrs = dict() tags = dict() for k, v in field["node"].items(): - if k[0] == '@': + if k[0] == "@": attrs[k[1:]] = v else: - if type(field["node"]['tag']) == dict: - tags[field["node"]['tag']["@k"]] = field["node"]['tag']["@v"].strip() + if type(field["node"]["tag"]) == dict: + tags[field["node"]["tag"]["@k"]] = field["node"]["tag"]["@v"].strip() else: - for pair in field['node']['tag']: - tags[pair['@k']] = pair['@v'] + for pair in field["node"]["tag"]: + tags[pair["@k"]] = pair["@v"] node = {"attrs": attrs, "tags": tags} self.data[node["attrs"]["id"]] = node @@ -392,7 +381,7 @@ def loadFile(self, "lat": node["@lat"][:10], "lon": node["@lon"][:10], } - if '@timestamp' in node: + if "@timestamp" in node: attrs["timestamp"] = node["@timestamp"] tags = dict() @@ -402,25 +391,25 @@ def loadFile(self, tags[tag["@k"]] = tag["@v"].strip() # continue else: - tags[node['tag']["@k"]] = node['tag']["@v"].strip() + tags[node["tag"]["@k"]] = node["tag"]["@v"].strip() # continue node = {"attrs": attrs, "tags": tags} self.data[node["attrs"]["id"]] = node return self.data def dump(self): - """Dump internal data structures, for debugging purposes only""" - for id, item in self.data.items(): + """Dump internal data structures, for debugging purposes only.""" + for _id, item in self.data.items(): for k, v in item["attrs"].items(): print(f"{k} = {v}") for k, v in item["tags"].items(): print(f"\t{k} = {v}") - def getFeature(self, - id: int, - ): - """ - Get the data for a feature from the loaded OSM data file + def getFeature( + self, + id: int, + ): + """Get the data for a feature from the loaded OSM data file. Args: id (int): The ID to retrieve the feasture of @@ -431,11 +420,9 @@ def getFeature(self, return self.data[id] def getFields(self): - """ - Extract all the tags used in this file - """ + """Extract all the tags used in this file.""" fields = list() - for id, item in self.data.items(): + for _id, item in self.data.items(): keys = list(item["tags"].keys()) for key in keys: if key not in fields: @@ -446,9 +433,7 @@ def getFields(self): if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" # Command Line options - parser = argparse.ArgumentParser( - description="This program conflates ODK data with existing features from OSM." - ) + parser = argparse.ArgumentParser(description="This program conflates ODK data with existing features from OSM.") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") parser.add_argument("-o", "--osmfile", required=True, help="OSM XML file created by Osm-Fieldwork") args = parser.parse_args() @@ -463,9 +448,7 @@ def getFields(self): log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) diff --git a/osm_fieldwork/sqlite.py b/osm_fieldwork/sqlite.py index ef3cb910..b8910c63 100755 --- a/osm_fieldwork/sqlite.py +++ b/osm_fieldwork/sqlite.py @@ -20,11 +20,12 @@ # import argparse -import os +import locale import logging -import sys +import os import sqlite3 -import locale +import sys + import mercantile # Instantiate logger @@ -32,21 +33,21 @@ class MapTile(object): - def __init__(self, - x: int = None, - y: int = None, - z: int = None, - filespec: str = None, - tile: 'MapTile' = None, - suffix="jpg", - ): - """ - This is a simple wrapper around mercantile.tile to associate a + def __init__( + self, + x: int = None, + y: int = None, + z: int = None, + filespec: str = None, + tile: "MapTile" = None, + suffix="jpg", + ): + """This is a simple wrapper around mercantile.tile to associate a filespec with the grid coordinates. Args: x (int): The X index for this tile - y (int): The Y index for this tile + y (int): The Y index for this tile z (int): The Z index for this tile if there is one filespec (str): The location of this within the map tile cache tile (MapTile): Make a copy of this object @@ -74,11 +75,8 @@ def __init__(self, self.x = tmp[2] self.y = tmp[1].replace("." + suffix, "") - def readImage(self, - base: str = "./" - ): - """ - Read a map tile out of the disk based map tile cache + def readImage(self, base: str = "./"): + """Read a map tile out of the disk based map tile cache. Args: base (str): The top level directory for the map tile cache @@ -91,7 +89,7 @@ def readImage(self, self.blob = file.read(size) def dump(self): - """Dump internal data structures, for debugging purposes only""" + """Dump internal data structures, for debugging purposes only.""" if self.z: print("Z: %r" % self.z) if self.x: @@ -104,12 +102,12 @@ def dump(self): class DataFile(object): - def __init__(self, - dbname: str = None, - suffix: str = "jpg", - ): - """ - Handle the sqlite3 database file + def __init__( + self, + dbname: str = None, + suffix: str = "jpg", + ): + """Handle the sqlite3 database file. Args: dbname (str): The name of the output sqlite file @@ -127,28 +125,26 @@ def __init__(self, self.toplevel = None self.suffix = suffix - def addBounds(self, - bounds: int, - ): - """ - Mbtiles has a bounds field, Osmand doesn't + def addBounds( + self, + bounds: int, + ): + """Mbtiles has a bounds field, Osmand doesn't. Args: bounds (int): The bounds value for ODK Collect mbtiles """ entry = str(bounds) entry = entry[1 : len(entry) - 1].replace(" ", "") - self.cursor.execute( - f"INSERT INTO metadata (name, value) VALUES('bounds', '{entry}')" - ) + self.cursor.execute(f"INSERT INTO metadata (name, value) VALUES('bounds', '{entry}')") # self.cursor.execute(f"INSERT INTO metadata (name, value) VALUES('minzoom', '9')") # self.cursor.execute(f"INSERT INTO metadata (name, value) VALUES('maxzoom', '15')") - def createDB(self, - dbname: str, - ): - """ - Create and sqlitedb in either mbtiles or Osman sqlitedb format + def createDB( + self, + dbname: str, + ): + """Create and sqlitedb in either mbtiles or Osman sqlitedb format. Args: dbname (str): The filespec of the sqlite output file @@ -161,38 +157,22 @@ def createDB(self, self.db = sqlite3.connect(dbname) self.cursor = self.db.cursor() if suffix == ".mbtiles": - self.cursor.execute( - "CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob)" - ) - self.cursor.execute( - "CREATE INDEX tiles_idx on tiles (zoom_level, tile_column, tile_row)" - ) + self.cursor.execute("CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob)") + self.cursor.execute("CREATE INDEX tiles_idx on tiles (zoom_level, tile_column, tile_row)") self.cursor.execute("CREATE TABLE metadata (name text, value text)") # These get populated later name = dbname description = "Created by osm_fieldwork/basemapper.py" self.cursor.execute("CREATE UNIQUE INDEX metadata_idx ON metadata (name)") - self.cursor.execute( - "INSERT INTO metadata (name, value) VALUES('version', '1.1')" - ) - self.cursor.execute( - "INSERT INTO metadata (name, value) VALUES('type', 'baselayer')" - ) - self.cursor.execute( - f"INSERT INTO metadata (name, value) VALUES('name', '{name}')" - ) - self.cursor.execute( - f"INSERT INTO metadata (name, value) VALUES('description', '{description}')" - ) + self.cursor.execute("INSERT INTO metadata (name, value) VALUES('version', '1.1')") + self.cursor.execute("INSERT INTO metadata (name, value) VALUES('type', 'baselayer')") + self.cursor.execute(f"INSERT INTO metadata (name, value) VALUES('name', '{name}')") + self.cursor.execute(f"INSERT INTO metadata (name, value) VALUES('description', '{description}')") # self.cursor.execute(f"INSERT INTO metadata (name, value) VALUES('bounds', '{bounds}')") - self.cursor.execute( - "INSERT INTO metadata (name, value) VALUES('format', 'jpg')" - ) + self.cursor.execute("INSERT INTO metadata (name, value) VALUES('format', 'jpg')") elif suffix == ".sqlitedb": # s is always 0 - self.cursor.execute( - "CREATE TABLE tiles (x int, y int, z int, s int, image blob, PRIMARY KEY (x,y,z,s));" - ) + self.cursor.execute("CREATE TABLE tiles (x int, y int, z int, s int, image blob, PRIMARY KEY (x,y,z,s));") self.cursor.execute("CREATE INDEX IND on tiles (x,y,z,s)") # Info is simple "2|4" for example, it gets populated later self.cursor.execute("CREATE TABLE info (maxzoom Int, minzoom Int);") @@ -202,12 +182,12 @@ def createDB(self, self.db.commit() logging.info("Created database file %s" % dbname) - def writeTiles(self, - tiles: list, - base: str ="./", - ): - """ - Write map tiles into the to the map tile cache + def writeTiles( + self, + tiles: list, + base: str = "./", + ): + """Write map tiles into the to the map tile cache. Args: tiles (list): The map tiles to write to the map tile cache @@ -219,11 +199,11 @@ def writeTiles(self, # xyz.dump() self.writeTile(xyz) - def writeTile(self, - tile: MapTile, - ): - """ - Write a map tile into the sqlite database file + def writeTile( + self, + tile: MapTile, + ): + """Write a map tile into the sqlite database file. Args: tile (MapTile): The map tile to write to the file @@ -250,15 +230,12 @@ def writeTile(self, self.db.commit() + if __name__ == "__main__": """This is just a hook so this file can be run standlone during development.""" - parser = argparse.ArgumentParser( - description="Create an mbtiles basemap for ODK Collect" - ) + parser = argparse.ArgumentParser(description="Create an mbtiles basemap for ODK Collect") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") - parser.add_argument( - "-d", "--database", default="test.mbtiles", help="Database file name" - ) + parser.add_argument("-d", "--database", default="test.mbtiles", help="Database file name") args = parser.parse_args() # if verbose, dump to the terminal. @@ -266,9 +243,7 @@ def writeTile(self, log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) diff --git a/osm_fieldwork/xforms.yaml b/osm_fieldwork/xforms.yaml index d23507db..da5a87d9 100644 --- a/osm_fieldwork/xforms.yaml +++ b/osm_fieldwork/xforms.yaml @@ -165,4 +165,3 @@ multiple: - healthcare - amenity_type - specialty - diff --git a/osm_fieldwork/xlsforms/__init__.py b/osm_fieldwork/xlsforms/__init__.py index ca6db153..4d3fd9c4 100644 --- a/osm_fieldwork/xlsforms/__init__.py +++ b/osm_fieldwork/xlsforms/__init__.py @@ -2,7 +2,6 @@ import os - xlsforms_path = os.path.dirname(os.path.abspath(__file__)) buildings = f"{xlsforms_path}/buildings.xls" diff --git a/osm_fieldwork/yamlfile.py b/osm_fieldwork/yamlfile.py index eb74e887..c8c0b62e 100755 --- a/osm_fieldwork/yamlfile.py +++ b/osm_fieldwork/yamlfile.py @@ -18,23 +18,24 @@ # along with Osm-Fieldwork. If not, see . # -import yaml import argparse import logging import sys +import yaml + # Instantiate logger log = logging.getLogger(__name__) class YamlFile(object): - """Config file in YAML format""" + """Config file in YAML format.""" - def __init__(self, - data: str, - ): - """ - This parses a yaml file into a dictionary for easy access. + def __init__( + self, + data: str, + ): + """This parses a yaml file into a dictionary for easy access. Args: data (str): The filespec of the YAML file to read @@ -43,18 +44,18 @@ def __init__(self, (YamlFile): An instance of this object """ self.filespec = None - #if data == str: + # if data == str: self.filespec = data self.file = open(data, "rb").read() self.yaml = yaml.load(self.file, Loader=yaml.Loader) - #else: + # else: # self.yaml = yaml.load(str(data), Loader=yaml.Loader) - def privateData(self, - keyword: str, - ): - """ - See if a keyword is in the private data category + def privateData( + self, + keyword: str, + ): + """See if a keyword is in the private data category. Args: keyword (str): The keyword to search for @@ -67,11 +68,11 @@ def privateData(self, return True return False - def ignoreData(self, - keyword: str, - ): - """ - See if a keyword is in the ignore data category + def ignoreData( + self, + keyword: str, + ): + """See if a keyword is in the ignore data category. Args: keyword (str): The keyword to search for @@ -84,11 +85,11 @@ def ignoreData(self, return True return False - def convertData(self, - keyword: str, - ): - """ - See if a keyword is in the convert data category + def convertData( + self, + keyword: str, + ): + """See if a keyword is in the convert data category. Args: keyword (str): The keyword to search for @@ -102,7 +103,7 @@ def convertData(self, return False def dump(self): - """Dump internal data structures, for debugging purposes only""" + """Dump internal data structures, for debugging purposes only.""" if self.filespec: print("YAML file: %s" % self.filespec) for key, values in self.yaml.items(): @@ -116,16 +117,16 @@ def dump(self): print(f"\t{i} = {j}") else: print(f"\t{k1} = {v1}") - print('------------------') + print("------------------") else: print(f"\t{v}") - def write(self, - table: list, - ): - """ - Add to the YAML file - + def write( + self, + table: list, + ): + """Add to the YAML file. + Args: table (list): The name of the database table @@ -133,7 +134,7 @@ def write(self, (str): The modified YAML data """ tab = " " - yaml = ["select:", f"{tab}\"osm_id\": id", f"{tab}tags:"] + yaml = ["select:", f'{tab}"osm_id": id', f"{tab}tags:"] for item in where: yaml.append(f"{tab}{tab}- {item}") yaml.append("from:") @@ -149,6 +150,7 @@ def write(self, yaml.append(f"{notnull}") return yaml + # # This script can be run standalone for debugging purposes. It's easier to debug # this way than using pytest, @@ -157,10 +159,7 @@ def write(self, """This is just a hook so this file can be run standlone during development.""" parser = argparse.ArgumentParser(description="Read and parse a YAML file") parser.add_argument("-v", "--verbose", action="store_true", help="verbose output") - parser.add_argument( - "-i", "--infile", required=True, default="./xforms.yaml", - help="The YAML input file" - ) + parser.add_argument("-i", "--infile", required=True, default="./xforms.yaml", help="The YAML input file") args = parser.parse_args() # if verbose, dump to the terminal. @@ -168,12 +167,10 @@ def write(self, log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(threadName)10s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(threadName)10s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) log.addHandler(ch) - + yaml1 = YamlFile(args.infile) yaml1.dump() diff --git a/tests/test_conflation.py b/tests/test_conflation.py index 082e02d9..61a0a626 100755 --- a/tests/test_conflation.py +++ b/tests/test_conflation.py @@ -20,7 +20,7 @@ import argparse import os -import sys + from osm_fieldwork.odk_merge import OdkMerge, conflateThread from osm_fieldwork.osmfile import OsmFile @@ -30,12 +30,13 @@ parser = argparse.ArgumentParser(description="Test odk_merge") parser.add_argument("--odk", default=f"{rootdir}/testdata/odk_pois.osm", help="The ODK file") parser.add_argument("--osm", default=f"{rootdir}/testdata/osm_buildings.geojson", help="The OSM data") -parser.add_argument("-d", "--database", default=f"PG:colorado", help="The database name") +parser.add_argument("-d", "--database", default="PG:colorado", help="The database name") parser.add_argument("-b", "--boundary", default=f"{rootdir}/testdata/Salida.geojson", help="The project AOI") args = parser.parse_args() + def test_file(): - """This tests conflating against the GeoJson data extract file""" + """This tests conflating against the GeoJson data extract file.""" passes = 0 osm = OsmFile() osmdata = osm.loadFile(args.odk) @@ -48,12 +49,13 @@ def test_file(): passes += 1 # The first feature is a match, so has the OSM ID, the second # feature doesn't match, so negative ID - if data[0]['attrs']['id'] > 0 and data[1]['attrs']['id'] < 0: + if data[0]["attrs"]["id"] > 0 and data[1]["attrs"]["id"] < 0: passes += 1 # duplicates have a fixme tag added - if 'fixme' in data[0]['tags'] and 'fixme' not in data[1]['tags']: + if "fixme" in data[0]["tags"] and "fixme" not in data[1]["tags"]: passes += 1 - assert(passes == 3) + assert passes == 3 + # FIXME update test_db to use local db in CI # def test_db(): diff --git a/tests/test_convert.py b/tests/test_convert.py index 67ee4528..92d9d978 100755 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -18,70 +18,74 @@ # along with Osm-Fieldwork. If not, see . # -import os -import sys import argparse -from osm_fieldwork.xlsforms import xlsforms_path +import os + from osm_fieldwork.convert import Convert +from osm_fieldwork.xlsforms import xlsforms_path # find the path of root tests dir rootdir = os.path.dirname(os.path.abspath(__file__)) -parser = argparse.ArgumentParser( - description="Read and convert a JSON file from ODK Central" -) +parser = argparse.ArgumentParser(description="Read and convert a JSON file from ODK Central") parser.add_argument("--infile", default=f"{rootdir}/testdata/testcamps.json", help="The JSON input file") args = parser.parse_args() path = xlsforms_path.replace("/xlsforms", "") csv = Convert(f"{path}/xforms.yaml") + def test_keywords(): - """Convert a feature""" + """Convert a feature.""" hits = 0 - if csv.convertData('fee'): - hits += 1 - if csv.convertData('sac_scale') is False: - hits += 1 + if csv.convertData("fee"): + hits += 1 + if csv.convertData("sac_scale") is False: + hits += 1 assert hits == 2 + def test_convert_tag(): - """Test tag conversion""" + """Test tag conversion.""" hits = 0 # Test a tag that gets converted - if csv.convertTag("altitude") == 'ele': + if csv.convertTag("altitude") == "ele": hits += 1 # Test a tag that doesn't get converted - if csv.convertTag("foobar") == 'foobar': + if csv.convertTag("foobar") == "foobar": hits += 1 assert hits == 2 + def test_single_value(): - """Test tag value conversion""" + """Test tag value conversion.""" hits = 0 # Test a value that gets converted - if csv.convertValue("building:floor", 'wood'): + if csv.convertValue("building:floor", "wood"): hits += 1 assert hits == 1 + def test_sub_value(): - """Test tag value conversion""" + """Test tag value conversion.""" hits = 0 # Test a value that gets converted - val = csv.convertValue("cemetery_services", 'cemetery') - if len(val) == 1 and val[0]['amenity'] == 'grave_yard': + val = csv.convertValue("cemetery_services", "cemetery") + if len(val) == 1 and val[0]["amenity"] == "grave_yard": hits += 1 assert hits == 1 + def test_multiple_value(): - """Test tag value conversion""" + """Test tag value conversion.""" hits = 0 # Test a value that gets converted - vals = csv.convertValue('amenity', 'coffee') - if len(vals) == 2 and vals[0]['amenity'] == 'cafe' and vals[1]['cuisine'] == 'coffee_shop': + vals = csv.convertValue("amenity", "coffee") + if len(vals) == 2 and vals[0]["amenity"] == "cafe" and vals[1]["cuisine"] == "coffee_shop": hits += 1 assert hits == 1 + # Run standalone for easier debugging when not under pytest if __name__ == "__main__": test_keywords() diff --git a/tests/test_csv.py b/tests/test_csv.py index 29b3882d..6429cbc4 100755 --- a/tests/test_csv.py +++ b/tests/test_csv.py @@ -20,14 +20,13 @@ import argparse import os -from osm_fieldwork.CSVDump import CSVDump + +from osm_fieldwork.CSVDump import CSVDump # find the path of root tests dir rootdir = os.path.dirname(os.path.abspath(__file__)) -parser = argparse.ArgumentParser( - description="Read and parse a CSV file from ODK Central" -) +parser = argparse.ArgumentParser(description="Read and parse a CSV file from ODK Central") parser.add_argument("--infile", default=f"{rootdir}/testdata/test.csv", help="The CSV input file") args = parser.parse_args() csv = CSVDump() @@ -37,12 +36,12 @@ def test_csv(): - """Make sure the CSV file got loaded and parsed""" + """Make sure the CSV file got loaded and parsed.""" assert len(data) > 0 def test_init(): - """Make sure the YAML file got loaded""" + """Make sure the YAML file got loaded.""" assert len(csv.yaml.yaml) > 0 diff --git a/tests/test_osm.py b/tests/test_osm.py index 2722de7b..37daf84d 100755 --- a/tests/test_osm.py +++ b/tests/test_osm.py @@ -18,17 +18,15 @@ # along with osm_fieldwork. If not, see . # -import os -from osm_fieldwork.convert import escape import argparse +import os + from osm_fieldwork.osmfile import OsmFile # find the path of root tests dir rootdir = os.path.dirname(os.path.abspath(__file__)) -parser = argparse.ArgumentParser( - description="Read and parse a CSV file from ODK Central" -) +parser = argparse.ArgumentParser(description="Read and parse a CSV file from ODK Central") parser.add_argument("--infile", default=f"{rootdir}/testdata/odk_pois.osm", help="The CSV input file") parser.add_argument("--outfile", default=f"{rootdir}/testdata/test-out.osm", help="The output OSM XML file") args = parser.parse_args() @@ -41,7 +39,7 @@ def test_init(): - """Make sure the OSM file is initialized""" + """Make sure the OSM file is initialized.""" assert os.path.exists(args.infile) diff --git a/tests/test_uriparser.py b/tests/test_uriparser.py index c5eac100..09f34c41 100755 --- a/tests/test_uriparser.py +++ b/tests/test_uriparser.py @@ -19,61 +19,65 @@ # import argparse -import os -import sys + from osm_fieldwork.make_data_extract import uriParser -parser = argparse.ArgumentParser( - description="Test odk_merge" -) +parser = argparse.ArgumentParser(description="Test odk_merge") parser.add_argument("--infile", help="The input file") args = parser.parse_args() out = None + + def test_dbname_only(): dbname = "testdb" db = uriParser(dbname) # print(db['dbname'] == dbname) - assert(db['dbname'] == dbname) + assert db["dbname"] == dbname + def test_dbname(): passes = 0 db = uriParser("fmtm:XxXx@localhost/underpass") - if db['dbname'] == 'underpass': + if db["dbname"] == "underpass": passes += 1 # print(db) db = uriParser("fmtm:XxXx/underpass") - if db['dbname'] == 'underpass': + if db["dbname"] == "underpass": passes += 1 # print(db) db = uriParser("fmtm:XxXx@localhost:5433/underpass") - if db['dbname'] == 'underpass': + if db["dbname"] == "underpass": passes += 1 - #print(db) + # print(db) # print(passes == 3) - assert(passes == 3) - + assert passes == 3 + + def test_host(): db = uriParser("fmtm@localhost") - dbhost = db['dbhost'] + dbhost = db["dbhost"] db2 = uriParser("fmtm@hostlocal") - db2host = db2['dbhost'] + db2host = db2["dbhost"] # print(dbhost == 'localhost' and db2host == 'hostlocal') - assert(dbhost == 'localhost' and db2host == 'hostlocal') + assert dbhost == "localhost" and db2host == "hostlocal" + def test_user(): db = uriParser("fmtm@localhost") - dbuser = db['dbuser'] - dbpass = db['dbpass'] + dbuser = db["dbuser"] + dbpass = db["dbpass"] # print(dbuser == 'fmtm', dbpass is None) - assert(dbuser == 'fmtm' and dbpass is None) + assert dbuser == "fmtm" and dbpass is None + def test_password(): db = uriParser("fmtm:XxXx@localhost") - dbuser = db['dbuser'] - dbpass = db['dbpass'] + dbuser = db["dbuser"] + dbpass = db["dbpass"] # print(dbuser == 'fmtm', dbpass == 'XxXx') - assert(dbuser == 'fmtm' and dbpass == 'XxXx') + assert dbuser == "fmtm" and dbpass == "XxXx" + def test_port(): db2 = uriParser("fmtm:XxXx@localhost:5433") @@ -81,7 +85,8 @@ def test_port(): db3 = uriParser("fmtm:XxXx@hostlocal:5433/fmtmdb") # print(db3) # print(db2['dbport'] == '5433' and db3['dbport'] == '5433') - assert(db2['dbport'] == '5433' and db3['dbport'] == '5433') + assert db2["dbport"] == "5433" and db3["dbport"] == "5433" + if __name__ == "__main__": # print("--- test_dbname_only() ---") diff --git a/tests/test_yaml.py b/tests/test_yaml.py index 2e2fa533..fe5dba35 100755 --- a/tests/test_yaml.py +++ b/tests/test_yaml.py @@ -19,15 +19,12 @@ # import argparse -import sys -import os + from osm_fieldwork.xlsforms import xlsforms_path from osm_fieldwork.yamlfile import YamlFile parser = argparse.ArgumentParser(description="Read and parse a YAML file") -parser.add_argument( - "--infile", help="The YAML input file" -) +parser.add_argument("--infile", help="The YAML input file") args = parser.parse_args() path = xlsforms_path.replace("/xlsforms", "") @@ -35,43 +32,48 @@ data = YamlFile(infile) # data.dump() + def test_load(): - """See if the file got loaded""" + """See if the file got loaded.""" hits = 0 if len(data.yaml.keys()) > 0: hits = 1 - if len(data.yaml['convert']) > 0: + if len(data.yaml["convert"]) > 0: hits += 1 - if len(data.yaml['ignore']) > 0: + if len(data.yaml["ignore"]) > 0: hits += 1 - if len(data.yaml['private']) > 0: + if len(data.yaml["private"]) > 0: hits += 1 assert hits == 4 + def test_good(): hits = 0 - if data.convertData('amenity'): + if data.convertData("amenity"): hits += 1 - if data.convertData('foobar') == False: + if data.convertData("foobar") is False: hits += 1 assert hits == 2 + def test_private(): hits = 0 - if data.privateData('income'): + if data.privateData("income"): hits += 1 - if data.privateData('foobar') == False: + if data.privateData("foobar") is False: hits += 1 assert hits == 2 + def test_ignore(): hits = 0 - if data.ignoreData('model'): + if data.ignoreData("model"): hits += 1 - if data.ignoreData('foobar') == False: + if data.ignoreData("foobar") is False: hits += 1 assert hits == 2 + # def test_bool_bad(): # assert "bad keyword" data.yaml['convert'] is not True diff --git a/tests/testdata/testcamps.json b/tests/testdata/testcamps.json index d3601da6..8165eba4 100644 --- a/tests/testdata/testcamps.json +++ b/tests/testdata/testcamps.json @@ -1,6925 +1,6209 @@ { - "value": [ - { - "start": "2023-07-31T10:19:26.690-06:00", - "end": "2023-07-31T10:19:40.212-06:00", - "today": "2023-07-31", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.0918728, - 39.8864339, - 2581.2026872125753 - ], - "properties": { - "accuracy": 26.996 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.09214237758091, - 39.88781215594042, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:3c562ff9-0ac5-4643-9495-0e1fbb2a6475" - }, - "__id": "uuid:3c562ff9-0ac5-4643-9495-0e1fbb2a6475", - "__system": { - "submissionDate": "2023-07-31T16:23:43.520Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "value": [ + { + "start": "2023-07-31T10:19:26.690-06:00", + "end": "2023-07-31T10:19:40.212-06:00", + "today": "2023-07-31", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.0918728, 39.8864339, 2581.2026872125753], + "properties": { + "accuracy": 26.996 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null + }, + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.09214237758091, 39.88781215594042, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:3c562ff9-0ac5-4643-9495-0e1fbb2a6475" + }, + "__id": "uuid:3c562ff9-0ac5-4643-9495-0e1fbb2a6475", + "__system": { + "submissionDate": "2023-07-31T16:23:43.520Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-31T10:18:48.527-06:00", + "end": "2023-07-31T10:19:24.094-06:00", + "today": "2023-07-31", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.0919964, 39.886546, 2579.0135998385126], + "properties": { + "accuracy": 11.571 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-31T10:18:48.527-06:00", - "end": "2023-07-31T10:19:24.094-06:00", - "today": "2023-07-31", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.0919964, - 39.886546, - 2579.0135998385126 - ], - "properties": { - "accuracy": 11.571 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.0918782, - 39.8864359, - 2578.904222855404 - ], - "properties": { - "accuracy": 25.81100082397461 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:d7ba57da-45f7-4072-90c6-c0f08d2231bc" - }, - "__id": "uuid:d7ba57da-45f7-4072-90c6-c0f08d2231bc", - "__system": { - "submissionDate": "2023-07-31T16:23:42.238Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.0918782, 39.8864359, 2578.904222855404], + "properties": { + "accuracy": 25.81100082397461 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:d7ba57da-45f7-4072-90c6-c0f08d2231bc" + }, + "__id": "uuid:d7ba57da-45f7-4072-90c6-c0f08d2231bc", + "__system": { + "submissionDate": "2023-07-31T16:23:42.238Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-31T10:14:55.408-06:00", + "end": "2023-07-31T10:15:10.655-06:00", + "today": "2023-07-31", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.0963182, 39.8988892, 2558.843825587143], + "properties": { + "accuracy": 9.074 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-31T10:14:55.408-06:00", - "end": "2023-07-31T10:15:10.655-06:00", - "today": "2023-07-31", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.0963182, - 39.8988892, - 2558.843825587143 - ], - "properties": { - "accuracy": 9.074 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.0963182, - 39.8988892, - 2558.843825587143 - ], - "properties": { - "accuracy": 9.074000358581543 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:22e42cdc-b2b1-4ea9-9ec1-611aef962e9e" - }, - "__id": "uuid:22e42cdc-b2b1-4ea9-9ec1-611aef962e9e", - "__system": { - "submissionDate": "2023-07-31T16:15:11.476Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.0963182, 39.8988892, 2558.843825587143], + "properties": { + "accuracy": 9.074000358581543 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:22e42cdc-b2b1-4ea9-9ec1-611aef962e9e" + }, + "__id": "uuid:22e42cdc-b2b1-4ea9-9ec1-611aef962e9e", + "__system": { + "submissionDate": "2023-07-31T16:15:11.476Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-31T10:14:13.169-06:00", + "end": "2023-07-31T10:14:23.570-06:00", + "today": "2023-07-31", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.0958886, 39.8989363, 2559.034782536643], + "properties": { + "accuracy": 6.9 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-31T10:14:13.169-06:00", - "end": "2023-07-31T10:14:23.570-06:00", - "today": "2023-07-31", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.0958886, - 39.8989363, - 2559.034782536643 - ], - "properties": { - "accuracy": 6.9 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.0958886, - 39.8989363, - 2559.034782536643 - ], - "properties": { - "accuracy": 6.900000095367432 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:66bc62e9-e883-4043-8ea4-f91f3881ac04" - }, - "__id": "uuid:66bc62e9-e883-4043-8ea4-f91f3881ac04", - "__system": { - "submissionDate": "2023-07-31T16:14:25.885Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.0958886, 39.8989363, 2559.034782536643], + "properties": { + "accuracy": 6.900000095367432 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:66bc62e9-e883-4043-8ea4-f91f3881ac04" + }, + "__id": "uuid:66bc62e9-e883-4043-8ea4-f91f3881ac04", + "__system": { + "submissionDate": "2023-07-31T16:14:25.885Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-31T10:11:09.261-06:00", + "end": "2023-07-31T10:11:40.802-06:00", + "today": "2023-07-31", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.0958431, 39.8983838, 2557.04833984375], + "properties": { + "accuracy": 8.083 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-31T10:11:09.261-06:00", - "end": "2023-07-31T10:11:40.802-06:00", - "today": "2023-07-31", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.0958431, - 39.8983838, - 2557.04833984375 - ], - "properties": { - "accuracy": 8.083 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.0958273, - 39.8983258, - 2559.6736018279553 - ], - "properties": { - "accuracy": 24.06100082397461 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:6910cdbe-b69e-4228-a488-7cc432ec0d1e" - }, - "__id": "uuid:6910cdbe-b69e-4228-a488-7cc432ec0d1e", - "__system": { - "submissionDate": "2023-07-31T16:11:43.654Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.0958273, 39.8983258, 2559.6736018279553], + "properties": { + "accuracy": 24.06100082397461 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:6910cdbe-b69e-4228-a488-7cc432ec0d1e" + }, + "__id": "uuid:6910cdbe-b69e-4228-a488-7cc432ec0d1e", + "__system": { + "submissionDate": "2023-07-31T16:11:43.654Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T15:40:09.205-06:00", + "end": "2023-07-30T15:40:26.718-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.0457171, 40.034619, 2971.1255768627534], + "properties": { + "accuracy": 4.63 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T15:40:09.205-06:00", - "end": "2023-07-30T15:40:26.718-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.0457171, - 40.034619, - 2971.1255768627534 - ], - "properties": { - "accuracy": 4.63 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.0457259, - 40.034624, - 2971.3723099221193 - ], - "properties": { - "accuracy": 17.3799991607666 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:08676c0f-6ec8-45c7-baef-8065a8284c70" - }, - "__id": "uuid:08676c0f-6ec8-45c7-baef-8065a8284c70", - "__system": { - "submissionDate": "2023-07-30T21:40:28.602Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.0457259, 40.034624, 2971.3723099221193], + "properties": { + "accuracy": 17.3799991607666 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:08676c0f-6ec8-45c7-baef-8065a8284c70" + }, + "__id": "uuid:08676c0f-6ec8-45c7-baef-8065a8284c70", + "__system": { + "submissionDate": "2023-07-30T21:40:28.602Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T15:15:05.043-06:00", + "end": "2023-07-30T15:15:16.098-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.0320477, 40.047342, 2725.009033203125], + "properties": { + "accuracy": 3.79 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T15:15:05.043-06:00", - "end": "2023-07-30T15:15:16.098-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.0320477, - 40.047342, - 2725.009033203125 - ], - "properties": { - "accuracy": 3.79 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.031976, - 40.0473575, - 2722.7164000865746 - ], - "properties": { - "accuracy": 5.11899995803833 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:46d1020a-c5c7-4014-b8bb-a50aaa0120cd" - }, - "__id": "uuid:46d1020a-c5c7-4014-b8bb-a50aaa0120cd", - "__system": { - "submissionDate": "2023-07-30T21:15:18.858Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.031976, 40.0473575, 2722.7164000865746], + "properties": { + "accuracy": 5.11899995803833 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:46d1020a-c5c7-4014-b8bb-a50aaa0120cd" + }, + "__id": "uuid:46d1020a-c5c7-4014-b8bb-a50aaa0120cd", + "__system": { + "submissionDate": "2023-07-30T21:15:18.858Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T14:56:28.011-06:00", + "end": "2023-07-30T14:56:39.129-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.1281741, 40.0486416, 2299.53838157457], + "properties": { + "accuracy": 33.853 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T14:56:28.011-06:00", - "end": "2023-07-30T14:56:39.129-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.1281741, - 40.0486416, - 2299.53838157457 - ], - "properties": { - "accuracy": 33.853 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.1281173, - 40.0486347, - 2299.866994671559 - ], - "properties": { - "accuracy": 8.972000122070312 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:7160f14a-0142-4433-9fd8-464f84c417b5" - }, - "__id": "uuid:7160f14a-0142-4433-9fd8-464f84c417b5", - "__system": { - "submissionDate": "2023-07-30T20:56:42.576Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.1281173, 40.0486347, 2299.866994671559], + "properties": { + "accuracy": 8.972000122070312 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:7160f14a-0142-4433-9fd8-464f84c417b5" + }, + "__id": "uuid:7160f14a-0142-4433-9fd8-464f84c417b5", + "__system": { + "submissionDate": "2023-07-30T20:56:42.576Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T14:55:46.802-06:00", + "end": "2023-07-30T14:56:02.486-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.1266835, 40.0481554, 2304.3235299103835], + "properties": { + "accuracy": 4.298 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T14:55:46.802-06:00", - "end": "2023-07-30T14:56:02.486-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.1266835, - 40.0481554, - 2304.3235299103835 - ], - "properties": { - "accuracy": 4.298 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.1266676, - 40.0481518, - 2304.1087975404685 - ], - "properties": { - "accuracy": 4.498000144958496 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:98cb5a58-23d3-41bf-8e33-0eeb540bae6b" - }, - "__id": "uuid:98cb5a58-23d3-41bf-8e33-0eeb540bae6b", - "__system": { - "submissionDate": "2023-07-30T20:56:40.693Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.1266676, 40.0481518, 2304.1087975404685], + "properties": { + "accuracy": 4.498000144958496 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:98cb5a58-23d3-41bf-8e33-0eeb540bae6b" + }, + "__id": "uuid:98cb5a58-23d3-41bf-8e33-0eeb540bae6b", + "__system": { + "submissionDate": "2023-07-30T20:56:40.693Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T14:55:18.531-06:00", + "end": "2023-07-30T14:55:32.444-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.1260883, 40.0478752, 2306.9520872149646], + "properties": { + "accuracy": 5.785 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T14:55:18.531-06:00", - "end": "2023-07-30T14:55:32.444-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.1260883, - 40.0478752, - 2306.9520872149646 - ], - "properties": { - "accuracy": 5.785 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.1259448, - 40.0477163, - 2306.5039734490115 - ], - "properties": { - "accuracy": 8.125 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:90ff38e2-6b4e-4e94-b3e3-2315bbff6082" - }, - "__id": "uuid:90ff38e2-6b4e-4e94-b3e3-2315bbff6082", - "__system": { - "submissionDate": "2023-07-30T20:56:37.809Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.1259448, 40.0477163, 2306.5039734490115], + "properties": { + "accuracy": 8.125 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:90ff38e2-6b4e-4e94-b3e3-2315bbff6082" + }, + "__id": "uuid:90ff38e2-6b4e-4e94-b3e3-2315bbff6082", + "__system": { + "submissionDate": "2023-07-30T20:56:37.809Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T14:53:05.004-06:00", + "end": "2023-07-30T14:53:59.586-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.1292545, 40.0486351, 2298.8790752459954], + "properties": { + "accuracy": 11.033 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T14:53:05.004-06:00", - "end": "2023-07-30T14:53:59.586-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.1292545, - 40.0486351, - 2298.8790752459954 - ], - "properties": { - "accuracy": 11.033 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.129346, - 40.0486695, - 2298.871127950042 - ], - "properties": { - "accuracy": 34.28200149536133 - } - }, - "image": null, - "comment": "There's 2" - } - }, - "meta": { - "instanceID": "uuid:93624a8e-27f7-4f62-be93-951656ab4136" - }, - "__id": "uuid:93624a8e-27f7-4f62-be93-951656ab4136", - "__system": { - "submissionDate": "2023-07-30T20:56:34.306Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.129346, 40.0486695, 2298.871127950042], + "properties": { + "accuracy": 34.28200149536133 } + }, + "image": null, + "comment": "There's 2" + } + }, + "meta": { + "instanceID": "uuid:93624a8e-27f7-4f62-be93-951656ab4136" + }, + "__id": "uuid:93624a8e-27f7-4f62-be93-951656ab4136", + "__system": { + "submissionDate": "2023-07-30T20:56:34.306Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T13:07:34.527-06:00", + "end": "2023-07-30T13:07:47.021-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.5863001, 40.0635192, 2764.971318135703], + "properties": { + "accuracy": 7.471 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T13:07:34.527-06:00", - "end": "2023-07-30T13:07:47.021-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.5863001, - 40.0635192, - 2764.971318135703 - ], - "properties": { - "accuracy": 7.471 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.5863752, - 40.0635784, - 2765.1581038307195 - ], - "properties": { - "accuracy": 10.36299991607666 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:aac609fe-db03-4065-8bb3-b7cdf15aa477" - }, - "__id": "uuid:aac609fe-db03-4065-8bb3-b7cdf15aa477", - "__system": { - "submissionDate": "2023-07-30T19:24:28.891Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.5863752, 40.0635784, 2765.1581038307195], + "properties": { + "accuracy": 10.36299991607666 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:aac609fe-db03-4065-8bb3-b7cdf15aa477" + }, + "__id": "uuid:aac609fe-db03-4065-8bb3-b7cdf15aa477", + "__system": { + "submissionDate": "2023-07-30T19:24:28.891Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T13:04:39.190-06:00", + "end": "2023-07-30T13:04:50.711-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.5969568, 40.0576603, 2721.6223273301484], + "properties": { + "accuracy": 5.085 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T13:04:39.190-06:00", - "end": "2023-07-30T13:04:50.711-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.5969568, - 40.0576603, - 2721.6223273301484 - ], - "properties": { - "accuracy": 5.085 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.5969676, - 40.0576523, - 2721.5251168498116 - ], - "properties": { - "accuracy": 8.005999565124512 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:e19d8a95-fd39-4ba3-80b6-81a17ce2ee77" - }, - "__id": "uuid:e19d8a95-fd39-4ba3-80b6-81a17ce2ee77", - "__system": { - "submissionDate": "2023-07-30T19:24:27.858Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.5969676, 40.0576523, 2721.5251168498116], + "properties": { + "accuracy": 8.005999565124512 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:e19d8a95-fd39-4ba3-80b6-81a17ce2ee77" + }, + "__id": "uuid:e19d8a95-fd39-4ba3-80b6-81a17ce2ee77", + "__system": { + "submissionDate": "2023-07-30T19:24:27.858Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T12:54:30.502-06:00", + "end": "2023-07-30T12:54:46.751-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.6433031, 40.0559696, 2618.982197465399], + "properties": { + "accuracy": 3.933 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T12:54:30.502-06:00", - "end": "2023-07-30T12:54:46.751-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.6433031, - 40.0559696, - 2618.982197465399 - ], - "properties": { - "accuracy": 3.933 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.6432818, - 40.0559866, - 2618.919417965507 - ], - "properties": { - "accuracy": 4.005000114440918 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:702a8008-387f-4b01-bea9-df860fd1edc2" - }, - "__id": "uuid:702a8008-387f-4b01-bea9-df860fd1edc2", - "__system": { - "submissionDate": "2023-07-30T19:24:26.811Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.6432818, 40.0559866, 2618.919417965507], + "properties": { + "accuracy": 4.005000114440918 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:702a8008-387f-4b01-bea9-df860fd1edc2" + }, + "__id": "uuid:702a8008-387f-4b01-bea9-df860fd1edc2", + "__system": { + "submissionDate": "2023-07-30T19:24:26.811Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T12:52:19.396-06:00", + "end": "2023-07-30T12:52:32.128-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.6458861, 40.0569125, 2625.040657669187], + "properties": { + "accuracy": 3.79 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T12:52:19.396-06:00", - "end": "2023-07-30T12:52:32.128-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.6458861, - 40.0569125, - 2625.040657669187 - ], - "properties": { - "accuracy": 3.79 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.6458861, - 40.0569125, - 2625.040657669187 - ], - "properties": { - "accuracy": 3.7899999618530273 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:175bfa10-753e-4e2b-bc96-90047bed9099" - }, - "__id": "uuid:175bfa10-753e-4e2b-bc96-90047bed9099", - "__system": { - "submissionDate": "2023-07-30T19:24:25.710Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.6458861, 40.0569125, 2625.040657669187], + "properties": { + "accuracy": 3.7899999618530273 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:175bfa10-753e-4e2b-bc96-90047bed9099" + }, + "__id": "uuid:175bfa10-753e-4e2b-bc96-90047bed9099", + "__system": { + "submissionDate": "2023-07-30T19:24:25.710Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T12:45:49.864-06:00", + "end": "2023-07-30T12:46:00.907-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.652705, 40.0632981, 2615.472900390625], + "properties": { + "accuracy": 4.108 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T12:45:49.864-06:00", - "end": "2023-07-30T12:46:00.907-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.652705, - 40.0632981, - 2615.472900390625 - ], - "properties": { - "accuracy": 4.108 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.6527356, - 40.0632959, - 2611.170675642187 - ], - "properties": { - "accuracy": 5.264999866485596 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:e419fd32-edbf-4c4e-acdc-ffd6d2ed64b8" - }, - "__id": "uuid:e419fd32-edbf-4c4e-acdc-ffd6d2ed64b8", - "__system": { - "submissionDate": "2023-07-30T19:24:23.421Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.6527356, 40.0632959, 2611.170675642187], + "properties": { + "accuracy": 5.264999866485596 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:e419fd32-edbf-4c4e-acdc-ffd6d2ed64b8" + }, + "__id": "uuid:e419fd32-edbf-4c4e-acdc-ffd6d2ed64b8", + "__system": { + "submissionDate": "2023-07-30T19:24:23.421Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T12:43:27.154-06:00", + "end": "2023-07-30T12:43:43.682-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.6570378, 40.0676866, 2623.854037461186], + "properties": { + "accuracy": 3.9 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T12:43:27.154-06:00", - "end": "2023-07-30T12:43:43.682-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.6570378, - 40.0676866, - 2623.854037461186 - ], - "properties": { - "accuracy": 3.9 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.6569911, - 40.0677107, - 2623.8073523914813 - ], - "properties": { - "accuracy": 4.442999839782715 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:ff59d2b0-d807-4788-a80c-a0e3f3e62893" - }, - "__id": "uuid:ff59d2b0-d807-4788-a80c-a0e3f3e62893", - "__system": { - "submissionDate": "2023-07-30T19:24:22.326Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.6569911, 40.0677107, 2623.8073523914813], + "properties": { + "accuracy": 4.442999839782715 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:ff59d2b0-d807-4788-a80c-a0e3f3e62893" + }, + "__id": "uuid:ff59d2b0-d807-4788-a80c-a0e3f3e62893", + "__system": { + "submissionDate": "2023-07-30T19:24:22.326Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T12:36:20.658-06:00", + "end": "2023-07-30T12:36:38.331-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.6788944, 40.0772694, 2714.6653014601306], + "properties": { + "accuracy": 28.422 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T12:36:20.658-06:00", - "end": "2023-07-30T12:36:38.331-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.6788944, - 40.0772694, - 2714.6653014601306 - ], - "properties": { - "accuracy": 28.422 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.6788387, - 40.0772608, - 2711.3034074987513 - ], - "properties": { - "accuracy": 4.611000061035156 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:0b981537-e5d2-4f04-85b5-a3d5baab5d08" - }, - "__id": "uuid:0b981537-e5d2-4f04-85b5-a3d5baab5d08", - "__system": { - "submissionDate": "2023-07-30T18:36:51.327Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.6788387, 40.0772608, 2711.3034074987513], + "properties": { + "accuracy": 4.611000061035156 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:0b981537-e5d2-4f04-85b5-a3d5baab5d08" + }, + "__id": "uuid:0b981537-e5d2-4f04-85b5-a3d5baab5d08", + "__system": { + "submissionDate": "2023-07-30T18:36:51.327Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:39:26.905-06:00", + "end": "2023-07-30T11:39:38.507-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.0667602, 40.2002634, 2629.909027681006], + "properties": { + "accuracy": 9.752 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:39:26.905-06:00", - "end": "2023-07-30T11:39:38.507-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.0667602, - 40.2002634, - 2629.909027681006 - ], - "properties": { - "accuracy": 9.752 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.0667602, - 40.2002634, - 2629.909027681006 - ], - "properties": { - "accuracy": 9.751999855041504 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:034c1254-0176-40cf-b1c0-736f3e76bc1a" - }, - "__id": "uuid:034c1254-0176-40cf-b1c0-736f3e76bc1a", - "__system": { - "submissionDate": "2023-07-30T17:55:59.187Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.0667602, 40.2002634, 2629.909027681006], + "properties": { + "accuracy": 9.751999855041504 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:034c1254-0176-40cf-b1c0-736f3e76bc1a" + }, + "__id": "uuid:034c1254-0176-40cf-b1c0-736f3e76bc1a", + "__system": { + "submissionDate": "2023-07-30T17:55:59.187Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:31:53.286-06:00", + "end": "2023-07-30T11:32:04.101-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.0800299, 40.1927102, 2784.5313290050503], + "properties": { + "accuracy": 9.599 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:31:53.286-06:00", - "end": "2023-07-30T11:32:04.101-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.0800299, - 40.1927102, - 2784.5313290050503 - ], - "properties": { - "accuracy": 9.599 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.0799998, - 40.1927194, - 2784.5173575210283 - ], - "properties": { - "accuracy": 12.432999610900879 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:a52796a1-dfe9-428d-b96f-9230849720a9" - }, - "__id": "uuid:a52796a1-dfe9-428d-b96f-9230849720a9", - "__system": { - "submissionDate": "2023-07-30T17:52:05.041Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.0799998, 40.1927194, 2784.5173575210283], + "properties": { + "accuracy": 12.432999610900879 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:a52796a1-dfe9-428d-b96f-9230849720a9" + }, + "__id": "uuid:a52796a1-dfe9-428d-b96f-9230849720a9", + "__system": { + "submissionDate": "2023-07-30T17:52:05.041Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:26:50.211-06:00", + "end": "2023-07-30T11:27:02.163-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.0841715, 40.1956253, 2710.8036913360943], + "properties": { + "accuracy": 7.287 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:26:50.211-06:00", - "end": "2023-07-30T11:27:02.163-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.0841715, - 40.1956253, - 2710.8036913360943 - ], - "properties": { - "accuracy": 7.287 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.0841715, - 40.1956253, - 2710.8036913360943 - ], - "properties": { - "accuracy": 7.2870001792907715 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:7b5dfed8-1b0a-452f-86c0-963317863c63" - }, - "__id": "uuid:7b5dfed8-1b0a-452f-86c0-963317863c63", - "__system": { - "submissionDate": "2023-07-30T17:52:03.667Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.0841715, 40.1956253, 2710.8036913360943], + "properties": { + "accuracy": 7.2870001792907715 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:7b5dfed8-1b0a-452f-86c0-963317863c63" + }, + "__id": "uuid:7b5dfed8-1b0a-452f-86c0-963317863c63", + "__system": { + "submissionDate": "2023-07-30T17:52:03.667Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:26:04.536-06:00", + "end": "2023-07-30T11:26:21.537-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.0843941, 40.1964838, 2705.198780014799], + "properties": { + "accuracy": 4.045 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:26:04.536-06:00", - "end": "2023-07-30T11:26:21.537-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.0843941, - 40.1964838, - 2705.198780014799 - ], - "properties": { - "accuracy": 4.045 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.0846642, - 40.1964705, - 2705.2284312838324 - ], - "properties": { - "accuracy": 13.48799991607666 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:f3666ab2-9b67-40e9-9925-144cb0fbcb55" - }, - "__id": "uuid:f3666ab2-9b67-40e9-9925-144cb0fbcb55", - "__system": { - "submissionDate": "2023-07-30T17:52:02.533Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.0846642, 40.1964705, 2705.2284312838324], + "properties": { + "accuracy": 13.48799991607666 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:f3666ab2-9b67-40e9-9925-144cb0fbcb55" + }, + "__id": "uuid:f3666ab2-9b67-40e9-9925-144cb0fbcb55", + "__system": { + "submissionDate": "2023-07-30T17:52:02.533Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:21:22.558-06:00", + "end": "2023-07-30T11:21:53.181-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1032658, 40.1901586, 2720.1283811981225], + "properties": { + "accuracy": 4.956 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:21:22.558-06:00", - "end": "2023-07-30T11:21:53.181-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1032658, - 40.1901586, - 2720.1283811981225 - ], - "properties": { - "accuracy": 4.956 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1032658, - 40.1901586, - 2720.1283811981225 - ], - "properties": { - "accuracy": 4.955999851226807 - } - }, - "image": null, - "comment": "There's several near here " - } - }, - "meta": { - "instanceID": "uuid:c0e30c2d-b417-46ff-8723-f1d4279fa2ec" - }, - "__id": "uuid:c0e30c2d-b417-46ff-8723-f1d4279fa2ec", - "__system": { - "submissionDate": "2023-07-30T17:52:01.321Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1032658, 40.1901586, 2720.1283811981225], + "properties": { + "accuracy": 4.955999851226807 } + }, + "image": null, + "comment": "There's several near here " + } + }, + "meta": { + "instanceID": "uuid:c0e30c2d-b417-46ff-8723-f1d4279fa2ec" + }, + "__id": "uuid:c0e30c2d-b417-46ff-8723-f1d4279fa2ec", + "__system": { + "submissionDate": "2023-07-30T17:52:01.321Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:19:00.545-06:00", + "end": "2023-07-30T11:19:45.357-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1033525, 40.1887703, 2727.454928204633], + "properties": { + "accuracy": 5.349 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:19:00.545-06:00", - "end": "2023-07-30T11:19:45.357-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1033525, - 40.1887703, - 2727.454928204633 - ], - "properties": { - "accuracy": 5.349 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1033897, - 40.1887455, - 2727.454928204633 - ], - "properties": { - "accuracy": 9.762999534606934 - } - }, - "image": null, - "comment": "There's the two big ones near herr" - } - }, - "meta": { - "instanceID": "uuid:155309d8-fddd-49cc-a8e8-813ed36ba835" - }, - "__id": "uuid:155309d8-fddd-49cc-a8e8-813ed36ba835", - "__system": { - "submissionDate": "2023-07-30T17:51:59.839Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1033897, 40.1887455, 2727.454928204633], + "properties": { + "accuracy": 9.762999534606934 } + }, + "image": null, + "comment": "There's the two big ones near herr" + } + }, + "meta": { + "instanceID": "uuid:155309d8-fddd-49cc-a8e8-813ed36ba835" + }, + "__id": "uuid:155309d8-fddd-49cc-a8e8-813ed36ba835", + "__system": { + "submissionDate": "2023-07-30T17:51:59.839Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:15:36.738-06:00", + "end": "2023-07-30T11:16:00.851-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1102597, 40.1812823, 2785.3709350672357], + "properties": { + "accuracy": 4.258 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:15:36.738-06:00", - "end": "2023-07-30T11:16:00.851-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1102597, - 40.1812823, - 2785.3709350672357 - ], - "properties": { - "accuracy": 4.258 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1102361, - 40.1812448, - 2787.8720703125 - ], - "properties": { - "accuracy": 6.998000144958496 - } - }, - "image": null, - "comment": "By nice creek" - } - }, - "meta": { - "instanceID": "uuid:5b1e1a5a-799c-45a2-b551-13df0eb488be" - }, - "__id": "uuid:5b1e1a5a-799c-45a2-b551-13df0eb488be", - "__system": { - "submissionDate": "2023-07-30T17:51:58.620Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1102361, 40.1812448, 2787.8720703125], + "properties": { + "accuracy": 6.998000144958496 } + }, + "image": null, + "comment": "By nice creek" + } + }, + "meta": { + "instanceID": "uuid:5b1e1a5a-799c-45a2-b551-13df0eb488be" + }, + "__id": "uuid:5b1e1a5a-799c-45a2-b551-13df0eb488be", + "__system": { + "submissionDate": "2023-07-30T17:51:58.620Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:09:06.828-06:00", + "end": "2023-07-30T11:09:30.516-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.12783, 40.187325, 2807.8626489301387], + "properties": { + "accuracy": 3.9 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:09:06.828-06:00", - "end": "2023-07-30T11:09:30.516-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.12783, - 40.187325, - 2807.8626489301387 - ], - "properties": { - "accuracy": 3.9 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.12783, - 40.187325, - 2807.8626489301387 - ], - "properties": { - "accuracy": 3.9000000953674316 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:fb72c576-ac6c-4846-89a7-28fb42da3073" - }, - "__id": "uuid:fb72c576-ac6c-4846-89a7-28fb42da3073", - "__system": { - "submissionDate": "2023-07-30T17:51:57.276Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.12783, 40.187325, 2807.8626489301387], + "properties": { + "accuracy": 3.9000000953674316 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:fb72c576-ac6c-4846-89a7-28fb42da3073" + }, + "__id": "uuid:fb72c576-ac6c-4846-89a7-28fb42da3073", + "__system": { + "submissionDate": "2023-07-30T17:51:57.276Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:07:11.712-06:00", + "end": "2023-07-30T11:07:26.218-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1282727, 40.1892407, 2813.620866000864], + "properties": { + "accuracy": 4.432 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:07:11.712-06:00", - "end": "2023-07-30T11:07:26.218-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1282727, - 40.1892407, - 2813.620866000864 - ], - "properties": { - "accuracy": 4.432 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1282589, - 40.1892094, - 2813.5676181666054 - ], - "properties": { - "accuracy": 5.0229997634887695 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:5134a572-1cba-418f-8bf6-abf9b97ad817" - }, - "__id": "uuid:5134a572-1cba-418f-8bf6-abf9b97ad817", - "__system": { - "submissionDate": "2023-07-30T17:51:55.857Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1282589, 40.1892094, 2813.5676181666054], + "properties": { + "accuracy": 5.0229997634887695 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:5134a572-1cba-418f-8bf6-abf9b97ad817" + }, + "__id": "uuid:5134a572-1cba-418f-8bf6-abf9b97ad817", + "__system": { + "submissionDate": "2023-07-30T17:51:55.857Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:03:31.918-06:00", + "end": "2023-07-30T11:03:42.952-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1292026, 40.1921515, 2829.4005800502664], + "properties": { + "accuracy": 6.356 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:03:31.918-06:00", - "end": "2023-07-30T11:03:42.952-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1292026, - 40.1921515, - 2829.4005800502664 - ], - "properties": { - "accuracy": 6.356 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1292026, - 40.1921515, - 2829.4005800502664 - ], - "properties": { - "accuracy": 6.355999946594238 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:0323268a-f1ae-4e26-bbaf-c9b4d24ae05b" - }, - "__id": "uuid:0323268a-f1ae-4e26-bbaf-c9b4d24ae05b", - "__system": { - "submissionDate": "2023-07-30T17:51:54.493Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1292026, 40.1921515, 2829.4005800502664], + "properties": { + "accuracy": 6.355999946594238 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:0323268a-f1ae-4e26-bbaf-c9b4d24ae05b" + }, + "__id": "uuid:0323268a-f1ae-4e26-bbaf-c9b4d24ae05b", + "__system": { + "submissionDate": "2023-07-30T17:51:54.493Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T11:01:43.253-06:00", + "end": "2023-07-30T11:01:59.951-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1291425, 40.1920187, 2827.210023306083], + "properties": { + "accuracy": 4.809 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T11:01:43.253-06:00", - "end": "2023-07-30T11:01:59.951-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1291425, - 40.1920187, - 2827.210023306083 - ], - "properties": { - "accuracy": 4.809 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1291512, - 40.1920398, - 2827.2689712519914 - ], - "properties": { - "accuracy": 7.389999866485596 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:e87df12c-3bf9-4887-835a-ea407e3cbf7e" - }, - "__id": "uuid:e87df12c-3bf9-4887-835a-ea407e3cbf7e", - "__system": { - "submissionDate": "2023-07-30T17:51:53.311Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1291512, 40.1920398, 2827.2689712519914], + "properties": { + "accuracy": 7.389999866485596 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:e87df12c-3bf9-4887-835a-ea407e3cbf7e" + }, + "__id": "uuid:e87df12c-3bf9-4887-835a-ea407e3cbf7e", + "__system": { + "submissionDate": "2023-07-30T17:51:53.311Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T10:59:48.925-06:00", + "end": "2023-07-30T11:00:09.937-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1304724, 40.1933038, 2830.654957417988], + "properties": { + "accuracy": 5.713 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T10:59:48.925-06:00", - "end": "2023-07-30T11:00:09.937-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1304724, - 40.1933038, - 2830.654957417988 - ], - "properties": { - "accuracy": 5.713 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.130545, - 40.1933329, - 2830.5342065651153 - ], - "properties": { - "accuracy": 8.5649995803833 - } - }, - "image": null, - "comment": "Never here" - } - }, - "meta": { - "instanceID": "uuid:3dd25313-79b6-43e9-8ff0-81dced26f623" - }, - "__id": "uuid:3dd25313-79b6-43e9-8ff0-81dced26f623", - "__system": { - "submissionDate": "2023-07-30T17:51:51.702Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.130545, 40.1933329, 2830.5342065651153], + "properties": { + "accuracy": 8.5649995803833 } + }, + "image": null, + "comment": "Never here" + } + }, + "meta": { + "instanceID": "uuid:3dd25313-79b6-43e9-8ff0-81dced26f623" + }, + "__id": "uuid:3dd25313-79b6-43e9-8ff0-81dced26f623", + "__system": { + "submissionDate": "2023-07-30T17:51:51.702Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T10:56:03.652-06:00", + "end": "2023-07-30T10:56:17.051-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1530168, 40.2040182, 2938.077581089209], + "properties": { + "accuracy": 7.134 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T10:56:03.652-06:00", - "end": "2023-07-30T10:56:17.051-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1530168, - 40.2040182, - 2938.077581089209 - ], - "properties": { - "accuracy": 7.134 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1530077, - 40.2039967, - 2937.872792038358 - ], - "properties": { - "accuracy": 8.940999984741211 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:393aae57-a7bd-4e4c-8301-72e1a8754356" - }, - "__id": "uuid:393aae57-a7bd-4e4c-8301-72e1a8754356", - "__system": { - "submissionDate": "2023-07-30T16:56:19.764Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1530077, 40.2039967, 2937.872792038358], + "properties": { + "accuracy": 8.940999984741211 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:393aae57-a7bd-4e4c-8301-72e1a8754356" + }, + "__id": "uuid:393aae57-a7bd-4e4c-8301-72e1a8754356", + "__system": { + "submissionDate": "2023-07-30T16:56:19.764Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T10:53:29.927-06:00", + "end": "2023-07-30T10:53:59.199-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1572975, 40.201615, 2961.08219741485], + "properties": { + "accuracy": 5.346 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T10:53:29.927-06:00", - "end": "2023-07-30T10:53:59.199-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1572975, - 40.201615, - 2961.08219741485 - ], - "properties": { - "accuracy": 5.346 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1573202, - 40.2015788, - 2964.1646566671634 - ], - "properties": { - "accuracy": 9.440999984741211 - } - }, - "image": null, - "comment": "Probably a camp hear here" - } - }, - "meta": { - "instanceID": "uuid:681f777a-2661-4b9e-af98-34e47a779cb2" - }, - "__id": "uuid:681f777a-2661-4b9e-af98-34e47a779cb2", - "__system": { - "submissionDate": "2023-07-30T16:55:16.611Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1573202, 40.2015788, 2964.1646566671634], + "properties": { + "accuracy": 9.440999984741211 } + }, + "image": null, + "comment": "Probably a camp hear here" + } + }, + "meta": { + "instanceID": "uuid:681f777a-2661-4b9e-af98-34e47a779cb2" + }, + "__id": "uuid:681f777a-2661-4b9e-af98-34e47a779cb2", + "__system": { + "submissionDate": "2023-07-30T16:55:16.611Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T10:41:06.582-06:00", + "end": "2023-07-30T10:41:24.936-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1918458, 40.2151474, 2807.907478007527], + "properties": { + "accuracy": 4.603 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T10:41:06.582-06:00", - "end": "2023-07-30T10:41:24.936-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1918458, - 40.2151474, - 2807.907478007527 - ], - "properties": { - "accuracy": 4.603 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1918201, - 40.2151389, - 2812.009579773568 - ], - "properties": { - "accuracy": 7.821000099182129 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:5adee31b-08c1-4408-b53b-b4655efa4d27" - }, - "__id": "uuid:5adee31b-08c1-4408-b53b-b4655efa4d27", - "__system": { - "submissionDate": "2023-07-30T16:55:13.274Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1918201, 40.2151389, 2812.009579773568], + "properties": { + "accuracy": 7.821000099182129 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:5adee31b-08c1-4408-b53b-b4655efa4d27" + }, + "__id": "uuid:5adee31b-08c1-4408-b53b-b4655efa4d27", + "__system": { + "submissionDate": "2023-07-30T16:55:13.274Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-30T10:17:27.281-06:00", + "end": "2023-07-30T10:18:10.638-06:00", + "today": "2023-07-30", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2279347, 40.1628309, 2632.8466796875], + "properties": { + "accuracy": 6.172 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "no", + "picnic_table": "yes", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-30T10:17:27.281-06:00", - "end": "2023-07-30T10:18:10.638-06:00", - "today": "2023-07-30", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2279347, - 40.1628309, - 2632.8466796875 - ], - "properties": { - "accuracy": 6.172 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "no", - "picnic_table": "yes", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2279273, - 40.1628662, - 2632.797316391394 - ], - "properties": { - "accuracy": 10.357999801635742 - } - }, - "image": null, - "comment": "There us probably a camp on this road " - } - }, - "meta": { - "instanceID": "uuid:04df5d32-c2b1-4226-9500-6ebaf9f1d556" - }, - "__id": "uuid:04df5d32-c2b1-4226-9500-6ebaf9f1d556", - "__system": { - "submissionDate": "2023-07-30T16:55:07.831Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2279273, 40.1628662, 2632.797316391394], + "properties": { + "accuracy": 10.357999801635742 } + }, + "image": null, + "comment": "There us probably a camp on this road " + } + }, + "meta": { + "instanceID": "uuid:04df5d32-c2b1-4226-9500-6ebaf9f1d556" + }, + "__id": "uuid:04df5d32-c2b1-4226-9500-6ebaf9f1d556", + "__system": { + "submissionDate": "2023-07-30T16:55:07.831Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T13:17:22.307-06:00", + "end": "2023-07-29T13:17:34.707-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2356048, 40.1507655, 2710.4224895174466], + "properties": { + "accuracy": 7.207 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T13:17:22.307-06:00", - "end": "2023-07-29T13:17:34.707-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2356048, - 40.1507655, - 2710.4224895174466 - ], - "properties": { - "accuracy": 7.207 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2356048, - 40.1507655, - 2710.4224895174466 - ], - "properties": { - "accuracy": 7.206999778747559 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:6fc79607-5752-420e-9ed8-e382d3e7144c" - }, - "__id": "uuid:6fc79607-5752-420e-9ed8-e382d3e7144c", - "__system": { - "submissionDate": "2023-07-30T16:55:06.103Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2356048, 40.1507655, 2710.4224895174466], + "properties": { + "accuracy": 7.206999778747559 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:6fc79607-5752-420e-9ed8-e382d3e7144c" + }, + "__id": "uuid:6fc79607-5752-420e-9ed8-e382d3e7144c", + "__system": { + "submissionDate": "2023-07-30T16:55:06.103Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T13:13:59.122-06:00", + "end": "2023-07-29T13:14:16.433-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2366646, 40.1517701, 2712.6689453125], + "properties": { + "accuracy": 9.261 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T13:13:59.122-06:00", - "end": "2023-07-29T13:14:16.433-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2366646, - 40.1517701, - 2712.6689453125 - ], - "properties": { - "accuracy": 9.261 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.236667, - 40.1517767, - 2712.177001953125 - ], - "properties": { - "accuracy": 10.427000045776367 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:f6aa49e8-a3fa-40a9-a2f8-924ccae59109" - }, - "__id": "uuid:f6aa49e8-a3fa-40a9-a2f8-924ccae59109", - "__system": { - "submissionDate": "2023-07-30T16:55:03.466Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.236667, 40.1517767, 2712.177001953125], + "properties": { + "accuracy": 10.427000045776367 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:f6aa49e8-a3fa-40a9-a2f8-924ccae59109" + }, + "__id": "uuid:f6aa49e8-a3fa-40a9-a2f8-924ccae59109", + "__system": { + "submissionDate": "2023-07-30T16:55:03.466Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T13:00:52.085-06:00", + "end": "2023-07-29T13:01:20.052-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2615696, 40.1339797, 2887.3583984375], + "properties": { + "accuracy": 6.273 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T13:00:52.085-06:00", - "end": "2023-07-29T13:01:20.052-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2615696, - 40.1339797, - 2887.3583984375 - ], - "properties": { - "accuracy": 6.273 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2615696, - 40.1339797, - 2887.3583984375 - ], - "properties": { - "accuracy": 6.2729997634887695 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:5bdaf40e-1ff7-49a0-ac4c-e044b840becc" - }, - "__id": "uuid:5bdaf40e-1ff7-49a0-ac4c-e044b840becc", - "__system": { - "submissionDate": "2023-07-30T16:54:50.089Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2615696, 40.1339797, 2887.3583984375], + "properties": { + "accuracy": 6.2729997634887695 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:5bdaf40e-1ff7-49a0-ac4c-e044b840becc" + }, + "__id": "uuid:5bdaf40e-1ff7-49a0-ac4c-e044b840becc", + "__system": { + "submissionDate": "2023-07-30T16:54:50.089Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:49:26.143-06:00", + "end": "2023-07-29T12:49:52.313-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2814026, 40.1260472, 3018.6347218606525], + "properties": { + "accuracy": 8.174 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:49:26.143-06:00", - "end": "2023-07-29T12:49:52.313-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2814026, - 40.1260472, - 3018.6347218606525 - ], - "properties": { - "accuracy": 8.174 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2814117, - 40.126131, - 3018.468184427754 - ], - "properties": { - "accuracy": 9.21500015258789 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:c4422809-7fac-4fea-a72c-62306ccefd3c" - }, - "__id": "uuid:c4422809-7fac-4fea-a72c-62306ccefd3c", - "__system": { - "submissionDate": "2023-07-30T16:54:39.116Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2814117, 40.126131, 3018.468184427754], + "properties": { + "accuracy": 9.21500015258789 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:c4422809-7fac-4fea-a72c-62306ccefd3c" + }, + "__id": "uuid:c4422809-7fac-4fea-a72c-62306ccefd3c", + "__system": { + "submissionDate": "2023-07-30T16:54:39.116Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:45:56.329-06:00", + "end": "2023-07-29T12:46:16.257-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2955597, 40.1181549, 3112.1857815472504], + "properties": { + "accuracy": 7.324 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:45:56.329-06:00", - "end": "2023-07-29T12:46:16.257-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2955597, - 40.1181549, - 3112.1857815472504 - ], - "properties": { - "accuracy": 7.324 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2955725, - 40.1181497, - 3112.496481072906 - ], - "properties": { - "accuracy": 7.367000102996826 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:d3ce6cf3-74c6-4b7e-80e3-432b6b0caa81" - }, - "__id": "uuid:d3ce6cf3-74c6-4b7e-80e3-432b6b0caa81", - "__system": { - "submissionDate": "2023-07-30T16:54:34.302Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2955725, 40.1181497, 3112.496481072906], + "properties": { + "accuracy": 7.367000102996826 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:d3ce6cf3-74c6-4b7e-80e3-432b6b0caa81" + }, + "__id": "uuid:d3ce6cf3-74c6-4b7e-80e3-432b6b0caa81", + "__system": { + "submissionDate": "2023-07-30T16:54:34.302Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:42:16.642-06:00", + "end": "2023-07-29T12:42:34.483-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2961317, 40.1097696, 3129.895486489578], + "properties": { + "accuracy": 4.048 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:42:16.642-06:00", - "end": "2023-07-29T12:42:34.483-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2961317, - 40.1097696, - 3129.895486489578 - ], - "properties": { - "accuracy": 4.048 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.296142, - 40.1098089, - 3128.452205627708 - ], - "properties": { - "accuracy": 7.11299991607666 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:5ba57ade-172e-4064-94a4-b18c3ebc9ea3" - }, - "__id": "uuid:5ba57ade-172e-4064-94a4-b18c3ebc9ea3", - "__system": { - "submissionDate": "2023-07-30T16:54:30.949Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.296142, 40.1098089, 3128.452205627708], + "properties": { + "accuracy": 7.11299991607666 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:5ba57ade-172e-4064-94a4-b18c3ebc9ea3" + }, + "__id": "uuid:5ba57ade-172e-4064-94a4-b18c3ebc9ea3", + "__system": { + "submissionDate": "2023-07-30T16:54:30.949Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:35:35.605-06:00", + "end": "2023-07-29T12:35:51.062-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3019591, 40.0987885, 3090.1892268429465], + "properties": { + "accuracy": 4.789 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:35:35.605-06:00", - "end": "2023-07-29T12:35:51.062-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3019591, - 40.0987885, - 3090.1892268429465 - ], - "properties": { - "accuracy": 4.789 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.30220794677734, - 40.09897159650819, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:cc33a136-eb3c-4ded-b5b9-abc7a550451f" - }, - "__id": "uuid:cc33a136-eb3c-4ded-b5b9-abc7a550451f", - "__system": { - "submissionDate": "2023-07-30T16:54:20.389Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.30220794677734, 40.09897159650819, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:cc33a136-eb3c-4ded-b5b9-abc7a550451f" + }, + "__id": "uuid:cc33a136-eb3c-4ded-b5b9-abc7a550451f", + "__system": { + "submissionDate": "2023-07-30T16:54:20.389Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:34:22.127-06:00", + "end": "2023-07-29T12:34:39.909-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.305731, 40.0975837, 3067.2326188813963], + "properties": { + "accuracy": 13.183 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:34:22.127-06:00", - "end": "2023-07-29T12:34:39.909-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.305731, - 40.0975837, - 3067.2326188813963 - ], - "properties": { - "accuracy": 13.183 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.30613163539341, - 40.09807118292398, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:992d56eb-1e33-4cab-8abe-d93be981d71a" - }, - "__id": "uuid:992d56eb-1e33-4cab-8abe-d93be981d71a", - "__system": { - "submissionDate": "2023-07-29T18:34:43.537Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.30613163539341, 40.09807118292398, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:992d56eb-1e33-4cab-8abe-d93be981d71a" + }, + "__id": "uuid:992d56eb-1e33-4cab-8abe-d93be981d71a", + "__system": { + "submissionDate": "2023-07-29T18:34:43.537Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:23:48.985-06:00", + "end": "2023-07-29T12:24:09.331-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3101025, 40.0898838, 2968.9538977811276], + "properties": { + "accuracy": 5.546 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:23:48.985-06:00", - "end": "2023-07-29T12:24:09.331-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3101025, - 40.0898838, - 2968.9538977811276 - ], - "properties": { - "accuracy": 5.546 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3100988, - 40.0899014, - 2969.3196727449786 - ], - "properties": { - "accuracy": 6.27400016784668 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:efbc9f92-6566-4247-98f7-c873423397e0" - }, - "__id": "uuid:efbc9f92-6566-4247-98f7-c873423397e0", - "__system": { - "submissionDate": "2023-07-29T18:28:09.963Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3100988, 40.0899014, 2969.3196727449786], + "properties": { + "accuracy": 6.27400016784668 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:efbc9f92-6566-4247-98f7-c873423397e0" + }, + "__id": "uuid:efbc9f92-6566-4247-98f7-c873423397e0", + "__system": { + "submissionDate": "2023-07-29T18:28:09.963Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:21:21.757-06:00", + "end": "2023-07-29T12:21:37.939-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3158601, 40.0847736, 2910.7158759174336], + "properties": { + "accuracy": 7.159 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:21:21.757-06:00", - "end": "2023-07-29T12:21:37.939-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3158601, - 40.0847736, - 2910.7158759174336 - ], - "properties": { - "accuracy": 7.159 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3158522, - 40.0846815, - 2910.704523816309 - ], - "properties": { - "accuracy": 7.684000015258789 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:0063a642-5219-4fbc-b57b-e20f724ad37d" - }, - "__id": "uuid:0063a642-5219-4fbc-b57b-e20f724ad37d", - "__system": { - "submissionDate": "2023-07-29T18:28:08.846Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3158522, 40.0846815, 2910.704523816309], + "properties": { + "accuracy": 7.684000015258789 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:0063a642-5219-4fbc-b57b-e20f724ad37d" + }, + "__id": "uuid:0063a642-5219-4fbc-b57b-e20f724ad37d", + "__system": { + "submissionDate": "2023-07-29T18:28:08.846Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T12:14:01.888-06:00", + "end": "2023-07-29T12:14:18.339-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3295154, 40.077149, 2792.9723205574564], + "properties": { + "accuracy": 14.176 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T12:14:01.888-06:00", - "end": "2023-07-29T12:14:18.339-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3295154, - 40.077149, - 2792.9723205574564 - ], - "properties": { - "accuracy": 14.176 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3296387, - 40.0770867, - 2793.0115105050395 - ], - "properties": { - "accuracy": 21.465999603271484 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:3d439b02-cc8a-4f7f-b4d8-5af1b998157d" - }, - "__id": "uuid:3d439b02-cc8a-4f7f-b4d8-5af1b998157d", - "__system": { - "submissionDate": "2023-07-29T18:14:44.037Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3296387, 40.0770867, 2793.0115105050395], + "properties": { + "accuracy": 21.465999603271484 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:3d439b02-cc8a-4f7f-b4d8-5af1b998157d" + }, + "__id": "uuid:3d439b02-cc8a-4f7f-b4d8-5af1b998157d", + "__system": { + "submissionDate": "2023-07-29T18:14:44.037Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T11:57:51.786-06:00", + "end": "2023-07-29T11:58:09.587-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3142015, 40.07092, 2635.7997716087425], + "properties": { + "accuracy": 7.65 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T11:57:51.786-06:00", - "end": "2023-07-29T11:58:09.587-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3142015, - 40.07092, - 2635.7997716087425 - ], - "properties": { - "accuracy": 7.65 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3141972, - 40.070875, - 2635.835414419698 - ], - "properties": { - "accuracy": 7.960999965667725 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:4665dc11-a006-4722-a450-f5226df5cb4d" - }, - "__id": "uuid:4665dc11-a006-4722-a450-f5226df5cb4d", - "__system": { - "submissionDate": "2023-07-29T18:06:37.006Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3141972, 40.070875, 2635.835414419698], + "properties": { + "accuracy": 7.960999965667725 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:4665dc11-a006-4722-a450-f5226df5cb4d" + }, + "__id": "uuid:4665dc11-a006-4722-a450-f5226df5cb4d", + "__system": { + "submissionDate": "2023-07-29T18:06:37.006Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T10:39:45.399-06:00", + "end": "2023-07-29T10:40:03.880-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2987388, 40.0454754, 2639.095058521331], + "properties": { + "accuracy": 6.857 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T10:39:45.399-06:00", - "end": "2023-07-29T10:40:03.880-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2987388, - 40.0454754, - 2639.095058521331 - ], - "properties": { - "accuracy": 6.857 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2987015, - 40.045487, - 2639.3372491438085 - ], - "properties": { - "accuracy": 9.640000343322754 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:74dd4c7c-be78-49e9-9909-25705db3ccd9" - }, - "__id": "uuid:74dd4c7c-be78-49e9-9909-25705db3ccd9", - "__system": { - "submissionDate": "2023-07-29T18:06:34.024Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2987015, 40.045487, 2639.3372491438085], + "properties": { + "accuracy": 9.640000343322754 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:74dd4c7c-be78-49e9-9909-25705db3ccd9" + }, + "__id": "uuid:74dd4c7c-be78-49e9-9909-25705db3ccd9", + "__system": { + "submissionDate": "2023-07-29T18:06:34.024Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T10:33:20.836-06:00", + "end": "2023-07-29T10:33:43.771-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3230948, 40.0652475, 2601.69274844231], + "properties": { + "accuracy": 7.109 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T10:33:20.836-06:00", - "end": "2023-07-29T10:33:43.771-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3230948, - 40.0652475, - 2601.69274844231 - ], - "properties": { - "accuracy": 7.109 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3230948, - 40.0652475, - 2601.69274844231 - ], - "properties": { - "accuracy": 7.109000205993652 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:d7d5fb07-7c21-4f76-9c5a-124a256da222" - }, - "__id": "uuid:d7d5fb07-7c21-4f76-9c5a-124a256da222", - "__system": { - "submissionDate": "2023-07-29T18:06:31.925Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3230948, 40.0652475, 2601.69274844231], + "properties": { + "accuracy": 7.109000205993652 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:d7d5fb07-7c21-4f76-9c5a-124a256da222" + }, + "__id": "uuid:d7d5fb07-7c21-4f76-9c5a-124a256da222", + "__system": { + "submissionDate": "2023-07-29T18:06:31.925Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T10:29:48.265-06:00", + "end": "2023-07-29T10:30:01.499-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3252198, 40.065182, 2591.1281987927714], + "properties": { + "accuracy": 4.625 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T10:29:48.265-06:00", - "end": "2023-07-29T10:30:01.499-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3252198, - 40.065182, - 2591.1281987927714 - ], - "properties": { - "accuracy": 4.625 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3252215, - 40.0651855, - 2590.991438877536 - ], - "properties": { - "accuracy": 5.551000118255615 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:6c94495f-b3b6-4207-92cc-497c99f6f8c6" - }, - "__id": "uuid:6c94495f-b3b6-4207-92cc-497c99f6f8c6", - "__system": { - "submissionDate": "2023-07-29T18:06:30.525Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3252215, 40.0651855, 2590.991438877536], + "properties": { + "accuracy": 5.551000118255615 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:6c94495f-b3b6-4207-92cc-497c99f6f8c6" + }, + "__id": "uuid:6c94495f-b3b6-4207-92cc-497c99f6f8c6", + "__system": { + "submissionDate": "2023-07-29T18:06:30.525Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T10:27:05.727-06:00", + "end": "2023-07-29T10:27:26.627-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3237011, 40.065261, 2598.0798399875125], + "properties": { + "accuracy": 4.602 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T10:27:05.727-06:00", - "end": "2023-07-29T10:27:26.627-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3237011, - 40.065261, - 2598.0798399875125 - ], - "properties": { - "accuracy": 4.602 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3236981, - 40.0652632, - 2598.0469951733257 - ], - "properties": { - "accuracy": 5.113999843597412 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:75ab8f8b-99ec-4b13-a8f2-ecc03a98fecf" - }, - "__id": "uuid:75ab8f8b-99ec-4b13-a8f2-ecc03a98fecf", - "__system": { - "submissionDate": "2023-07-29T18:06:29.209Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3236981, 40.0652632, 2598.0469951733257], + "properties": { + "accuracy": 5.113999843597412 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:75ab8f8b-99ec-4b13-a8f2-ecc03a98fecf" + }, + "__id": "uuid:75ab8f8b-99ec-4b13-a8f2-ecc03a98fecf", + "__system": { + "submissionDate": "2023-07-29T18:06:29.209Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-29T10:25:39.272-06:00", + "end": "2023-07-29T10:26:11.571-06:00", + "today": "2023-07-29", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3235552, 40.0647141, 2597.2012574279097], + "properties": { + "accuracy": 5.838 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": null, + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-29T10:25:39.272-06:00", - "end": "2023-07-29T10:26:11.571-06:00", - "today": "2023-07-29", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3235552, - 40.0647141, - 2597.2012574279097 - ], - "properties": { - "accuracy": 5.838 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": null, - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3235488, - 40.0647018, - 2598.8583984375 - ], - "properties": { - "accuracy": 4.948999881744385 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:819d5230-280f-4d19-95a4-bc1130e054c9" - }, - "__id": "uuid:819d5230-280f-4d19-95a4-bc1130e054c9", - "__system": { - "submissionDate": "2023-07-29T18:06:27.851Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3235488, 40.0647018, 2598.8583984375], + "properties": { + "accuracy": 4.948999881744385 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:819d5230-280f-4d19-95a4-bc1130e054c9" + }, + "__id": "uuid:819d5230-280f-4d19-95a4-bc1130e054c9", + "__system": { + "submissionDate": "2023-07-29T18:06:27.851Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-23T08:55:04.160-06:00", + "end": "2023-07-23T08:56:08.593-06:00", + "today": "2023-07-23", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.9158965, 40.0333999, 1879.9000244140625], + "properties": { + "accuracy": 4.095 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_site", + "internet_access": "no", + "cellular": "yes", + "openfire": null, + "picnic_table": null, + "shelter": null, + "ref": null, + "fee": "yes", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-23T08:55:04.160-06:00", - "end": "2023-07-23T08:56:08.593-06:00", - "today": "2023-07-23", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.9158965, - 40.0333999, - 1879.9000244140625 - ], - "properties": { - "accuracy": 4.095 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_site", - "internet_access": "no", - "cellular": "yes", - "openfire": null, - "picnic_table": null, - "shelter": null, - "ref": null, - "fee": "yes", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": "Meeker RV Park", - "toilet": "no", - "cost": null, - "amenity": "bear_box", - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.9158499, - 40.033769, - 1879.9000244140625 - ], - "properties": { - "accuracy": 20.183000564575195 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:989a6275-ac03-43e4-8f85-658eeb4d6b63" - }, - "__id": "uuid:989a6275-ac03-43e4-8f85-658eeb4d6b63", - "__system": { - "submissionDate": "2023-07-23T14:56:10.790Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": "Meeker RV Park", + "toilet": "no", + "cost": null, + "amenity": "bear_box", + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.9158499, 40.033769, 1879.9000244140625], + "properties": { + "accuracy": 20.183000564575195 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:989a6275-ac03-43e4-8f85-658eeb4d6b63" + }, + "__id": "uuid:989a6275-ac03-43e4-8f85-658eeb4d6b63", + "__system": { + "submissionDate": "2023-07-23T14:56:10.790Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T19:34:14.617-06:00", + "end": "2023-07-21T19:34:31.003-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3023441, 39.7702317, 3220.635737832648], + "properties": { + "accuracy": 6.707 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": 4, + "fee": "yes", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T19:34:14.617-06:00", - "end": "2023-07-21T19:34:31.003-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3023441, - 39.7702317, - 3220.635737832648 - ], - "properties": { - "accuracy": 6.707 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": 4, - "fee": "yes", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3023441, - 39.7702317, - 3220.635737832648 - ], - "properties": { - "accuracy": 6.706999778747559 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:13129066-09de-41aa-ba44-b8641a8c9eea" - }, - "__id": "uuid:13129066-09de-41aa-ba44-b8641a8c9eea", - "__system": { - "submissionDate": "2023-07-22T15:15:30.813Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3023441, 39.7702317, 3220.635737832648], + "properties": { + "accuracy": 6.706999778747559 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:13129066-09de-41aa-ba44-b8641a8c9eea" + }, + "__id": "uuid:13129066-09de-41aa-ba44-b8641a8c9eea", + "__system": { + "submissionDate": "2023-07-22T15:15:30.813Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T19:32:07.561-06:00", + "end": "2023-07-21T19:32:30.965-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3020462, 39.7702789, 3220.446228290206], + "properties": { + "accuracy": 7.989 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": 3, + "fee": "yes", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T19:32:07.561-06:00", - "end": "2023-07-21T19:32:30.965-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3020462, - 39.7702789, - 3220.446228290206 - ], - "properties": { - "accuracy": 7.989 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": 3, - "fee": "yes", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3020462, - 39.7702789, - 3220.446228290206 - ], - "properties": { - "accuracy": 7.988999843597412 - } - }, - "image": null, - "comment": "Ditec3" - } - }, - "meta": { - "instanceID": "uuid:50b9d653-c5de-4c43-a2c6-c5e3e4733483" - }, - "__id": "uuid:50b9d653-c5de-4c43-a2c6-c5e3e4733483", - "__system": { - "submissionDate": "2023-07-22T15:15:29.484Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3020462, 39.7702789, 3220.446228290206], + "properties": { + "accuracy": 7.988999843597412 } + }, + "image": null, + "comment": "Ditec3" + } + }, + "meta": { + "instanceID": "uuid:50b9d653-c5de-4c43-a2c6-c5e3e4733483" + }, + "__id": "uuid:50b9d653-c5de-4c43-a2c6-c5e3e4733483", + "__system": { + "submissionDate": "2023-07-22T15:15:29.484Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T19:31:11.781-06:00", + "end": "2023-07-21T19:31:38.083-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3018643, 39.7702844, 3218.050330555803], + "properties": { + "accuracy": 9.005 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": 2, + "fee": "yes", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T19:31:11.781-06:00", - "end": "2023-07-21T19:31:38.083-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3018643, - 39.7702844, - 3218.050330555803 - ], - "properties": { - "accuracy": 9.005 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": 2, - "fee": "yes", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3018317, - 39.7703367, - 3218.295144127886 - ], - "properties": { - "accuracy": 10.777000427246094 - } - }, - "image": null, - "comment": "Site 2 forbreal" - } - }, - "meta": { - "instanceID": "uuid:49cadd3d-8071-49bb-83a7-8d2c269e862a" - }, - "__id": "uuid:49cadd3d-8071-49bb-83a7-8d2c269e862a", - "__system": { - "submissionDate": "2023-07-22T15:15:28.188Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3018317, 39.7703367, 3218.295144127886], + "properties": { + "accuracy": 10.777000427246094 } + }, + "image": null, + "comment": "Site 2 forbreal" + } + }, + "meta": { + "instanceID": "uuid:49cadd3d-8071-49bb-83a7-8d2c269e862a" + }, + "__id": "uuid:49cadd3d-8071-49bb-83a7-8d2c269e862a", + "__system": { + "submissionDate": "2023-07-22T15:15:28.188Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T19:29:27.186-06:00", + "end": "2023-07-21T19:29:50.250-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3019919, 39.7702663, 3219.076825280391], + "properties": { + "accuracy": 5.904 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": 2, + "fee": "yes", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T19:29:27.186-06:00", - "end": "2023-07-21T19:29:50.250-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3019919, - 39.7702663, - 3219.076825280391 - ], - "properties": { - "accuracy": 5.904 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": 2, - "fee": "yes", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3019919, - 39.7702663, - 3219.076825280391 - ], - "properties": { - "accuracy": 5.9039998054504395 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:1f19d9c7-1e42-48d2-937f-0a75e23b0fd8" - }, - "__id": "uuid:1f19d9c7-1e42-48d2-937f-0a75e23b0fd8", - "__system": { - "submissionDate": "2023-07-22T15:15:26.917Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3019919, 39.7702663, 3219.076825280391], + "properties": { + "accuracy": 5.9039998054504395 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:1f19d9c7-1e42-48d2-937f-0a75e23b0fd8" + }, + "__id": "uuid:1f19d9c7-1e42-48d2-937f-0a75e23b0fd8", + "__system": { + "submissionDate": "2023-07-22T15:15:26.917Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T19:12:12.426-06:00", + "end": "2023-07-21T19:12:31.442-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3003297, 39.7702353, 3208.2823449980033], + "properties": { + "accuracy": 5.969 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": 6, + "fee": "yes", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T19:12:12.426-06:00", - "end": "2023-07-21T19:12:31.442-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3003297, - 39.7702353, - 3208.2823449980033 - ], - "properties": { - "accuracy": 5.969 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": 6, - "fee": "yes", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3002836, - 39.7703213, - 3208.3206410808352 - ], - "properties": { - "accuracy": 16.43600082397461 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:15519343-c865-4e38-bb3e-4b76794a27fa" - }, - "__id": "uuid:15519343-c865-4e38-bb3e-4b76794a27fa", - "__system": { - "submissionDate": "2023-07-22T15:15:25.636Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3002836, 39.7703213, 3208.3206410808352], + "properties": { + "accuracy": 16.43600082397461 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:15519343-c865-4e38-bb3e-4b76794a27fa" + }, + "__id": "uuid:15519343-c865-4e38-bb3e-4b76794a27fa", + "__system": { + "submissionDate": "2023-07-22T15:15:25.636Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T19:08:03.275-06:00", + "end": "2023-07-21T19:10:14.955-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3006893, 39.7705683, 3204.418212890625], + "properties": { + "accuracy": 9.644 + } + }, + "all": { + "first_screen": { + "model": "extended", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": 5, + "fee": "yes", + "firepit": "yes", + "bbq": "no", + "bear_box": "no", + "backcountry": "yes" }, - { - "start": "2023-07-21T19:08:03.275-06:00", - "end": "2023-07-21T19:10:14.955-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3006893, - 39.7705683, - 3204.418212890625 - ], - "properties": { - "accuracy": 9.644 - } - }, - "all": { - "first_screen": { - "model": "extended", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": 5, - "fee": "yes", - "firepit": "yes", - "bbq": "no", - "bear_box": "no", - "backcountry": "yes" - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": "6", - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3006812, - 39.7705918, - 3213.0667616714272 - ], - "properties": { - "accuracy": 9.204999923706055 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:912ded0e-b2af-4805-9653-99bf0a796088" - }, - "__id": "uuid:912ded0e-b2af-4805-9653-99bf0a796088", - "__system": { - "submissionDate": "2023-07-22T15:15:24.532Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": "6", + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3006812, 39.7705918, 3213.0667616714272], + "properties": { + "accuracy": 9.204999923706055 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:912ded0e-b2af-4805-9653-99bf0a796088" + }, + "__id": "uuid:912ded0e-b2af-4805-9653-99bf0a796088", + "__system": { + "submissionDate": "2023-07-22T15:15:24.532Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T16:42:05.100-06:00", + "end": "2023-07-21T16:42:59.911-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.3018869, 39.7695782, 3225.336433741109], + "properties": { + "accuracy": 4.624 + } + }, + "all": { + "first_screen": { + "model": "extended", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "yes", + "shelter": null, + "ref": 1, + "fee": "yes", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T16:42:05.100-06:00", - "end": "2023-07-21T16:42:59.911-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.3018869, - 39.7695782, - 3225.336433741109 - ], - "properties": { - "accuracy": 4.624 - } - }, - "all": { - "first_screen": { - "model": "extended", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "yes", - "shelter": null, - "ref": 1, - "fee": "yes", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": "6", - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.3018402, - 39.7695335, - 3225.357074512917 - ], - "properties": { - "accuracy": 5.317999839782715 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:277f1d00-6d3d-48c7-a9e9-ce662c8f2c49" - }, - "__id": "uuid:277f1d00-6d3d-48c7-a9e9-ce662c8f2c49", - "__system": { - "submissionDate": "2023-07-22T15:15:19.287Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": "6", + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.3018402, 39.7695335, 3225.357074512917], + "properties": { + "accuracy": 5.317999839782715 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:277f1d00-6d3d-48c7-a9e9-ce662c8f2c49" + }, + "__id": "uuid:277f1d00-6d3d-48c7-a9e9-ce662c8f2c49", + "__system": { + "submissionDate": "2023-07-22T15:15:19.287Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T16:40:08.213-06:00", + "end": "2023-07-21T16:40:17.975-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2979588, 39.7637469, 3228.548261836604], + "properties": { + "accuracy": 13.272 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T16:40:08.213-06:00", - "end": "2023-07-21T16:40:17.975-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2979588, - 39.7637469, - 3228.548261836604 - ], - "properties": { - "accuracy": 13.272 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2979591, - 39.7637624, - 3228.54236311625 - ], - "properties": { - "accuracy": 19.88599967956543 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:84b0c946-ba7f-42f2-bbed-9536af9527da" - }, - "__id": "uuid:84b0c946-ba7f-42f2-bbed-9536af9527da", - "__system": { - "submissionDate": "2023-07-22T15:15:18.484Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2979591, 39.7637624, 3228.54236311625], + "properties": { + "accuracy": 19.88599967956543 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:84b0c946-ba7f-42f2-bbed-9536af9527da" + }, + "__id": "uuid:84b0c946-ba7f-42f2-bbed-9536af9527da", + "__system": { + "submissionDate": "2023-07-22T15:15:18.484Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T16:35:52.538-06:00", + "end": "2023-07-21T16:36:01.749-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2887937, 39.7508419, 3227.5949105553414], + "properties": { + "accuracy": 9.755 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T16:35:52.538-06:00", - "end": "2023-07-21T16:36:01.749-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2887937, - 39.7508419, - 3227.5949105553414 - ], - "properties": { - "accuracy": 9.755 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2887903, - 39.7508408, - 3228.1484082705574 - ], - "properties": { - "accuracy": 10.746999740600586 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:052f03d3-3dd2-4a58-b68c-b53a9f4b313a" - }, - "__id": "uuid:052f03d3-3dd2-4a58-b68c-b53a9f4b313a", - "__system": { - "submissionDate": "2023-07-22T15:15:17.467Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2887903, 39.7508408, 3228.1484082705574], + "properties": { + "accuracy": 10.746999740600586 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:052f03d3-3dd2-4a58-b68c-b53a9f4b313a" + }, + "__id": "uuid:052f03d3-3dd2-4a58-b68c-b53a9f4b313a", + "__system": { + "submissionDate": "2023-07-22T15:15:17.467Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T16:35:00.047-06:00", + "end": "2023-07-21T16:35:10.991-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2860692, 39.7492254, 3226.160457799949], + "properties": { + "accuracy": 7.445 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T16:35:00.047-06:00", - "end": "2023-07-21T16:35:10.991-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2860692, - 39.7492254, - 3226.160457799949 - ], - "properties": { - "accuracy": 7.445 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2860851, - 39.7492297, - 3226.1403241176126 - ], - "properties": { - "accuracy": 8.260000228881836 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:5681e754-955f-4e3c-b59f-bc2d503a8540" - }, - "__id": "uuid:5681e754-955f-4e3c-b59f-bc2d503a8540", - "__system": { - "submissionDate": "2023-07-22T15:15:16.699Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2860851, 39.7492297, 3226.1403241176126], + "properties": { + "accuracy": 8.260000228881836 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:5681e754-955f-4e3c-b59f-bc2d503a8540" + }, + "__id": "uuid:5681e754-955f-4e3c-b59f-bc2d503a8540", + "__system": { + "submissionDate": "2023-07-22T15:15:16.699Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T16:19:53.413-06:00", + "end": "2023-07-21T16:20:04.068-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.2335881, 39.6813578, 3084.988069683332], + "properties": { + "accuracy": 5.288 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T16:19:53.413-06:00", - "end": "2023-07-21T16:20:04.068-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.2335881, - 39.6813578, - 3084.988069683332 - ], - "properties": { - "accuracy": 5.288 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.2336031, - 39.6813525, - 3084.394227783314 - ], - "properties": { - "accuracy": 5.940999984741211 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:39aa4ca8-f6be-4c64-9466-7bd9454af624" - }, - "__id": "uuid:39aa4ca8-f6be-4c64-9466-7bd9454af624", - "__system": { - "submissionDate": "2023-07-22T15:15:15.647Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.2336031, 39.6813525, 3084.394227783314], + "properties": { + "accuracy": 5.940999984741211 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:39aa4ca8-f6be-4c64-9466-7bd9454af624" + }, + "__id": "uuid:39aa4ca8-f6be-4c64-9466-7bd9454af624", + "__system": { + "submissionDate": "2023-07-22T15:15:15.647Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T16:12:23.890-06:00", + "end": "2023-07-21T16:12:37.269-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1996988, 39.680565, 3088.2870489614857], + "properties": { + "accuracy": 6.866 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T16:12:23.890-06:00", - "end": "2023-07-21T16:12:37.269-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1996988, - 39.680565, - 3088.2870489614857 - ], - "properties": { - "accuracy": 6.866 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1996968, - 39.6805739, - 3088.4336645572257 - ], - "properties": { - "accuracy": 8.041000366210938 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:23886af2-0f54-4df8-ab49-cfa9df1ca7af" - }, - "__id": "uuid:23886af2-0f54-4df8-ab49-cfa9df1ca7af", - "__system": { - "submissionDate": "2023-07-22T15:15:14.540Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1996968, 39.6805739, 3088.4336645572257], + "properties": { + "accuracy": 8.041000366210938 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:23886af2-0f54-4df8-ab49-cfa9df1ca7af" + }, + "__id": "uuid:23886af2-0f54-4df8-ab49-cfa9df1ca7af", + "__system": { + "submissionDate": "2023-07-22T15:15:14.540Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:52:45.189-06:00", + "end": "2023-07-21T15:52:58.010-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1704523, 39.6645794, 2986.225427903305], + "properties": { + "accuracy": 6.899 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:52:45.189-06:00", - "end": "2023-07-21T15:52:58.010-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1704523, - 39.6645794, - 2986.225427903305 - ], - "properties": { - "accuracy": 6.899 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1704592, - 39.6645191, - 2985.9150390625 - ], - "properties": { - "accuracy": 9.625 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:5cd296b5-8053-4ad4-8d15-d39832b4e668" - }, - "__id": "uuid:5cd296b5-8053-4ad4-8d15-d39832b4e668", - "__system": { - "submissionDate": "2023-07-21T22:01:46.974Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1704592, 39.6645191, 2985.9150390625], + "properties": { + "accuracy": 9.625 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:5cd296b5-8053-4ad4-8d15-d39832b4e668" + }, + "__id": "uuid:5cd296b5-8053-4ad4-8d15-d39832b4e668", + "__system": { + "submissionDate": "2023-07-21T22:01:46.974Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:51:08.356-06:00", + "end": "2023-07-21T15:52:41.771-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1719025, 39.6648001, 2981.389993850314], + "properties": { + "accuracy": 5.449 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:51:08.356-06:00", - "end": "2023-07-21T15:52:41.771-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1719025, - 39.6648001, - 2981.389993850314 - ], - "properties": { - "accuracy": 5.449 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1717467, - 39.6647323, - 2979.4717490889752 - ], - "properties": { - "accuracy": 8.00100040435791 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:56332214-c54b-4b58-a58b-56991f59d35a" - }, - "__id": "uuid:56332214-c54b-4b58-a58b-56991f59d35a", - "__system": { - "submissionDate": "2023-07-21T22:01:45.835Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1717467, 39.6647323, 2979.4717490889752], + "properties": { + "accuracy": 8.00100040435791 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:56332214-c54b-4b58-a58b-56991f59d35a" + }, + "__id": "uuid:56332214-c54b-4b58-a58b-56991f59d35a", + "__system": { + "submissionDate": "2023-07-21T22:01:45.835Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:50:06.458-06:00", + "end": "2023-07-21T15:50:24.646-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1715043, 39.6648983, 2981.16552734375], + "properties": { + "accuracy": 7.609 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:50:06.458-06:00", - "end": "2023-07-21T15:50:24.646-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1715043, - 39.6648983, - 2981.16552734375 - ], - "properties": { - "accuracy": 7.609 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1714685, - 39.6647696, - 2981.045152708874 - ], - "properties": { - "accuracy": 8.866999626159668 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:a873c61f-3a6f-4623-bda5-afebd10c5810" - }, - "__id": "uuid:a873c61f-3a6f-4623-bda5-afebd10c5810", - "__system": { - "submissionDate": "2023-07-21T22:01:44.585Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1714685, 39.6647696, 2981.045152708874], + "properties": { + "accuracy": 8.866999626159668 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:a873c61f-3a6f-4623-bda5-afebd10c5810" + }, + "__id": "uuid:a873c61f-3a6f-4623-bda5-afebd10c5810", + "__system": { + "submissionDate": "2023-07-21T22:01:44.585Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:46:48.595-06:00", + "end": "2023-07-21T15:47:04.000-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1724838, 39.6583808, 2952.4241226154227], + "properties": { + "accuracy": 9.277 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:46:48.595-06:00", - "end": "2023-07-21T15:47:04.000-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1724838, - 39.6583808, - 2952.4241226154227 - ], - "properties": { - "accuracy": 9.277 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.17260557539024, - 39.65828840923989, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:7ed7d9a5-79d9-4061-b3a9-2c08a08f5f48" - }, - "__id": "uuid:7ed7d9a5-79d9-4061-b3a9-2c08a08f5f48", - "__system": { - "submissionDate": "2023-07-21T22:01:43.328Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.17260557539024, 39.65828840923989, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:7ed7d9a5-79d9-4061-b3a9-2c08a08f5f48" + }, + "__id": "uuid:7ed7d9a5-79d9-4061-b3a9-2c08a08f5f48", + "__system": { + "submissionDate": "2023-07-21T22:01:43.328Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:46:05.807-06:00", + "end": "2023-07-21T15:46:21.002-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1716577, 39.6577124, 2953.572067001279], + "properties": { + "accuracy": 11.843 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:46:05.807-06:00", - "end": "2023-07-21T15:46:21.002-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1716577, - 39.6577124, - 2953.572067001279 - ], - "properties": { - "accuracy": 11.843 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1716369, - 39.6577092, - 2953.520659869752 - ], - "properties": { - "accuracy": 16.017000198364258 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:c73fa164-8cee-4444-9fe2-1f74614d57f5" - }, - "__id": "uuid:c73fa164-8cee-4444-9fe2-1f74614d57f5", - "__system": { - "submissionDate": "2023-07-21T22:01:41.887Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1716369, 39.6577092, 2953.520659869752], + "properties": { + "accuracy": 16.017000198364258 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:c73fa164-8cee-4444-9fe2-1f74614d57f5" + }, + "__id": "uuid:c73fa164-8cee-4444-9fe2-1f74614d57f5", + "__system": { + "submissionDate": "2023-07-21T22:01:41.887Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:44:15.743-06:00", + "end": "2023-07-21T15:44:27.549-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1664083, 39.6573923, 2936.731827439056], + "properties": { + "accuracy": 15.029 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:44:15.743-06:00", - "end": "2023-07-21T15:44:27.549-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1664083, - 39.6573923, - 2936.731827439056 - ], - "properties": { - "accuracy": 15.029 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1664083, - 39.6573923, - 2936.646285815867 - ], - "properties": { - "accuracy": 21.576000213623047 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:79a90522-dec4-41dc-8f48-2649024a6aaa" - }, - "__id": "uuid:79a90522-dec4-41dc-8f48-2649024a6aaa", - "__system": { - "submissionDate": "2023-07-21T21:44:33.234Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1664083, 39.6573923, 2936.646285815867], + "properties": { + "accuracy": 21.576000213623047 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:79a90522-dec4-41dc-8f48-2649024a6aaa" + }, + "__id": "uuid:79a90522-dec4-41dc-8f48-2649024a6aaa", + "__system": { + "submissionDate": "2023-07-21T21:44:33.234Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:42:42.271-06:00", + "end": "2023-07-21T15:42:52.823-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1677554, 39.6547254, 2929.6882632957504], + "properties": { + "accuracy": 14.305 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:42:42.271-06:00", - "end": "2023-07-21T15:42:52.823-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1677554, - 39.6547254, - 2929.6882632957504 - ], - "properties": { - "accuracy": 14.305 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1677592, - 39.6547252, - 2928.293496967388 - ], - "properties": { - "accuracy": 21.281999588012695 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:45873d5c-2e45-4120-9669-8dc10f749182" - }, - "__id": "uuid:45873d5c-2e45-4120-9669-8dc10f749182", - "__system": { - "submissionDate": "2023-07-21T21:43:25.362Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1677592, 39.6547252, 2928.293496967388], + "properties": { + "accuracy": 21.281999588012695 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:45873d5c-2e45-4120-9669-8dc10f749182" + }, + "__id": "uuid:45873d5c-2e45-4120-9669-8dc10f749182", + "__system": { + "submissionDate": "2023-07-21T21:43:25.362Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:42:00.497-06:00", + "end": "2023-07-21T15:42:15.327-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1672231, 39.6551323, 2929.860698040349], + "properties": { + "accuracy": 5.611 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:42:00.497-06:00", - "end": "2023-07-21T15:42:15.327-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1672231, - 39.6551323, - 2929.860698040349 - ], - "properties": { - "accuracy": 5.611 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1671865, - 39.6551455, - 2929.7210628817784 - ], - "properties": { - "accuracy": 6.138000011444092 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:9ab25e2a-db95-46e1-b82e-6205eefa3453" - }, - "__id": "uuid:9ab25e2a-db95-46e1-b82e-6205eefa3453", - "__system": { - "submissionDate": "2023-07-21T21:43:24.075Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1671865, 39.6551455, 2929.7210628817784], + "properties": { + "accuracy": 6.138000011444092 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:9ab25e2a-db95-46e1-b82e-6205eefa3453" + }, + "__id": "uuid:9ab25e2a-db95-46e1-b82e-6205eefa3453", + "__system": { + "submissionDate": "2023-07-21T21:43:24.075Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:40:58.911-06:00", + "end": "2023-07-21T15:41:18.825-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1668815, 39.6562096, 2924.3551379358], + "properties": { + "accuracy": 6.063 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:40:58.911-06:00", - "end": "2023-07-21T15:41:18.825-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1668815, - 39.6562096, - 2924.3551379358 - ], - "properties": { - "accuracy": 6.063 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1668462, - 39.6562508, - 2924.3323363887453 - ], - "properties": { - "accuracy": 16.891000747680664 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:e502ff5b-7941-4dfa-9428-85f5eca76b79" - }, - "__id": "uuid:e502ff5b-7941-4dfa-9428-85f5eca76b79", - "__system": { - "submissionDate": "2023-07-21T21:41:28.014Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1668462, 39.6562508, 2924.3323363887453], + "properties": { + "accuracy": 16.891000747680664 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:e502ff5b-7941-4dfa-9428-85f5eca76b79" + }, + "__id": "uuid:e502ff5b-7941-4dfa-9428-85f5eca76b79", + "__system": { + "submissionDate": "2023-07-21T21:41:28.014Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:21:56.580-06:00", + "end": "2023-07-21T15:22:10.710-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1119301, 39.6678327, 2396.9620567615166], + "properties": { + "accuracy": 5.241 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:21:56.580-06:00", - "end": "2023-07-21T15:22:10.710-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1119301, - 39.6678327, - 2396.9620567615166 - ], - "properties": { - "accuracy": 5.241 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.11205248655229, - 39.667397467890304, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:7f7b4c3e-bc74-4791-af41-bcc30b54c2bf" - }, - "__id": "uuid:7f7b4c3e-bc74-4791-af41-bcc30b54c2bf", - "__system": { - "submissionDate": "2023-07-21T21:22:14.715Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.11205248655229, 39.667397467890304, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:7f7b4c3e-bc74-4791-af41-bcc30b54c2bf" + }, + "__id": "uuid:7f7b4c3e-bc74-4791-af41-bcc30b54c2bf", + "__system": { + "submissionDate": "2023-07-21T21:22:14.715Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:21:33.702-06:00", + "end": "2023-07-21T15:21:54.217-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1119416, 39.6678313, 2396.6883884168774], + "properties": { + "accuracy": 5.455 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:21:33.702-06:00", - "end": "2023-07-21T15:21:54.217-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1119416, - 39.6678313, - 2396.6883884168774 - ], - "properties": { - "accuracy": 5.455 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1118736692482, - 39.66812659542975, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:c518b599-60d9-4235-8004-b4b6189951ca" - }, - "__id": "uuid:c518b599-60d9-4235-8004-b4b6189951ca", - "__system": { - "submissionDate": "2023-07-21T21:21:57.656Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1118736692482, 39.66812659542975, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:c518b599-60d9-4235-8004-b4b6189951ca" + }, + "__id": "uuid:c518b599-60d9-4235-8004-b4b6189951ca", + "__system": { + "submissionDate": "2023-07-21T21:21:57.656Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:18:29.665-06:00", + "end": "2023-07-21T15:18:50.845-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1036074, 39.6635726, 2306.19865138811], + "properties": { + "accuracy": 10.242 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:18:29.665-06:00", - "end": "2023-07-21T15:18:50.845-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1036074, - 39.6635726, - 2306.19865138811 - ], - "properties": { - "accuracy": 10.242 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1037973, - 39.6635323, - 2309.118452684388 - ], - "properties": { - "accuracy": 9.069999694824219 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:60f7cb9f-13e8-4e38-ab8e-e37fe0fe33ff" - }, - "__id": "uuid:60f7cb9f-13e8-4e38-ab8e-e37fe0fe33ff", - "__system": { - "submissionDate": "2023-07-21T21:18:51.789Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1037973, 39.6635323, 2309.118452684388], + "properties": { + "accuracy": 9.069999694824219 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:60f7cb9f-13e8-4e38-ab8e-e37fe0fe33ff" + }, + "__id": "uuid:60f7cb9f-13e8-4e38-ab8e-e37fe0fe33ff", + "__system": { + "submissionDate": "2023-07-21T21:18:51.789Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:12:15.092-06:00", + "end": "2023-07-21T15:12:31.911-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.1006068, 39.673528, 2102.4377881687124], + "properties": { + "accuracy": 5.486 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:12:15.092-06:00", - "end": "2023-07-21T15:12:31.911-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.1006068, - 39.673528, - 2102.4377881687124 - ], - "properties": { - "accuracy": 5.486 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.1006136, - 39.6734967, - 2102.305741120833 - ], - "properties": { - "accuracy": 6.77400016784668 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:f2b9168c-07b4-4f53-bbe8-43ec8f008eaa" - }, - "__id": "uuid:f2b9168c-07b4-4f53-bbe8-43ec8f008eaa", - "__system": { - "submissionDate": "2023-07-21T21:18:29.690Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.1006136, 39.6734967, 2102.305741120833], + "properties": { + "accuracy": 6.77400016784668 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:f2b9168c-07b4-4f53-bbe8-43ec8f008eaa" + }, + "__id": "uuid:f2b9168c-07b4-4f53-bbe8-43ec8f008eaa", + "__system": { + "submissionDate": "2023-07-21T21:18:29.690Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-21T15:08:20.549-06:00", + "end": "2023-07-21T15:08:41.258-06:00", + "today": "2023-07-21", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-107.0959155, 39.6773568, 1987.0652246857899], + "properties": { + "accuracy": 5.844 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-21T15:08:20.549-06:00", - "end": "2023-07-21T15:08:41.258-06:00", - "today": "2023-07-21", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -107.0959155, - 39.6773568, - 1987.0652246857899 - ], - "properties": { - "accuracy": 5.844 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -107.0959155, - 39.6773568, - 1987.0652246857899 - ], - "properties": { - "accuracy": 5.843999862670898 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:5f22bbb1-0075-4537-b5ee-858a9a8a5cc1" - }, - "__id": "uuid:5f22bbb1-0075-4537-b5ee-858a9a8a5cc1", - "__system": { - "submissionDate": "2023-07-21T21:18:28.641Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-107.0959155, 39.6773568, 1987.0652246857899], + "properties": { + "accuracy": 5.843999862670898 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:5f22bbb1-0075-4537-b5ee-858a9a8a5cc1" + }, + "__id": "uuid:5f22bbb1-0075-4537-b5ee-858a9a8a5cc1", + "__system": { + "submissionDate": "2023-07-21T21:18:28.641Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:59:42.990-06:00", + "end": "2023-07-20T15:59:55.717-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.7594597, 39.7569017, 2526.9887733556952], + "properties": { + "accuracy": 3.9 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:59:42.990-06:00", - "end": "2023-07-20T15:59:55.717-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.7594597, - 39.7569017, - 2526.9887733556952 - ], - "properties": { - "accuracy": 3.9 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.75915852570162, - 39.75621458487067, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:ac2b2839-4062-47c5-8cee-a3ad51e0ed00" - }, - "__id": "uuid:ac2b2839-4062-47c5-8cee-a3ad51e0ed00", - "__system": { - "submissionDate": "2023-07-20T21:59:59.482Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.75915852570162, 39.75621458487067, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:ac2b2839-4062-47c5-8cee-a3ad51e0ed00" + }, + "__id": "uuid:ac2b2839-4062-47c5-8cee-a3ad51e0ed00", + "__system": { + "submissionDate": "2023-07-20T21:59:59.482Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:57:20.896-06:00", + "end": "2023-07-20T15:57:41.534-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.7566413, 39.7556361, 2515.039889377351], + "properties": { + "accuracy": 4.724 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:57:20.896-06:00", - "end": "2023-07-20T15:57:41.534-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.7566413, - 39.7556361, - 2515.039889377351 - ], - "properties": { - "accuracy": 4.724 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.75610661659297, - 39.754206225479294, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:4eb0fe3d-ba62-48d4-aec4-3712c85b2847" - }, - "__id": "uuid:4eb0fe3d-ba62-48d4-aec4-3712c85b2847", - "__system": { - "submissionDate": "2023-07-20T21:57:43.897Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.75610661659297, 39.754206225479294, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:4eb0fe3d-ba62-48d4-aec4-3712c85b2847" + }, + "__id": "uuid:4eb0fe3d-ba62-48d4-aec4-3712c85b2847", + "__system": { + "submissionDate": "2023-07-20T21:57:43.897Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:44:35.776-06:00", + "end": "2023-07-20T15:44:47.214-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.753977, 39.7564542, 2479.8549222925917], + "properties": { + "accuracy": 9.962 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:44:35.776-06:00", - "end": "2023-07-20T15:44:47.214-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.753977, - 39.7564542, - 2479.8549222925917 - ], - "properties": { - "accuracy": 9.962 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.7539756, - 39.7564494, - 2479.998414479827 - ], - "properties": { - "accuracy": 3.9000000953674316 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:e6845ad2-6e1c-4e37-bf25-4734415d1b63" - }, - "__id": "uuid:e6845ad2-6e1c-4e37-bf25-4734415d1b63", - "__system": { - "submissionDate": "2023-07-20T21:44:49.640Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.7539756, 39.7564494, 2479.998414479827], + "properties": { + "accuracy": 3.9000000953674316 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:e6845ad2-6e1c-4e37-bf25-4734415d1b63" + }, + "__id": "uuid:e6845ad2-6e1c-4e37-bf25-4734415d1b63", + "__system": { + "submissionDate": "2023-07-20T21:44:49.640Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:41:35.633-06:00", + "end": "2023-07-20T15:41:51.302-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.7519523, 39.7567738, 2493.6077771562805], + "properties": { + "accuracy": 5.52 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:41:35.633-06:00", - "end": "2023-07-20T15:41:51.302-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.7519523, - 39.7567738, - 2493.6077771562805 - ], - "properties": { - "accuracy": 5.52 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.7519463, - 39.7567795, - 2495.291259765625 - ], - "properties": { - "accuracy": 10.96500015258789 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:67d3311a-8249-48dd-89c7-e16c4178a32f" - }, - "__id": "uuid:67d3311a-8249-48dd-89c7-e16c4178a32f", - "__system": { - "submissionDate": "2023-07-20T21:41:53.380Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.7519463, 39.7567795, 2495.291259765625], + "properties": { + "accuracy": 10.96500015258789 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:67d3311a-8249-48dd-89c7-e16c4178a32f" + }, + "__id": "uuid:67d3311a-8249-48dd-89c7-e16c4178a32f", + "__system": { + "submissionDate": "2023-07-20T21:41:53.380Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:37:43.295-06:00", + "end": "2023-07-20T15:38:08.281-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.7508553, 39.7540343, 2470.09619140625], + "properties": { + "accuracy": 6.812 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:37:43.295-06:00", - "end": "2023-07-20T15:38:08.281-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.7508553, - 39.7540343, - 2470.09619140625 - ], - "properties": { - "accuracy": 6.812 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.7508855, - 39.7540372, - 2469.757727953605 - ], - "properties": { - "accuracy": 14.72599983215332 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:445a5084-0efb-4503-a1e0-79f70d495949" - }, - "__id": "uuid:445a5084-0efb-4503-a1e0-79f70d495949", - "__system": { - "submissionDate": "2023-07-20T21:38:10.597Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.7508855, 39.7540372, 2469.757727953605], + "properties": { + "accuracy": 14.72599983215332 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:445a5084-0efb-4503-a1e0-79f70d495949" + }, + "__id": "uuid:445a5084-0efb-4503-a1e0-79f70d495949", + "__system": { + "submissionDate": "2023-07-20T21:38:10.597Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:34:46.664-06:00", + "end": "2023-07-20T15:35:16.276-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.7494989, 39.75337, 2474.126843037268], + "properties": { + "accuracy": 5.269 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "no", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:34:46.664-06:00", - "end": "2023-07-20T15:35:16.276-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.7494989, - 39.75337, - 2474.126843037268 - ], - "properties": { - "accuracy": 5.269 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "no", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.7494973, - 39.7533723, - 2474.1051720781684 - ], - "properties": { - "accuracy": 5.831999778747559 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:bcdfd56e-f222-4fcc-83f9-69c6fc042d46" - }, - "__id": "uuid:bcdfd56e-f222-4fcc-83f9-69c6fc042d46", - "__system": { - "submissionDate": "2023-07-20T21:35:19.423Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.7494973, 39.7533723, 2474.1051720781684], + "properties": { + "accuracy": 5.831999778747559 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:bcdfd56e-f222-4fcc-83f9-69c6fc042d46" + }, + "__id": "uuid:bcdfd56e-f222-4fcc-83f9-69c6fc042d46", + "__system": { + "submissionDate": "2023-07-20T21:35:19.423Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:19:00.904-06:00", + "end": "2023-07-20T15:19:22.076-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.7048318, 39.7426337, 2275.2785320539565], + "properties": { + "accuracy": 11.186 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:19:00.904-06:00", - "end": "2023-07-20T15:19:22.076-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.7048318, - 39.7426337, - 2275.2785320539565 - ], - "properties": { - "accuracy": 11.186 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.7048455, - 39.7426508, - 2275.58530266829 - ], - "properties": { - "accuracy": 24.572999954223633 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:a0333d65-d58a-4bad-b5d0-31b625b3a16a" - }, - "__id": "uuid:a0333d65-d58a-4bad-b5d0-31b625b3a16a", - "__system": { - "submissionDate": "2023-07-20T21:19:23.222Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.7048455, 39.7426508, 2275.58530266829], + "properties": { + "accuracy": 24.572999954223633 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:a0333d65-d58a-4bad-b5d0-31b625b3a16a" + }, + "__id": "uuid:a0333d65-d58a-4bad-b5d0-31b625b3a16a", + "__system": { + "submissionDate": "2023-07-20T21:19:23.222Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:16:11.883-06:00", + "end": "2023-07-20T15:16:24.025-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.6991102, 39.7435664, 2241.470678959833], + "properties": { + "accuracy": 5.168 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:16:11.883-06:00", - "end": "2023-07-20T15:16:24.025-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.6991102, - 39.7435664, - 2241.470678959833 - ], - "properties": { - "accuracy": 5.168 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.6991102, - 39.7435664, - 2241.470678959833 - ], - "properties": { - "accuracy": 5.168000221252441 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:38b10552-9f25-49e6-b369-642f59973210" - }, - "__id": "uuid:38b10552-9f25-49e6-b369-642f59973210", - "__system": { - "submissionDate": "2023-07-20T21:18:33.309Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.6991102, 39.7435664, 2241.470678959833], + "properties": { + "accuracy": 5.168000221252441 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:38b10552-9f25-49e6-b369-642f59973210" + }, + "__id": "uuid:38b10552-9f25-49e6-b369-642f59973210", + "__system": { + "submissionDate": "2023-07-20T21:18:33.309Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:13:53.852-06:00", + "end": "2023-07-20T15:14:15.323-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.688263, 39.7419985, 2208.572930075765], + "properties": { + "accuracy": 24.494 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:13:53.852-06:00", - "end": "2023-07-20T15:14:15.323-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.688263, - 39.7419985, - 2208.572930075765 - ], - "properties": { - "accuracy": 24.494 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.6882595, - 39.7419681, - 2208.738222753441 - ], - "properties": { - "accuracy": 25.73699951171875 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:1dcbf3ea-c3d3-4585-b371-5da49af169ae" - }, - "__id": "uuid:1dcbf3ea-c3d3-4585-b371-5da49af169ae", - "__system": { - "submissionDate": "2023-07-20T21:14:17.376Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.6882595, 39.7419681, 2208.738222753441], + "properties": { + "accuracy": 25.73699951171875 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:1dcbf3ea-c3d3-4585-b371-5da49af169ae" + }, + "__id": "uuid:1dcbf3ea-c3d3-4585-b371-5da49af169ae", + "__system": { + "submissionDate": "2023-07-20T21:14:17.376Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-20T15:11:30.965-06:00", + "end": "2023-07-20T15:12:54.803-06:00", + "today": "2023-07-20", + "deviceid": "collect:Qc50gXnrMR8RMlTz", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-106.684516, 39.738967, 2196.5458984375], + "properties": { + "accuracy": 6.461 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "no", + "cellular": "yes", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-20T15:11:30.965-06:00", - "end": "2023-07-20T15:12:54.803-06:00", - "today": "2023-07-20", - "deviceid": "collect:Qc50gXnrMR8RMlTz", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -106.684516, - 39.738967, - 2196.5458984375 - ], - "properties": { - "accuracy": 6.461 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "no", - "cellular": "yes", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -106.68438607458579, - 39.73907436485314, - 0 - ], - "properties": { - "accuracy": 0 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:c55cb020-5c64-49af-a3cd-1679f1a2bf1f" - }, - "__id": "uuid:c55cb020-5c64-49af-a3cd-1679f1a2bf1f", - "__system": { - "submissionDate": "2023-07-20T21:12:57.609Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:Qc50gXnrMR8RMlTz", - "edits": 0, - "formVersion": "2023-07-16 16:06:18" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-106.68438607458579, 39.73907436485314, 0], + "properties": { + "accuracy": 0 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:c55cb020-5c64-49af-a3cd-1679f1a2bf1f" + }, + "__id": "uuid:c55cb020-5c64-49af-a3cd-1679f1a2bf1f", + "__system": { + "submissionDate": "2023-07-20T21:12:57.609Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:Qc50gXnrMR8RMlTz", + "edits": 0, + "formVersion": "2023-07-16 16:06:18" + } + }, + { + "start": "2023-07-16T16:31:34.634-06:00", + "end": "2023-07-16T16:35:10.932-06:00", + "today": "2023-07-16", + "deviceid": "collect:LQ7fhRjfsnDT5qAU", + "phonenumber": null, + "username": "rsavoye", + "email": null, + "warmup": null, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "yes", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-16T16:31:34.634-06:00", - "end": "2023-07-16T16:35:10.932-06:00", - "today": "2023-07-16", - "deviceid": "collect:LQ7fhRjfsnDT5qAU", - "phonenumber": null, - "username": "rsavoye", - "email": null, - "warmup": null, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "yes", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -105.5184718, - 39.9124265, - 2677 - ], - "properties": { - "accuracy": 91.12000274658203 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:94ab2182-6feb-46df-9737-8ab924fb92d5" - }, - "__id": "uuid:94ab2182-6feb-46df-9737-8ab924fb92d5", - "__system": { - "submissionDate": "2023-07-16T22:35:13.324Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:LQ7fhRjfsnDT5qAU", - "edits": 0, - "formVersion": "2023-07-16 15:09:40" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-105.5184718, 39.9124265, 2677], + "properties": { + "accuracy": 91.12000274658203 } + }, + "image": null, + "comment": null + } + }, + "meta": { + "instanceID": "uuid:94ab2182-6feb-46df-9737-8ab924fb92d5" + }, + "__id": "uuid:94ab2182-6feb-46df-9737-8ab924fb92d5", + "__system": { + "submissionDate": "2023-07-16T22:35:13.324Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:LQ7fhRjfsnDT5qAU", + "edits": 0, + "formVersion": "2023-07-16 15:09:40" + } + }, + { + "start": "2023-07-16T15:40:02.959-06:00", + "end": "2023-07-16T15:41:08.940-06:00", + "today": "2023-07-16", + "deviceid": "collect:LQ7fhRjfsnDT5qAU", + "phonenumber": null, + "username": null, + "email": null, + "warmup": { + "type": "Point", + "coordinates": [-105.5182407, 39.9126691, 2658], + "properties": { + "accuracy": 42.88 + } + }, + "all": { + "first_screen": { + "model": "basic", + "camptype": "camp_pitch", + "internet_access": "yes", + "cellular": "no", + "openfire": "yes", + "picnic_table": "no", + "shelter": null, + "ref": null, + "fee": "no", + "firepit": null, + "bbq": null, + "bear_box": null, + "backcountry": null }, - { - "start": "2023-07-16T15:40:02.959-06:00", - "end": "2023-07-16T15:41:08.940-06:00", - "today": "2023-07-16", - "deviceid": "collect:LQ7fhRjfsnDT5qAU", - "phonenumber": null, - "username": null, - "email": null, - "warmup": { - "type": "Point", - "coordinates": [ - -105.5182407, - 39.9126691, - 2658 - ], - "properties": { - "accuracy": 42.88 - } - }, - "all": { - "first_screen": { - "model": "basic", - "camptype": "camp_pitch", - "internet_access": "yes", - "cellular": "no", - "openfire": "yes", - "picnic_table": "no", - "shelter": null, - "ref": null, - "fee": "no", - "firepit": null, - "bbq": null, - "bear_box": null, - "backcountry": null - }, - "second_screen": { - "power_supply": null, - "drinking_water": null, - "features": null, - "name": null, - "toilet": null, - "cost": null, - "amenity": null, - "group_only": null, - "where": { - "type": "Point", - "coordinates": [ - -105.5182346, - 39.9126651, - 2666 - ], - "properties": { - "accuracy": 32.69599914550781 - } - }, - "image": null, - "comment": null - } - }, - "meta": { - "instanceID": "uuid:68df7dd1-463d-4cb9-9541-d30a247a9c1e" - }, - "__id": "uuid:68df7dd1-463d-4cb9-9541-d30a247a9c1e", - "__system": { - "submissionDate": "2023-07-16T21:41:12.294Z", - "updatedAt": null, - "submitterId": "491", - "submitterName": "rob", - "attachmentsPresent": 0, - "attachmentsExpected": 0, - "status": null, - "reviewState": null, - "deviceId": "collect:LQ7fhRjfsnDT5qAU", - "edits": 0, - "formVersion": "2023-07-16 15:09:40" + "second_screen": { + "power_supply": null, + "drinking_water": null, + "features": null, + "name": null, + "toilet": null, + "cost": null, + "amenity": null, + "group_only": null, + "where": { + "type": "Point", + "coordinates": [-105.5182346, 39.9126651, 2666], + "properties": { + "accuracy": 32.69599914550781 } + }, + "image": null, + "comment": null } - ], - "@odata.context": "https://odk.hotosm.org/v1/projects/19/forms/camping-nodata.svc/$metadata#Submissions" + }, + "meta": { + "instanceID": "uuid:68df7dd1-463d-4cb9-9541-d30a247a9c1e" + }, + "__id": "uuid:68df7dd1-463d-4cb9-9541-d30a247a9c1e", + "__system": { + "submissionDate": "2023-07-16T21:41:12.294Z", + "updatedAt": null, + "submitterId": "491", + "submitterName": "rob", + "attachmentsPresent": 0, + "attachmentsExpected": 0, + "status": null, + "reviewState": null, + "deviceId": "collect:LQ7fhRjfsnDT5qAU", + "edits": 0, + "formVersion": "2023-07-16 15:09:40" + } + } + ], + "@odata.context": "https://odk.hotosm.org/v1/projects/19/forms/camping-nodata.svc/$metadata#Submissions" }