diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..4bd5580 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version, and other tools you might need +build: + os: ubuntu-24.04 + tools: + python: "3.13" + + apt_packages: + - libgdal-dev + - gdal-bin + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally, but recommended, +# declare the Python requirements required to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/README.md b/README.md index 815c409..5987f49 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,33 @@ # ![GeodePy](https://github.com/GeoscienceAustralia/GeodePy/blob/master/docs/geodepy-logo.png) -[![Travis](https://img.shields.io/travis/GeoscienceAustralia/GeodePy/master.svg?label=Travis%20CI)](https://travis-ci.org/GeoscienceAustralia/GeodePy) [![Coverage Status](https://coveralls.io/repos/github/GeoscienceAustralia/GeodePy/badge.svg)](https://coveralls.io/github/GeoscienceAustralia/GeodePy) +GeodePy is a python package for precise geodetic and survey computations. -This is a package of tools for manipulating geospatial datasets using Python and tested in Python 3.6.4. +## Documentation -### Tutorials +See [here](https://geodepy.readthedocs.io/) for documentation around downloading and using GeodePy. -See [here](https://github.com/GeoscienceAustralia/GeodePy/tree/master/docs/tutorials) for worked examples of common GeodePy functions and routines. +## Features -### Dependencies +GeodePy includes a variety of features for geodesy and geospatial data manipulation, including: + +* Converting between coordinate types +* Transforming between datums +* Calculating geodetic distances and bearings +* Working with geoid models +* Surveying calculations +* Various classes for angles, coordinates, and datums +* Statistics +* And more! + +## Installation + +GeodePy is available on PyPi: + +```console +$ pip install geodepy +``` + +## Dependencies This package requires the following PyPI Packages installed: @@ -19,32 +38,21 @@ SciPy Additionally, the geodepy.height module requires the GDAL library (tested using GDAL 3.0.4). For more information, see [here](https://gdal.org/index.html) for information about GDAL, [here](https://anaconda.org/conda-forge/gdal) for Anaconda support for GDAL and [here](http://www.gisinternals.com/release.php) for GDAL Binaries for Windows. -### Testing +## Testing Run: `python -m unittest discover geodepy/tests/ --verbose` -## API - -``` -cd api/ -virtualenv env -source env/bin/activate -pip install -r requirements.txt -zappa deploy dev -``` - -For subsequent updating run: `zappa update dev` - -### Authors +## Authors * **Craig Harrison** - *Project Management* - [harry093](https://github.com/harry093) * **Josh Batchelor** - *Initial Work, Geodesy and Surveying* - [BatchelorJ](https://github.com/BatchelorJ) * **Jonathan Mettes** - *Testing, Integration and Deployment* - [jmettes](https://github.com/jmettes) * **Jack McCubbine** - *Height Module* - [JackMcCubbineGA](https://github.com/JackMcCubbineGA) +* **Kyran Cook** - *Documentation and Uplift* - [Kyran-Cook](https://github.com/Kyran-Cook) See also the list of [contributors](https://github.com/GeoscienceAustralia/geodepy/graphs/contributors) who participated in this project. -### License +## License Copyright 2018-2020 Geoscience Australia diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..a0ba58f --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,32 @@ +a { + color: #00718e; +} + +a .sidebar-navigation{ + color: #00718e; +} + +.sidebar-tree a { + color: #00718e !important; /* Sidebar links */ +} + +.sidebar-tree svg { + color: #00718e !important; /* Normal arrow color */ +} + +/* Hide the TOC caption heading on the index page */ +.toctree-wrapper > p.caption { + display: none; +} + +/* Style the custom TOC expander arrows */ +.toctree-expander { + color: #00718e; /* Your preferred color */ +} + +/* Optional: Hover effect */ +.toctree-expander:hover { + color: #005f73; +} + + diff --git a/docs/_static/custom.js b/docs/_static/custom.js new file mode 100644 index 0000000..d4df3ba --- /dev/null +++ b/docs/_static/custom.js @@ -0,0 +1,29 @@ +document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll(".collapsible-toc li").forEach(function (item) { + const sublist = item.querySelector("ul"); + const link = item.querySelector("a"); + if (sublist && link) { + const toggle = document.createElement("span"); + toggle.innerHTML = ` + + + + `; + toggle.style.cursor = "pointer"; + toggle.style.marginLeft = "8px"; + + toggle.addEventListener("click", function () { + if (sublist.style.display === "none") { + sublist.style.display = "block"; + toggle.querySelector("polyline").setAttribute("points", "6 15 12 9 18 15"); + } else { + sublist.style.display = "none"; + toggle.querySelector("polyline").setAttribute("points", "6 9 12 15 18 9"); + } + }); + + sublist.style.display = "none"; + link.insertAdjacentElement("afterend", toggle); + } + }); +}); diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 0000000..5bf516d Binary files /dev/null and b/docs/_static/favicon.ico differ diff --git a/docs/_static/geodepy-logo-dark.png b/docs/_static/geodepy-logo-dark.png new file mode 100644 index 0000000..c5d37fe Binary files /dev/null and b/docs/_static/geodepy-logo-dark.png differ diff --git a/docs/_static/geodepy-logo-light.png b/docs/_static/geodepy-logo-light.png new file mode 100644 index 0000000..c9c321f Binary files /dev/null and b/docs/_static/geodepy-logo-light.png differ diff --git a/docs/_static/geodepy-logo.png b/docs/_static/geodepy-logo.png new file mode 100644 index 0000000..5224e81 Binary files /dev/null and b/docs/_static/geodepy-logo.png differ diff --git a/docs/_static/robots.txt b/docs/_static/robots.txt new file mode 100644 index 0000000..538b69c --- /dev/null +++ b/docs/_static/robots.txt @@ -0,0 +1,6 @@ +# test +User-agent: * + +Disallow: # Allow everything + +Sitemap: https://geodepy.readthedocs.io/sitemap.xml \ No newline at end of file diff --git a/docs/_static/sitemap.xml b/docs/_static/sitemap.xml new file mode 100644 index 0000000..eaa1496 --- /dev/null +++ b/docs/_static/sitemap.xml @@ -0,0 +1,312 @@ + + + + + + + https://geodepy.readthedocs.io/en/latest/ + 2025-11-17T01:27:53+00:00 + 1.00 + + + https://geodepy.readthedocs.io/en/latest/installation.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/quickstart.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/index.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/angles.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/constants.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/convert.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/coord.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/geodesy.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/gnss.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/height.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/inputoutput.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/ntv2reader.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/statistics.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/survey.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/features/transform.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/tutorials/index.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/tutorials/anglestut.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/tutorials/coordtut.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/tutorials/vincenty.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/tutorials/convert.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/tutorials/transform.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/community/contributing.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/community/authors.html + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/_sources/index.rst.txt + 2025-11-17T01:27:53+00:00 + 0.80 + + + https://geodepy.readthedocs.io/en/latest/index.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/installation.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/quickstart.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/index.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/angles.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/angles.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/constants.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/constants.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/convert.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/convert.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/coord.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/coord.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/geodesy.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/geodesy.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/gnss.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/gnss.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/height.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/inputoutput.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/inputoutput.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/ntv2reader.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/ntv2reader.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/statistics.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/statistics.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/survey.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/survey.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/features/transform.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_modules/geodepy/transform.html + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/tutorials/index.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/tutorials/anglestut.rst.txt + 2025-11-17T01:27:53+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/tutorials/coordtut.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/tutorials/vincenty.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/tutorials/convert.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/tutorials/transform.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/community/contributing.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + https://geodepy.readthedocs.io/en/latest/_sources/community/authors.rst.txt + 2025-11-13T22:45:29+00:00 + 0.64 + + + + \ No newline at end of file diff --git a/docs/_templates/sidebar/extra.html b/docs/_templates/sidebar/extra.html new file mode 100644 index 0000000..9d377f1 --- /dev/null +++ b/docs/_templates/sidebar/extra.html @@ -0,0 +1,6 @@ + diff --git a/docs/community/authors.rst b/docs/community/authors.rst new file mode 100644 index 0000000..1952baf --- /dev/null +++ b/docs/community/authors.rst @@ -0,0 +1,12 @@ +Authors +======= + +GeodePy is developed and maintained by the Geoscience Australia National Geodesy Team. Over time, many contributors have helped improve the package. + +* **Craig Harrison** - Project Management - `harry093 `_ +* **Josh Batchelor** - Initial Work, Geodesy and Surveying - `BatchelorJ `_ +* **Jonathan Mettes** - Testing, Integration and Development - `jmettes `_ +* **Jack McCubbine** - Height Module - `JackMcCubbineGA `_ +* **Kyran Cook** - Documentation and Uplift - `Kyran-Cook `_ + +See also the list of `contributors `_ who have participated in this project. \ No newline at end of file diff --git a/docs/community/contributing.rst b/docs/community/contributing.rst new file mode 100644 index 0000000..8e53c49 --- /dev/null +++ b/docs/community/contributing.rst @@ -0,0 +1,62 @@ +Contributing +================== + +We welcome contributions from the community to help improve GeodePy! Whether you're fixing bugs, +adding new features, or enhancing documentation, your input is valuable. Open source projects +live and die based on the support they recieve. + +This document outlines some of the guidlines and advice for contributing to GeodePy. + +Code of Conduct +---------------- + +By participating in this project, you agree to abide by the +`Python Software Foundation Code of Conduct `_. +Please read it to understand the expectations for behavior when contributing to this project. + +Coding Style Guide +------------------ + +GeodePy uses `Black `_ to keep coding style consistent while still being accessable to all. +Black uses `PEP 8 `_ coding style, an industry standard for python code. Before +any commits to GeodePy ensure Black has been used. + +.. _code: + +Code Contributions +------------------ + +When contributing code please follow these steps: + +1. Fork the repository on `GitHub `_. +2. Run tests on current code to ensure it works on your system (See :ref:`Testing `) +3. Create tests that demonstrate your bug or feature. +4. Make changes, ensuring coding sytle guide is abided by. +5. Run all tests again including one added and ensure all tests pass. +6. Send a Github Pull Request to the repository's **master** branch + +Our project maintainers have the last word on if contributions are suitable or not. If your contribution is rejected dont despair! +Following the guidlines above will give you the best chance of getting accpeted. + +Documentation Contributions +--------------------------- + +Documentation imporvements are always welcome! We understand that good documentation is important for all users of a package. +The documentation files can be found in the docs/ folder. They are written in `reStructedText `_, +and use `Sphinx `_ to generate the documentation. + +When contributing documentation please follow the style of current documentation, having a semi-formal yet friendly approach. +Ensure any code in documentation is well commeneted to ensure parameters are well understood. + +Bug Reports +------------ + +We welcome all bug reports! Before you raise one though please check the `GitHub issues `_, +both open and closed, to confirm the bug hastn been reported before. If you do submit a bug report ensure that the bug is clearly described, +giving the situation that caused the bug and some repeatable code for testing. + +Feature Requests +---------------- + +If you believe a feature is missing, feel free to raise a feature request. Keep in mind that being an open source project requested features may +or may not be implemented. If there is a feature you really need consider creating it yourself and :ref:`submitting the code `. \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..97e241e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,66 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + + +import os +import sys + +# Add the path to your local source code +sys.path.insert(0, os.path.abspath("..")) # Adjust to your directory + + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'GeodePy' +copyright = '2025, Geoscience Australia' +author = 'Geoscience Australia' +release = '0.6.0' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.viewcode",'sphinx_copybutton'] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +html_sidebars = { + "**": [ + "sidebar/brand.html", # Logo + "sidebar/extra.html", # Custom tagline + badge + "sidebar/scroll-start.html", + "sidebar/search.html", + "sidebar/navigation.html", + "sidebar/scroll-end.html", + ] +} + +autoclass_content = 'both' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'furo' +html_static_path = ['_static'] +html_theme_options = { + "light_logo": "geodepy-logo-light.png", + "dark_logo": "geodepy-logo-dark.png", + "sidebar_hide_name": True, +} + +html_css_files = [ + 'custom.css', +] +html_js_files = [ + 'custom.js', +] +html_favicon = '_static/favicon.ico' +em_dash_char = chr(8212) +html_title = "GeodePy: Geodesy in Python " + em_dash_char+ " v" + release + " Documentation" +ogp_site_name = "GeodePy: Geodesy in Python" +html_extra_path = ["_static/robots.txt", "_static/sitemap.xml"] + +autodoc_mock_imports = ["osgeo","gdal","GDAL"] \ No newline at end of file diff --git a/docs/features/angles.rst b/docs/features/angles.rst new file mode 100644 index 0000000..101b179 --- /dev/null +++ b/docs/features/angles.rst @@ -0,0 +1,148 @@ +.. _features/angles: + +Angles +====== + +Angle Types +----------- + +GeodePy supports Angular Notation in 9 different formats + ++------------------------+--------------------------------------------------------------+ +| ABRV | FORMAT (type) | ++========================+==============================================================+ +| rad | Radians (stored as float) | ++------------------------+--------------------------------------------------------------+ +| dec | Decimal Degrees (stored as float) | ++------------------------+--------------------------------------------------------------+ +| :ref:`dec ` | Decimal Degrees (via DECAngle class) | ++------------------------+--------------------------------------------------------------+ +| hp | Hewlett Packard (HP) Notation (stored as float) | ++------------------------+--------------------------------------------------------------+ +| :ref:`hpa ` | Hewlett Packard (HP) Notation (via HPAngle class) | ++------------------------+--------------------------------------------------------------+ +| gon | Gradians (stored as float) | ++------------------------+--------------------------------------------------------------+ +| :ref:`gona ` | Gradians (via GONAngle class) | ++------------------------+--------------------------------------------------------------+ +| :ref:`dms ` | Degrees, Minutes and Seconds Notation (via DMSAngle class) | ++------------------------+--------------------------------------------------------------+ +| :ref:`ddm ` | Degrees and Decimal Minutes Notation (via DDMAngle class) | ++------------------------+--------------------------------------------------------------+ + +Conversion between all formats is supported as shown below: + +* Radians to/from Decimal Degrees via built in math.radians and math.degrees + +* Formats as floats to all other types via functions in the form abrv2abrv + + e.g. gon2hpa() + +* DECAngle, HPAngle, GONAngle, DMSAngle and DDMAngle class objects via methods in + the form CLASS.abrv() + + e.g. HPAngle(value).dec() + +All **angle classes** can be seen below: + +.. _DECAngle: + +.. autoclass:: geodepy.angles.DECAngle + :members: + +.. _HPAngle: + +.. autoclass:: geodepy.angles.HPAngle + :members: + +.. _GONAngle: + +.. autoclass:: geodepy.angles.GONAngle + :members: + +.. _DMSAngle: + +.. autoclass:: geodepy.angles.DMSAngle + :members: + +.. _DDMAngle: + +.. autoclass:: geodepy.angles.DDMAngle + :members: + +.. _converstions: + +------------------- + +Angle Conversions +----------------- + +All conversion functions can be seen below. + +For converting **decimal degree** to other formats: + +.. autofunction:: geodepy.angles.dec2hp + +.. autofunction:: geodepy.angles.dec2hpa + +.. autofunction:: geodepy.angles.dec2gon + +.. autofunction:: geodepy.angles.dec2gona + +.. autofunction:: geodepy.angles.dec2dms + +.. autofunction:: geodepy.angles.dec2ddm + +------------------- + +For converting **Hewlett Packard** to other formats: + + +.. autofunction:: geodepy.angles.hp2dec + +.. autofunction:: geodepy.angles.hp2deca + +.. autofunction:: geodepy.angles.hp2rad + +.. autofunction:: geodepy.angles.hp2gon + +.. autofunction:: geodepy.angles.hp2gona + +.. autofunction:: geodepy.angles.hp2dms + +.. autofunction:: geodepy.angles.hp2ddm + +------------------- + +For converting **Gradians** to other formats: + +.. autofunction:: geodepy.angles.gon2dec + +.. autofunction:: geodepy.angles.gon2deca + +.. autofunction:: geodepy.angles.gon2hp + +.. autofunction:: geodepy.angles.gon2hpa + +.. autofunction:: geodepy.angles.gon2rad + +.. autofunction:: geodepy.angles.gon2dms + +.. autofunction:: geodepy.angles.gon2ddm + +------------------- + +Other Converstion Functions: + +.. autofunction:: geodepy.angles.dd2sec + +.. + + .. autofunction:: geodepy.angles.dec2hp_v + + .. autofunction:: geodepy.angles.hp2dec_v + +Angle Type Checking +------------------- + +.. autofunction:: geodepy.angles.angular_typecheck diff --git a/docs/features/constants.rst b/docs/features/constants.rst new file mode 100644 index 0000000..b75ec96 --- /dev/null +++ b/docs/features/constants.rst @@ -0,0 +1,147 @@ +.. _features/constants: + +Constants +========= + +This module contains constants commonly used in geodetic calculations, including ellipsoids, projections and transformations. + +Classes +-------- + +GeodePy provides four classes for handling commonly used geodetic constants. These include a class for ellipsoids, projections, tranformations and tranformations sigmas. +These classes can be used to create objects that store the relevant parameters for each type of constant, and provide methods for accessing and manipulating these parameters. + + +.. autoclass:: geodepy.constants.Ellipsoid + :members: + +.. autoclass:: geodepy.constants.Projection + :members: + +.. _transclass: + +.. autoclass:: geodepy.constants.Transformation + :members: + + .. automethod:: __neg__ + .. automethod:: __add__ + +.. autoclass:: geodepy.constants.TransformationSD + :members: + +Predefined Constants +---------------------- + +GeodePy also includes a set of predefined constants for commonly used ellipsoids, projections and transformations. These can be seen below. + +Ellipsoid +^^^^^^^^^ + +For :class:`~geodepy.constants.Ellipsoid`: + ++-----------------------------+---------------------+-------------------------------------------------------------+ +| Name | Type | Description | ++=============================+=====================+=============================================================+ +| grs80 | Ellipsoid | Geodetic Reference System 1980 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| wgs84 | Ellipsoid | World Geodetic System 1984 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| ans | Ellipsoid | Australian National Spheroid | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| intl24 | Ellipsoid | International (Hayford) 1924 | ++-----------------------------+---------------------+-------------------------------------------------------------+ + +Projection +^^^^^^^^^^ + +For :class:`~geodepy.constants.Projection`: + ++-----------------------------+---------------------+-------------------------------------------------------------+ +| Name | Type | Description | ++=============================+=====================+=============================================================+ +| utm | Projection | Universal Transverse Mercator projection | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| isg | Projection | Integrated Survey Grid (NSW, AGD66) | ++-----------------------------+---------------------+-------------------------------------------------------------+ + +.. _features/constants/transform: + +Transformation +^^^^^^^^^^^^^^ + +For :class:`~geodepy.constants.Transformation`: + ++-----------------------------+---------------------+-------------------------------------------------------------+ +| Name | Type | Description | ++=============================+=====================+=============================================================+ +| gda94_to_gda2020 | Transformation | GDA94 to GDA2020 (national) | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2014_to_gda2020 | Transformation | ITRF2014 to GDA2020 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| atrf2014_to_gda2020 | Transformation | ATRF2014 to GDA2020 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2008_to_gda94 | Transformation | ITRF2008 to GDA94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2005_to_gda94 | Transformation | ITRF2005 to GDA94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2000_to_gda94 | Transformation | ITRF2000 to GDA94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf97_to_gda94 | Transformation | ITRF97 to GDA94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf96_to_gda94 | Transformation | ITRF96 to GDA94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| agd84_to_gda94 | Transformation | AGD84 to GDA94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| agd66_to_gda94 | Transformation | AGD66 to GDA94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| agd66_to_gda94_act | Transformation | AGD66 to GDA94 (ACT region) | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| agd66_to_gda94_tas | Transformation | AGD66 to GDA94 (Tasmania region) | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| agd66_to_gda94_vicnsw | Transformation | AGD66 to GDA94 (Vic/NSW region) | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| agd66_to_gda94_nt | Transformation | AGD66 to GDA94 (NT region) | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf2014 | Transformation | ITRF2020 to ITRF2014 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf2014_vel | Transformation | ITRF2020 to ITRF2014 (velocity only) | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf2008 | Transformation | ITRF2020 to ITRF2008 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf2005 | Transformation | ITRF2020 to ITRF2005 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf2000 | Transformation | ITRF2020 to ITRF2000 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf97 | Transformation | ITRF2020 to ITRF97 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf96 | Transformation | ITRF2020 to ITRF96 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf94 | Transformation | ITRF2020 to ITRF94 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf93 | Transformation | ITRF2020 to ITRF93 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf92 | Transformation | ITRF2020 to ITRF92 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf91 | Transformation | ITRF2020 to ITRF91 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf90 | Transformation | ITRF2020 to ITRF90 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf89 | Transformation | ITRF2020 to ITRF89 | ++-----------------------------+---------------------+-------------------------------------------------------------+ +| itrf2020_to_itrf88 | Transformation | ITRF2020 to ITRF88 | ++-----------------------------+---------------------+-------------------------------------------------------------+ + + +All other combinations of ITRF transformations are available. + +IERS to GeodePy Transformation +------------------------------ + +GeeodePy also includes a function for converting from IERS transfomration parameters to a GeodePy Transformation object. + +.. autofunction:: geodepy.constants.iers2trans + +Height +--------- + +The module also contains locations for files commonly used in height converstions that will be used in the :ref:`height ` module. \ No newline at end of file diff --git a/docs/features/convert.rst b/docs/features/convert.rst new file mode 100644 index 0000000..4824b0d --- /dev/null +++ b/docs/features/convert.rst @@ -0,0 +1,30 @@ +.. _features/convert: + +Converting +=========== + +This module contains functions for converting between different coordinate types. Each of the functions can be seen below. + +.. autofunction:: geodepy.convert.polar2rect + +.. autofunction:: geodepy.convert.rect2polar + +.. autofunction:: geodepy.convert.rect_radius + +.. autofunction:: geodepy.convert.alpha_coeff + +.. autofunction:: geodepy.convert.beta_coeff + +.. autofunction:: geodepy.convert.psfandgridconv + +.. autofunction:: geodepy.convert.geo2grid + +.. autofunction:: geodepy.convert.grid2geo + +.. autofunction:: geodepy.convert.xyz2llh + +.. autofunction:: geodepy.convert.llh2xyz + +.. autofunction:: geodepy.convert.date_to_yyyydoy + +.. autofunction:: geodepy.convert.yyyydoy_to_date \ No newline at end of file diff --git a/docs/features/coord.rst b/docs/features/coord.rst new file mode 100644 index 0000000..9bfb112 --- /dev/null +++ b/docs/features/coord.rst @@ -0,0 +1,15 @@ +.. _features/coord: + +Coordinates +=========== + +This module contains classes for coordinates and functions for converting between different coordinate types. + +.. autoclass:: geodepy.coord.CoordCart + :members: + +.. autoclass:: geodepy.coord.CoordGeo + :members: + +.. autoclass:: geodepy.coord.CoordTM + :members: diff --git a/docs/features/geodesy.rst b/docs/features/geodesy.rst new file mode 100644 index 0000000..b37aa29 --- /dev/null +++ b/docs/features/geodesy.rst @@ -0,0 +1,34 @@ +.. _features/geodesy: + +Geodesy +========= + +This module includes functions for geodetic calculations. + +Convertions +------------ + +.. autofunction:: geodepy.geodesy.enu2xyz + +.. autofunction:: geodepy.geodesy.xyz2enu + +Vincenty +-------- + +.. autofunction:: geodepy.geodesy.vincdir + +.. autofunction:: geodepy.geodesy.vincinv + +.. autofunction:: geodepy.geodesy.vincdir_utm + +.. autofunction:: geodepy.geodesy.vincinv_utm + +Other Geodetic Functions +-------------------------- + +.. autofunction:: geodepy.geodesy.line_sf + +.. autofunction:: geodepy.geodesy.rho + +.. autofunction:: geodepy.geodesy.nu + diff --git a/docs/features/gnss.rst b/docs/features/gnss.rst new file mode 100644 index 0000000..d85f198 --- /dev/null +++ b/docs/features/gnss.rst @@ -0,0 +1,60 @@ +.. _features/gnss: + +Sinex +===== + +This module includes functions for dealing with sinex files. + +Reading Sinex Files +---------------------- + +.. autofunction:: geodepy.gnss.list_sinex_blocks +.. autofunction:: geodepy.gnss.read_sinex_comments +.. autofunction:: geodepy.gnss.read_sinex_header_line +.. autofunction:: geodepy.gnss.read_sinex_custom +.. autofunction:: geodepy.gnss.read_sinex_estimate +.. autofunction:: geodepy.gnss.read_sinex_matrix +.. autofunction:: geodepy.gnss.read_sinex_sites +.. autofunction:: geodepy.gnss.read_disconts +.. autofunction:: geodepy.gnss.read_solution_epochs +.. autofunction:: geodepy.gnss.read_sinex_header_block +.. autofunction:: geodepy.gnss.read_sinex_file_reference_block +.. autofunction:: geodepy.gnss.read_sinex_input_acknowledgments_block +.. autofunction:: geodepy.gnss.read_sinex_solution_statistics_block +.. autofunction:: geodepy.gnss.read_sinex_site_receiver_block +.. autofunction:: geodepy.gnss.read_sinex_site_antenna_block +.. autofunction:: geodepy.gnss.read_sinex_site_gps_phase_center_block +.. autofunction:: geodepy.gnss.read_sinex_site_eccentricity_block +.. autofunction:: geodepy.gnss.read_sinex_site_id_block +.. autofunction:: geodepy.gnss.read_sinex_solution_epochs_block +.. autofunction:: geodepy.gnss.read_sinex_solution_estimate_block +.. autofunction:: geodepy.gnss.read_sinex_solution_apriori_block +.. autofunction:: geodepy.gnss.read_sinex_solution_matrix_estimate_block +.. autofunction:: geodepy.gnss.read_sinex_solution_matrix_apriori_block + +Writing Sinex Files +-------------------- + +.. autofunction:: geodepy.gnss.print_sinex_comments +.. autofunction:: geodepy.gnss.set_creation_time +.. autofunction:: geodepy.gnss.dataframe2sinex_solution_estimate +.. autofunction:: geodepy.gnss.dataframe2sinex_solution_apriori +.. autofunction:: geodepy.gnss.dataframe2matrix_snx_vcv +.. autofunction:: geodepy.gnss.dataframe2matrix_solution_matrix_estimate +.. autofunction:: geodepy.gnss.dataframe2sinex_solution_matrix_estimate +.. autofunction:: geodepy.gnss.writeSINEX + +Converting Sinex Data to DataFrames +------------------------------------ + +.. autofunction:: geodepy.gnss.matrix2dataframe_solution_matrix_estimate +.. autofunction:: geodepy.gnss.sinex2dataframe_solution_estimate +.. autofunction:: geodepy.gnss.sinex2dataframe_solution_apriori +.. autofunction:: geodepy.gnss.sinex2dataframe_solution_matrix_estimate + +Specific Sinex Functions +-------------------------- + +.. autofunction:: geodepy.gnss.remove_stns_sinex +.. autofunction:: geodepy.gnss.remove_velocity_sinex +.. autofunction:: geodepy.gnss.remove_matrixzeros_sinex \ No newline at end of file diff --git a/docs/features/height.rst b/docs/features/height.rst new file mode 100644 index 0000000..a7887e7 --- /dev/null +++ b/docs/features/height.rst @@ -0,0 +1,46 @@ +.. _features/height: + +Vertical Datums +================= + +This module includes functions for dealing with height datums and geoids. + +.. note:: GDAL is needed for this module. Please consult the GDAL pypi for instructions on installing GDAL + +Converting Between Height Datums +-------------------------------- + +The following functions all convert between different height datums with a particular focus on Australian datums. + +.. autofunction:: geodepy.height.GPS_to_AHD +.. autofunction:: geodepy.height.AHD_to_GPS +.. autofunction:: geodepy.height.GPS_to_AVWS +.. autofunction:: geodepy.height.AVWS_to_GPS +.. autofunction:: geodepy.height.AHD_to_AVWS +.. autofunction:: geodepy.height.AVWS_to_AHD +.. autofunction:: geodepy.height.GPS_to_AUSGeoid98 +.. autofunction:: geodepy.height.AUSGeoid98_to_GPS +.. autofunction:: geodepy.height.GPS_to_AUSGeoid09 +.. autofunction:: geodepy.height.AUSGeoid09_to_GPS +.. autofunction:: geodepy.height.DOV +.. autofunction:: geodepy.height.DOV_09 +.. autofunction:: geodepy.height.DOV_98 + +Calculating Gravity +------------------- + +The functions below can be used to calculate different components of gravity. + +.. autofunction:: geodepy.height.mean_surface_grav +.. autofunction:: geodepy.height.interp_grav +.. autofunction:: geodepy.height.normal_grav +.. autofunction:: geodepy.height.mean_normal_grav +.. autofunction:: geodepy.height.normal_correction +.. autofunction:: geodepy.height.normal_orthometric_correction + +Auxilary Function +----------------- + +Functions used to enable other functions in the module. + +.. autofunction:: geodepy.height.interp_file \ No newline at end of file diff --git a/docs/features/index.rst b/docs/features/index.rst new file mode 100644 index 0000000..31c2e95 --- /dev/null +++ b/docs/features/index.rst @@ -0,0 +1,24 @@ +.. _features/index: + +Features +======== + +This section provides an overview of all features available in GeodePy, split into their respective modules. + +.. container:: collapsible-toc + + .. toctree:: + :maxdepth: 2 + + angles + constants + convert + coord + geodesy + gnss + height + inputoutput + ntv2reader + statistics + survey + transform \ No newline at end of file diff --git a/docs/features/inputoutput.rst b/docs/features/inputoutput.rst new file mode 100644 index 0000000..8586ef7 --- /dev/null +++ b/docs/features/inputoutput.rst @@ -0,0 +1,18 @@ +.. _features/inputoutput: + +Input and Output +================ + +This module has functions that operate as a backend for a GUI. The functions have been rewritten from the transform module using pandas. + +Converting +----------------------- + +.. autofunction:: geodepy.inputoutput.grid2geoio +.. autofunction:: geodepy.inputoutput.geo2gridio + + +Transformations +---------------- + +.. autofunction:: geodepy.inputoutput.gdatrans7 diff --git a/docs/features/ntv2reader.rst b/docs/features/ntv2reader.rst new file mode 100644 index 0000000..5caae8c --- /dev/null +++ b/docs/features/ntv2reader.rst @@ -0,0 +1,26 @@ +.. _features/ntv2reader: + +NTV2 Reader +=============== + +Tihs module provides functionality to read and utilize NTV2 grid files for coordinate transformations. +It has been adapted from Jaimie Dodd's ntv2reader.py + +Classes +------- + +.. autoclass:: geodepy.ntv2reader.NTv2Grid + :members: + +.. autoclass:: geodepy.ntv2reader.SubGrid + :members: + +Functions +--------- + +.. autofunction:: geodepy.ntv2reader.read_ntv2_file +.. autofunction:: geodepy.ntv2reader.interpolate_ntv2 +.. autofunction:: geodepy.ntv2reader.read_node +.. autofunction:: geodepy.ntv2reader.bilinear_interpolation +.. autofunction:: geodepy.ntv2reader.bicubic_interpolation + diff --git a/docs/features/statistics.rst b/docs/features/statistics.rst new file mode 100644 index 0000000..6eb6471 --- /dev/null +++ b/docs/features/statistics.rst @@ -0,0 +1,26 @@ +.. _features/statistics: + +Statistics +========== + +This module looks at calculating error, VCV matrix transformations, and rotation matrices. + +Error +------ + +.. autofunction:: geodepy.statistics.error_ellipse + +.. autofunction:: geodepy.statistics.relative_error +.. autofunction:: geodepy.statistics.circ_hz_pu +.. autofunction:: geodepy.statistics.k_val95 + +VCV Matrix Transformations +------------------------------- + +.. autofunction:: geodepy.statistics.vcv_cart2local +.. autofunction:: geodepy.statistics.vcv_local2cart + +Rotation Matrix +--------------- + +.. autofunction:: geodepy.statistics.rotation_matrix \ No newline at end of file diff --git a/docs/features/survey.rst b/docs/features/survey.rst new file mode 100644 index 0000000..1faab1e --- /dev/null +++ b/docs/features/survey.rst @@ -0,0 +1,26 @@ +.. _features/survey: + +Surveying Calculations +======================= + +This module provides various surveying calculation commonly used in geodetic and surveying applications. + +EDM Corrections +----------------- + +.. autofunction:: geodepy.survey.va_conv +.. autofunction:: geodepy.survey.first_vel_params +.. autofunction:: geodepy.survey.part_h2o_vap_press +.. autofunction:: geodepy.survey.first_vel_corrn +.. autofunction:: geodepy.survey.mets_partial_differentials +.. autofunction:: geodepy.survey.refractivity_constants +.. autofunction:: geodepy.survey.phase_refractivity +.. autofunction:: geodepy.survey.group_refractivity +.. autofunction:: geodepy.survey.humidity2part_water_vapour_press + + +Angle Calculations +------------------ + +.. autofunction:: geodepy.survey.joins +.. autofunction:: geodepy.survey.radiations \ No newline at end of file diff --git a/docs/features/transform.rst b/docs/features/transform.rst new file mode 100644 index 0000000..1be51df --- /dev/null +++ b/docs/features/transform.rst @@ -0,0 +1,27 @@ +.. _features/transform: + +Transforming +================= + +This module provides functions for transforming coordinates between different geodetic datums. It includes methods for performing Helmert transformations, functions for various +common geodetic transformations and support for NTv2 2D grid-based transformations. + +Helmert Transformations +----------------------- + +.. autofunction:: geodepy.transform.conform7 + +.. autofunction:: geodepy.transform.conform14 + +Common Geodetic Transformations +-------------------------------- + +.. autofunction:: geodepy.transform.transform_mga94_to_mga2020 +.. autofunction:: geodepy.transform.transform_mga2020_to_mga94 +.. autofunction:: geodepy.transform.transform_atrf2014_to_gda2020 +.. autofunction:: geodepy.transform.transform_gda2020_to_atrf2014 + +NTv2 Transformations +---------------------- + +.. autofunction:: geodepy.transform.ntv2_2d diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..c43071b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,70 @@ +GeodePy: Geodesy in Python +====================================================== + +Release v0.6.0 + +.. image:: https://static.pepy.tech/personalized-badge/geodepy?period=total&units=INTERNATIONAL_SYSTEM&left_color=GREY&right_color=BLUE&left_text=downloads + :target: https://pepy.tech/project/geodepy + :alt: Total Downloads Badge + +.. image:: https://img.shields.io/badge/license-Apache_2.0-green + :target: https://opensource.org/licenses/Apache-2.0 + :alt: License Badge + +.. image:: https://img.shields.io/badge/PyPi-GeodePy-yellow + :target: https://pypi.org/project/geodepy/ + :alt: Pypi Badge + +.. image:: https://img.shields.io/badge/Paper-GDA2020_Technical_Manual-556472 + :target: https://www.anzlic.gov.au/ICSM + :alt: Paper Badge + +**GeodePy** is a python package for precise geodetic and survey computations. + +------------------- + +Features +-------- + +GeodePy includes a variety of :ref:`features ` for geodesy and geospatial data manipulation, including: + +* :ref:`Converting ` between coordinate types +* :ref:`Transforming ` between datums +* Calculating :ref:`geodetic ` distances and bearings +* Working with :ref:`geoid ` models +* :ref:`Surveying ` calculations +* Various classes for :ref:`angles ` , :ref:`coordinates `, and :ref:`datums ` +* :ref:`Statistics ` +* And more! + +User Guide +-------------- + +This section contains the user guide for GeodePy, including installation, instructions, features and tutorials. + +.. toctree:: + :maxdepth: 2 + :caption: User Guide + + installation + quickstart + features/index + tutorials/index + +Community Guide +--------------- + +This section contains information for contributors to the GeodePy project. + +.. toctree:: + :maxdepth: 3 + :caption: Community Guide + + community/contributing + community/authors + +.. meta:: + :google-site-verification: i5FHdaluOAMrReuJqp3xes1IEXOpwgcMMBriCqpU0lE + :description lang=en: + GeodePy: A Python library for geodetic computations, coordinate transformations, + and surveying tools. Includes tutorials, installation guides, and examples for geospatial professionals. \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..e01bb72 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,64 @@ +.. _installation: + +Installation +============ + +This section contains instructions for installing GeodePy. + +Installing via pip +-------------------- +The recommended way to install GeodePy is via pip. You can do this by running the following command in terminal: + +.. code:: bash + + pip install geodepy + + +Installing from source +---------------------- +If you prefer to install GeodePy from source, you can clone the repository from GitHub and use the python files: + +.. code:: bash + + git clone https://github.com/GeoscienceAustralia/GeodePy.git + +.. _updating: + +Requirements +------------ + +GeodePy has some requirements that also need to be installed. Not all packages will be used for all modules. +The requirements can be seen below. + +.. code:: + + NumPy + SciPy + GDAL #only needed for height module + +GDAL is only used for the heights module and sometimes can be difficult to install. For more information on +installing GDAL see the `GDAL pypi `_ page. + +Updating GeodePy +----------------- +To update GeodePy to the latest version, you can use pip with the upgrade flag: + +.. code:: bash + + pip install --upgrade geodepy + +.. _testing: + +Testing GeodePy +----------------- + +GeodePy can be tested using the a unit test. This can be run as seen below. + +.. caution:: The directory may need to be changed to point to the correct test folder + +.. code:: python + + python3 -m unittest discover GeodePy/geodepy/tests --verbose + +All tests should complete successfully. If not you may need to install some required +packages. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..183c55a --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,52 @@ +Quickstart +========== + +Ready to get started? This page gives a good introduction to using GeodePy. + +First, ensure that: + +* GeodePy is :ref:`installed ` +* GeodePy is :ref:`up-to-date ` + +Let's get started with a simple example. + +Transforming from MGA94 to MGA2020 +----------------------------------- + +Begin by importing GeodePy: + +.. code:: python + + import geodepy.transform as tr + +Next, define some coordinate values in MGA94: + +.. code:: python + + zone_94 = 55 + east_94 = 696053.3373 + north_94 = 6086610.1338 + +Now, transform this coordinate to MGA2020: + +.. code:: python + + (zone_20, east_20, north_20, _, _) = tr.transform_mga94_to_mga2020( + zone_94, + east_94, + north_94 + ) + +Finally, print the transformed coordinate: + +.. code:: python + + print(zone_20, east_20, north_20) + +This will output the latitude, longitude, and height of the coordinate in MGA2020. + +.. code:: + + >> 55 696053.872 6086611.549 + +For more detailed examples and tutorials, please refer to the :ref:`features ` and :ref:`tutorial ` section. \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..3af9295 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,10 @@ +furo +pandas +sphinxext-opengraph +#gdal +numpy>=1.26 +wheel>=0.41 +setuptools>=67 +scipy>=1.11 +#gdal[numpy]=="$(gdal-config --version).*" +sphinx-copybutton \ No newline at end of file diff --git a/docs/tutorials/STR1AUSPOS.SNX b/docs/tutorials/STR1AUSPOS.SNX new file mode 100644 index 0000000..4902d0e --- /dev/null +++ b/docs/tutorials/STR1AUSPOS.SNX @@ -0,0 +1,650 @@ +%=SNX 2.01 XYZ 25:335:01280 IGS 25:333:00000 25:333:86370 P 00045 0 S +*------------------------------------------------------------------------------- ++FILE/REFERENCE +*INFO_TYPE_________ INFO________________________________________________________ + DESCRIPTION My agency/institute + OUTPUT One-session solution generated by RNX2SNX BPE + CONTACT My e-mail address + SOFTWARE Bernese GNSS Software Version 5.2 + HARDWARE My computer + INPUT IGS/IGLOS GNSS tracking data +-FILE/REFERENCE +*------------------------------------------------------------------------------- ++INPUT/ACKNOWLEDGMENTS +*AGY DESCRIPTION________________________________________________________________ + XYZ My agency/institute and its address + IGS International GNSS Service +-INPUT/ACKNOWLEDGMENTS +*------------------------------------------------------------------------------- ++SOLUTION/STATISTICS +*_STATISTICAL PARAMETER________ __VALUE(S)____________ + NUMBER OF OBSERVATIONS 54963 + NUMBER OF UNKNOWNS 460 + NUMBER OF DEGREES OF FREEDOM 54503 + PHASE MEASUREMENTS SIGMA 0.00100 + SAMPLING INTERVAL (SECONDS) 180 + VARIANCE FACTOR 2.542769992487420 +-SOLUTION/STATISTICS +*------------------------------------------------------------------------------- ++SITE/ID +*CODE PT __DOMES__ T _STATION DESCRIPTION__ APPROX_LON_ APPROX_LAT_ _APP_H_ + ALIC A 50137M001 P ALIC 50137M001 133 53 7.9 -23 40 12.4 603.2 + BRDW A AUM000200 P BRDW AUM000200 149 47 6.7 -35 26 47.3 679.5 + CEDU A 50138M001 P CEDU 50138M001 133 48 35.4 -31 51 60.0 144.7 + CNWD A AUM000464 P CNWD AUM000464 149 1 58.0 -35 12 23.0 593.5 + GNGN A AUM000415 P GNGN AUM000415 149 7 49.9 -35 11 5.8 645.1 + HOB2 A 50116M004 P HOB2 50116M004 147 26 19.5 -42 48 16.9 41.0 + MCHL A 59905M001 P MCHL 59905M001 148 8 41.9 -26 21 32.0 534.6 + MOBS A 50182M001 P MOBS 50182M001 144 58 31.2 -37 49 45.8 40.6 + PRCE A AUM000318 P PRCE AUM000318 149 5 20.4 -35 21 48.8 639.7 + STR1 A 50119M002 P STR1 50119M002 149 0 36.2 -35 18 55.9 799.9 + STR2 A 50119M001 P STR2 50119M001 149 0 36.6 -35 18 58.1 802.5 + SYM1 A 59899M001 P SYM1 59899M001 149 9 39.8 -35 20 33.0 592.2 + TID1 A 50103M108 P TID1 50103M108 148 58 48.0 -35 23 57.1 665.3 + TOW2 A 50140M001 P TOW2 50140M001 147 3 20.5 -19 16 9.4 88.1 + WLMD A AUM000483 P WLMD AUM000483 149 8 15.8 -35 35 40.3 850.2 +-SITE/ID +*------------------------------------------------------------------------------- ++SITE/RECEIVER +*SITE PT SOLN T DATA_START__ DATA_END____ DESCRIPTION_________ S/N__ FIRMWARE___ + ALIC A 1 P 25:333:00000 25:333:86370 SEPT POLARX5 ----- ----------- + BRDW A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- + CEDU A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- + CNWD A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- + GNGN A 1 P 25:333:00000 25:333:86370 LEICA GR30 ----- ----------- + HOB2 A 1 P 25:333:00000 25:333:86370 SEPT POLARX5 ----- ----------- + MCHL A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- + MOBS A 1 P 25:333:00000 25:333:86370 SEPT POLARX5 ----- ----------- + PRCE A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- + STR1 A 1 P 25:333:00000 25:333:86370 SEPT POLARX5 ----- ----------- + STR2 A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- + SYM1 A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- + TID1 A 1 P 25:333:00000 25:333:86370 SEPT POLARX5 ----- ----------- + TOW2 A 1 P 25:333:00000 25:333:86370 SEPT POLARX5 ----- ----------- + WLMD A 1 P 25:333:00000 25:333:86370 TRIMBLE ALLOY ----- ----------- +-SITE/RECEIVER +*------------------------------------------------------------------------------- ++SITE/ANTENNA +*SITE PT SOLN T DATA_START__ DATA_END____ DESCRIPTION_________ S/N__ + ALIC A 1 P 25:333:00000 25:333:86370 TWIVC6050 NONE ----- + BRDW A 1 P 25:333:00000 25:333:86370 LEIAR10 NONE ----- + CEDU A 1 P 25:333:00000 25:333:86370 TWIVC6050 NONE ----- + CNWD A 1 P 25:333:00000 25:333:86370 TRM57971.00 NONE ----- + GNGN A 1 P 25:333:00000 25:333:86370 LEIAR25.R4 LEIT ----- + HOB2 A 1 P 25:333:00000 25:333:86370 LEIAR25.R4 NONE ----- + MCHL A 1 P 25:333:00000 25:333:86370 TRM59800.00 NONE ----- + MOBS A 1 P 25:333:00000 25:333:86370 JAVRINGANT_DM NONE ----- + PRCE A 1 P 25:333:00000 25:333:86370 TRM59800.00 NONE ----- + STR1 A 1 P 25:333:00000 25:333:86370 ASH701945C_M NONE ----- + STR2 A 1 P 25:333:00000 25:333:86370 LEIAR25.R3 NONE ----- + SYM1 A 1 P 25:333:00000 25:333:86370 TRM59800.00 NONE ----- + TID1 A 1 P 25:333:00000 25:333:86370 AOAD/M_T NONE ----- + TOW2 A 1 P 25:333:00000 25:333:86370 LEIAR25.R3 NONE ----- + WLMD A 1 P 25:333:00000 25:333:86370 TRM57971.00 NONE ----- +-SITE/ANTENNA +*------------------------------------------------------------------------------- ++SITE/GPS_PHASE_CENTER +* UP____ NORTH_ EAST__ UP____ NORTH_ EAST__ +*DESCRIPTION_________ S/N__ L1->ARP(M)__________ L2->ARP(M)__________ + AOAD/M_T NONE ----- 0.0918 0.0007 -.0005 0.1203 -.0003 -.0007 IGS20_2226 + ASH701945C_M NONE ----- 0.0909 0.0001 0.0005 0.1176 -.0003 0.0001 IGS20_2226 + JAVRINGANT_DM NONE ----- 0.0901 0.0001 0.0013 0.1178 -.0002 -.0001 IGS20_2226 + LEIAR10 NONE ----- 0.0875 0.0003 -.0000 0.0829 -.0001 -.0003 IGS20_2226 + LEIAR25.R3 NONE ----- 0.1606 0.0008 0.0006 0.1587 -.0001 0.0001 IGS20_2226 + LEIAR25.R4 LEIT ----- 0.1590 0.0007 0.0012 0.1550 0.0001 -.0000 IGS20_2226 + LEIAR25.R4 NONE ----- 0.1596 0.0006 0.0010 0.1561 0.0008 0.0002 IGS20_2226 + TRM57971.00 NONE ----- 0.0642 0.0008 0.0003 0.0583 -.0001 0.0011 IGS20_2226 + TRM59800.00 NONE ----- 0.0893 0.0006 0.0014 0.1178 -.0002 0.0000 IGS20_2226 + TWIVC6050 NONE ----- 0.1277 0.0009 0.0011 0.1377 0.0001 0.0001 IGS20_2226 +-SITE/GPS_PHASE_CENTER +*------------------------------------------------------------------------------- ++SITE/ECCENTRICITY +* UP______ NORTH___ EAST____ +*SITE PT SOLN T DATA_START__ DATA_END____ AXE ARP->BENCHMARK(M)_________ + ALIC A 1 P 25:333:00000 25:333:86370 UNE 0.0250 0.0000 0.0000 + BRDW A 1 P 25:333:00000 25:333:86370 UNE 0.0000 0.0000 0.0000 + CEDU A 1 P 25:333:00000 25:333:86370 UNE 0.0250 0.0000 0.0000 + CNWD A 1 P 25:333:00000 25:333:86370 UNE 0.0000 0.0000 0.0000 + GNGN A 1 P 25:333:00000 25:333:86370 UNE 0.0000 0.0000 0.0000 + HOB2 A 1 P 25:333:00000 25:333:86370 UNE 0.0250 0.0000 0.0000 + MCHL A 1 P 25:333:00000 25:333:86370 UNE 0.0000 0.0000 0.0000 + MOBS A 1 P 25:333:00000 25:333:86370 UNE 0.0021 0.0000 0.0000 + PRCE A 1 P 25:333:00000 25:333:86370 UNE 0.0000 0.0000 0.0000 + STR1 A 1 P 25:333:00000 25:333:86370 UNE 0.0040 0.0000 0.0000 + STR2 A 1 P 25:333:00000 25:333:86370 UNE 0.0000 0.0000 0.0000 + SYM1 A 1 P 25:333:00000 25:333:86370 UNE 0.0500 0.0000 0.0000 + TID1 A 1 P 25:333:00000 25:333:86370 UNE 0.0614 0.0000 0.0000 + TOW2 A 1 P 25:333:00000 25:333:86370 UNE 0.0033 0.0000 0.0000 + WLMD A 1 P 25:333:00000 25:333:86370 UNE 0.0000 0.0000 0.0000 +-SITE/ECCENTRICITY +*------------------------------------------------------------------------------- ++SOLUTION/EPOCHS +*CODE PT SOLN T _DATA_START_ __DATA_END__ _MEAN_EPOCH_ + ALIC A 1 P 25:333:00000 25:333:86370 25:333:43185 + BRDW A 1 P 25:333:00000 25:333:86370 25:333:43185 + CEDU A 1 P 25:333:00000 25:333:86370 25:333:43185 + CNWD A 1 P 25:333:00000 25:333:86370 25:333:43185 + GNGN A 1 P 25:333:00000 25:333:86370 25:333:43185 + HOB2 A 1 P 25:333:00000 25:333:86370 25:333:43185 + MCHL A 1 P 25:333:00000 25:333:86370 25:333:43185 + MOBS A 1 P 25:333:00000 25:333:86370 25:333:43185 + PRCE A 1 P 25:333:00000 25:333:86370 25:333:43185 + STR1 A 1 P 25:333:00000 25:333:86370 25:333:43185 + STR2 A 1 P 25:333:00000 25:333:86370 25:333:43185 + SYM1 A 1 P 25:333:00000 25:333:86370 25:333:43185 + TID1 A 1 P 25:333:00000 25:333:86370 25:333:43185 + TOW2 A 1 P 25:333:00000 25:333:86370 25:333:43185 + WLMD A 1 P 25:333:00000 25:333:86370 25:333:43185 +-SOLUTION/EPOCHS +*------------------------------------------------------------------------------- ++SOLUTION/ESTIMATE +*INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __ESTIMATED VALUE____ _STD_DEV___ + 1 STAX ALIC A 1 25:333:43200 m 0 -.405205296884358E+07 .135326E-02 + 2 STAY ALIC A 1 25:333:43200 m 0 0.421283595074131E+07 .127519E-02 + 3 STAZ ALIC A 1 25:333:43200 m 0 -.254510426632942E+07 .109485E-02 + 4 STAX BRDW A 1 25:333:43200 m 1 -.449563574371494E+07 .147360E-02 + 5 STAY BRDW A 1 25:333:43200 m 1 0.261807870995070E+07 .107203E-02 + 6 STAZ BRDW A 1 25:333:43200 m 1 -.367872621627262E+07 .118932E-02 + 7 STAX CEDU A 1 25:333:43200 m 0 -.375347344765168E+07 .123981E-02 + 8 STAY CEDU A 1 25:333:43200 m 0 0.391274104154810E+07 .112093E-02 + 9 STAZ CEDU A 1 25:333:43200 m 0 -.334795939837226E+07 .104734E-02 + 10 STAX CNWD A 1 25:333:43200 m 1 -.447401704941117E+07 .135356E-02 + 11 STAY CNWD A 1 25:333:43200 m 1 0.268477936812352E+07 .102041E-02 + 12 STAZ CNWD A 1 25:333:43200 m 1 -.365694052024367E+07 .112350E-02 + 13 STAX GNGN A 1 25:333:43200 m 1 -.447980388862464E+07 .140262E-02 + 14 STAY GNGN A 1 25:333:43200 m 1 0.267786547952707E+07 .104980E-02 + 15 STAZ GNGN A 1 25:333:43200 m 1 -.365502795992759E+07 .117064E-02 + 16 STAX HOB2 A 1 25:333:43200 m 0 -.395007248507361E+07 .127720E-02 + 17 STAY HOB2 A 1 25:333:43200 m 0 0.252241541108797E+07 .973794E-03 + 18 STAZ HOB2 A 1 25:333:43200 m 0 -.431163715891603E+07 .117631E-02 + 19 STAX MCHL A 1 25:333:43200 m 0 -.485785914335216E+07 .129832E-02 + 20 STAY MCHL A 1 25:333:43200 m 0 0.301846433108235E+07 .985368E-03 + 21 STAZ MCHL A 1 25:333:43200 m 0 -.281498294035595E+07 .100019E-02 + 22 STAX MOBS A 1 25:333:43200 m 0 -.413063698909842E+07 .124569E-02 + 23 STAY MOBS A 1 25:333:43200 m 0 0.289495316638602E+07 .971112E-03 + 24 STAZ MOBS A 1 25:333:43200 m 0 -.389052997068141E+07 .108642E-02 + 25 STAX PRCE A 1 25:333:43200 m 1 -.446803833535855E+07 .138803E-02 + 26 STAY PRCE A 1 25:333:43200 m 1 0.267523089794727E+07 .103112E-02 + 27 STAZ PRCE A 1 25:333:43200 m 1 -.367120425346500E+07 .114553E-02 + 28 STAX STR1 A 1 25:333:43200 m 2 -.446710341345650E+07 .138818E-02 + 29 STAY STR1 A 1 25:333:43200 m 2 0.268303948291627E+07 .104936E-02 + 30 STAZ STR1 A 1 25:333:43200 m 2 -.366694848486371E+07 .114659E-02 + 31 STAX STR2 A 1 25:333:43200 m 1 -.446707546604151E+07 .134927E-02 + 32 STAY STR2 A 1 25:333:43200 m 1 0.268301185689456E+07 .101992E-02 + 33 STAZ STR2 A 1 25:333:43200 m 1 -.366700678395247E+07 .112272E-02 + 34 STAX SYM1 A 1 25:333:43200 m 1 -.447252743133314E+07 .140385E-02 + 35 STAY SYM1 A 1 25:333:43200 m 1 0.267028240895937E+07 .104634E-02 + 36 STAZ SYM1 A 1 25:333:43200 m 1 -.366927072310628E+07 .115919E-02 + 37 STAX TID1 A 1 25:333:43200 m 0 -.446099717658782E+07 .124009E-02 + 38 STAY TID1 A 1 25:333:43200 m 0 0.268255708796363E+07 .956070E-03 + 39 STAZ TID1 A 1 25:333:43200 m 0 -.367444236821582E+07 .105884E-02 + 40 STAX TOW2 A 1 25:333:43200 m 0 -.505458359889967E+07 .147113E-02 + 41 STAY TOW2 A 1 25:333:43200 m 0 0.327550403797471E+07 .107361E-02 + 42 STAZ TOW2 A 1 25:333:43200 m 0 -.209153816250262E+07 .104290E-02 + 43 STAX WLMD A 1 25:333:43200 m 1 -.445768965020828E+07 .137286E-02 + 44 STAY WLMD A 1 25:333:43200 m 1 0.266388829154876E+07 .103283E-02 + 45 STAZ WLMD A 1 25:333:43200 m 1 -.369219679352788E+07 .113982E-02 +-SOLUTION/ESTIMATE +*------------------------------------------------------------------------------- ++SOLUTION/APRIORI +*INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __APRIORI VALUE______ _STD_DEV___ + 1 STAX ALIC A 1 25:333:43200 m 0 -.405205297112000E+07 .148623E-02 + 2 STAY ALIC A 1 25:333:43200 m 0 0.421283595405000E+07 .151885E-02 + 3 STAZ ALIC A 1 25:333:43200 m 0 -.254510426863000E+07 .122116E-02 + 4 STAX BRDW A 1 25:333:43200 m 1 -.449563574610000E+07 .473082E-02 + 5 STAY BRDW A 1 25:333:43200 m 1 0.261807870828000E+07 .367577E-02 + 6 STAZ BRDW A 1 25:333:43200 m 1 -.367872621613000E+07 .425528E-02 + 7 STAX CEDU A 1 25:333:43200 m 0 -.375347345022000E+07 .142683E-02 + 8 STAY CEDU A 1 25:333:43200 m 0 0.391274104234000E+07 .145806E-02 + 9 STAZ CEDU A 1 25:333:43200 m 0 -.334795939994000E+07 .135665E-02 + 10 STAX CNWD A 1 25:333:43200 m 1 -.447401704700000E+07 .471727E-02 + 11 STAY CNWD A 1 25:333:43200 m 1 0.268477937157000E+07 .370766E-02 + 12 STAZ CNWD A 1 25:333:43200 m 1 -.365694051946000E+07 .424282E-02 + 13 STAX GNGN A 1 25:333:43200 m 1 -.447980389408000E+07 .472090E-02 + 14 STAY GNGN A 1 25:333:43200 m 1 0.267786547836000E+07 .370433E-02 + 15 STAZ GNGN A 1 25:333:43200 m 1 -.365502796241000E+07 .424170E-02 + 16 STAX HOB2 A 1 25:333:43200 m 0 -.395007248535000E+07 .146480E-02 + 17 STAY HOB2 A 1 25:333:43200 m 0 0.252241541326000E+07 .120976E-02 + 18 STAZ HOB2 A 1 25:333:43200 m 0 -.431163716084000E+07 .154458E-02 + 19 STAX MCHL A 1 25:333:43200 m 0 -.485785914175000E+07 .165473E-02 + 20 STAY MCHL A 1 25:333:43200 m 0 0.301846432680000E+07 .129207E-02 + 21 STAZ MCHL A 1 25:333:43200 m 0 -.281498293888000E+07 .126411E-02 + 22 STAX MOBS A 1 25:333:43200 m 0 -.413063698823000E+07 .150142E-02 + 23 STAY MOBS A 1 25:333:43200 m 0 0.289495316855000E+07 .127025E-02 + 24 STAZ MOBS A 1 25:333:43200 m 0 -.389052997090000E+07 .145964E-02 + 25 STAX PRCE A 1 25:333:43200 m 1 -.446803833668000E+07 .471346E-02 + 26 STAY PRCE A 1 25:333:43200 m 1 0.267523090147000E+07 .370302E-02 + 27 STAZ PRCE A 1 25:333:43200 m 1 -.367120425559000E+07 .425098E-02 + 28 STAX STR1 A 1 25:333:43200 m 2 -.446710340998000E+07 .316228E+01 + 29 STAY STR1 A 1 25:333:43200 m 2 0.268303948540000E+07 .316228E+01 + 30 STAZ STR1 A 1 25:333:43200 m 2 -.366694848335000E+07 .316228E+01 + 31 STAX STR2 A 1 25:333:43200 m 1 -.446707546436000E+07 .471279E-02 + 32 STAY STR2 A 1 25:333:43200 m 1 0.268301186190000E+07 .370674E-02 + 33 STAZ STR2 A 1 25:333:43200 m 1 -.366700678492000E+07 .424852E-02 + 34 STAX SYM1 A 1 25:333:43200 m 1 -.447252743479000E+07 .471631E-02 + 35 STAY SYM1 A 1 25:333:43200 m 1 0.267028240809000E+07 .370066E-02 + 36 STAZ SYM1 A 1 25:333:43200 m 1 -.366927072527000E+07 .424988E-02 + 37 STAX TID1 A 1 25:333:43200 m 0 -.446099717582000E+07 .156967E-02 + 38 STAY TID1 A 1 25:333:43200 m 0 0.268255708801000E+07 .123551E-02 + 39 STAZ TID1 A 1 25:333:43200 m 0 -.367444236933000E+07 .141761E-02 + 40 STAX TOW2 A 1 25:333:43200 m 0 -.505458359421000E+07 .169796E-02 + 41 STAY TOW2 A 1 25:333:43200 m 0 0.327550403371000E+07 .133788E-02 + 42 STAZ TOW2 A 1 25:333:43200 m 0 -.209153815859000E+07 .115592E-02 + 43 STAX WLMD A 1 25:333:43200 m 1 -.445768965324000E+07 .470682E-02 + 44 STAY WLMD A 1 25:333:43200 m 1 0.266388829281000E+07 .369749E-02 + 45 STAZ WLMD A 1 25:333:43200 m 1 -.369219679510000E+07 .426297E-02 +-SOLUTION/APRIORI +*------------------------------------------------------------------------------- ++SOLUTION/MATRIX_ESTIMATE L COVA +*PARA1 PARA2 ____PARA2+0__________ ____PARA2+1__________ ____PARA2+2__________ + 1 1 0.18313251758458E-05 + 2 1 -0.12446803211099E-05 0.16261047203566E-05 + 3 1 0.99041950765541E-06 -0.88439735938875E-06 0.11986899802161E-05 + 4 1 0.60720169666580E-06 -0.11178206490719E-06 0.24481898967766E-06 + 4 4 0.21714964468366E-05 + 5 1 -0.25058797539008E-06 0.42263910365637E-06 -0.20998826836413E-06 + 5 4 -0.10883472895099E-05 0.11492441729680E-05 + 6 1 0.20197951135204E-06 -0.68533332432339E-07 0.49482435325907E-06 + 6 4 0.12321374675753E-05 -0.77650487428426E-06 0.14144842646653E-05 + 7 1 0.71497476213081E-06 -0.23624964267895E-06 0.34131076231423E-06 + 7 4 0.66835078114721E-06 -0.29883070274025E-06 0.27208048865004E-06 + 7 7 0.15371256431748E-05 + 8 1 -0.26541862633228E-06 0.48604152430863E-06 -0.21830545983121E-06 + 8 4 -0.17419673934612E-06 0.47950000484479E-06 -0.14045970583046E-06 + 8 7 -0.93531779181409E-06 0.12564880121829E-05 + 9 1 0.29509800588011E-06 -0.16854853897712E-06 0.56186488364487E-06 + 9 4 0.25045807642912E-06 -0.21647646139218E-06 0.52699243767530E-06 + 9 7 0.83922743899257E-06 -0.71550217999144E-06 0.10969166101051E-05 + 10 1 0.61345380122862E-06 -0.11291981105910E-06 0.25159532081622E-06 + 10 4 0.85738018572355E-06 -0.38866418648227E-06 0.38397938710730E-06 + 10 7 0.68731007875196E-06 -0.18459512711256E-06 0.26176263134490E-06 + 10 10 0.18321129635728E-05 + 11 1 -0.25057306044180E-06 0.42188088826040E-06 -0.21109685694509E-06 + 11 4 -0.38263830593942E-06 0.62732947596038E-06 -0.28264758389844E-06 + 11 7 -0.30499172482855E-06 0.48317433968276E-06 -0.21976741576961E-06 + 11 10 -0.90998444287501E-06 0.10412457998900E-05 + 12 1 0.20739882552800E-06 -0.70620063735542E-07 0.50096376763951E-06 + 12 4 0.38536509528030E-06 -0.28782192937367E-06 0.63933918928962E-06 + 12 7 0.28386545020017E-06 -0.14741378042849E-06 0.53523393433917E-06 + 12 10 0.10243317304501E-05 -0.66671344683614E-06 0.12622464450380E-05 + 13 1 0.61119707715655E-06 -0.11194249443855E-06 0.24906553444454E-06 + 13 4 0.85660531184486E-06 -0.38718565687625E-06 0.38336909161503E-06 + 13 7 0.68563872583001E-06 -0.18387969973921E-06 0.25978521119402E-06 + 13 10 0.88538309122330E-06 -0.40360486309802E-06 0.40407127287227E-06 + 13 13 0.19673463922330E-05 + 14 1 -0.24984500609563E-06 0.42144517577557E-06 -0.20992902663074E-06 + 14 4 -0.38197256780984E-06 0.62612010189745E-06 -0.28217219508997E-06 + 14 7 -0.30478836541130E-06 0.48311673362465E-06 -0.21914066067576E-06 + 14 10 -0.40416591001933E-06 0.64134872930104E-06 -0.29966653280293E-06 + 14 13 -0.98855048324643E-06 0.11020866526624E-05 + 15 1 0.20518479856570E-06 -0.69141898872116E-07 0.49781598394603E-06 + 15 4 0.38520935132402E-06 -0.28695725847490E-06 0.63878937086241E-06 + 15 7 0.28248122201738E-06 -0.14640587691482E-06 0.53277608559143E-06 + 15 10 0.40455138589538E-06 -0.29960838238104E-06 0.65718951370136E-06 + 15 13 0.11292334192108E-05 -0.72839178992815E-06 0.13704017863535E-05 + 16 1 0.51270672677878E-06 -0.91216495537073E-07 0.19913799548431E-06 + 16 4 0.73103731686482E-06 -0.30339164489098E-06 0.32782586429052E-06 + 16 7 0.55845086155457E-06 -0.13704363105769E-06 0.20631279626835E-06 + 16 10 0.73358419480137E-06 -0.30355881294131E-06 0.32784666934807E-06 + 16 13 0.73187189767831E-06 -0.30233784240710E-06 0.32734375906355E-06 + 16 16 0.16312483040586E-05 + 17 1 -0.19054569014535E-06 0.39305903341189E-06 -0.17472108929078E-06 + 17 4 -0.30387257214266E-06 0.55358336390790E-06 -0.23976248571624E-06 + 17 7 -0.22526450373053E-06 0.43639827735975E-06 -0.18045948640935E-06 + 17 10 -0.30853271662851E-06 0.55578016887369E-06 -0.24219141048312E-06 + 17 13 -0.30637380274027E-06 0.55410232005899E-06 -0.24102759908556E-06 + 17 16 -0.80143663733217E-06 0.94827526147262E-06 + 18 1 0.11204557290389E-06 -0.51929163420445E-07 0.41306904102127E-06 + 18 4 0.26314498854697E-06 -0.19116492348676E-06 0.54845092472007E-06 + 18 7 0.14727537628843E-06 -0.91957646650555E-07 0.44021045841429E-06 + 18 10 0.25623276488765E-06 -0.18672425112690E-06 0.54314835527857E-06 + 18 13 0.25568724098637E-06 -0.18630492042342E-06 0.54292939453546E-06 + 18 16 0.10052480204109E-05 -0.65221359934128E-06 0.13837146663820E-05 + 19 1 0.51459867734714E-06 -0.72888302866032E-07 0.16771447541041E-06 + 19 4 0.69983899191141E-06 -0.28852740720020E-06 0.25942604130010E-06 + 19 7 0.56712320727291E-06 -0.12493495444105E-06 0.16827117243447E-06 + 19 10 0.69872928301337E-06 -0.28553320959086E-06 0.26039885582955E-06 + 19 13 0.69750105895292E-06 -0.28519409355163E-06 0.25944140646805E-06 + 19 16 0.56646062901514E-06 -0.20458671682632E-06 0.12762127243502E-06 + 19 19 0.16856376760474E-05 + 20 1 -0.19404558835678E-06 0.38469016913097E-06 -0.15526870419990E-06 + 20 4 -0.28405124009569E-06 0.54616407955237E-06 -0.19954428627428E-06 + 20 7 -0.23845702451555E-06 0.43527684037446E-06 -0.15988359517757E-06 + 20 10 -0.28864394477901E-06 0.54733766674288E-06 -0.20375462320482E-06 + 20 13 -0.28732364499386E-06 0.54665326205736E-06 -0.20280595273798E-06 + 20 16 -0.20022833914486E-06 0.47277362948981E-06 -0.10012665072539E-06 + 20 19 -0.82715506997585E-06 0.97095083257411E-06 + 21 1 0.17571444797785E-06 -0.47248966737353E-07 0.45324042307734E-06 + 21 4 0.32238049849596E-06 -0.24958309014591E-06 0.55998852325341E-06 + 21 7 0.25564161830099E-06 -0.12695544303688E-06 0.48543373508658E-06 + 21 10 0.32733783547082E-06 -0.25053500683282E-06 0.56500481297682E-06 + 21 13 0.32555571981887E-06 -0.24977811102349E-06 0.56318741062449E-06 + 21 16 0.24642912333361E-06 -0.19309064727718E-06 0.43919477000515E-06 + 21 19 0.81973066514025E-06 -0.54785932877988E-06 0.10003799910012E-05 + 22 1 0.53360243538236E-06 -0.96179092191242E-07 0.21001514589377E-06 + 22 4 0.73820246786459E-06 -0.32290955952207E-06 0.32129931868500E-06 + 22 7 0.59903802447066E-06 -0.15916950697156E-06 0.22147859066750E-06 + 22 10 0.74892294312561E-06 -0.32654706527455E-06 0.32691688069770E-06 + 22 13 0.74616399970220E-06 -0.32519728008428E-06 0.32556765305011E-06 + 22 16 0.64597979354267E-06 -0.26315853017503E-06 0.22681957381330E-06 + 22 19 0.59668352203105E-06 -0.23719150839373E-06 0.26962830223750E-06 + 22 22 0.15517483174710E-05 + 23 1 -0.19603802230762E-06 0.39488193295443E-06 -0.17407686697139E-06 + 23 4 -0.29979929153184E-06 0.56003014283891E-06 -0.22887891129980E-06 + 23 7 -0.24134282312733E-06 0.44670807463634E-06 -0.18192514665973E-06 + 23 10 -0.30807070254904E-06 0.56370644777785E-06 -0.23381480515370E-06 + 23 13 -0.30561443240693E-06 0.56208613283909E-06 -0.23237750855558E-06 + 23 16 -0.24428775268408E-06 0.50592520054783E-06 -0.16037357660951E-06 + 23 19 -0.21552237647443E-06 0.49094299243455E-06 -0.19956221479939E-06 + 23 22 -0.76882104748867E-06 0.94305845028362E-06 + 24 1 0.14630721180271E-06 -0.55695281989907E-07 0.44110178228647E-06 + 24 4 0.28948405967271E-06 -0.22436279066026E-06 0.55879100054770E-06 + 24 7 0.20912718014069E-06 -0.11972131362147E-06 0.47375545833730E-06 + 24 10 0.29263217043843E-06 -0.22446090181980E-06 0.56045054263397E-06 + 24 13 0.29080534352571E-06 -0.22363786525505E-06 0.55912053455459E-06 + 24 16 0.26198419709407E-06 -0.20009667298434E-06 0.50094970320718E-06 + 24 19 0.17742662461395E-06 -0.15064781747110E-06 0.48531588256123E-06 + 24 22 0.87910999473376E-06 -0.59755425593137E-06 0.11803125717319E-05 + 25 1 0.61087028117632E-06 -0.11278783312926E-06 0.24922663002009E-06 + 25 4 0.85995561655863E-06 -0.38919743168048E-06 0.38587705689173E-06 + 25 7 0.68149013332661E-06 -0.18205913513573E-06 0.25861876494583E-06 + 25 10 0.86580037507735E-06 -0.39074368414182E-06 0.38930319417052E-06 + 25 13 0.86291517919348E-06 -0.38924827860869E-06 0.38776983886197E-06 + 25 16 0.73370776082288E-06 -0.30861240841231E-06 0.25969305466211E-06 + 25 19 0.69772657618284E-06 -0.28716312075340E-06 0.32556325251567E-06 + 25 22 0.74710328735023E-06 -0.30706747891770E-06 0.29315870611801E-06 + 25 25 0.19266237882635E-05 + 26 1 -0.24995634708332E-06 0.42216378519264E-06 -0.21050264583875E-06 + 26 4 -0.38416098166572E-06 0.62747090618807E-06 -0.28366609170589E-06 + 26 7 -0.30272775165356E-06 0.48218268843361E-06 -0.21869623214845E-06 + 26 10 -0.39147457234048E-06 0.63075066732744E-06 -0.28835525874365E-06 + 26 13 -0.38896456124344E-06 0.62910655277581E-06 -0.28687538339299E-06 + 26 16 -0.30433658338010E-06 0.55608767832745E-06 -0.18910322120149E-06 + 26 19 -0.28484529140508E-06 0.54613394746688E-06 -0.24926214816263E-06 + 26 22 -0.32596190960975E-06 0.56322276112811E-06 -0.22499249044697E-06 + 26 25 -0.95046895272469E-06 0.10632032992476E-05 + 27 1 0.20539899546885E-06 -0.70455000292755E-07 0.49892126308725E-06 + 27 4 0.38613941916832E-06 -0.28739273013885E-06 0.63977318601760E-06 + 27 7 0.27998179889144E-06 -0.14568356520510E-06 0.53277753155349E-06 + 27 10 0.38797442796935E-06 -0.28693037826808E-06 0.64140290099010E-06 + 27 13 0.38597938564757E-06 -0.28597590514561E-06 0.63984908315011E-06 + 27 16 0.32813667900211E-06 -0.24210818052691E-06 0.54574373015433E-06 + 27 19 0.25843656644089E-06 -0.20169147576488E-06 0.56229599605714E-06 + 27 22 0.32564461219451E-06 -0.23284037679303E-06 0.56070519959572E-06 + 27 25 0.10856096033544E-05 -0.69604111478899E-06 0.13122441669521E-05 + 28 1 0.62625029713934E-06 -0.11938027876830E-06 0.25954822264859E-06 + 28 4 0.87484136991102E-06 -0.40020377473070E-06 0.39510211065639E-06 + 28 7 0.70101338980675E-06 -0.19216454794661E-06 0.26992750618594E-06 + 28 10 0.88114095719463E-06 -0.40041342242934E-06 0.39855037347202E-06 + 28 13 0.87840254829281E-06 -0.39909548841108E-06 0.39684408645720E-06 + 28 16 0.74602064193630E-06 -0.31614375969171E-06 0.26379354784865E-06 + 28 19 0.71126816171768E-06 -0.29652025354038E-06 0.33498446361632E-06 + 28 22 0.76186998382837E-06 -0.31561013342106E-06 0.30063546033788E-06 + 28 25 0.87963819985251E-06 -0.40027591379396E-06 0.39713576664712E-06 + 28 28 0.19270486454271E-05 + 29 1 -0.25806553977530E-06 0.42771414729055E-06 -0.21629791999446E-06 + 29 4 -0.39308490722542E-06 0.63630472398426E-06 -0.28988276836872E-06 + 29 7 -0.31281157776953E-06 0.48957220389982E-06 -0.22502022657722E-06 + 29 10 -0.39938709341783E-06 0.63806251176995E-06 -0.29401306825644E-06 + 29 13 -0.39700764636298E-06 0.63653286554115E-06 -0.29239638440942E-06 + 29 16 -0.31023743035612E-06 0.56184567335894E-06 -0.19153471779969E-06 + 29 19 -0.29252972709343E-06 0.55357059104480E-06 -0.25507674091522E-06 + 29 22 -0.33347054441299E-06 0.56969213018052E-06 -0.22942209058335E-06 + 29 25 -0.39839275900773E-06 0.63763375848972E-06 -0.29270777078736E-06 + 29 28 -0.98238948570818E-06 0.11011532078946E-05 + 30 1 0.21485623497539E-06 -0.74731603441139E-07 0.50845166708964E-06 + 30 4 0.39612493681934E-06 -0.29551139972265E-06 0.64928302509202E-06 + 30 7 0.29199238306494E-06 -0.15240563450063E-06 0.54301840351647E-06 + 30 10 0.39824352721536E-06 -0.29446381949165E-06 0.65113285514604E-06 + 30 13 0.39609755276994E-06 -0.29342799131085E-06 0.64914602573169E-06 + 30 16 0.33602391610183E-06 -0.24786497398846E-06 0.55123157165795E-06 + 30 19 0.26728412807215E-06 -0.20854804398011E-06 0.57195324254906E-06 + 30 22 0.33502903973472E-06 -0.23919694528806E-06 0.56843489137648E-06 + 30 25 0.39810710622267E-06 -0.29460812207635E-06 0.65030553839146E-06 + 30 28 0.10878689789092E-05 -0.71677631109229E-06 0.13146635319986E-05 + 31 1 0.61366696878299E-06 -0.11308441732295E-06 0.25201512782314E-06 + 31 4 0.85741123063657E-06 -0.38888694862921E-06 0.38436661544559E-06 + 31 7 0.68777276396403E-06 -0.18495534433527E-06 0.26234646735838E-06 + 31 10 0.86820306725913E-06 -0.39277086380843E-06 0.39043447402729E-06 + 31 13 0.86467609590523E-06 -0.39072654825568E-06 0.38841397628412E-06 + 31 16 0.73500254539077E-06 -0.30949260899765E-06 0.25759348259798E-06 + 31 19 0.69815191781200E-06 -0.28842931014226E-06 0.32734385008136E-06 + 31 22 0.75087085121230E-06 -0.30915300002292E-06 0.29406926996170E-06 + 31 25 0.86573154578781E-06 -0.39166116088107E-06 0.38823484678522E-06 + 31 28 0.88567973443506E-06 -0.40245152024992E-06 0.40124740894174E-06 + 31 31 0.18205319000935E-05 + 32 1 -0.25047618177526E-06 0.42187488490136E-06 -0.21118734050063E-06 + 32 4 -0.38271031102379E-06 0.62752168095066E-06 -0.28293294953351E-06 + 32 7 -0.30500662920577E-06 0.48325279827709E-06 -0.21992629584069E-06 + 32 10 -0.39262491204635E-06 0.63221770169807E-06 -0.28914297725317E-06 + 32 13 -0.38959556882318E-06 0.63008452093564E-06 -0.28728178981962E-06 + 32 16 -0.30436140732699E-06 0.55637839007104E-06 -0.18749771005744E-06 + 32 19 -0.28523518714411E-06 0.54725097888467E-06 -0.25058565353248E-06 + 32 22 -0.32745692575626E-06 0.56427594873315E-06 -0.22514956751166E-06 + 32 25 -0.39077164324973E-06 0.63091892155017E-06 -0.28712989913510E-06 + 32 28 -0.40324134488735E-06 0.64027869261108E-06 -0.29672428881197E-06 + 32 31 -0.90655531823587E-06 0.10402420348260E-05 + 33 1 0.20727069929633E-06 -0.70693103864199E-07 0.50105679862040E-06 + 33 4 0.38500614636470E-06 -0.28768584959087E-06 0.63943573986139E-06 + 33 7 0.28362988566991E-06 -0.14743030292972E-06 0.53539134606254E-06 + 33 10 0.38968159228780E-06 -0.28871803981764E-06 0.64280021611284E-06 + 33 13 0.38716394651708E-06 -0.28732575720619E-06 0.64067612350487E-06 + 33 16 0.32845376441037E-06 -0.24257418457619E-06 0.54409928852106E-06 + 33 19 0.25963596527631E-06 -0.20330958214354E-06 0.56470939205009E-06 + 33 22 0.32758771784342E-06 -0.23412948359116E-06 0.56113936312000E-06 + 33 25 0.38881965039624E-06 -0.28814365877584E-06 0.64135863719049E-06 + 33 28 0.40075409441812E-06 -0.29585258114943E-06 0.65338451909263E-06 + 33 31 0.10188112556806E-05 -0.66575830781901E-06 0.12605017656541E-05 + 34 1 0.61123938064269E-06 -0.11330287074252E-06 0.24929635131522E-06 + 34 4 0.86015433258998E-06 -0.38848182918976E-06 0.38547135837977E-06 + 34 7 0.68110872731881E-06 -0.18195348612724E-06 0.25836640686549E-06 + 34 10 0.86391051230443E-06 -0.38858785801602E-06 0.38783124475498E-06 + 34 13 0.86154283759022E-06 -0.38750060573632E-06 0.38652718243948E-06 + 34 16 0.73248346294275E-06 -0.30727530249708E-06 0.25938957692536E-06 + 34 19 0.69741090250632E-06 -0.28616044537177E-06 0.32431658121223E-06 + 34 22 0.74611615205591E-06 -0.30618042639336E-06 0.29278071305085E-06 + 34 25 0.88880507221338E-06 -0.40464388091202E-06 0.40620720735160E-06 + 34 28 0.87867650126369E-06 -0.39705218831552E-06 0.39718307302660E-06 + 34 31 0.86365978800471E-06 -0.38847619666063E-06 0.38719472507878E-06 + 34 34 0.19707819137805E-05 + 35 1 -0.25075220523626E-06 0.42286943604259E-06 -0.21107302486836E-06 + 35 4 -0.38402759487779E-06 0.62649061802367E-06 -0.28302681277825E-06 + 35 7 -0.30299784560222E-06 0.48249162753764E-06 -0.21905717648852E-06 + 35 10 -0.39009127490080E-06 0.62893409185014E-06 -0.28719870596917E-06 + 35 13 -0.38797656845400E-06 0.62763895625875E-06 -0.28592034611250E-06 + 35 16 -0.30384511686645E-06 0.55532566332800E-06 -0.18925674725221E-06 + 35 19 -0.28440762859639E-06 0.54514319977587E-06 -0.24816500121795E-06 + 35 22 -0.32575780243301E-06 0.56291853370598E-06 -0.22519068483360E-06 + 35 25 -0.40541360043201E-06 0.64116628494589E-06 -0.29997336170420E-06 + 35 28 -0.39957688680596E-06 0.63641489352611E-06 -0.29388441729958E-06 + 35 31 -0.39012435470691E-06 0.62895031901396E-06 -0.28684106781092E-06 + 35 34 -0.98455091167798E-06 0.10948244712078E-05 + 36 1 0.20575749099760E-06 -0.70842937475658E-07 0.49907477103885E-06 + 36 4 0.38603914467337E-06 -0.28644988834645E-06 0.63914162874441E-06 + 36 7 0.27993222226123E-06 -0.14570196059192E-06 0.53272947988917E-06 + 36 10 0.38688454046243E-06 -0.28540804400086E-06 0.64034528546271E-06 + 36 13 0.38517173564093E-06 -0.28471256018946E-06 0.63891536951202E-06 + 36 16 0.32794052237121E-06 -0.24160287225147E-06 0.54604737905287E-06 + 36 19 0.25780350049714E-06 -0.20053257144382E-06 0.56093042884987E-06 + 36 22 0.32541379982879E-06 -0.23256903617427E-06 0.56080181001413E-06 + 36 25 0.40639713468542E-06 -0.29947208700965E-06 0.65814050832649E-06 + 36 28 0.39654912899507E-06 -0.29164806078551E-06 0.64957307181086E-06 + 36 31 0.38699397779900E-06 -0.28546747282026E-06 0.64014938865837E-06 + 36 34 0.11200008183638E-05 -0.72490004916822E-06 0.13437269364974E-05 + 37 1 0.53823452175608E-06 -0.83402276039030E-07 0.20288316374243E-06 + 37 4 0.74987609935461E-06 -0.31801857077651E-06 0.31719717933768E-06 + 37 7 0.60050585029966E-06 -0.14342712324837E-06 0.21108185433146E-06 + 37 10 0.75757856206082E-06 -0.32049143431703E-06 0.32109292159646E-06 + 37 13 0.75543765371975E-06 -0.31927748806604E-06 0.32026475306763E-06 + 37 16 0.64413186852640E-06 -0.25127616832768E-06 0.21110847656282E-06 + 37 19 0.61136544513217E-06 -0.23173864919419E-06 0.26680767501684E-06 + 37 22 0.65700293054663E-06 -0.25086749111540E-06 0.24113679294325E-06 + 37 25 0.75708673217306E-06 -0.32077837486484E-06 0.32056310827632E-06 + 37 28 0.77016088478048E-06 -0.32712031885153E-06 0.32890556149373E-06 + 37 31 0.75898944310135E-06 -0.32109391243147E-06 0.32141052535214E-06 + 37 34 0.75581652666309E-06 -0.32013293312855E-06 0.32012535428651E-06 + 37 37 0.15378204366958E-05 + 38 1 -0.20877459477454E-06 0.39130725634941E-06 -0.17914673064076E-06 + 38 4 -0.32098237195880E-06 0.56919502607295E-06 -0.23906005047271E-06 + 38 7 -0.25552739984627E-06 0.44466179404850E-06 -0.18640758694553E-06 + 38 10 -0.32895840010472E-06 0.57295246620097E-06 -0.24382835594021E-06 + 38 13 -0.32669736882655E-06 0.57135419958590E-06 -0.24261283695338E-06 + 38 16 -0.25437637551795E-06 0.50792610451830E-06 -0.15861191003046E-06 + 38 19 -0.23594742930666E-06 0.49816160380348E-06 -0.21035602271191E-06 + 38 22 -0.27472582927662E-06 0.51535874784995E-06 -0.19127836703174E-06 + 38 25 -0.32871797183921E-06 0.57298561643827E-06 -0.24327212859522E-06 + 38 28 -0.33634249966019E-06 0.57878813248373E-06 -0.24913060942806E-06 + 38 31 -0.32963049977944E-06 0.57329162851923E-06 -0.24387129356715E-06 + 38 34 -0.32727062424329E-06 0.57206102586195E-06 -0.24255392439870E-06 + 38 37 -0.74244187299225E-06 0.91407063785647E-06 + 39 1 0.16007452181496E-06 -0.49079404444593E-07 0.44634490926994E-06 + 39 4 0.31447458471799E-06 -0.23447332717953E-06 0.56998335717935E-06 + 39 7 0.22683804798899E-06 -0.11586207264868E-06 0.47801028614965E-06 + 39 10 0.31659744942313E-06 -0.23425851434544E-06 0.57108594830336E-06 + 39 13 0.31538067491065E-06 -0.23363132805983E-06 0.57040469011739E-06 + 39 16 0.27014802103772E-06 -0.19931650444839E-06 0.49197828554738E-06 + 39 19 0.20406415465332E-06 -0.16047813040871E-06 0.50038570480907E-06 + 39 22 0.26696971234074E-06 -0.19094676548333E-06 0.50401964844799E-06 + 39 25 0.31746210492512E-06 -0.23504468092517E-06 0.57143607397588E-06 + 39 28 0.32449081006797E-06 -0.23911463481158E-06 0.57896525688570E-06 + 39 31 0.31751326203714E-06 -0.23467993106437E-06 0.57139645973453E-06 + 39 34 0.31662768159040E-06 -0.23467813958894E-06 0.57112664014994E-06 + 39 37 0.84891731741357E-06 -0.56047582635674E-06 0.11211456278614E-05 + 40 1 0.46030037931331E-06 -0.79985032540748E-07 0.89442636954846E-07 + 40 4 0.61582603035083E-06 -0.22386628863007E-06 0.17147586477756E-06 + 40 7 0.47001934526563E-06 -0.89402083642462E-07 0.71196503484494E-07 + 40 10 0.60591305268760E-06 -0.21614017139358E-06 0.16698222924711E-06 + 40 13 0.60644786044620E-06 -0.21651537568899E-06 0.16703075870858E-06 + 40 16 0.51085531701894E-06 -0.15884896463203E-06 0.88219267311912E-07 + 40 19 0.67000563766898E-06 -0.25419159123220E-06 0.26763524792562E-06 + 40 22 0.51249268739332E-06 -0.15571670519146E-06 0.98340491748821E-07 + 40 25 0.60562449060356E-06 -0.21587364353392E-06 0.16565275096345E-06 + 40 28 0.61706560727770E-06 -0.22223570948402E-06 0.17234778403034E-06 + 40 31 0.60521354642892E-06 -0.21586778429867E-06 0.16641651020354E-06 + 40 34 0.60588714439007E-06 -0.21529003490408E-06 0.16491656908952E-06 + 40 37 0.53234904828937E-06 -0.17502992329128E-06 0.12242189073323E-06 + 40 40 0.21642344066692E-05 + 41 1 -0.15756240654015E-06 0.38614463090177E-06 -0.10136299047719E-06 + 41 4 -0.22526779936610E-06 0.49730296421016E-06 -0.13892307348677E-06 + 41 7 -0.17550678673151E-06 0.40964443314405E-06 -0.95003759329649E-07 + 41 10 -0.22522190957259E-06 0.49629627619140E-06 -0.14039179594244E-06 + 41 13 -0.22474221565795E-06 0.49583946895615E-06 -0.13986123716530E-06 + 41 16 -0.16022542103593E-06 0.43667008953407E-06 -0.70358382522253E-07 + 41 19 -0.26639556840711E-06 0.51326383797866E-06 -0.20986882213560E-06 + 41 22 -0.18148438245118E-06 0.44742859302203E-06 -0.97790931865913E-07 + 41 25 -0.22439927303997E-06 0.49530048555417E-06 -0.13874076174124E-06 + 41 28 -0.23173478758442E-06 0.50156400305841E-06 -0.14386251951870E-06 + 41 31 -0.22491015856279E-06 0.49624231831038E-06 -0.14005002268028E-06 + 41 34 -0.22359302617469E-06 0.49409227870526E-06 -0.13735934619902E-06 + 41 37 -0.17774096769123E-06 0.45294548242503E-06 -0.10494696160422E-06 + 41 40 -0.11117867147466E-05 0.11526310769898E-05 + 42 1 0.16737388929894E-06 -0.58380084926313E-07 0.42408667751497E-06 + 42 4 0.30869558479965E-06 -0.23764952109645E-06 0.52880258601035E-06 + 42 7 0.23132326733878E-06 -0.12140617575979E-06 0.44713727676431E-06 + 42 10 0.31042129347887E-06 -0.23677773254898E-06 0.53197228382895E-06 + 42 13 0.30920895986251E-06 -0.23617949127113E-06 0.53046773865660E-06 + 42 16 0.24049689179141E-06 -0.18614697427138E-06 0.42384672070020E-06 + 42 19 0.33443592717952E-06 -0.24860049032188E-06 0.59250139357823E-06 + 42 22 0.25241774273943E-06 -0.18701941179889E-06 0.45491419654046E-06 + 42 25 0.30872916531398E-06 -0.23553573143802E-06 0.52926811555645E-06 + 42 28 0.31783127276820E-06 -0.24109010901885E-06 0.53835194682481E-06 + 42 31 0.31039562735740E-06 -0.23686451009299E-06 0.53174424847873E-06 + 42 34 0.30764947900826E-06 -0.23423149936815E-06 0.52771843750963E-06 + 42 37 0.25249354361690E-06 -0.19796637748919E-06 0.47016308924077E-06 + 42 40 0.99734370521810E-06 -0.65726015439279E-06 0.10876303031773E-05 + 43 1 0.61288361335598E-06 -0.11360149782631E-06 0.25072422212017E-06 + 43 4 0.88167212947618E-06 -0.40410779626787E-06 0.40447481633811E-06 + 43 7 0.68440531752899E-06 -0.18374987276217E-06 0.26066176842218E-06 + 43 10 0.86524624326028E-06 -0.38975251673053E-06 0.38921246966944E-06 + 43 13 0.86304084502859E-06 -0.38855921818636E-06 0.38820554612484E-06 + 43 16 0.73631283886502E-06 -0.30928586879516E-06 0.26175760873929E-06 + 43 19 0.69699886590086E-06 -0.28651416458251E-06 0.32534934207590E-06 + 43 22 0.74889692952117E-06 -0.30749776201103E-06 0.29441995249104E-06 + 43 25 0.86437579028355E-06 -0.38953739124561E-06 0.38801962760903E-06 + 43 28 0.88069353336957E-06 -0.39834684808888E-06 0.39879911673596E-06 + 43 31 0.86574789492321E-06 -0.38995947849047E-06 0.38907010965607E-06 + 43 34 0.86253435389580E-06 -0.38809393671763E-06 0.38680348691848E-06 + 43 37 0.75606922011587E-06 -0.32710891060278E-06 0.31702287039832E-06 + 43 40 0.60564232604378E-06 -0.22371036573456E-06 0.30883620299821E-06 + 43 43 0.18847391148122E-05 + 44 1 -0.25075597381489E-06 0.42248795839023E-06 -0.21107842248837E-06 + 44 4 -0.40025081850939E-06 0.64061669265676E-06 -0.29828954732842E-06 + 44 7 -0.30385580685782E-06 0.48292447290575E-06 -0.21960016633412E-06 + 44 10 -0.39092771940797E-06 0.63010347136499E-06 -0.28837913356666E-06 + 44 13 -0.38871180279658E-06 0.62855186366114E-06 -0.28709825539429E-06 + 44 16 -0.30523443166788E-06 0.55615324321275E-06 -0.19011994106747E-06 + 44 19 -0.28478056681410E-06 0.54599875801746E-06 -0.24940206715946E-06 + 44 22 -0.32689922866503E-06 0.56348954376041E-06 -0.22584096451262E-06 + 44 25 -0.38988514158629E-06 0.62936251733760E-06 -0.28699442611991E-06 + 44 28 -0.40057667787211E-06 0.63749301231088E-06 -0.29498915033254E-06 + 44 31 -0.39129762827204E-06 0.63032414865198E-06 -0.28829465077860E-06 + 44 34 -0.38782019974639E-06 0.62752150981059E-06 -0.28537445838798E-06 + 44 37 -0.31978151232699E-06 0.57176156090364E-06 -0.23466683762012E-06 + 44 40 -0.21620689105544E-06 0.49519484043981E-06 -0.23583056166806E-06 + 44 43 -0.94513250265978E-06 0.10667453899861E-05 + 45 1 0.20499341026237E-06 -0.69978134412737E-07 0.49850058828569E-06 + 45 4 0.40386975800637E-06 -0.30047221512850E-06 0.65875003129958E-06 + 45 7 0.27991467153091E-06 -0.14571530144176E-06 0.53289477990340E-06 + 45 10 0.38675129588067E-06 -0.28574383338680E-06 0.64091029306760E-06 + 45 13 0.38523614976997E-06 -0.28496378879847E-06 0.63971229873844E-06 + 45 16 0.32963678812912E-06 -0.24238810732900E-06 0.54771717626775E-06 + 45 19 0.25706067935640E-06 -0.20050702687164E-06 0.56148123004424E-06 + 45 22 0.32599577856765E-06 -0.23262528434008E-06 0.56132311746442E-06 + 45 25 0.38697469341903E-06 -0.28589095374174E-06 0.64032017640665E-06 + 45 28 0.39665426111013E-06 -0.29188593836360E-06 0.65012774448397E-06 + 45 31 0.38736794531554E-06 -0.28606958069228E-06 0.64112045583186E-06 + 45 34 0.38544567695009E-06 -0.28457102291999E-06 0.63909113672847E-06 + 45 37 0.31925175189256E-06 -0.24175861594475E-06 0.57084155198813E-06 + 45 40 0.16513850212546E-06 -0.13778295921768E-06 0.52874020931726E-06 + 45 43 0.10628761159766E-05 -0.69265821041102E-06 0.12991930202379E-05 +-SOLUTION/MATRIX_ESTIMATE L COVA +*------------------------------------------------------------------------------- ++SOLUTION/MATRIX_APRIORI L COVA +*PARA1 PARA2 ____PARA2+0__________ ____PARA2+1__________ ____PARA2+2__________ + 1 1 0.56166953949758E-05 + 2 1 -0.32015824797399E-05 0.58659206994890E-05 + 3 1 0.19384677121603E-05 -0.20153849250635E-05 0.37918288556709E-05 + 4 4 0.56908927892607E-04 + 5 4 -0.19874035985813E-04 0.34356057449110E-04 + 6 4 0.27988037494753E-04 -0.16299115228613E-04 0.46043063553305E-04 + 7 7 0.51766740972841E-05 + 8 7 -0.27555247048693E-05 0.54057599317385E-05 + 9 7 0.23630461129425E-05 -0.24633150157250E-05 0.46799554885607E-05 + 10 10 0.56583325745704E-04 + 11 10 -0.20282733067171E-04 0.34954731919756E-04 + 12 10 0.27688994915795E-04 -0.16615681520319E-04 0.45773636291309E-04 + 13 13 0.56670352363395E-04 + 14 13 -0.20256324185511E-04 0.34892028237890E-04 + 15 13 0.27709837746465E-04 -0.16563943348117E-04 0.45749638006285E-04 + 16 16 0.54558768173523E-05 + 17 16 -0.18702399522615E-05 0.37213908058013E-05 + 18 16 0.32040618660723E-05 -0.20460320832070E-05 0.60663833336922E-05 + 19 19 0.69624622257830E-05 + 20 19 -0.27503621813042E-05 0.42450381192491E-05 + 21 19 0.25706578000453E-05 -0.15972959773905E-05 0.40632558404585E-05 + 22 22 0.57320706695300E-05 + 23 22 -0.22441635724779E-05 0.41028267546809E-05 + 24 22 0.30227026756129E-05 -0.21184584153205E-05 0.54175110083161E-05 + 25 25 0.56491963409203E-04 + 26 25 -0.20183410576163E-04 0.34867414545340E-04 + 27 25 0.27759610941838E-04 -0.16621023234007E-04 0.45949930870037E-04 + 28 28 0.25427699924874E+02 + 29 28 0.00000000000000E+00 0.25427699924874E+02 + 30 28 0.00000000000000E+00 0.00000000000000E+00 0.25427699924874E+02 + 31 31 0.56475899464454E-04 + 32 31 -0.20236684481955E-04 0.34937414195043E-04 + 33 31 0.27720429932202E-04 -0.16649425987640E-04 0.45896715031911E-04 + 34 34 0.56560316189353E-04 + 35 34 -0.20166602322332E-04 0.34823023627464E-04 + 36 34 0.27773258089048E-04 -0.16581774750815E-04 0.45926288869716E-04 + 37 37 0.62650609085969E-05 + 38 37 -0.22451925667934E-05 0.38814999589394E-05 + 39 37 0.30822484113930E-05 -0.18534661639794E-05 0.51100054787299E-05 + 40 40 0.73309493250912E-05 + 41 40 -0.31052617503657E-05 0.45513664204283E-05 + 42 40 0.19872209764312E-05 -0.12877718219221E-05 0.33974974097712E-05 + 43 43 0.56332893455308E-04 + 44 43 -0.20050127596547E-04 0.34763303791646E-04 + 45 43 0.27852082645210E-04 -0.16644235615421E-04 0.46209598890547E-04 +-SOLUTION/MATRIX_APRIORI L COVA +%ENDSNX diff --git a/docs/tutorials/STR1AUSPOS_new.SNX b/docs/tutorials/STR1AUSPOS_new.SNX new file mode 100644 index 0000000..8767459 --- /dev/null +++ b/docs/tutorials/STR1AUSPOS_new.SNX @@ -0,0 +1,40 @@ +['*-------------------------------------------------------------------------------', '+FILE/REFERENCE', '*INFO_TYPE_________ INFO________________________________________________________', ' DESCRIPTION My agency/institute', ' OUTPUT One-session solution generated by RNX2SNX BPE', ' CONTACT My e-mail address', ' SOFTWARE Bernese GNSS Software Version 5.2', ' HARDWARE My computer', ' INPUT IGS/IGLOS GNSS tracking data', '-FILE/REFERENCE', '*-------------------------------------------------------------------------------', '+INPUT/ACKNOWLEDGMENTS', '*AGY DESCRIPTION________________________________________________________________', ' XYZ My agency/institute and its address', ' IGS International GNSS Service', '-INPUT/ACKNOWLEDGMENTS', '*-------------------------------------------------------------------------------', '+SOLUTION/STATISTICS', '*_STATISTICAL PARAMETER________ __VALUE(S)____________', ' NUMBER OF OBSERVATIONS 54963', ' NUMBER OF UNKNOWNS 460', ' NUMBER OF DEGREES OF FREEDOM 54503', ' PHASE MEASUREMENTS SIGMA 0.00100', ' SAMPLING INTERVAL (SECONDS) 180', ' VARIANCE FACTOR 2.542769992487420', '-SOLUTION/STATISTICS', '*-------------------------------------------------------------------------------']*------------------------------------------------------------------------------- ++SITE/ID +*CODE PT __DOMES__ T _STATION DESCRIPTION__ APPROX_LON_ APPROX_LAT_ _APP_H_ + ALIC A 50137M001 P ALIC 50137M001 133 53 7.9 -23 40 12.4 603.2 + BRDW A AUM000200 P BRDW AUM000200 149 47 6.7 -35 26 47.3 679.5 + CEDU A 50138M001 P CEDU 50138M001 133 48 35.4 -31 51 60.0 144.7 + CNWD A AUM000464 P CNWD AUM000464 149 1 58.0 -35 12 23.0 593.5 + GNGN A AUM000415 P GNGN AUM000415 149 7 49.9 -35 11 5.8 645.1 + HOB2 A 50116M004 P HOB2 50116M004 147 26 19.5 -42 48 16.9 41.0 + MCHL A 59905M001 P MCHL 59905M001 148 8 41.9 -26 21 32.0 534.6 + MOBS A 50182M001 P MOBS 50182M001 144 58 31.2 -37 49 45.8 40.6 + PRCE A AUM000318 P PRCE AUM000318 149 5 20.4 -35 21 48.8 639.7 + STR1 A 50119M002 P STR1 50119M002 149 0 36.2 -35 18 55.9 799.9 + STR2 A 50119M001 P STR2 50119M001 149 0 36.6 -35 18 58.1 802.5 + SYM1 A 59899M001 P SYM1 59899M001 149 9 39.8 -35 20 33.0 592.2 + TID1 A 50103M108 P TID1 50103M108 148 58 48.0 -35 23 57.1 665.3 + TOW2 A 50140M001 P TOW2 50140M001 147 3 20.5 -19 16 9.4 88.1 + WLMD A AUM000483 P WLMD AUM000483 149 8 15.8 -35 35 40.3 850.2 +-SITE/ID +*------------------------------------------------------------------------------- ++SOLUTION/EPOCHS +*CODE PT SOLN T _DATA_START_ __DATA_END__ _MEAN_EPOCH_ + ALIC A 1 P 25:333:00000 25:333:86370 25:333:43185 + BRDW A 1 P 25:333:00000 25:333:86370 25:333:43185 + CEDU A 1 P 25:333:00000 25:333:86370 25:333:43185 + CNWD A 1 P 25:333:00000 25:333:86370 25:333:43185 + GNGN A 1 P 25:333:00000 25:333:86370 25:333:43185 + HOB2 A 1 P 25:333:00000 25:333:86370 25:333:43185 + MCHL A 1 P 25:333:00000 25:333:86370 25:333:43185 + MOBS A 1 P 25:333:00000 25:333:86370 25:333:43185 + PRCE A 1 P 25:333:00000 25:333:86370 25:333:43185 + STR1 A 1 P 25:333:00000 25:333:86370 25:333:43185 + STR2 A 1 P 25:333:00000 25:333:86370 25:333:43185 + SYM1 A 1 P 25:333:00000 25:333:86370 25:333:43185 + TID1 A 1 P 25:333:00000 25:333:86370 25:333:43185 + TOW2 A 1 P 25:333:00000 25:333:86370 25:333:43185 + WLMD A 1 P 25:333:00000 25:333:86370 25:333:43185 +-SOLUTION/EPOCHS +*------------------------------------------------------------------------------- +%ENDSNX \ No newline at end of file diff --git a/docs/tutorials/anglestut.rst b/docs/tutorials/anglestut.rst new file mode 100644 index 0000000..2bf80af --- /dev/null +++ b/docs/tutorials/anglestut.rst @@ -0,0 +1,204 @@ +.. _tutorials/angles: + +Angle Classes and Converstions +================================= + +GeodePy has 5 main angle classes to represent angles in different formats. These will be explored here along with how to convert between these types. + +The 5 classes are: + +* :ref:`Degrees, Minutes and Second (dms) ` +* :ref:`Degrees and Decimal Minutes (ddm) ` +* :ref:`Decimal Degrees (dec) ` +* :ref:`HP notation (hpa) ` +* :ref:`Gradians (gona) ` + +Classes +------- + +.. _tut/dms: + +Degrees, Minutes and Seconds (dms) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Angles expressed in degrees, minutes, and seconds. + +- **Format:** ``ddd° mm' ss.s"`` +- **Conversion:** 1° = 60′, 1′ = 60″ +- **Example:** ``123° 34' 56.2"`` + +To initalise a dms class: + +.. code:: python + + angle1 = geodepy.angles.DMSAngle(d, m, s) + +.. _tut/ddm: + +Degrees and Decimal Minutes (ddm) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Angles expressed in degrees and minutes, with minutes shown as a decimal fraction. + +- **Format:** ``ddd° mm.mm'`` +- **Example:** ``123° 34.933'`` + +To initalise a ddm class: + +.. code:: python + + angle1 = geodepy.angles.DDMAngle(d, dm) + +.. _tut/dd: + +Decimal Degrees (dec) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Angles expressed entirely in decimal degrees. + +- **Format:** ``ddd.ddd°`` +- **Example:** ``123.5823°`` + +To initalise a dec class: + +.. code:: python + + angle1 = geodepy.angles.DECAngle(d) + +.. _tut/hp: + +HP Notation (hpa) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +HP notation expresses latitude and longitude as positive values with hemisphere indicators. + +- **Format:** ``ddd.mmssss`` +- **Example:** ``123.231524°`` + +To initalise a hpa class: + +.. code:: python + + angle1 = geodepy.angles.HPAngle(hp) + +.. _tut/grad: + +Gradians (gona) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +A metric-based angle unit where a full circle equals 400 gradians. + +- **Format** ``ggg.ggg`` +- **Conversion:** 1 grad = 0.9° +- **Example:** ``137.5`` + +To initalise a gona class: + +.. code:: python + + angle1 = geodepy.angles.GONAngle(gon) + + +Using Angle Classes +-------------------- + +Angle classes can be used to hold angle variables, transform to different angle types and +complete arthimitc. These will be explored below. + +First import GeodePy. + +.. code:: python + + import geodepy.angles as angles + +In this example a DMS angle will be created. This object can be initalised by including the degrees, minutes and seconds as arguments. + +.. code:: python + + angle1 = angles.DMSAngle(30, 5, 42) + print(angle1) + + >>30 5 42 + +Using this class we can get individual variables for degree minute and seconds componets seperately. + +.. code:: python + + print(angle1.degree) + print(angle1.minute) + print(angle1.second) + + >>30 + >>5 + >>42 + +The methods within the class can also be used to convert the angle into different types. + +.. code:: python + + print(angle1.ddm()) + print(angle1.dec()) + print(angle1.gona()) + print(angle1.hpa()) + print(angle1.rad()) + + >>30 5.7 + >>30.095 + >>33.4388888889 + >>30.0542 + >>0.5252568383876934 + +This can be completed with any of the 5 angle classes within GeodePy. + +Arthimitc can be completed on angle classes as seen below. First a new angle needs to be defined. +This will be done using the DDM Angle class + +.. code:: python + + angle2 = angles.DDMAngle(40, 10.52) + +Now this new anlge class can be added or subtracted from the first class + +.. code:: python + + angle3 = angle1 + angle2 + angle4 = angle2 - angle1 + + print(angle3) + print(type(angle3)) + print(angle4) + print(type(angle4)) + + >>70 16 13.2 + >> + >>10 4.82 + >> + +As can be seen here, simple math can be completed on these classes. It should be noted that +the result will have the class of the first variable in the calculation. + +.. note:: Operations can only be preformed when both angles are an object. + +The following operators can be preformed on angle objects: + ++----------------------+------------------+ +| Operation | Method | ++======================+==================+ +| Addition | '+' | ++----------------------+------------------+ +| Subtraction | '-' | ++----------------------+------------------+ +| Multiplication | '*' | ++----------------------+------------------+ +| Division | '/' | ++----------------------+------------------+ +| Equality | '==' | ++----------------------+------------------+ +| Not Equal | '!=' | ++----------------------+------------------+ +| Less Than | '<' | ++----------------------+------------------+ +| Greater Than | '>' | ++----------------------+------------------+ +| Modulo | '%' | ++----------------------+------------------+ +| Round | round() | ++----------------------+------------------+ + +.. caution:: Basic arthimitc should not be completed on HPA class. These should be converted to decimal degree first. + diff --git a/docs/tutorials/coordtut.rst b/docs/tutorials/coordtut.rst new file mode 100644 index 0000000..59288b0 --- /dev/null +++ b/docs/tutorials/coordtut.rst @@ -0,0 +1,153 @@ +.. _tutorials/coord: + +Coordinate Classes and Conversions +============================================= + +GeodePy has 3 coordinate classes that can be used to store coordinates and convert between coordinate types. + +The three different classes are: + +- :ref:`CoordCart - Cartesian Coordinates (x, y, z) ` +- :ref:`CoordGeo - Geographic Coordinates (lat, long, H) ` +- :ref:`CoordTM - Transverse Mercator Coordinates (e, n, H) ` + +To learn more about these corrdinate types refer to the `GDA2020 technical manual `_. + +Classes +-------- + +.. _tut/cart: + +Cartesian Coordinates +^^^^^^^^^^^^^^^^^^^^^^ +Cartesian coordinates represent points in three dimensions (X, Y, Z), typically in an Earth-Centered, Earth-Fixed (ECEF) system. +In this class an "n" value can also be added representing seperation between ellipsoid and geiod. + +- **Description:** Defines a point by its distance along three perpendicular axes. +- **Format:** ``(X, Y, Z)`` in meters. +- **Example:** ``( -4052051.0, 4212831.0, -2545100.0 )`` + +To initalise a cartesian coordinate class: + +.. code:: python + + coord1 = geodepy.coord.CoordCart(x, y, z, n=None) + +.. _tut/geo: + +Geographic Coordinates +^^^^^^^^^^^^^^^^^^^^^^ +Geographic coordinates express positions on the Earth's surface using latitude, longitude, and optionally height. + +- **Description:** Latitude and longitude define angular position relative to the equator and prime meridian. +- **Format:** ``(latitude, longitude, height)`` +- **Example:** ``(-33.8650°, 151.2094°, 58)`` + +To initalise a geographic coordinate class: + +.. code:: python + + coord1 = geodepy.coord.CoordGeo(lat, long, ell_ht=None) + +.. _tut/tm: + +Transverse Mercator Coordinates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +A projected coordinate system that maps the curved Earth onto a flat plane using the Transverse Mercator projection. + +- **Description:** Represents positions as Easting and Northing values in meters. +- **Format:** ``(Zone, Easting, Northing, Height)`` +- **Example:** ``(55, 334567.89, 6254321.12, 58.2)`` + +To initalise a transverse mercator coordinate class: + +.. code:: python + + coord1 = geodepy.coord.CoordTM(zone, east, north, ell_ht=None) + +Converting Between Classes +-------------------------- + +Coordinate classes can be used to hold coordinate variables and convert between different coordinate types. +This will be explored here. + +First import GeodePy. + +.. code:: python + + import geodepy.coord + import geodepy.geodesy + +We can now create a coordinate obect. For this example we will use a transverse mercator coordinate. + +.. code:: python + + coord1 = geodepy.coord.CoordTM(55, 696053.337, 6086610.13) #(zone, E, N) + print(coord1) + + >>CoordTM: Zone: 55 East: 696053.337 North: 6086610.13 Ell_Ht: None Orth_Ht: None Hemisphere: South + +This object can now be transformed into the other classes using the inbuilt methods. + +.. code:: python + + print(coord1.cart()) + print(coord1.geo()) + + >>CoordCart: X: -4471828.926838844 Y: 2670252.9985762094 Z: -3669113.8962611817 NVal: None + >>CoordGeo: Lat: -35.3445551951 Lon: 149.15740394128 Ell_Ht: None Orth_Ht: None + +Individual variables from a coordinate class can be used within different functions. + +.. code:: python + + coord1Geo = coord1.geo() + print(geodepy.geodesy.rho(coord1Geo.lat)) # to calculate radius of curvature of ellipsoid + + >>6356788.983764104 + +Using Convert Functions +----------------------- + +Instead of using classes, function can also be used to convert between coordinate types. The two main conversions function are: + +- geo2grid - Converts from geographic (lat, long, h) to grid (E, N, u) +- xyz2llh - Converts from cartesian (x, y, z) to geographic (lat, long, h) + +Both of these functions can be reversed to convert the other way. + +To convert using functions first geodepy needs to be imported. + +.. code:: python + + import geodepy.geodesy + import geodepy.convert + +Now either of the functions can be used + +.. code:: python + + coordGeo = geodepy.convert.grid2geo(55, 696053.337, 6086610.13) #zone, E, N + print(coordGeo) + + >>(-35.34455523, 149.15740394, 1.0000737, 1.2484390010290551) + + coordllh = geodepy.convert.xyz2llh( + -4471828.926838844, #x + 2670252.9985762094, #y + -3669113.8962611817 #z + ) + + print(coordllh) + + >>(-35.34455523, 149.15740394, 0) + +These function can be used together to convert between grid and cartesian. + +.. code:: python + + coordGeo = geodepy.convert.grid2geo(55, 696053.337, 6086610.13) #zone, E, N + coordCart = geodepy.convert.llh2xyz(coordGeo[0],coordGeo[1]) + print(coordCart) + + >>(-4471828.924896575, 2670252.9973158445, -3669113.8995236363) \ No newline at end of file diff --git a/docs/tutorials/edmcorrectiontut.rst b/docs/tutorials/edmcorrectiontut.rst new file mode 100644 index 0000000..156b7a1 --- /dev/null +++ b/docs/tutorials/edmcorrectiontut.rst @@ -0,0 +1,96 @@ +.. _tutorials/edm: + +EDM Corrections +================ + +Within GeodePy, EDM corrections can be completed. To complete an EDM correction the following data is needed. + +- Carrier wavelength of instrument +- Modulation frequency and unit length or reference refractive index +- Distance measured +- Zentith angle +- Temperature +- Pressure +- Relative humidity +- (Optional) Co2 ppm + +Using these we can calculate the first velocity correction and reduce to a horizontal distance. + +First, the variables need to be defined. Below is the values for a Lecia Viva. + +.. code:: python + + import geodepy.survey + + wavelength = 0.658 #micrometers + modFreq = 9.9902213*10e6 #hz + unitLength = 1.5 #m + dist = 145.265 #m + zAngle = 91.15678 #dec + temp = 26 #°c + pressure = 1010.8 #hPa + relHum = 37 #% + co2ppm = 345 #ppm + +First Velocity Parameters +------------------------- + +Now we can calculate the first velocity parameters. + +.. code:: python + + params = first_vel_params(wavelength, modFreq, None, unitLength) + + print(params) + + >>(286.3433 80.6752) + +First Velocity Correction +------------------------- + +Using these parameters we can calculate the first velocity correction. + +.. code:: python + + correction = geodepy.survey.first_vel_corrn(dist, params, temp, pressure, relHum) + + print(correction) + + >>0.0020656 + +Now this an be applied to the distance to get a corrected distance. + +.. code:: python + + corrDist = dist + correction + + print(corrDist) + + >>145.2670656 + +The first velocity correction can also be calculated using the Co2 ppm. +This gives a more accurate result but requires the co2 ppm and the wavelength. + +.. code:: python + + correction2 = geodepy.survey.first_vel_corrn(dist, params, temp, pressure, relHum, + wavelength=wavelength, CO2_ppm=co2ppm) + + print(correction2) + + print(dist + correction2) + + >>0.0020756 + >>145.2670756 + +Now the horizontal distance can be found using the zenith angle and corrected distance. + +.. code:: python + + horzDist = geodepy.survey.va_conv(zAngle, corrDist) + + print(horzDist) + + >>(-1.15678, 145.2670656, 145.2374597, -2.9326875) + +Where the third value in the tuple is the horizontal distance and the last value is the change in height. diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst new file mode 100644 index 0000000..15e8cb6 --- /dev/null +++ b/docs/tutorials/index.rst @@ -0,0 +1,25 @@ +.. _tutorials/index: + +Tutorials +=========== + +This section contains a selection of tutorials that showcase the main features of GeodePy. +It is recommended to complete these tutorials in the order seen below however each tutorial +is self-contained. + +These tutorials should be used as a guide only, with any final calculations being checked +by a compotent professional. + +.. container:: collapsible-toc + + .. toctree:: + :maxdepth: 2 + + anglestut + coordtut + vincentytut + edmcorrectiontut + transformtut + timedeptranstut + verticaldatumtut + sinextut \ No newline at end of file diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - Angles.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Angles.ipynb new file mode 100644 index 0000000..dfd8323 --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Angles.ipynb @@ -0,0 +1,232 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Angle Classes and Conversions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "GeodePy has five main angle classes to represent angles in different formats. We will explore them here along with conversions between types.\n", + "\n", + "**Classes:**\n", + "- Degrees, Minutes and Seconds (**DMS**)\n", + "- Degrees and Decimal Minutes (**DDM**)\n", + "- Decimal Degrees (**DEC**)\n", + "- HP notation (**HPA**)\n", + "- Gradians (**GON**)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Degrees, Minutes and Seconds (DMS)\n", + "Angles expressed in degrees, minutes, and seconds.\n", + "\n", + "- **Format:** `ddd° mm' ss.s\"`\n", + "- **Conversion:** 1° = 60′, 1′ = 60\"\n", + "- **Example:** `123° 34' 56.2\"`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# To initialise a DMS angle:\n", + "import geodepy.angles as angles\n", + "angle1 = angles.DMSAngle(30, 5, 42)\n", + "print(angle1) # Expected: 30 5 42" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Degrees and Decimal Minutes (DDM)\n", + "Angles expressed in degrees and minutes, with minutes shown as a decimal fraction.\n", + "\n", + "- **Format:** `ddd° mm.mm'`\n", + "- **Example:** `123° 34.933'`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialise a DDM angle:\n", + "import geodepy.angles as angles\n", + "angle_ddm = angles.DDMAngle(40, 10.52)\n", + "print(angle_ddm)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Decimal Degrees (DEC)\n", + "Angles expressed entirely in decimal degrees.\n", + "\n", + "- **Format:** `ddd.ddd°`\n", + "- **Example:** `123.5823°`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialise a DEC angle:\n", + "import geodepy.angles as angles\n", + "angle_dec = angles.DECAngle(30.095)\n", + "print(angle_dec)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## HP Notation (HPA)\n", + "HP notation expresses latitude/longitude as positive values with hemisphere indicators.\n", + "\n", + "- **Format:** `ddd.mmssss`\n", + "- **Example:** `123.231524°`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialise an HPA angle:\n", + "import geodepy.angles as angles\n", + "angle_hpa = angles.HPAngle(30.0542)\n", + "print(angle_hpa)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Gradians (GON)\n", + "A metric-based angle unit where a full circle equals 400 gradians.\n", + "\n", + "- **Format:** `ggg.ggg`\n", + "- **Conversion:** 1 grad = 0.9°\n", + "- **Example:** `137.5`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialise a GON angle:\n", + "import geodepy.angles as angles\n", + "angle_gon = angles.GONAngle(33.4388888889)\n", + "print(angle_gon)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using Angle Classes\n", + "Angle classes can be used in a few different ways. In the example below the class is used to access individual components and perform conversions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.angles as angles\n", + "angle1 = angles.DMSAngle(30, 5, 42)\n", + "print('DMS:', angle1)\n", + "print('degree:', angle1.degree)\n", + "print('minute:', angle1.minute)\n", + "print('second:', angle1.second)\n", + "\n", + "# Conversions\n", + "print('DDM:', angle1.ddm())\n", + "print('DEC:', angle1.dec())\n", + "print('GON:', angle1.gona())\n", + "print('HPA:', angle1.hpa())\n", + "print('RAD:', angle1.rad())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Arithmetic with Angle Classes\n", + "You can add/subtract/multiply/divide angle objects. The result takes the class of the **left operand**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "angle2 = angles.DDMAngle(40, 10.52)\n", + "angle3 = angle1 + angle2\n", + "angle4 = angle2 - angle1\n", + "print('angle3:', angle3, type(angle3))\n", + "print('angle4:', angle4, type(angle4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Supported operations\n", + "- Addition: `+`\n", + "- Subtraction: `-`\n", + "- Multiplication: `*`\n", + "- Division: `/`\n", + "- Equality: `==`\n", + "- Not equal: `!=`\n", + "- Less than: `<`\n", + "- Greater than: `>`\n", + "- Modulo: `%`\n", + "- Round: `round()`\n", + "\n", + "> **Note:** Basic arithmetic should not be performed on HPA angles. Convert to decimal degrees first." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - Connecting Points.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Connecting Points.ipynb new file mode 100644 index 0000000..128e692 --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Connecting Points.ipynb @@ -0,0 +1,144 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Connecting Two Points\n", + "\n", + "GeodePy can calculate the relationship between two points in two ways: on a **flat plane** and on a **curved (ellipsoidal) plane**.\n", + "\n", + "For background on geodetic formulas, see the [GDA2020 Technical Manual](https://www.anzlic.gov.au/sites/default/files/files/GDA2020%20Technical%20Manual%20V1.8_published.pdf)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Flat plane\n", + "Calculate distance and bearing between two grid coordinates using `survey.joins`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.survey\n", + "import geodepy.angles\n", + "\n", + "connection = geodepy.survey.joins(696053.337, 6086610.13, 696770.781, 6086089.772)\n", + "bearing = connection[1]\n", + "bearing = geodepy.angles.dec2dms(bearing)\n", + "print(f\"The connection is: {connection[0]:.4f} @ {bearing}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here the first number is the **distance** and the second is the **bearing** (converted to DMS for clarity)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also determine the second point given the first coordinate, a bearing, and a distance using `survey.radiations`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "coord = geodepy.survey.radiations(696053.337, 6086610.13, bearing.dec(), 886.2834)\n", + "print(f\"The point 2 coord is: {coord[0]:.3f} {coord[1]:.3f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As shown, this matches the second point used in `survey.joins`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Curved plane\n", + "Use **Vincenty** formulas to compute joins on the ellipsoid. This example uses the UTM Vincenty formula however the same process applies with lat/lon versions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.geodesy\n", + "import geodepy.angles\n", + "\n", + "connection2 = geodepy.geodesy.vincinv_utm(55, 696053.337, 6086610.13, 55, 696770.781, 6086089.772)\n", + "bearing2 = connection2[1]\n", + "bearing2 = geodepy.angles.dec2dms(bearing2)\n", + "print(f\"The connection is: {connection2[0]:.4f} @ {bearing2}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The curved‑plane result differs slightly from the flat‑plane one, as expected." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Compute the second point on the ellipsoid with `vincdir_utm`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "coord2 = geodepy.geodesy.vincdir_utm(55, 696053.337, 6086610.13, bearing2.dec(), 886.2839)\n", + "print(f\"The point 2 coord is: {coord2[1]:.3f} {coord2[2]:.3f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, this matches the earlier coordinate." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - Coordinates.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Coordinates.ipynb new file mode 100644 index 0000000..11b4054 --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Coordinates.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Coordinate Classes and Conversions\n", + "\n", + "GeodePy has **three** coordinate classes that can be used to store coordinates and convert between coordinate types.\n", + "The three different classes are:\n", + "\n", + "- **CoordCart – Cartesian Coordinates**\n", + "- **CoordGeo – Geographic Coordinates**\n", + "- **CoordTM – Transverse Mercator Coordinates**\n", + "\n", + "For more background on these coordinate types, see the [GDA2020 Technical Manual](https://www.anzlic.gov.au/sites/default/files/files/GDA2020%20Technical%20Manual%20V1.8_published.pdf)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Classes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Cartesian Coordinates (CoordCart)\n", + "Cartesian coordinates represent points in three dimensions **(X, Y, Z)**, typically in an **Earth‑Centered, Earth‑Fixed (ECEF)** system.\n", + "In this class an optional `n` value can be added representing separation between ellipsoid and geoid.\n", + "\n", + "- **Description:** Defines a point by its distance along three perpendicular axes.\n", + "- **Format:** `(X, Y, Z)` in meters.\n", + "- **Example:** `(-4052051.0, 4212831.0, -2545100.0)`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialise a Cartesian coordinate\n", + "import geodepy.coord as coord\n", + "cart1 = coord.CoordCart(-4052051.0, 4212831.0, -2545100.0)\n", + "print(cart1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geographic Coordinates (CoordGeo)\n", + "Geographic coordinates express positions on the Earth's surface using **latitude**, **longitude**, and optionally **height**.\n", + "\n", + "- **Description:** Latitude and longitude define angular position relative to the equator and prime meridian.\n", + "- **Format:** `(latitude, longitude, height)`\n", + "- **Example:** `(-33.8650°, 151.2094°, 58)`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialise a Geographic coordinate\n", + "geo1 = coord.CoordGeo(-33.8650, 151.2094, 58)\n", + "print(geo1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Transverse Mercator Coordinates (CoordTM)\n", + "A projected coordinate system that maps the curved Earth onto a flat plane using the **Transverse Mercator** projection.\n", + "\n", + "- **Description:** Represents positions as Easting and Northing values in meters.\n", + "- **Format:** `(Zone, Easting, Northing, Height)`\n", + "- **Example:** `(55, 334567.89, 6254321.12, 58.2)`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Initialise a Transverse Mercator coordinate\n", + "tm1 = coord.CoordTM(55, 334567.89, 6254321.12, 58.2)\n", + "print(tm1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Converting Between Classes\n", + "**First import GeodePy**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.coord\n", + "import geodepy.geodesy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now create a coordinate object. For this example we will use a **Transverse Mercator** coordinate." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "coord1 = geodepy.coord.CoordTM(55, 696053.337, 6086610.13)\n", + "print(coord1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This object can now be transformed into the other classes using the inbuilt methods." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(coord1.cart())\n", + "print(coord1.geo())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Individual variables from a coordinate class can be used within different functions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "coord1Geo = coord1.geo()\n", + "print(geodepy.geodesy.rho(coord1Geo.lat)) # to calculate radius of curvature of ellipsoid" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using Convert Functions\n", + "Instead of using classes, functions can also be used to convert between coordinate types. The two main conversion functions are:\n", + "\n", + "- `geo2grid` — Converts from geographic (lat, lon, h) to grid (E, N, u)\n", + "- `xyz2llh` — Converts from Cartesian (X, Y, Z) to geographic (lat, lon, h)\n", + "\n", + "Both of these functions can be reversed to convert the other way.\n", + "\n", + "**Import modules:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.geodesy\n", + "import geodepy.convert" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Use either function:**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "coordGeo = geodepy.convert.grid2geo(55, 696053.337, 6086610.13)\n", + "print(coordGeo)\n", + "\n", + "coordllh = geodepy.convert.xyz2llh(-4471828.926838844, 2670252.9985762094, -3669113.8962611817)\n", + "print(coordllh)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These functions can be used together to convert between grid and Cartesian." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "coordGeo = geodepy.convert.grid2geo(55, 696053.337, 6086610.13)\n", + "coordCart = geodepy.convert.llh2xyz(coordGeo[0], coordGeo[1])\n", + "print(coordCart)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - EDM Corrections.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - EDM Corrections.ipynb new file mode 100644 index 0000000..5f8862f --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - EDM Corrections.ipynb @@ -0,0 +1,170 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# EDM Corrections\n", + "\n", + "Tihs tutorial shows how to compute first velocity parameters, apply first velocity corrections (with and without CO₂), and reduce a measured distance to horizontal using GeodePy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Inputs required\n", + "To complete an EDM correction you need:\n", + "- Carrier wavelength of instrument\n", + "- Modulation frequency and unit length (or reference refractive index)\n", + "- Measured distance\n", + "- Zenith angle\n", + "- Temperature (°C)\n", + "- Pressure (hPa)\n", + "- Relative humidity (%)\n", + "- (Optional) CO₂ ppm\n", + "\n", + "Using these we can calculate the first velocity correction and reduce to a horizontal distance." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First the variables need to be defined. Below is some example paramters for the Lecia Viva." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.survey\n", + "\n", + "# Instrument and observation values\n", + "wavelength = 0.658 # micrometers\n", + "modFreq = 9.9902213*10e6 # Hz\n", + "unitLength = 1.5 # m\n", + "dist = 145.265 # m\n", + "zAngle = 91.15678 # decimal degrees (zenith)\n", + "temp = 26 # °C\n", + "pressure = 1010.8 # hPa\n", + "relHum = 37 # %\n", + "co2ppm = 345 # ppm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## First velocity parameters\n", + "\n", + "Now we can calculate the first velocity parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from geodepy.survey import first_vel_params\n", + "params = first_vel_params(wavelength, modFreq, None, unitLength)\n", + "print(params) # Expected approx: (286.3433, 80.6752)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## First Velocity Correction\n", + "\n", + "Using these parameters we can calculate the first velocity correction. This correction can then be applied to the distance to get a final corrected distance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "corr1 = geodepy.survey.first_vel_corrn(dist, params, temp, pressure, relHum)\n", + "print(corr1)\n", + "\n", + "# Apply correction to distance\n", + "corrDist = dist + corr1\n", + "print(corrDist)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## First Velocity Correction with CO2\n", + "\n", + "The first velocity correction can also be calculated using the Co2 ppm. This gives a more accurate result but requires the Co2 ppm and the wavelength." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "corr2 = geodepy.survey.first_vel_corrn(dist, params, temp, pressure, relHum,\n", + " wavelength=wavelength, CO2_ppm=co2ppm)\n", + "print(corr2)\n", + "print(dist + corr2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Horizontal Distance\n", + "\n", + "Now the horizontal distacne can be found using the zenith angle and corrected distance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "horz = geodepy.survey.va_conv(zAngle, corrDist)\n", + "print(horz) # Tuple: (zenith_deg, slope_dist, horiz_dist, delta_h)" + ] + }, + { + "cell_type": "markdown", + "id": "cbecf4a5", + "metadata": {}, + "source": [ + "The third value in the tuple is the horizontal distance and the last value is the change in height." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - Sinex.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Sinex.ipynb new file mode 100644 index 0000000..66a277e --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Sinex.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SINEX Files\n", + "\n", + "GeodePy has the ability to **read** and **write** SINEX files.\n", + "This tutorial demonstrates how to read selected parts of a SINEX file and how to write a new SINEX file.\n", + "\n", + "We will use the example file **`STR1AUSPOS.SNX`** from the docs folder (generated by AUSPOS).\n", + "\n", + "**NOTE:** To make the below blocks work you may need to change the file path to be appropriate for your machine." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reading SINEX Files\n", + "SINEX files are split into blocks delimited by lines starting with `+BLOCKNAME` and ending with `-BLOCKNAME`.\n", + "Use `read_sinex_blocks` to list the blocks present in a file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.gnss as gnss\n", + "gnss.list_sinex_blocks('GeodePy/docs/tutorials/STR1AUSPOS.SNX')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Particular blocks can be read using other GeodePy functions. Below the acknowledgments block will be read." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ack = gnss.read_sinex_input_acknowledgments_block('GeodePy/docs/tutorials/STR1AUSPOS.SNX')\n", + "for line in ack:\n", + " print(line)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All blocks that can be read by GeodePy can be found in the sinex features page. If a certain block is needed that cant be found here any lines can be read using the read_sinex_custom function as seen below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "custom_lines = gnss.read_sinex_custom('GeodePy/docs/tutorials/STR1AUSPOS.SNX', 5, 6)\n", + "print(custom_lines)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing SINEX Files\n", + "Use `writeSINEX` to create a new SINEX file from selected blocks.\n", + "In this example we will write a file containing **Header**, **Site ID**, and **Solution Epochs** blocks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "header = gnss.read_sinex_header_block('GeodePy/docs/tutorials/STR1AUSPOS.SNX')\n", + "site_id = gnss.read_sinex_site_id_block('GeodePy/docs/tutorials/STR1AUSPOS.SNX')\n", + "sol_epochs = gnss.read_sinex_solution_epochs_block('GeodePy/docs/tutorials/STR1AUSPOS.SNX')\n", + "\n", + "output_path = 'GeodePy/docs/tutorials/STR1AUSPOS_new.SNX'\n", + "gnss.writeSINEX(output_path, header=header, siteID=site_id, solutionEpochs=sol_epochs)\n", + "print('Wrote:', output_path)\n", + "# The new file can be found in the docs folder." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - Time Dependant Transformations.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Time Dependant Transformations.ipynb new file mode 100644 index 0000000..4665dfe --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Time Dependant Transformations.ipynb @@ -0,0 +1,335 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Time Dependent Transformations\n", + "\n", + "In this tutorial we discuss **time dependent transformations**. To see transformations between static datums, view the *datum transformation* tutorial.\n", + "\n", + "For more details on time dependent transformations, refer to the [GDA2020 Technical Manual](https://www.anzlic.gov.au/sites/default/files/files/GDA2020%20Technical%20Manual%20V1.8_published.pdf)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Time dependent transformations are more complex than transformations between static datums. First we will complete a simple example." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Common Example\n", + "Here we convert between **ATRF2014** and **GDA2020** (dynamic → static).\n", + "We will transform a coordinate in ATRF2014 at epoch **1/1/2011**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First import GeodePy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.transform\n", + "from datetime import date" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use the function to transform from ATRF2014 to GDA2020." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x_20, y_20, z_20, vcv = geodepy.transform.transform_atrf2014_to_gda2020(-4050762.770917, 4220880.800229, -2533400.199554, date(2011,1,1))\n", + "print(x_20, y_20, z_20)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the **GDA2020** coordinate." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Transforming between Dynamic and Static Datums\n", + "The simplest time dependent transformations go between static and dynamic datums. This is because direct transformation parameters are often available." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we transform from **GDA94** to **ITRF2008** at **1/1/2007** (without a dedicated function)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.transform\n", + "import geodepy.constants\n", + "from datetime import date" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To reach ITRF2008, we will use the **GDA94 → ITRF2008** transformation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z, vcv = geodepy.transform.conform14(-4050763.124034, 4220880.753100, -2533399.713463, date(2007,1,1), geodepy.constants.gda94_to_itrf2008)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the **ITRF2008** coordinate on **1/1/2007**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Transforming Between Two Dynamic Datums\n", + "Transforming between two dynamic datums is more complex and requires extra steps and considerations." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Example: Transform **ITRF2008 (1/1/2007)** → **ITRF2020 (1/1/2030)**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.constants\n", + "import geodepy.transform\n", + "from datetime import date" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First convert ITRF2008 to **ITRF2014** (so plate motion can be applied). Plate motion can only be applied to **ITRF2014** or **ATRF2014**. Enter the **ITRF2008 epoch** so the result remains at that epoch." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z, vcv = geodepy.transform.conform14(-4050762.612530, 4220880.821783, -2533400.416214, date(2007, 1, 1), geodepy.constants.itrf2008_to_itrf2014)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we have the ITRF2014 coordinate at 1/1/2007.\n", + "Now move to **1/1/2030** using the **ITRF2014 → GDA2020** transformation which approximates plate motion in Australia. For other plates, use an appropriate plate motion model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This process has to be done carefully. From 2007 to 2030 is **23 years** of motion. The reference epoch of the ITRF2014→GDA2020 transformation is **2020**. Subtract **23** from **2020** to get the desired epoch (enter **1/1/1997**).\n", + "\n", + "> **Caution:** The plate motion model for Australia (itrf2014_to_gda2020) should only be used between 2005 - 2035. For transformations outside this range refer to the next section on older datum transformations. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z, vcv = geodepy.transform.conform14(x, y, z, date(1997, 1, 1), geodepy.constants.itrf2014_to_gda2020)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is now the **ITRF2014** coordinate at **1/1/2030**. Convert this to **ITRF2020** at **1/1/2030**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z, vcv = geodepy.transform.conform14(x, y, z, date(2030,1,1), geodepy.constants.itrf2014_to_itrf2020)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the final coordinate in **ITRF2020** at **1/1/2030**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Transforming Between Older Dynamic Datums\n", + "The Australian plate motion model should only be used between **2005–2035**. For older datums, use a different method." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Example: Transform **ITRF88 (1/1/1988)** → **ITRF2014 (1/1/2030)**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Caution:** This method only works for coordinates within Australia." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.constants\n", + "import geodepy.transform\n", + "from datetime import date" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To go from an old dynamic datum to a more current datum, transformations to static datums should be completed first. In this case transforming to GDA2020 will give the most accurate result, before then transforming back to ITRF2014. To get to GDA2020 the ITRF88 coordinate first needs to be transformed to ITRF2014 at the 1/1/1988." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Transform ITRF88 to **ITRF2014** at **1/1/1988**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z, vcv = geodepy.transform.conform14(-4050763.124645, 4220880.752269, -2533399.717044, date(1988,1,1), geodepy.constants.itrf88_to_itrf2014)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can transform this to **GDA2020** (still entering **1988** epoch)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z, vcv = geodepy.transform.conform14(x, y, z, date(1988,1,1), geodepy.constants.itrf2014_to_gda2020)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now change to **ITRF2014** at **1/1/2030**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z, vcv = geodepy.transform.conform14(x, y, z, date(2030,1,1), geodepy.constants.gda2020_to_itrf2014)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is now the **ITRF2014** coordinate at **1/1/2030**." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - Transformations.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Transformations.ipynb new file mode 100644 index 0000000..d0af31a --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Transformations.ipynb @@ -0,0 +1,273 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Datum Transformation\n", + "\n", + "GeodePy has the ability to **transform between datums**. Here we discuss how to change between datums **without changing the reference epoch**.\n", + "(For changing epochs, see the *time dependent* tutorial.)\n", + "\n", + "For more on transformations, refer to the [GDA2020 Technical Manual](https://www.anzlic.gov.au/sites/default/files/files/GDA2020%20Technical%20Manual%20V1.8_published.pdf)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Common Example\n", + "The most common datum transformation is converting from **MGA94** to **MGA2020**.\n", + "This is handled by a function in the transformation module that:\n", + "1) Converts the grid input to Cartesian **(XYZ)**,\n", + "2) Runs a **7‑parameter Helmert** transformation using the **GDA94→GDA2020** transformation constant.\n", + "We'll break down how this function works later." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Import GeodePy**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.transform" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Define MGA94 coordinates**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zone_94 = 55\n", + "east_94 = 696053.3373\n", + "north_94 = 6086610.1338" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Transform to MGA2020**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(zone_20, east_20, north_20, _, _) = geodepy.transform.transform_mga94_to_mga2020(\n", + " zone_94, east_94, north_94\n", + ")\n", + "\n", + "print(zone_20, east_20, north_20)" + ] + }, + { + "cell_type": "markdown", + "id": "5d57b113", + "metadata": {}, + "source": [ + "This is the MGA2020 coordinates." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Constructing a new Transformation Function\n", + "Now we'll complete a transformation **without a dedicated function** and then wrap it into our own convenient function.\n", + "Example: transform from **AGD84** to **GDA2020**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Import modules**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.transform\n", + "import geodepy.constants\n", + "import geodepy.angles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Starting coordinates (AGD84)**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lat = geodepy.angles.DMSAngle(-23, 33, 25.21)\n", + "long = geodepy.angles.DMSAngle(133, 49, 13.87)\n", + "height = 427.863\n", + "print(f\"The AGD84 position is {lat}, {long}, {height}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All transformations in GeodePy use **Cartesian (XYZ)** coordinates. Convert LLH to XYZ:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z = geodepy.transform.llh2xyz(lat, long, height)\n", + "print(x, y, z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Transformation parameters** are provided in `geodepy.constants`. If the required transform is missing, you can add one using the *transformation class*. Here we'll use **AGD84→GDA94**." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(geodepy.constants.agd84_to_gda94)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Perform a **7‑parameter Helmert** transformation (AGD84→GDA94):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x_94, y_94, z_94, _ = geodepy.transform.conform7(x, y, z, geodepy.constants.agd84_to_gda94)\n", + "print(x_94, y_94, z_94)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Tip:** The final return from `conform7` includes a VCV matrix. If you don't pass a VCV in, the returned VCV will be `None`. Use `_` to ignore it when you don't need it." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now transform **GDA94→GDA2020** using the same method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x_20, y_20, z_20, _ = geodepy.transform.conform7(x_94, y_94, z_94, geodepy.constants.gda94_to_gda2020)\n", + "print(x_20, y_20, z_20)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Convert Cartesian back to geographic (lat, lon, h):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lat_20, long_20, height_20 = geodepy.transform.xyz2llh(x_20, y_20, z_20)\n", + "print(f\"The GDA2020 position is {geodepy.angles.dec2dms(lat_20)}, {geodepy.angles.dec2dms(long_20)}, {height_20}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Combine into one function**:\n", + "\n", + "We can now combine this into one convenient function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def transform_agd84_to_gda2020(lat, long, height):\n", + " x, y, z = geodepy.transform.llh2xyz(lat, long, height)\n", + " x_94, y_94, z_94, _ = geodepy.transform.conform7(x, y, z, geodepy.constants.agd84_to_gda94)\n", + " x_20, y_20, z_20, _ = geodepy.transform.conform7(x_94, y_94, z_94, geodepy.constants.gda94_to_gda2020)\n", + " return geodepy.transform.xyz2llh(x_20, y_20, z_20)\n", + "\n", + "lat_new, long_new, height_new = transform_agd84_to_gda2020(lat, long, height)\n", + "print(f\"The GDA2020 position is {geodepy.angles.dec2dms(lat_new)}, {geodepy.angles.dec2dms(long_new)}, {height_new}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/juypter notebooks/GeodePy Tutorial - Vertical Datums.ipynb b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Vertical Datums.ipynb new file mode 100644 index 0000000..75a1d37 --- /dev/null +++ b/docs/tutorials/juypter notebooks/GeodePy Tutorial - Vertical Datums.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Vertical Datums\n", + "\n", + "This tutorial works through converting between different **vertical datums** and working with **gravity**.\n", + "\n", + "For background on vertical datums, see the [GDA2020 Technical Manual](https://www.anzlic.gov.au/sites/default/files/files/GDA2020%20Technical%20Manual%20V1.8_published.pdf)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Converting Between Vertical Datums\n", + "GeodePy allows conversion between vertical datums in Australia. Below we convert from **ellipsoidal height → AHD → AVWS**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Import module**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.height" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Ellipsoidal height → AHD**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "rl = geodepy.height.GPS_to_AHD(-35.34405212, 149.15847673, 594.495)\n", + "print(rl[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**AHD → AVWS**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "rl = geodepy.height.AHD_to_AVWS(-35.34405212, 149.15847673, rl[0])\n", + "print(rl[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Finding Gravity Values\n", + "GeodePy can also compute gravity values at points around Australia." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Import module**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geodepy.height" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use `mean_surface_grav` to get mean surface gravity between two points. If both points are the same, this returns gravity at a single location. This will be explored below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grav = geodepy.height.mean_surface_grav(-35.34405212, 149.15847673, 575.176,\n", + " -35.34405212, 149.15847673, 575.176)\n", + "print(grav)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To compute the average **normal gravity** between the ellipsoid and a given height, use `mean_normal_grav`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grav = geodepy.height.mean_normal_grav(-35.34405212, 594.495)\n", + "print(grav)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/tutorials/GeodePy Tutorial - Angular Notation Formats.ipynb b/docs/tutorials/juypter notebooks/ss/GeodePy Tutorial - Angular Notation Formats.ipynb similarity index 100% rename from docs/tutorials/GeodePy Tutorial - Angular Notation Formats.ipynb rename to docs/tutorials/juypter notebooks/ss/GeodePy Tutorial - Angular Notation Formats.ipynb diff --git a/docs/tutorials/GeodePy Tutorial - Time-Dependent Datum Transformations.ipynb b/docs/tutorials/juypter notebooks/ss/GeodePy Tutorial - Time-Dependent Datum Transformations.ipynb similarity index 63% rename from docs/tutorials/GeodePy Tutorial - Time-Dependent Datum Transformations.ipynb rename to docs/tutorials/juypter notebooks/ss/GeodePy Tutorial - Time-Dependent Datum Transformations.ipynb index b34e36a..3585bf5 100644 --- a/docs/tutorials/GeodePy Tutorial - Time-Dependent Datum Transformations.ipynb +++ b/docs/tutorials/juypter notebooks/ss/GeodePy Tutorial - Time-Dependent Datum Transformations.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -36,20 +36,9 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(53, 386353.2371, 7381852.2967, 587.5814)" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "bob = (53, 386353.2371, 7381852.2967, 587.5814)\n", "bob" @@ -65,20 +54,9 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-23.67011015602, 133.88552163574)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "lat, lon, psf, grid_conv = grid2geo(bob[0], bob[1], bob[2])\n", "lat, lon" @@ -93,20 +71,9 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-4052042.7920922847, 4212825.645301947, -2545098.301585565)" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "x, y, z = llh2xyz(lat, lon, bob[3])\n", "x, y, z" @@ -122,28 +89,9 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Transformation: From 'ITRF2005' to 'ITRF2014'\n", - "Reference Epoch: datetime.date(2010, 1, 1)\n", - " tx: -0.0026m + -0.0003m/yr\n", - " ty: -0.001m + -0.0m/yr\n", - " tz: 0.0023m + 0.0001m/yr\n", - " sc: -0.00092ppm + -3e-05ppm/yr\n", - " rx: -0.0\" + -0.0\"/yr\n", - " ry: -0.0\" + -0.0\"/yr\n", - " rz: -0.0\" + -0.0\"/yr" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "-itrf2014_to_itrf2005" ] @@ -157,28 +105,9 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Transformation: From 'ITRF2014' to 'GDA2020'\n", - "Reference Epoch: datetime.date(2020, 1, 1)\n", - " tx: 0m + 0m/yr\n", - " ty: 0m + 0m/yr\n", - " tz: 0m + 0m/yr\n", - " sc: 0ppm + 0ppm/yr\n", - " rx: 0\" + 0.00150379\"/yr\n", - " ry: 0\" + 0.00118346\"/yr\n", - " rz: 0\" + 0.00120716\"/yr" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "itrf2014_to_gda2020" ] @@ -192,42 +121,20 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-4052042.7920922847, 4212825.645301947, -2545098.301585565)" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "x, y, z" ] }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-4052042.399457001, 4212825.696901389, -2545098.8412879077)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x2, y2, z2, _ = conform14(x, y, z, date(2030, 1, 1), itrf2014_to_gda2020)\n", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x2, y2, z2, _ = conform14(x, y, z, date(2010, 1, 1), itrf2014_to_gda2020)\n", "x2, y2, z2" ] }, @@ -248,20 +155,9 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-4052042.398329122, 4212825.69202559, -2545098.836646417)" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "x3, y3, z3, _ = conform7(x2, y2, z2, -itrf2014_to_itrf2005)\n", "x3, y3, z3" @@ -278,22 +174,11 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-4052042.005693805, 4212825.74362497, -2545099.3763487404)" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x4, y4, z4, _ = conform14(x3, y3, z3, date(2030, 1, 1), itrf2014_to_gda2020)\n", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x4, y4, z4, _ = conform14(x3, y3, z3, date(2010, 1, 1), itrf2014_to_gda2020)\n", "x4, y4, z4" ] }, @@ -308,20 +193,9 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(-23.67012076198506, 133.8855154120127, 587.5785029232502)" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "lat_end, lon_end, ell_ht_end = xyz2llh(x4, y4, z4)\n", "lat_end, lon_end, ell_ht_end" @@ -336,20 +210,9 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(53, 386352.6116, 7381851.1174)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "hem_end, zone_end, east_end, north_end, psf_end, grid_conv_end = geo2grid(lat_end, lon_end)\n", "zone_end, east_end, north_end" @@ -364,20 +227,9 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(53, 386352.6116, 7381851.1174, 587.5785029232502)" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "zone_end, east_end, north_end, ell_ht_end" ] @@ -391,20 +243,9 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(53, 386352.6116, 7381851.1174, 587.5785029232502)" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "bob_end = (zone_end, east_end, north_end, ell_ht_end)\n", "bob_end" @@ -412,20 +253,9 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(53, 386353.2371, 7381852.2967, 587.5814)" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "bob" ] @@ -440,20 +270,9 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(53, 386352.6116, 7381851.1174, 587.5785029232502)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "def bobtransform(zone, east, north, ell_ht):\n", " lat, lon, psf, grid_conv = grid2geo(zone, east, north)\n", @@ -479,7 +298,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -493,7 +312,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/tutorials/sinextut.rst b/docs/tutorials/sinextut.rst new file mode 100644 index 0000000..50cc10e --- /dev/null +++ b/docs/tutorials/sinextut.rst @@ -0,0 +1,83 @@ +.. _tutorials/sinex: + +Sinex Files +============ + +Geodepy has the ability to read and write sinex files. Below we will explore how to +read certain parts of a sinex file and ultimately how to write a new sinex file. In +this tutorial the sinex file "STR1AUSPOS.SNX" found in the docs folder will be used. This was +generated using AUSPOS. + +Reading Sinex Files +------------------- + +Sinex files are split into different blocks all containing different types of information. +These blocks are deliniated by a starting line that uses a "+BLOCKNAME" and an ending line +using a "-BLOCKNAME". The blocks found in a sinex file can be printed using the read_sinex_blocks +function as seen below. + +.. code:: python + + import geodepy.gnss + + geodepy.gnss.read_sinex_blocks("GeodePy/docs/tutorials/STR1AUSPOS.SNX") + + >>FILE/REFERENCE + >>INPUT/ACKNOWLEDGMENTS + >>SOLUTION/STATISTICS + >>... + +Particular blocks can be read using other GeodePy functions. Below the acknowledgments +block will be read. + +.. code:: python + + print(geodepy.gnss.read_sinex_input_acknowledgments_block("GeodePy/docs/tutorials/STR1AUSPOS.SNX")) + + >>['+INPUT/ACKNOWLEDGMENTS', + '*AGY DESCRIPTION________________________________________________________________', + ' XYZ My agency/institute and its address', + ' IGS International GNSS Service', + '-INPUT/ACKNOWLEDGMENTS'] + +All blocks that can be read by GeodePy can be found in the :ref:`sinex features ` +page. If a certain block is needed that can't be found here, any lines can be read using +the read_sinex_custom function as seen below. + +.. code:: python + + print(geodepy.gnss.read_sinex_custom( + "GeodePy/docs/tutorials/STR1AUSPOS.SNX", #file name + 5, #start line + 6 #end line + )) + + >>['DESCRIPTION My agency/institute', + 'OUTPUT One-session solution generated by RNX2SNX BPE'] + +Writing Sinex Files +------------------- + +GeodePy can also be used to write new sinex files. This is done using the writeSINEX +function. This function uses many of the read sinex functions to create blocks. Below +we will explore the process of taking a sinex file and writing a new sinex file with +only a select few blocks. + +.. code:: python + + import geodepy.gnss + + #The blocks in new file will be header, site ID and solution epochs + + headerSTR1 = geodepy.gnss.read_sinex_header_block("GeodePy/docs/tutorials/STR1AUSPOS.SNX") + siteIDSTR1 = geodepy.gnss.read_sinex_site_id_block("GeodePy/docs/tutorials/STR1AUSPOS.SNX") + epochSTR1 = geodepy.gnss.read_sinex_solution_epochs_block("GeodePy/docs/tutorials/STR1AUSPOS.SNX") + + geodepy.gnss.writeSINEX( + "GeodePy/docs/tutorials/STR1AUSPOS_new.SNX", #new file path + header=headerSTR1, #header str + siteID=siteIDSTR1, #siteID str + solutionEpochs=epochSTR1 #solution Epochs str + ) + +This file can also be found in the docs folder. diff --git a/docs/tutorials/timedeptranstut.rst b/docs/tutorials/timedeptranstut.rst new file mode 100644 index 0000000..2fb898f --- /dev/null +++ b/docs/tutorials/timedeptranstut.rst @@ -0,0 +1,218 @@ +.. _tutorials/timetrans: + +Time Dependant Transformations +============================== + +In this tutorial we will discuss time dependant transformations. +To see transformations between static datums view the :ref:`datum transformation ` tutorial. +To learn more about time dependant transformations refer to the `GDA2020 technical manual `_. + +Time dependant transformation are much more complex then transformation between static datums. First we will complete a simply example. + +Common Example +-------------- + +Here we will convert between ATRF2014 and GDA2020. This is transforming from a dynamic datum to a static datum. + +In this example we will transform a coordinate in ATRF2014 at epoch 1/1/2011. + +First we will import geodepy. + +.. code:: python + + import geodepy.tranform + from datetime import date + +Now the function for transforming from ATRF2014 to GDA2020 can be used. + +.. code:: python + + x_20, y_20, z_20, vcv = geodepy.transform.transform_atrf2014_to_gda2020( + -4050762.770917, #x + 4220880.800229, #y + -2533400.199554, #z + date(2011,1,1) #ATRF2014 epoch + ) + + print(x_20, y_20, z_20) + + >>-4050763.124034 4220880.753100 -2533399.713463 + +This is the GDA2020 coordinate. + +Transforming between Dynamic and Static Datums +---------------------------------------------- + +The simpliest time dependant transformations go between static and dynamic datums. + Below we will complete such a transformation that doesn't include a dedicated function. + +Here we will transform from GDA94 to ITRF2008 at 1/1/2007. + +.. code:: python + + import geodepy.tranform + import geodepy.constants + from datetime import date + +To go from GDA94 to ITRF2008 we need to investigate what transformations are present in GeodePy. This can be found +in :ref:`this ` table. Here we can see that there is a direct transformation from GDA94 to ITRF2008. +We will use this to complete our transformation. + +.. code:: python + + x, y, z, vcv = geodepy.transform.conform14( + -4050763.124034, #x + 4220880.753100, #y + -2533399.713463, #z + date(2007,1,1), #ITRF2008 epoch + geodepy.constants.gda94_to_itrf2008 #transformation parameters + ) + + print(x, y, z) + + >>-4050763.585602 4220880.607715 -2533398.980318 + +This is the ITRF2008 coordinate on 1/1/2007 + +Transforming Between Two Dynamic Datums +--------------------------------------- + +Transforming between two dynamic datums is more complex, requiring a few more steps and considerations. +For this example we will transform from ITRF2008 at 1/1/2007 to ITRF2020 at 1/1/2030. + +.. code:: python + + import geodepy.constant + import geodepy.transform + +First the ITRF2008 coordinates need to be converted to ITRF2014. This is completed so that the plate motion +between 2007 and 2030 can be applied. The plate motion can only be applied to coordinates in ITRF2014 or +ATRF2014. When completeing this transformation the date of the ITRF2008 epoch is entered. This means the +resulting ITRF2020 cooridnate will be at the IRTF2008 epoch. + +.. code:: python + + x, y, z, vcv = geodepy.transform.conform14( + -4050762.612530, #x + 4220880.821783, #y + -2533400.416214, #z + date(2007, 1, 1), #ITRF2008 epoch + geodepy.constants.itrf2008_to_itrf2014 + ) + + print(x, y, z) + + >>-4050762.614575 4220880.820347 -2533400.419192 + +Now we have an ITRF2014 coordinate at 1/1/2007. Now this needs to be moved to the 1/1/2030. This can be +done using the ITRF2014 to GDA2020 transformation which approximates plate motion in Australia. To complete +this transformation on another plate a different plate motion model should be used. + +Some careful math needs to be completed here. To go from 2007 to 2030, 23 years +of plate motion needs to be added. The reference epoch of the ITRF2014 to GDA2020 transformation is 2020. +As such 23 needs to be subtracted from 2020 to get the desired motion. This means the epoch 1/1/1993 should be entered. + +.. caution:: + Transformations using the plate motion model of itrf2014_to_gda2020 should only be completed for epochs between + 2005 - 2035. For transformations outside of this range refer to the :ref:`next ` section. + + +.. code:: python + + x, y, z, vcv = geodepy.transform.conform14( + x, + y, + z, + date(1997, 1, 1), #plate motion epoch + geodepy.constants.itrf2014_to_gda2020 #transformation paramters + ) + + print(x, y, z) + + >>-4050763.516973 4220880.699906 -2533399.176976 + +This is now the ITRF2014 corrdinate at 1/1/2030. Now we can convert this ITRF2014 cooridnate to ITRF2020. + +.. code:: python + + x, y, z, vcv = geodepy.transform.conform14( + x, + y, + z, + date(2030,1,1), #ITRF2014 epoch + geodepy.constants.itrf2014_to_itrf2020 #transformation paramter + ) + + print(x, y, z) + + >>-4050763.517274 4220880.704079 -2533399.182440 + +This is the final cooridnate in ITRF2020 at 1/1/2030. + +.. _tutorials/transold: + +Transforming Between Older Dynamic Datums +----------------------------------------- + +The Australia plate motion model should only be used between the years of 2005 to 2035. If a datum older then +this needs to be transformed a different method should be used. For this example we will tranform from ITRF88 +at 1/1/1988 to ITRF2014 at 1/1/2030. + +.. caution:: This method only works for coordinates within Australia. + +.. code:: python + + import geodepy.constant + import geodepy.transform + +To go from an old dynamic datum to a more current datum, transformations to a static datums should be completed first. +In this case transforming to GDA2020 is most accurate, before then transforming back to ITRF2014. To get to GDA2020 +the ITRF88 coordinate first needs to be transformed to ITRF2014 at the 1/1/1988. + +.. code:: python + + x, y, z, vcv = geodepy.transform.conform14( + -4050763.124645, #x + 4220880.752269, #y + -2533399.717044, #z + date(1988,1,1), #ITRF88 epoch + geodepy.constants.itrf88_to_itrf2014 #transformation parameters + ) + + print(x, y, z) + + >>-4050763.116490 4220880.700494 -2533399.614981 + +This is the ITRF2014 coordinate at 1/1/1988. Now this needs to be transformed into GDA2020 + +.. code:: python + + x, y, z, vcv = geodepy.transform.conform14( + x, + y, + z, + date(1988,1,1), #ITRF2014 epoch + geodepy.constants.itrf2014_to_gda2020 #transformation parameters + ) + + print(x, y, z) + + >>-4050764.372112 4220880.532909 -2533397.886526 + +Now this corrinate can be changed to ITRF2014 at 1/1/2030. + +.. code:: python + + x, y, z, vcv = geodepy.transform.conform14( + x, + y, + z, + date(2030,1,1), #ITRF2020 epoch + geodepy.constants.gda2020_to_itrf2014 #transformation parameters + ) + + print(x, y, z) + + >>-4050764.764547 4220880.480531 -2533397.346309 + +This is now the ITRF2014 coordinate at 1/1/2030. \ No newline at end of file diff --git a/docs/tutorials/transformtut.rst b/docs/tutorials/transformtut.rst new file mode 100644 index 0000000..88ba5fb --- /dev/null +++ b/docs/tutorials/transformtut.rst @@ -0,0 +1,185 @@ +.. _tutorials/transform: + +Datum Transformation +===================== + +GeodePy has the ability to tranform between datums. Here we will discuss how to +change between datums without changing the reference epoch while in the +:ref:`time dependant ` tutorial we will discuss changing epochs. +To learn more about transformation refer to the `GDA2020 technical manual `_. + +Common Example +-------------- + +The most common example for datum transformation is converting from MGA94 to MGA2020. +This is handled by a function in the transformation module that converts the grid +input to a caresian coordinate (xyz), and then runs a 7 paramter helmert transformation +using the gda94_to_gda2020 transformation constant. This process will be explored more in +the :ref:`latter ` part of this tutorial but first lets use this function. + +Begin by importing GeodePy. + +.. code:: python + + import geodepy.transform + +Next, define some coordinate values in MGA94. + +.. code:: python + + zone_94 = 55 + east_94 = 696053.3373 + north_94 = 6086610.1338 + +Now, transform this coordinate to MGA2020. + +.. code:: python + + (zone_20, east_20, north_20, _, _) = geodepy.transform.transform_mga94_to_mga2020( + zone_94, + east_94, + north_94 + ) + + print(zone_20, east_20, north_20) + + >>55 696053.872 6086611.549 + +This it the MGA2020 coordinates. + +To complete this transformation a function from the transformation module was used. This +will not always be the case. In the next section transformations where a function is not already +present will be explored. + +.. _tutorials/transformfunc: + +Constructing a new Transformation Function +------------------------------------------ + +Here we will explore how to complete a transformation between datums without a dedicated function. +We will then create a new function for this new transformation. Here we are going to transform from AGD84 to GDA2020. + +First import GeodePy. + +.. code:: python + + import geodepy.transform + import geodepy.constants + import geodepy.angles + +Now we need some starting coordinates. + +.. code:: python + + lat = geodepy.angles.DMSAngle(-23,33,25.21) + long = geodepy.angles.DMSAngle(133,49,13.87) + height = 427.863 + + print(f"The AGD84 position is {lat}, {long}, {height}" + + >>The AGD84 position is -23 33 25.21, 133 49 13.87, 427.863 + +All transformations in GeodePy need to be completed with corrdinates in cartesian (xyz) form. Lets transform to xyz. + +.. code:: python + + x, y, z = geodepy.transform.llh2xyz(lat, long, height) + print(x, y, z) + + >>-4050634.051819 4220935.13646 -2533555.369303 + +Now we need some transformation parameters. Within Geodepy there are many transformaton parameters already +present within the constants module. A table of these can be seen :ref:`here `. If the +transformation needed isn't currently in Geodepy, new transformations can be added using the :ref:`transformation class `. +Here the agd66_to_gda94 transformation will be used. + +.. code:: python + + print(geodepy.constants.agd84_to_gda94) + + >>Transformation: From 'AGD84' to 'GDA94' + >>Reference Epoch: 0 + >>tx: -117.763m + 0.0m/yr + >>ty: -51.51m + 0.0m/yr + >>tz: 139.061m + 0.0m/yr + >>sc: -0.191ppm + 0.0ppm/yr + >>rx: -0.292" + 0.0"/yr + >>ry: -0.443" + 0.0"/yr + >>rz: -0.277" + 0.0"/yr + +These transformation parameters will be used to complete a 7 paramter helmert transformation. + +.. code:: python + + x_94, y_94, z_94, _ = geodepy.transform.conform7( + x, + y, + z, + geodepy.constants.agd84_to_gda94 #transformation parameters + ) + print(x_94, y_94, z_94) + + >>-4050762.150962 4220880.96717 -2533401.14935 + +.. tip:: + The "_" in the conform7 variables is for a vcv matrix. If you dont input a vcv matrix + then the resulting variable will be None. However as the variable needs to be assigned + for the function to work using "_" meets to requirement but doesn't store the variable + in a meaningful way. + +Now we need to transform from GDA94 to GDA2020 using the same method but with the gda94_to_gda2020 +transformation class. + +.. code:: python + + x_20, y_20, z_20, _ = geodepy.transform.conform7( + x_94, + y_94, + z_94, + geodepy.constants.gda94_to_gda2020 #transformation parameters + ) + + print(x_20, y_20, z_20) + + >>-4050763.124034 4220880.75310 -2533399.713463 + +Now the cartesian coordinates can be converted back to geographic coordinates (lat, long) + +.. code:: python + + lat_20, long_20, height_20 = geodepy.transform.xyz2llh(x_20, y_20, z_20) + + print(f"The GDA2020 position is + {geodepy.angles.dec2dms(lat_20)}, + {geodepy.angles.dec2dms(long_20)}, + {height_20}") + + >>The GDA2020 position is -23 33 19.921108332, 133 49 18.481110119, 411.61055134 + +All of this can now be combined into one function. + +.. code:: python + + def transform_agd84_to_gda2020(lat, long, height): + + x, y, z = geodepy.transform.llh2xyz(lat, long, height) + x_94, y_94, z_94, _ = geodepy.transform.conform7( + x, y, z, + geodepy.constants.agd84_to_gda94 + ) + x_20, y_20, z_20, _ = geodepy.transform.conform7( + x_94, y_94, z_94, + geodepy.constants.gda94_to_gda2020 + ) + + return geodepy.transform.xyz2llh(x_20, y_20, z_20) + + lat_new, long_new, height_new = transform_agd84_to_gda2020(lat, long, height) + + print(f"The GDA2020 position is + {geodepy.angles.dec2dms(lat_new)}, + {geodepy.angles.dec2dms(long_new)}, + {height_new}") + + >>The GDA2020 position is -23 33 19.921108332, 133 49 18.481110119, 411.61055134 + diff --git a/docs/tutorials/verticaldatumtut.rst b/docs/tutorials/verticaldatumtut.rst new file mode 100644 index 0000000..80647d9 --- /dev/null +++ b/docs/tutorials/verticaldatumtut.rst @@ -0,0 +1,88 @@ +.. _tutorials/vert: + +Vertical Datums +=============== + +This tutorial will work through converting between different vertical datums and finding gravity values. +To learn more about vertical datums refer to the +`GDA2020 technical manual `_. + +Converting Between Vertical Datums +---------------------------------- + +GeodePy allows you to convert between different vertical datums within Australia. Below we will convert from +an ellipsoidal height to AHD and then to AVWS. + +.. code:: python + + import geodepy.height + +First we will convert from ellipsoidal height to AHD. + +.. code:: python + + rl = geodepy.height.GPS_to_AHD( + -35.34405212, #lat + 149.15847673, #long + 594.495 #ellipsoidal height + ) + + print(rl[0]) + + >>[575.176] + +This is the AHD height. Now we can convert to AVWS. + +.. code:: python + + rl = geodepy.height.AHD_to_AVWS( + -35.34405212, #lat + 149.15847673, #long + rl[0] #AHD + ) + + print(rl[0]) + + >>[575.303] + +Finding Gravity Values +---------------------- + +GeodePy can also find gravity values at any point around Australia. This can be seen below. + +.. code:: python + + import geodepy.height + +To find the gravity at any point the mean_surface_grav function can be used. +Normally this function is used to find the mean surface gravty between two points, however +if the same lat and long are used for both point A and B then the gravity at a set location can be found. + +.. code:: python + + grav = geodepy.height.mean_surface_grav( + -35.34405212, #lat of A + 149.15847673, #long of A + 575.176, #AHD + -35.34405212, #lat of B + 149.15847673, #lat of B + 575.176 #AHD + ) + + print(grav) + + >>[9.79603591] + +To calculate the average gravity between the ellipsoid and a certain height the following function can be used. + +.. code:: python + + grav = geodepy.height.mean_normal_grav( + -35.34405212, #lat + 594.495 #ellipsoid height + ) + + print(grav) + + >>9.79671297 + diff --git a/docs/tutorials/vincentytut.rst b/docs/tutorials/vincentytut.rst new file mode 100644 index 0000000..8e90db4 --- /dev/null +++ b/docs/tutorials/vincentytut.rst @@ -0,0 +1,95 @@ +.. _tutorials/vincenty: + +Connecting Two Points +===================== + +GeodePy can calculate the relationship between two points in two different ways, either on a flat plane or curved plane. +To learn more about geodetic formulas refer to the `GDA2020 technical manual `_. + +Flat plane +---------- + +To calculate the distance and bearing between two points the following command can be used. Here the coordinates of two +points are entered. + +.. code:: python + + import geodepy.survey + import geodepy.angles + + connection = geodepy.survey.joins( + 696053.337, #easting of A + 6086610.13, #northing of A + 696770.781, #easting of B + 6086089.772 #northing of B + ) + bearing = connection[1] + bearing = geodepy.angles.dec2dms(bearing) + + print(f"The connection is: {connection[0]:.4f} @ {bearing}") + + >>The connection is: 886.2834 @ 125 57 11.37834570 + +Here the first number is the horizontal distance while the second is the bearing, here converted to DMS. + +You can also determine the coordinate of a second point given the first coordinate and a bearing and distance. + +.. code:: python + + coord = geodepy.survey.radiations( + 696053.337, #easting of A + 6086610.13, #northing of A + bearing.dec(), #bearing A to B + 886.2834 #distance A to B + ) + print(f"The point 2 coord is: {coord[0]:.3f} {coord[1]:.3f}") + + >>The point 2 coord is: 696770.781 6086089.772 + +As can be seen this value matches the coordinate of the second point entered in the joins function. + +Curved Plane +------------ + +To calculate the joins between two points on a curved plane, vincenty's formula can be used. Within GeodePy there +are two types of Vincenty's that either use geographic coordinates or grid coordinates. In this example we will +use the utm vincenty functions that uses grid coordinates but this same process can be undertaken using latitude and longitude. + +.. code:: python + + import geodepy.geodesy + import geodepy.angles + + connection2 = geodepy.geodesy.vincinv_utm( + 55, #zone of A + 696053.337, #easting of A + 6086610.13, #northing of A + 55, #zone of B + 696770.781, #easting of B + 6086089.772 #northing of B + ) + bearing2 = connection2[1] + bearing2 = geodepy.angles.dec2dms(bearing2) + + print(f"The connection is: {connection2[0]:.4f} @ {bearing2}") + + >>The connection is: 886.2839 @ 125 57 11.1186109 + +As seen here the connection is slightly different between the flat and curved plane. + +The coordinate of a second point can also be calculated. + +.. code:: python + + coord2 = geodepy.geodesy.vincdir_utm( + 55, #zone of A + 696053.337, #easting of A + 6086610.13, #northing of A + bearing2.dec(), #bearing A to B + 886.2839 #distance A to B + ) + print(f"The point 2 coord is: {coord2[1]:.3f} {coord2[2]:.3f}") + + >>The point 2 coord is: 696770.781 6086089.772 + +Again it can be seen that this coordinate matches what was entered earlier. \ No newline at end of file diff --git a/geodepy/angles.py b/geodepy/angles.py index 87f93ef..96532e3 100644 --- a/geodepy/angles.py +++ b/geodepy/angles.py @@ -37,11 +37,13 @@ class DECAngle(float): """ Class for working with angles in Decimal Degrees + Note: GeodePy also supports working with angles in Decimal Degrees as floats """ def __init__(self, dec_angle=0.0): """ + :param dec_angle: float Decimal Degrees angle """ super().__init__() @@ -49,58 +51,61 @@ def __init__(self, dec_angle=0.0): def __repr__(self): if self.dec_angle >= 0: - return '{DECAngle: +' + str(self.dec_angle) + '}' + return "{DECAngle: +" + str(self.dec_angle) + "}" else: # negative - return '{DECAngle: ' + str(self.dec_angle) + '}' + return "{DECAngle: " + str(self.dec_angle) + "}" def __add__(self, other): try: return DECAngle(self.dec() + other.dec()) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __radd__(self, other): try: return DECAngle(other.dec() + self.dec()) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __sub__(self, other): try: return DECAngle(self.dec() - other.dec()) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __rsub__(self, other): try: return DECAngle(other.dec() - self.dec()) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __mul__(self, other): try: return DECAngle(self.dec() * other) except TypeError: - raise TypeError('Multiply only defined between DECAngle Object ' - 'and Int or Float') + raise TypeError( + "Multiply only defined between DECAngle Object " "and Int or Float" + ) def __rmul__(self, other): try: return DECAngle(other * self.dec()) except TypeError: - raise TypeError('Multiply only defined between DECAngle Object ' - 'and Int or Float') + raise TypeError( + "Multiply only defined between DECAngle Object " "and Int or Float" + ) def __truediv__(self, other): try: return DECAngle(self.dec() / other) except TypeError: - raise TypeError('Division only defined between DECAngle Object ' - 'and Int or Float') + raise TypeError( + "Division only defined between DECAngle Object " "and Int or Float" + ) def __abs__(self): return DECAngle(abs(self.dec_angle)) @@ -135,6 +140,7 @@ def __round__(self, n=None): def rad(self): """ Convert to radians + :return: radians :rtype: float """ @@ -143,6 +149,7 @@ def rad(self): def dec(self): """ Convert to Decimal Degrees (float) + :return: Decimal Degrees :rtype: float """ @@ -151,6 +158,7 @@ def dec(self): def hp(self): """ Convert to HP Notation + :return: HP Notation (DDD.MMSSSS) :rtype: float """ @@ -159,6 +167,7 @@ def hp(self): def hpa(self): """ Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) :rtype: HPAngle """ @@ -167,6 +176,7 @@ def hpa(self): def gon(self): """ Convert to Gradians (float) + :return: Gradians :rtype: float """ @@ -175,6 +185,7 @@ def gon(self): def gona(self): """ Convert to Gradians (class) + :return: Gradians :rtype: GONAngle """ @@ -183,6 +194,7 @@ def gona(self): def dms(self): """ Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object :rtype: DMSAngle """ @@ -191,6 +203,7 @@ def dms(self): def ddm(self): """ Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object :rtype: DDMAngle """ @@ -200,77 +213,85 @@ def ddm(self): class HPAngle(object): """ Class for working with angles in Hewlett-Packard (HP) format + Note: GeodePy also supports working with angles in HP format as floats """ + def __init__(self, hp_angle=0.0): """ :param hp_angle: float HP angle """ self.hp_angle = float(hp_angle) - hp_dec_str = f'{self.hp_angle:.17f}'.split('.')[1] + hp_dec_str = f"{self.hp_angle:.17f}".split(".")[1] if int(hp_dec_str[0]) > 5: - raise ValueError(f'Invalid HP Notation: 1st decimal place greater ' - f'than 5: {self.hp_angle}') + raise ValueError( + f"Invalid HP Notation: 1st decimal place greater " + f"than 5: {self.hp_angle}" + ) if len(hp_dec_str) > 2: if int(hp_dec_str[2]) > 5: raise ValueError( - f'Invalid HP Notation: 3rd decimal place greater ' - f'than 5: {self.hp_angle}') + f"Invalid HP Notation: 3rd decimal place greater " + f"than 5: {self.hp_angle}" + ) def __repr__(self): if self.hp_angle >= 0: - return '{HPAngle: +' + str(self.hp_angle) + '}' + return "{HPAngle: +" + str(self.hp_angle) + "}" else: # negative - return '{HPAngle: ' + str(self.hp_angle) + '}' + return "{HPAngle: " + str(self.hp_angle) + "}" def __add__(self, other): try: return HPAngle(dec2hp(self.dec() + other.dec())) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __radd__(self, other): try: return HPAngle(dec2hp(other.dec() + self.dec())) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __sub__(self, other): try: return HPAngle(dec2hp(self.dec() - other.dec())) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __rsub__(self, other): try: return HPAngle(dec2hp(other.dec() - self.dec())) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __mul__(self, other): try: return HPAngle(dec2hp(self.dec() * other)) except TypeError: - raise TypeError('Multiply only defined between Angle objects and ' - 'Int or Float') + raise TypeError( + "Multiply only defined between Angle objects and " "Int or Float" + ) def __rmul__(self, other): try: return HPAngle(dec2hp(other * self.dec())) except TypeError: - raise TypeError('Multiply only defined between Angle objects and ' - 'Int or Float') + raise TypeError( + "Multiply only defined between Angle objects and " "Int or Float" + ) def __truediv__(self, other): try: return HPAngle(dec2hp(self.dec() / other)) except TypeError: - raise TypeError('Division only defined between HPAngle objects ' - 'and Int or Float') + raise TypeError( + "Division only defined between HPAngle objects " "and Int or Float" + ) def __abs__(self): return HPAngle(abs(self.hp_angle)) @@ -305,6 +326,7 @@ def __round__(self, n=None): def rad(self): """ Convert to Radians + :return: Radians :rtype: float """ @@ -313,6 +335,7 @@ def rad(self): def dec(self): """ Convert to Decimal Degrees (float) + :return: Decimal Degrees :rtype: float """ @@ -321,6 +344,7 @@ def dec(self): def deca(self): """ Convert to Decimal Degrees (class) + :return: Decimal Degrees :rtype: DECAngle """ @@ -329,6 +353,7 @@ def deca(self): def hp(self): """ Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) :rtype: float """ @@ -337,6 +362,7 @@ def hp(self): def gon(self): """ Convert to Gradians (float) + :return: Gradians :rtype: float """ @@ -345,6 +371,7 @@ def gon(self): def gona(self): """ Convert to Gradians (class) + :return: Gradians :rtype: GONAngle """ @@ -353,6 +380,7 @@ def gona(self): def dms(self): """ Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object :rtype: DMSAngle """ @@ -361,6 +389,7 @@ def dms(self): def ddm(self): """ Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object :rtype: DDMAngle """ @@ -370,8 +399,10 @@ def ddm(self): class GONAngle(object): """ Class for working with angles in Gradians (90 degrees == 100 Gradians) + Note: GeodePy also supports working with angles in Gradians as floats """ + def __init__(self, gon_angle=0.0): """ :param gon_angle: float Gradian angle @@ -381,58 +412,61 @@ def __init__(self, gon_angle=0.0): def __repr__(self): if self.gon_angle >= 0: - return '{GONAngle: +' + str(self.gon_angle) + '}' + return "{GONAngle: +" + str(self.gon_angle) + "}" else: # negative - return '{GONAngle: ' + str(self.gon_angle) + '}' + return "{GONAngle: " + str(self.gon_angle) + "}" def __add__(self, other): try: return GONAngle(dec2gon(self.dec() + other.dec())) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __radd__(self, other): try: return GONAngle(dec2gon(other.dec() + self.dec())) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __sub__(self, other): try: return GONAngle(dec2gon(self.dec() - other.dec())) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __rsub__(self, other): try: return GONAngle(dec2gon(other.dec() - self.dec())) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __mul__(self, other): try: return GONAngle(dec2gon(self.dec() * other)) except TypeError: - raise TypeError('Multiply only defined between Angle objects and ' - 'Int or Float') + raise TypeError( + "Multiply only defined between Angle objects and " "Int or Float" + ) def __rmul__(self, other): try: return GONAngle(dec2gon(other * self.dec())) except TypeError: - raise TypeError('Multiply only defined between Angle objects and ' - 'Int or Float') + raise TypeError( + "Multiply only defined between Angle objects and " "Int or Float" + ) def __truediv__(self, other): try: return GONAngle(dec2gon(self.dec() / other)) except TypeError: - raise TypeError('Division only defined between HPAngle objects ' - 'and Int or Float') + raise TypeError( + "Division only defined between HPAngle objects " "and Int or Float" + ) def __abs__(self): return GONAngle(abs(self.gon_angle)) @@ -467,6 +501,7 @@ def __round__(self, n=None): def rad(self): """ Convert to Radians + :return: Radians :rtype: float """ @@ -475,6 +510,7 @@ def rad(self): def dec(self): """ Convert to Decimal Degrees (float) + :return: Decimal Degrees :rtype: float """ @@ -483,6 +519,7 @@ def dec(self): def deca(self): """ Convert to Decimal Degrees (class) + :return: Decimal Degrees :rtype: DECAngle """ @@ -491,6 +528,7 @@ def deca(self): def hp(self): """ Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) :rtype: float """ @@ -499,6 +537,7 @@ def hp(self): def hpa(self): """ Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) :rtype: HPAngle """ @@ -507,6 +546,7 @@ def hpa(self): def gon(self): """ Convert to Gradians (float) + :return: Gradians :rtype: float """ @@ -515,6 +555,7 @@ def gon(self): def dms(self): """ Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object :rtype: DMSAngle """ @@ -523,6 +564,7 @@ def dms(self): def ddm(self): """ Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object :rtype: DDMAngle """ @@ -533,8 +575,10 @@ class DMSAngle(object): """ Class for working with angles in Degrees, Minutes and Seconds format """ + def __init__(self, degree, minute=0, second=0.0, positive=None): """ + :param degree: Angle: whole degrees component (floats truncated) Alt: formatted string '±DDD MM SS.SSS' :type degree: float | str @@ -544,9 +588,10 @@ def __init__(self, degree, minute=0, second=0.0, positive=None): :type second: float :param positive: Optional. True is positive, False is negative. Evaluated from deg/min/sec where None :type positive: bool + """ # evaluate sign - if positive is False or str(degree)[0] == '-': + if positive is False or str(degree)[0] == "-": self.positive = False else: self.positive = True @@ -571,60 +616,71 @@ def __init__(self, degree, minute=0, second=0.0, positive=None): def __repr__(self): if self.positive: - signsymbol = '+' + signsymbol = "+" else: - signsymbol = '-' - return '{DMSAngle: ' + signsymbol + str(self.degree) + 'd ' +\ - str(self.minute) + 'm ' + str(self.second) + 's}' + signsymbol = "-" + return ( + "{DMSAngle: " + + signsymbol + + str(self.degree) + + "d " + + str(self.minute) + + "m " + + str(self.second) + + "s}" + ) def __add__(self, other): try: return dec2dms(self.dec() + other.dec()) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __radd__(self, other): try: return dec2dms(other.dec() + self.dec()) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __sub__(self, other): try: return dec2dms(self.dec() - other.dec()) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __rsub__(self, other): try: return dec2dms(other.dec() - self.dec()) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __mul__(self, other): try: return dec2dms(self.dec() * other) except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') + raise TypeError( + "Multiply only defined between DMSAngle Object " "and Int or Float" + ) def __rmul__(self, other): try: return dec2dms(other * self.dec()) except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') + raise TypeError( + "Multiply only defined between DMSAngle Object " "and Int or Float" + ) def __truediv__(self, other): try: return dec2dms(self.dec() / other) except TypeError: - raise TypeError('Division only defined between DMSAngle Object ' - 'and Int or Float') + raise TypeError( + "Division only defined between DMSAngle Object " "and Int or Float" + ) def __abs__(self): return DMSAngle(self.degree, self.minute, self.second) @@ -649,11 +705,11 @@ def __gt__(self, other): def __str__(self): if self.positive: - return (str(self.degree) + ' ' + str(self.minute) + ' ' - + str(self.second)) + return str(self.degree) + " " + str(self.minute) + " " + str(self.second) else: - return ('-' + str(self.degree) + ' ' + str(self.minute) + ' ' - + str(self.second)) + return ( + "-" + str(self.degree) + " " + str(self.minute) + " " + str(self.second) + ) def __round__(self, n=None): if self.positive: @@ -667,6 +723,7 @@ def __mod__(self, other): def rad(self): """ Convert to Radians + :return: Radians :rtype: float """ @@ -675,6 +732,7 @@ def rad(self): def dec(self): """ Convert to Decimal Degrees (float) + :return: Decimal Degrees :rtype: float """ @@ -686,6 +744,7 @@ def dec(self): def deca(self): """ Convert to Decimal Degrees (class) + :return: Decimal Degrees :rtype: DECAngle """ @@ -694,6 +753,7 @@ def deca(self): def hp(self): """ Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) :rtype: float """ @@ -705,6 +765,7 @@ def hp(self): def hpa(self): """ Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) :rtype: HPAngle """ @@ -713,6 +774,7 @@ def hpa(self): def gon(self): """ Convert to Gradians (float) + :return: Gradians :rtype: float """ @@ -721,6 +783,7 @@ def gon(self): def gona(self): """ Convert to Gradians (class) + :return: Gradians :rtype: GONAngle """ @@ -729,19 +792,21 @@ def gona(self): def ddm(self): """ Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object :rtype: DDMAngle """ if self.positive: - return DDMAngle(self.degree, self.minute + (self.second/60)) + return DDMAngle(self.degree, self.minute + (self.second / 60)) else: - return -DDMAngle(self.degree, self.minute + (self.second/60)) + return -DDMAngle(self.degree, self.minute + (self.second / 60)) class DDMAngle(object): """ Class for working with angles in Degrees, Decimal Minutes format """ + def __init__(self, degree, minute=0.0, positive=None): """ :param degree: Angle: whole degrees component (floats truncated) @@ -753,7 +818,7 @@ def __init__(self, degree, minute=0.0, positive=None): """ # evaluate sign - if positive is False or str(degree)[0] == '-': + if positive is False or str(degree)[0] == "-": self.positive = False else: self.positive = True @@ -765,7 +830,7 @@ def __init__(self, degree, minute=0.0, positive=None): # Convert formatted string 'DDD MM.MMMM' to DDMAngle if type(degree) == str: - str_pts = degree.split(' ') + str_pts = degree.split(" ") degree = int(str_pts[0]) minute = float(str_pts[1]) @@ -774,60 +839,69 @@ def __init__(self, degree, minute=0.0, positive=None): def __repr__(self): if self.positive: - signsymbol = '+' + signsymbol = "+" else: - signsymbol = '-' - return '{DDMAngle: ' + signsymbol + str(self.degree) + 'd ' + \ - str(self.minute) + 'm}' + signsymbol = "-" + return ( + "{DDMAngle: " + + signsymbol + + str(self.degree) + + "d " + + str(self.minute) + + "m}" + ) def __add__(self, other): try: return dec2ddm(self.dec() + other.dec()) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __radd__(self, other): try: return dec2ddm(other.dec() + self.dec()) except AttributeError: - raise TypeError('Can only add Angle objects with .dec() method ' - 'together') + raise TypeError("Can only add Angle objects with .dec() method " "together") def __sub__(self, other): try: return dec2ddm(self.dec() - other.dec()) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __rsub__(self, other): try: return dec2ddm(other.dec() - self.dec()) except AttributeError: - raise TypeError('Can only subtract Angle objects with .dec() method' - ' together') + raise TypeError( + "Can only subtract Angle objects with .dec() method" " together" + ) def __mul__(self, other): try: return dec2ddm(self.dec() * other) except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') + raise TypeError( + "Multiply only defined between DMSAngle Object " "and Int or Float" + ) def __rmul__(self, other): try: return dec2ddm(other * self.dec()) except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') + raise TypeError( + "Multiply only defined between DMSAngle Object " "and Int or Float" + ) def __truediv__(self, other): try: return dec2ddm(self.dec() / other) except TypeError: - raise TypeError('Division only defined between DMSAngle Object ' - 'and Int or Float') + raise TypeError( + "Division only defined between DMSAngle Object " "and Int or Float" + ) def __abs__(self): return DDMAngle(self.degree, self.minute) @@ -852,9 +926,9 @@ def __gt__(self, other): def __str__(self): if self.positive: - return str(self.degree) + ' ' + str(self.minute) + return str(self.degree) + " " + str(self.minute) else: - return '-' + str(self.degree) + ' ' + str(self.minute) + return "-" + str(self.degree) + " " + str(self.minute) def __round__(self, n=None): if self.positive: @@ -865,10 +939,10 @@ def __round__(self, n=None): def __mod__(self, other): return dec2ddm(self.dec() % other) - def rad(self): """ Convert to Radians + :return: Radians :rtype: float """ @@ -877,6 +951,7 @@ def rad(self): def dec(self): """ Convert to Decimal Degrees (float) + :return: Decimal Degrees :rtype: float """ @@ -888,6 +963,7 @@ def dec(self): def deca(self): """ Convert to Decimal Degrees (class) + :return: Decimal Degrees :rtype: DECAngle """ @@ -896,6 +972,7 @@ def deca(self): def hp(self): """ Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) :rtype: float """ @@ -908,6 +985,7 @@ def hp(self): def hpa(self): """ Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) :rtype: HPAngle """ @@ -916,6 +994,7 @@ def hpa(self): def gon(self): """ Convert to Gradians (float) + :return: Gradians :rtype: float """ @@ -924,6 +1003,7 @@ def gon(self): def gona(self): """ Convert to Gradians (class) + :return: Gradians :rtype: GONAngle """ @@ -932,6 +1012,7 @@ def gona(self): def dms(self): """ Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object :rtype: DMSAngle """ @@ -944,6 +1025,7 @@ def dms(self): # Functions converting from Decimal Degrees (float) to other formats + def dec2hp(dec): """ Converts Decimal Degrees to HP Notation (float) @@ -951,33 +1033,34 @@ def dec2hp(dec): :type dec: float :return: HP Notation (DDD.MMSSSS) :rtype: float - """ + """ minute, second = divmod(abs(dec) * 3600, 60) degree, minute = divmod(minute, 60) # floating point precision is 13 places for the variable 'dec' where values # are between 256 and 512 degrees. Precision improves for smaller angles. - # In calculating the variable 'second' the precision is degraded by a factor of 3600 + # In calculating the variable 'second' the precision is degraded by a factor of 3600 # Therefore 'second' should be rounded to 9 DP and tested for carry. if round(second, 9) == 60: second = 0 minute += 1 - + # to avoid precision issues with floating point operations # a string will be built to represent a sexagesimal number and then converted to float - degree = f'{int(degree)}' - minute = f'{int(minute):02}' - second = f'{second:012.9f}'.rstrip('0').replace('.', '') - - hp_string = f'{degree}.{minute}{second}' + degree = f"{int(degree)}" + minute = f"{int(minute):02}" + second = f"{second:012.9f}".rstrip("0").replace(".", "") + + hp_string = f"{degree}.{minute}{second}" hp = float(hp_string) - + return hp if dec >= 0 else -hp def dec2hpa(dec): """ Converts Decimal Degrees to HP Angle Object + :param dec: Decimal Degrees :type dec: float :return: HP Angle Object (DDD.MMSSSS) @@ -989,17 +1072,19 @@ def dec2hpa(dec): def dec2gon(dec): """ Converts Decimal Degrees to Gradians + :param dec: Decimal Degrees :type dec: float :return: Gradians :rtype: float """ - return 10/9 * dec + return 10 / 9 * dec def dec2gona(dec): """ Converts Decimal Degrees to Gradians (class) + :param dec: Decimal Degrees :type dec: float :return: Gradians @@ -1011,6 +1096,7 @@ def dec2gona(dec): def dec2dms(dec): """ Converts Decimal Degrees to Degrees, Minutes, Seconds Object + :param dec: Decimal Degrees :type dec: float :return: Degrees, Minutes, Seconds Object @@ -1018,13 +1104,17 @@ def dec2dms(dec): """ minute, second = divmod(abs(dec) * 3600, 60) degree, minute = divmod(minute, 60) - return (DMSAngle(degree, minute, second, positive=True) if dec >= 0 - else DMSAngle(degree, minute, second, positive=False)) + return ( + DMSAngle(degree, minute, second, positive=True) + if dec >= 0 + else DMSAngle(degree, minute, second, positive=False) + ) def dec2ddm(dec): """ Converts Decimal Degrees to Degrees, Decimal Minutes Object + :param dec: Decimal Degrees :type dec: float :return: Degrees, Decimal Minutes Object @@ -1033,14 +1123,20 @@ def dec2ddm(dec): minute, second = divmod(abs(dec) * 3600, 60) degree, minute = divmod(minute, 60) minute = minute + (second / 60) - return DDMAngle(degree, minute, positive=True) if dec >= 0 else DDMAngle(degree, minute, positive=False) + return ( + DDMAngle(degree, minute, positive=True) + if dec >= 0 + else DDMAngle(degree, minute, positive=False) + ) # Functions converting from Hewlett-Packard (HP) format to other formats + def hp2dec(hp): """ Converts HP Notation to Decimal Degrees + :param hp: HP Notation (DDD.MMSSSS) :type hp: float :return: Decimal Degrees @@ -1048,18 +1144,20 @@ def hp2dec(hp): """ # Check if 1st and 3rd decimal place greater than 5 (invalid HP Notation) hp = float(hp) - hp_deg_str, hp_mmss_str = f'{hp:.13f}'.split('.') + hp_deg_str, hp_mmss_str = f"{hp:.13f}".split(".") if int(hp_mmss_str[0]) > 5: - raise ValueError(f'Invalid HP Notation: 1st decimal place greater ' - f'than 5: {hp}') + raise ValueError( + f"Invalid HP Notation: 1st decimal place greater " f"than 5: {hp}" + ) if len(hp_mmss_str) > 2: if int(hp_mmss_str[2]) > 5: - raise ValueError(f'Invalid HP Notation: 3rd decimal place greater ' - f'than 5: {hp}') + raise ValueError( + f"Invalid HP Notation: 3rd decimal place greater " f"than 5: {hp}" + ) # parse string to avoid precision problems with floating point ops and base 10 numbers deg = abs(int(hp_deg_str)) min = int(hp_mmss_str[:2]) - sec = float(hp_mmss_str[2:4] + '.' + hp_mmss_str[4:]) + sec = float(hp_mmss_str[2:4] + "." + hp_mmss_str[4:]) dec = sec / 3600 + min / 60 + deg return dec if hp >= 0 else -dec @@ -1068,6 +1166,7 @@ def hp2dec(hp): def hp2deca(hp): """ Converts HP Notation to DECAngle Object + :param hp: HP Notation (DDD.MMSSSS) :type hp: float :return: Decimal Degrees Object @@ -1079,6 +1178,7 @@ def hp2deca(hp): def hp2rad(hp): """ Converts HP Notation to radians + :param hp: HP Notation (DDD.MMSSSS) :type hp: float :return: radians @@ -1090,6 +1190,7 @@ def hp2rad(hp): def hp2gon(hp): """ Converts HP Notation to Gradians + :param hp: HP Notation (DDD.MMSSSS) :type hp: float :return: Gradians @@ -1101,6 +1202,7 @@ def hp2gon(hp): def hp2gona(hp): """ Converts HP Notation to Gradians (class) + :param hp: HP Notation (DDD.MMSSSS) :type hp: float :return: Gradians @@ -1112,6 +1214,7 @@ def hp2gona(hp): def hp2dms(hp): """ Converts HP Notation to Degrees, Minutes, Seconds Object + :param hp: HP Notation (DDD.MMSSSS) :type hp: float :return: Degrees, Minutes, Seconds Object @@ -1119,13 +1222,17 @@ def hp2dms(hp): """ degmin, second = divmod(abs(hp) * 1000, 10) degree, minute = divmod(degmin, 100) - return (DMSAngle(degree, minute, second * 10, positive=True) if hp >= 0 - else DMSAngle(degree, minute, second * 10, positive=False)) + return ( + DMSAngle(degree, minute, second * 10, positive=True) + if hp >= 0 + else DMSAngle(degree, minute, second * 10, positive=False) + ) def hp2ddm(hp): """ Converts HP Notation to Degrees, Decimal Minutes Object + :param hp: HP Notation (DDD.MMSSSS) :type hp: float :return: Degrees, Decimal Minutes Object @@ -1134,25 +1241,32 @@ def hp2ddm(hp): degmin, second = divmod(abs(hp) * 1000, 10) degree, minute = divmod(degmin, 100) minute = minute + (second / 6) - return DDMAngle(degree, minute, positive=True) if hp >= 0 else DDMAngle(degree, minute, positive=False) + return ( + DDMAngle(degree, minute, positive=True) + if hp >= 0 + else DDMAngle(degree, minute, positive=False) + ) # Functions converting from Gradians format to other formats + def gon2dec(gon): """ Converts Gradians to Decimal Degrees + :param gon: Gradians :type gon: float :return: Decimal Degrees :rtype: float """ - return 9/10 * gon + return 9 / 10 * gon def gon2deca(gon): """ Converts Gradians to DECAngle Object + :param gon: Gradians :type gon: float :return: Decimal Degrees Object @@ -1164,6 +1278,7 @@ def gon2deca(gon): def gon2hp(gon): """ Converts Gradians to HP Notation (float) + :param gon: Gradians :type gon: float :return: HP Notation (DDD.MMSSSS) @@ -1175,6 +1290,7 @@ def gon2hp(gon): def gon2hpa(gon): """ Converts Gradians to HP Angle Object + :param gon: Gradians :type gon: float :return: HP Angle Object (DDD.MMSSSS) @@ -1186,6 +1302,7 @@ def gon2hpa(gon): def gon2rad(gon): """ Converts Gradians to radians + :param gon: Gradians :type gon: float :return: Radians @@ -1197,6 +1314,7 @@ def gon2rad(gon): def gon2dms(gon): """ Converts Gradians to Degrees, Minutes, Seconds Object + :param gon: Gradians :type gon: float :return: Degrees, Minutes, Seconds Object @@ -1208,6 +1326,7 @@ def gon2dms(gon): def gon2ddm(gon): """ Converts Gradians to Degrees, Decimal Minutes Object + :param gon: Gradians :type gon: float :return: Degrees, Decimal Minutes Object @@ -1218,9 +1337,11 @@ def gon2ddm(gon): # Miscellaneous other useful functions + def dd2sec(dd): """ Converts angle in decimal degrees to angle in seconds + :param dd: Decimal Degrees :return: Seconds """ @@ -1247,7 +1368,14 @@ def hp2dec_v(hp): def angular_typecheck(angle): - # Converts Angle Objects to Decimal Degrees (float) for computations + """ + Converts Angle Objects to Decimal Degrees (float) for computations or returns + float input as-is. + + :param angle: Angle Object or float + :return: Decimal Degrees + :rtype: float + """ supported_types = [DMSAngle, DDMAngle, DECAngle, HPAngle, GONAngle] if type(angle) in supported_types: return angle.dec() diff --git a/geodepy/constants.py b/geodepy/constants.py index bc6e536..bb9cf34 100644 --- a/geodepy/constants.py +++ b/geodepy/constants.py @@ -15,6 +15,13 @@ # Ellipsoid constants class Ellipsoid(object): def __init__(self, semimaj, inversef): + ''' + Ellipsoid Parameters + + :param semimaj: Semi-major axis (m) + :param inversef: Inverse flattening + + ''' self.semimaj = semimaj self.inversef = inversef self.f = 1 / self.inversef @@ -23,8 +30,8 @@ def __init__(self, semimaj, inversef): self.ecc2sq = float(self.ecc1sq / (1 - self.ecc1sq)) self.ecc1 = sqrt(self.ecc1sq) self.n = float(self.f / (2 - self.f)) - self.n2 = self.n ** 2 - self.meanradius = (2 * self.semimaj + self.semimin)/3 + self.n2 = self.n**2 + self.meanradius = (2 * self.semimaj + self.semimin) / 3 # Geodetic Reference System 1980 @@ -52,6 +59,7 @@ class Projection(object): def __init__(self, falseeast, falsenorth, cmscale, zonewidth, initialcm): """ Transverse Mercator Projection Parameters + :param falseeast: Easting (m) assigned to Central Meridian :param falsenorth: Northing (m) assigned to Equator :param cmscale: Central Meridian Scale Factor (unitless, 1 is no scale) @@ -75,48 +83,125 @@ def __init__(self, falseeast, falsenorth, cmscale, zonewidth, initialcm): # Helmert 14 parameter transformation class Transformation(object): - def __init__(self, from_datum, to_datum, ref_epoch, - tx, ty, tz, sc, rx, ry, rz, - d_tx=0.0, d_ty=0.0, d_tz=0.0, d_sc=0.0, - d_rx=0.0, d_ry=0.0, d_rz=0.0, tf_sd=None): - self.from_datum = from_datum # Datum transforming from - self.to_datum = to_datum # Datum transforming to - self.ref_epoch = ref_epoch # Ref. epoch (datetime.date object) - self.tx = tx # Translation in X (m) - self.ty = ty # Translation in Y (m) - self.tz = tz # Translation in Z (m) - self.sc = sc # Scale factor (ppm) - self.rx = rx # Rotation about X (arcsec) - self.ry = ry # Rotation about Y (arcsec) - self.rz = rz # Rotation about Z (arcsec) - self.d_tx = d_tx # Translation rate of change in X (m/yr) - self.d_ty = d_ty # Translation rate of change in Y (m/yr) - self.d_tz = d_tz # Translation rate of change in Z (m/yr) - self.d_sc = d_sc # Scale factor rate of change (ppm/yr) - self.d_rx = d_rx # Rot rate of change about X (arcsec/yr) - self.d_ry = d_ry # Rot rate of change about Y (arcsec/yr) - self.d_rz = d_rz # Rot rate of change about Z (arcsec/yr) - self.tf_sd = tf_sd # TransformationSD object + def __init__( + + self, + from_datum, + to_datum, + ref_epoch, + tx, + ty, + tz, + sc, + rx, + ry, + rz, + d_tx=0.0, + d_ty=0.0, + d_tz=0.0, + d_sc=0.0, + d_rx=0.0, + d_ry=0.0, + d_rz=0.0, + tf_sd=None, + ): + """ + Transformation Parameters + + :param from_datum: Datum transforming from + :param to_datum: Datum transforming to + :param ref_epoch: Ref. epoch + :type ref_epoch: datetime.date + :param tx: Translation in X (m) + :param ty: Translation in Y (m) + :param tz: Translation in Z (m) + :param sc: Scale factor (ppm) + :param rx: Rotation about X (arcsec) + :param ry: Rotation about Y (arcsec) + :param rz: Rotation about Z (arcsec) + :param d_tx: Translation rate of change in X (m/yr) + :param d_ty: Translation rate of change in Y (m/yr) + :param d_tz: Translation rate of change in Z (m/yr) + :param d_sc: Scale factor rate of change (ppm/yr) + :param d_rx: Rotate rate of change about X (arcsec/yr) + :param d_ry: Rotate rate of change about Y (arcsec/yr) + :param d_rz: Rotate rate of change about Z (arcsec/yr) + :param tf_sd: TransformationSD object + :type tf_sd: TransformationSD + """ + self.from_datum = from_datum # Datum transforming from + self.to_datum = to_datum # Datum transforming to + self.ref_epoch = ref_epoch # Ref. epoch (datetime.date object) + self.tx = tx # Translation in X (m) + self.ty = ty # Translation in Y (m) + self.tz = tz # Translation in Z (m) + self.sc = sc # Scale factor (ppm) + self.rx = rx # Rotation about X (arcsec) + self.ry = ry # Rotation about Y (arcsec) + self.rz = rz # Rotation about Z (arcsec) + self.d_tx = d_tx # Translation rate of change in X (m/yr) + self.d_ty = d_ty # Translation rate of change in Y (m/yr) + self.d_tz = d_tz # Translation rate of change in Z (m/yr) + self.d_sc = d_sc # Scale factor rate of change (ppm/yr) + self.d_rx = d_rx # Rot rate of change about X (arcsec/yr) + self.d_ry = d_ry # Rot rate of change about Y (arcsec/yr) + self.d_rz = d_rz # Rot rate of change about Z (arcsec/yr) + self.tf_sd = tf_sd # TransformationSD object def __repr__(self): - return ('Transformation: ' - + 'From ' + repr(self.from_datum) + - ' to ' + repr(self.to_datum) + '\n' - + 'Reference Epoch: ' + repr(self.ref_epoch) + '\n' - + ' tx: ' + repr(self.tx) + 'm + ' + repr(self.d_tx) + - 'm/yr' + '\n' - + ' ty: ' + repr(self.ty) + 'm + ' + repr(self.d_ty) + - 'm/yr' + '\n' - + ' tz: ' + repr(self.tz) + 'm + ' + repr(self.d_tz) + - 'm/yr' + '\n' - + ' sc: ' + repr(self.sc) + 'ppm + ' + repr(self.d_sc) + - 'ppm/yr' + '\n' - + ' rx: ' + repr(self.rx) + '\" + ' + repr(self.d_rx) + - '\"/yr' + '\n' - + ' ry: ' + repr(self.ry) + '\" + ' + repr(self.d_ry) + - '\"/yr' + '\n' - + ' rz: ' + repr(self.rz) + '\" + ' + repr(self.d_rz) + - '\"/yr' + '\n') + return ( + "Transformation: " + + "From " + + repr(self.from_datum) + + " to " + + repr(self.to_datum) + + "\n" + + "Reference Epoch: " + + repr(self.ref_epoch) + + "\n" + + " tx: " + + repr(self.tx) + + "m + " + + repr(self.d_tx) + + "m/yr" + + "\n" + + " ty: " + + repr(self.ty) + + "m + " + + repr(self.d_ty) + + "m/yr" + + "\n" + + " tz: " + + repr(self.tz) + + "m + " + + repr(self.d_tz) + + "m/yr" + + "\n" + + " sc: " + + repr(self.sc) + + "ppm + " + + repr(self.d_sc) + + "ppm/yr" + + "\n" + + " rx: " + + repr(self.rx) + + '" + ' + + repr(self.d_rx) + + '"/yr' + + "\n" + + " ry: " + + repr(self.ry) + + '" + ' + + repr(self.d_ry) + + '"/yr' + + "\n" + + " rz: " + + repr(self.rz) + + '" + ' + + repr(self.d_rz) + + '"/yr' + + "\n" + ) def __neg__(self): """ @@ -126,17 +211,26 @@ def __neg__(self): :return: reversed direction transformation object """ - return Transformation(self.to_datum, - self.from_datum, - self.ref_epoch, - -self.tx, -self.ty, -self.tz, - -self.sc, - -self.rx, -self.ry, -self.rz, - -self.d_tx, -self.d_ty, -self.d_tz, - -self.d_sc, - -self.d_rx, -self.d_ry, -self.d_rz, - tf_sd=self.tf_sd - ) + return Transformation( + self.to_datum, + self.from_datum, + self.ref_epoch, + -self.tx, + -self.ty, + -self.tz, + -self.sc, + -self.rx, + -self.ry, + -self.rz, + -self.d_tx, + -self.d_ty, + -self.d_tz, + -self.d_sc, + -self.d_rx, + -self.d_ry, + -self.d_rz, + tf_sd=self.tf_sd, + ) def __add__(self, other): """ @@ -145,70 +239,135 @@ def __add__(self, other): :param other: datetime.date object :return: transformation object with parameters and ref epoch moved to - the specified date + the specified date """ if type(other) == date: - timediff = (other - self.ref_epoch).days/365.25 + timediff = (other - self.ref_epoch).days / 365.25 if type(self.tf_sd) == TransformationSD: - self.tf_sd.sd_tx = (self.tf_sd.sd_tx**2 + (self.tf_sd.sd_d_tx * timediff)**2) ** 0.5 - self.tf_sd.sd_ty = (self.tf_sd.sd_ty**2 + (self.tf_sd.sd_d_ty * timediff)**2) ** 0.5 - self.tf_sd.sd_tz = (self.tf_sd.sd_tz**2 + (self.tf_sd.sd_d_tz * timediff)**2) ** 0.5 - self.tf_sd.sd_sc = (self.tf_sd.sd_sc**2 + (self.tf_sd.sd_d_sc * timediff)**2) ** 0.5 - self.tf_sd.sd_rx = (self.tf_sd.sd_rx**2 + (self.tf_sd.sd_d_rx * timediff)**2) ** 0.5 - self.tf_sd.sd_ry = (self.tf_sd.sd_ry**2 + (self.tf_sd.sd_d_ry * timediff)**2) ** 0.5 - self.tf_sd.sd_rz = (self.tf_sd.sd_rz**2 + (self.tf_sd.sd_d_rz * timediff)**2) ** 0.5 - - return Transformation(self.to_datum, - self.from_datum, - other, - round(self.tx + (self.d_tx * timediff), 8), - round(self.ty + (self.d_ty * timediff), 8), - round(self.tz + (self.d_tz * timediff), 8), - round(self.sc + (self.d_sc * timediff), 8), - round(self.rx + (self.d_rx * timediff), 8), - round(self.ry + (self.d_ry * timediff), 8), - round(self.rz + (self.d_rz * timediff), 8), - self.d_tx, - self.d_ty, - self.d_tz, - self.d_sc, - self.d_rx, - self.d_ry, - self.d_rz, - self.tf_sd - ) + self.tf_sd.sd_tx = ( + self.tf_sd.sd_tx**2 + (self.tf_sd.sd_d_tx * timediff) ** 2 + ) ** 0.5 + self.tf_sd.sd_ty = ( + self.tf_sd.sd_ty**2 + (self.tf_sd.sd_d_ty * timediff) ** 2 + ) ** 0.5 + self.tf_sd.sd_tz = ( + self.tf_sd.sd_tz**2 + (self.tf_sd.sd_d_tz * timediff) ** 2 + ) ** 0.5 + self.tf_sd.sd_sc = ( + self.tf_sd.sd_sc**2 + (self.tf_sd.sd_d_sc * timediff) ** 2 + ) ** 0.5 + self.tf_sd.sd_rx = ( + self.tf_sd.sd_rx**2 + (self.tf_sd.sd_d_rx * timediff) ** 2 + ) ** 0.5 + self.tf_sd.sd_ry = ( + self.tf_sd.sd_ry**2 + (self.tf_sd.sd_d_ry * timediff) ** 2 + ) ** 0.5 + self.tf_sd.sd_rz = ( + self.tf_sd.sd_rz**2 + (self.tf_sd.sd_d_rz * timediff) ** 2 + ) ** 0.5 + + return Transformation( + self.to_datum, + self.from_datum, + other, + round(self.tx + (self.d_tx * timediff), 8), + round(self.ty + (self.d_ty * timediff), 8), + round(self.tz + (self.d_tz * timediff), 8), + round(self.sc + (self.d_sc * timediff), 8), + round(self.rx + (self.d_rx * timediff), 8), + round(self.ry + (self.d_ry * timediff), 8), + round(self.rz + (self.d_rz * timediff), 8), + self.d_tx, + self.d_ty, + self.d_tz, + self.d_sc, + self.d_rx, + self.d_ry, + self.d_rz, + self.tf_sd, + ) else: - ValueError('supports adding datetime.date objects only') + ValueError("supports adding datetime.date objects only") class TransformationSD(object): - def __init__(self, sd_tx=None, sd_ty=None, sd_tz=None, sd_sc=None, - sd_rx=None, sd_ry=None, sd_rz=None, - sd_d_tx=None, sd_d_ty=None, sd_d_tz=None, sd_d_sc=None, - sd_d_rx=None, sd_d_ry=None, sd_d_rz=None): - self.sd_tx = sd_tx # one-sigma uncertainty of tx (m) - self.sd_ty = sd_ty # one-sigma uncertainty of ty (m) - self.sd_tz = sd_tz # one-sigma uncertainty of tz (m) - self.sd_sc = sd_sc # one-sigma uncertainty of sc (ppm) - self.sd_rx = sd_rx # one-sigma uncertainty of rx (arcsec/yr) - self.sd_ry = sd_ry # one-sigma uncertainty of ry (arcsec/yr) - self.sd_rz = sd_rz # one-sigma uncertainty of rz (arcsec/yr) - self.sd_d_tx = sd_d_tx # one-sigma uncertainty of d_tx (m/yr) - self.sd_d_ty = sd_d_ty # one-sigma uncertainty of d_ty (m/yr) - self.sd_d_tz = sd_d_tz # one-sigma uncertainty of d_tz (m/yr) - self.sd_d_sc = sd_d_sc # one-sigma uncertainty of d_sc (ppm/yr) - self.sd_d_rx = sd_d_rx # one-sigma uncertainty of d_rx (arcsec/yr) - self.sd_d_ry = sd_d_ry # one-sigma uncertainty of d_ry (arcsec/yr) - self.sd_d_rz = sd_d_rz # one-sigma uncertainty of d_rz (arcsec/yr) - - -def iers2trans(itrf_from, itrf_to, ref_epoch, tx, ty, tz, sc, rx, ry, rz, - d_tx, d_ty, d_tz, d_sc, d_rx, d_ry, d_rz): + def __init__( + self, + sd_tx=None, + sd_ty=None, + sd_tz=None, + sd_sc=None, + sd_rx=None, + sd_ry=None, + sd_rz=None, + sd_d_tx=None, + sd_d_ty=None, + sd_d_tz=None, + sd_d_sc=None, + sd_d_rx=None, + sd_d_ry=None, + sd_d_rz=None, + ): + ''' + Transformation Standard Deviation Parameters + + :param sd_tx: one-sigma uncertainty of tx (m) + :param sd_ty: one-sigma uncertainty of ty (m) + :param sd_tz: one-sigma uncertainty of tz (m) + :param sd_sc: one-sigma uncertainty of sc (ppm) + :param sd_rx: one-sigma uncertainty of rx (arcsec/yr) + :param sd_ry: one-sigma uncertainty of ry (arcsec/yr) + :param sd_rz: one-sigma uncertainty of rz (arcsec/yr) + :param sd_d_tx: one-sigma uncertainty of d_tx (m/yr) + :param sd_d_ty: one-sigma uncertainty of d_ty (m/yr) + :param sd_d_tz: one-sigma uncertainty of d_tz (m/yr) + :param sd_d_sc: one-sigma uncertainty of d_sc (ppm/yr) + :param sd_d_rx: one-sigma uncertainty of d_rx (arcsec/yr) + :param sd_d_ry: one-sigma uncertainty of d_ry (arcsec/yr) + :param sd_d_rz: one-sigma uncertainty of d_rz (arcsec/yr) + + ''' + self.sd_tx = sd_tx # one-sigma uncertainty of tx (m) + self.sd_ty = sd_ty # one-sigma uncertainty of ty (m) + self.sd_tz = sd_tz # one-sigma uncertainty of tz (m) + self.sd_sc = sd_sc # one-sigma uncertainty of sc (ppm) + self.sd_rx = sd_rx # one-sigma uncertainty of rx (arcsec/yr) + self.sd_ry = sd_ry # one-sigma uncertainty of ry (arcsec/yr) + self.sd_rz = sd_rz # one-sigma uncertainty of rz (arcsec/yr) + self.sd_d_tx = sd_d_tx # one-sigma uncertainty of d_tx (m/yr) + self.sd_d_ty = sd_d_ty # one-sigma uncertainty of d_ty (m/yr) + self.sd_d_tz = sd_d_tz # one-sigma uncertainty of d_tz (m/yr) + self.sd_d_sc = sd_d_sc # one-sigma uncertainty of d_sc (ppm/yr) + self.sd_d_rx = sd_d_rx # one-sigma uncertainty of d_rx (arcsec/yr) + self.sd_d_ry = sd_d_ry # one-sigma uncertainty of d_ry (arcsec/yr) + self.sd_d_rz = sd_d_rz # one-sigma uncertainty of d_rz (arcsec/yr) + + +def iers2trans( + itrf_from, + itrf_to, + ref_epoch, + tx, + ty, + tz, + sc, + rx, + ry, + rz, + d_tx, + d_ty, + d_tz, + d_sc, + d_rx, + d_ry, + d_rz, +): """ Used to convert IERS transformation parameters into GeodePy Transformation class parameters. Note: All rotation and delta rotation terms have the sign change applied. + :param itrf_from: ITRF realization transforming from :param itrf_to: ITRF realization transforming to :param ref_epoch: Reference epoch (YYYY.DOY) @@ -228,14 +387,25 @@ class parameters. :param d_rz: Rate of change in Rotation about X (milliarcsec/yr) :return: Transformation object following the Australian convention """ - return Transformation(itrf_from, itrf_to, ref_epoch, - round(tx / 1000, 8), round(ty / 1000, 8), - round(tz / 1000, 8), round(sc / 1000, 8), - round(-rx / 1000, 8), round(-ry / 1000, 8), - round(-rz / 1000, 8), round(d_tx / 1000, 8), - round(d_ty / 1000, 8), round(d_tz / 1000, 8), - round(d_sc / 1000, 8), round(-d_rx / 1000, 8), - round(-d_ry / 1000, 8), round(-d_rz / 1000, 8)) + return Transformation( + itrf_from, + itrf_to, + ref_epoch, + round(tx / 1000, 8), + round(ty / 1000, 8), + round(tz / 1000, 8), + round(sc / 1000, 8), + round(-rx / 1000, 8), + round(-ry / 1000, 8), + round(-rz / 1000, 8), + round(d_tx / 1000, 8), + round(d_ty / 1000, 8), + round(d_tz / 1000, 8), + round(d_sc / 1000, 8), + round(-d_rx / 1000, 8), + round(-d_ry / 1000, 8), + round(-d_rz / 1000, 8), + ) """ @@ -271,16 +441,28 @@ class parameters. # Ref: https://www.icsm.gov.au/gda2020-and-gda94-technical-manuals gda94_to_gda2020_sd = TransformationSD( - sd_tx=0.0007, sd_ty=0.0006, sd_tz=0.0007, + sd_tx=0.0007, + sd_ty=0.0006, + sd_tz=0.0007, sd_sc=0.00010, - sd_rx=0.000011, sd_ry=0.000010, sd_rz=0.000011,) + sd_rx=0.000011, + sd_ry=0.000010, + sd_rz=0.000011, +) gda94_to_gda2020 = Transformation( - from_datum='GDA94', to_datum='GDA2020', ref_epoch=0, - tx=0.06155, ty=-0.01087, tz=-0.04019, + from_datum="GDA94", + to_datum="GDA2020", + ref_epoch=0, + tx=0.06155, + ty=-0.01087, + tz=-0.04019, sc=-0.009994, - rx=-0.0394924, ry=-0.0327221, rz=-0.0328979, - tf_sd=gda94_to_gda2020_sd) + rx=-0.0394924, + ry=-0.0327221, + rz=-0.0328979, + tf_sd=gda94_to_gda2020_sd, +) gda2020_to_gda94 = -gda94_to_gda2020 @@ -293,19 +475,42 @@ class parameters. # Ref: https://www.legislation.gov.au/Details/F2017L01352 itrf2014_to_gda2020_sd = TransformationSD( - sd_tx=0.00, sd_ty=0.00, sd_tz=0.00, + sd_tx=0.00, + sd_ty=0.00, + sd_tz=0.00, sd_sc=0.00, - sd_rx=0.00, sd_ry=0.00, sd_rz=0.00, - sd_d_tx=0.00, sd_d_ty=0.00, sd_d_tz=0.00, + sd_rx=0.00, + sd_ry=0.00, + sd_rz=0.00, + sd_d_tx=0.00, + sd_d_ty=0.00, + sd_d_tz=0.00, sd_d_sc=0.00, - sd_d_rx=0.00000417, sd_d_ry=0.00000401, sd_d_rz=0.00000370) + sd_d_rx=0.00000417, + sd_d_ry=0.00000401, + sd_d_rz=0.00000370, +) itrf2014_to_gda2020 = Transformation( - from_datum='ITRF2014', to_datum='GDA2020', ref_epoch=date(2020, 1, 1), - tx=0, ty=0, tz=0, sc=0, rx=0, ry=0, rz=0, - d_tx=0, d_ty=0, d_tz=0, d_sc=0, - d_rx=0.00150379, d_ry=0.00118346, d_rz=0.00120716, - tf_sd=itrf2014_to_gda2020_sd) + from_datum="ITRF2014", + to_datum="GDA2020", + ref_epoch=date(2020, 1, 1), + tx=0, + ty=0, + tz=0, + sc=0, + rx=0, + ry=0, + rz=0, + d_tx=0, + d_ty=0, + d_tz=0, + d_sc=0, + d_rx=0.00150379, + d_ry=0.00118346, + d_rz=0.00120716, + tf_sd=itrf2014_to_gda2020_sd, +) gda2020_to_itrf2014 = -itrf2014_to_gda2020 @@ -315,19 +520,42 @@ class parameters. # Ref: https://www.icsm.gov.au/australian-terrestrial-reference-frame atrf2014_to_gda2020_sd = TransformationSD( - sd_tx=0.00, sd_ty=0.00, sd_tz=0.00, + sd_tx=0.00, + sd_ty=0.00, + sd_tz=0.00, sd_sc=0.00, - sd_rx=0.00, sd_ry=0.00, sd_rz=0.00, - sd_d_tx=0.00, sd_d_ty=0.00, sd_d_tz=0.00, + sd_rx=0.00, + sd_ry=0.00, + sd_rz=0.00, + sd_d_tx=0.00, + sd_d_ty=0.00, + sd_d_tz=0.00, sd_d_sc=0.00, - sd_d_rx=0.00000417, sd_d_ry=0.00000401, sd_d_rz=0.00000370) + sd_d_rx=0.00000417, + sd_d_ry=0.00000401, + sd_d_rz=0.00000370, +) atrf2014_to_gda2020 = Transformation( - from_datum='ATRF2014', to_datum='GDA2020', ref_epoch=date(2020, 1, 1), - tx=0, ty=0, tz=0, sc=0, rx=0, ry=0, rz=0, - d_tx=0, d_ty=0, d_tz=0, d_sc=0, - d_rx=0.00150379, d_ry=0.00118346, d_rz=0.00120716, - tf_sd=atrf2014_to_gda2020_sd) + from_datum="ATRF2014", + to_datum="GDA2020", + ref_epoch=date(2020, 1, 1), + tx=0, + ty=0, + tz=0, + sc=0, + rx=0, + ry=0, + rz=0, + d_tx=0, + d_ty=0, + d_tz=0, + d_sc=0, + d_rx=0.00150379, + d_ry=0.00118346, + d_rz=0.00120716, + tf_sd=atrf2014_to_gda2020_sd, +) # GDA94 to ITRF transformation parameters [Dawson and Woods (2010)] @@ -336,131 +564,273 @@ class parameters. # Transformation Standard Deviations itrf2008_to_gda94_sd = TransformationSD( - sd_tx=0.00091, sd_ty=0.00078, sd_tz=0.00106, + sd_tx=0.00091, + sd_ty=0.00078, + sd_tz=0.00106, sd_sc=0.000126, - sd_rx=0.0000221, sd_ry=0.0000236, sd_rz=0.0000194, - sd_d_tx=0.00008, sd_d_ty=0.00007, sd_d_tz=0.00011, + sd_rx=0.0000221, + sd_ry=0.0000236, + sd_rz=0.0000194, + sd_d_tx=0.00008, + sd_d_ty=0.00007, + sd_d_tz=0.00011, sd_d_sc=0.000013, - sd_d_rx=0.0000028, sd_d_ry=0.0000030, sd_d_rz=0.0000023) + sd_d_rx=0.0000028, + sd_d_ry=0.0000030, + sd_d_rz=0.0000023, +) itrf2005_to_gda94_sd = TransformationSD( - sd_tx=0.00256, sd_ty=0.00187, sd_tz=0.00337, + sd_tx=0.00256, + sd_ty=0.00187, + sd_tz=0.00337, sd_sc=0.000227, - sd_rx=0.0000883, sd_ry=0.0000972, sd_rz=0.0000600, - sd_d_tx=0.00028, sd_d_ty=0.00020, sd_d_tz=0.00036, + sd_rx=0.0000883, + sd_ry=0.0000972, + sd_rz=0.0000600, + sd_d_tx=0.00028, + sd_d_ty=0.00020, + sd_d_tz=0.00036, sd_d_sc=0.000022, - sd_d_rx=0.0000096, sd_d_ry=0.0000106, sd_d_rz=0.0000070) + sd_d_rx=0.0000096, + sd_d_ry=0.0000106, + sd_d_rz=0.0000070, +) itrf2000_to_gda94_sd = TransformationSD( - sd_tx=0.00874, sd_ty=0.00412, sd_tz=0.01105, + sd_tx=0.00874, + sd_ty=0.00412, + sd_tz=0.01105, sd_sc=0.000423, - sd_rx=0.0002852, sd_ry=0.0003602, sd_rz=0.0001567, - sd_d_tx=0.00166, sd_d_ty=0.00083, sd_d_tz=0.00209, + sd_rx=0.0002852, + sd_ry=0.0003602, + sd_rz=0.0001567, + sd_d_tx=0.00166, + sd_d_ty=0.00083, + sd_d_tz=0.00209, sd_d_sc=0.000076, - sd_d_rx=0.0000537, sd_d_ry=0.0000677, sd_d_rz=0.0000317) + sd_d_rx=0.0000537, + sd_d_ry=0.0000677, + sd_d_rz=0.0000317, +) itrf97_to_gda94_sd = TransformationSD( - sd_tx=0.01107, sd_ty=0.00574, sd_tz=0.01427, + sd_tx=0.01107, + sd_ty=0.00574, + sd_tz=0.01427, sd_sc=0.000425, - sd_rx=0.0003757, sd_ry=0.0004642, sd_rz=0.0001955, - sd_d_tx=0.00276, sd_d_ty=0.00136, sd_d_tz=0.00371, + sd_rx=0.0003757, + sd_ry=0.0004642, + sd_rz=0.0001955, + sd_d_tx=0.00276, + sd_d_ty=0.00136, + sd_d_tz=0.00371, sd_d_sc=0.000101, - sd_d_rx=0.0000967, sd_d_ry=0.0001196, sd_d_rz=0.0000453) + sd_d_rx=0.0000967, + sd_d_ry=0.0001196, + sd_d_rz=0.0000453, +) itrf96_to_gda94_sd = TransformationSD( - sd_tx=0.02033, sd_ty=0.01059, sd_tz=0.02866, + sd_tx=0.02033, + sd_ty=0.01059, + sd_tz=0.02866, sd_sc=0.000653, - sd_rx=0.0007627, sd_ry=0.0009049, sd_rz=0.0003225, - sd_d_tx=0.00753, sd_d_ty=0.00391, sd_d_tz=0.01074, + sd_rx=0.0007627, + sd_ry=0.0009049, + sd_rz=0.0003225, + sd_d_tx=0.00753, + sd_d_ty=0.00391, + sd_d_tz=0.01074, sd_d_sc=0.000228, - sd_d_rx=0.0002850, sd_d_ry=0.0003382, sd_d_rz=0.0001169) + sd_d_rx=0.0002850, + sd_d_ry=0.0003382, + sd_d_rz=0.0001169, +) # Transformations itrf2008_to_gda94 = Transformation( - from_datum='ITRF2008', to_datum='GDA94', ref_epoch=date(1994, 1, 1), - tx=-0.08468, ty=-0.01942, tz=0.03201, + from_datum="ITRF2008", + to_datum="GDA94", + ref_epoch=date(1994, 1, 1), + tx=-0.08468, + ty=-0.01942, + tz=0.03201, sc=0.00971, - rx=-0.0004254, ry=0.0022578, rz=0.0024015, - d_tx=0.00142, d_ty=0.00134, d_tz=0.00090, + rx=-0.0004254, + ry=0.0022578, + rz=0.0024015, + d_tx=0.00142, + d_ty=0.00134, + d_tz=0.00090, d_sc=0.000109, - d_rx=0.0015461, d_ry=0.0011820, d_rz=0.0011551, - tf_sd=itrf2008_to_gda94_sd) + d_rx=0.0015461, + d_ry=0.0011820, + d_rz=0.0011551, + tf_sd=itrf2008_to_gda94_sd, +) itrf2005_to_gda94 = Transformation( - from_datum='ITRF2005', to_datum='GDA94', ref_epoch=date(1994, 1, 1), - tx=-0.07973, ty=-0.00686, tz=0.03803, + from_datum="ITRF2005", + to_datum="GDA94", + ref_epoch=date(1994, 1, 1), + tx=-0.07973, + ty=-0.00686, + tz=0.03803, sc=0.006636, - rx=-0.0000351, ry=0.0021211, rz=0.0021411, - d_tx=0.00225, d_ty=-0.00062, d_tz=-0.00056, + rx=-0.0000351, + ry=0.0021211, + rz=0.0021411, + d_tx=0.00225, + d_ty=-0.00062, + d_tz=-0.00056, d_sc=0.000294, - d_rx=0.0014707, d_ry=0.0011443, d_rz=0.0011701, - tf_sd=itrf2005_to_gda94_sd) + d_rx=0.0014707, + d_ry=0.0011443, + d_rz=0.0011701, + tf_sd=itrf2005_to_gda94_sd, +) itrf2000_to_gda94 = Transformation( - from_datum='ITRF2000', to_datum='GDA94', ref_epoch=date(1994, 1, 1), - tx=-0.04591, ty=-0.02985, tz=-0.02037, + from_datum="ITRF2000", + to_datum="GDA94", + ref_epoch=date(1994, 1, 1), + tx=-0.04591, + ty=-0.02985, + tz=-0.02037, sc=0.00707, - rx=-0.0016705, ry=0.0004594, rz=0.0019356, - d_tx=-0.00466, d_ty=0.00355, d_tz=0.01124, + rx=-0.0016705, + ry=0.0004594, + rz=0.0019356, + d_tx=-0.00466, + d_ty=0.00355, + d_tz=0.01124, d_sc=0.000249, - d_rx=0.0017454, d_ry=0.0014868, d_rz=0.001224, - tf_sd=itrf2000_to_gda94_sd) + d_rx=0.0017454, + d_ry=0.0014868, + d_rz=0.001224, + tf_sd=itrf2000_to_gda94_sd, +) itrf97_to_gda94 = Transformation( - from_datum='ITRF97', to_datum='GDA94', ref_epoch=date(1994, 1, 1), - tx=-0.01463, ty=-0.02762, tz=-0.02532, + from_datum="ITRF97", + to_datum="GDA94", + ref_epoch=date(1994, 1, 1), + tx=-0.01463, + ty=-0.02762, + tz=-0.02532, sc=0.006695, - rx=-0.0017893, ry=-0.0006047, rz=0.0009962, - d_tx=-0.00860, d_ty=0.00036, d_tz=0.01125, + rx=-0.0017893, + ry=-0.0006047, + rz=0.0009962, + d_tx=-0.00860, + d_ty=0.00036, + d_tz=0.01125, d_sc=0.000007, - d_rx=0.0016394, d_ry=0.0015198, d_rz=0.0013801, - tf_sd=itrf97_to_gda94_sd) + d_rx=0.0016394, + d_ry=0.0015198, + d_rz=0.0013801, + tf_sd=itrf97_to_gda94_sd, +) itrf96_to_gda94 = Transformation( - from_datum='ITRF96', to_datum='GDA94', ref_epoch=date(1994, 1, 1), - tx=0.02454, ty=-0.03643, tz=-0.06812, + from_datum="ITRF96", + to_datum="GDA94", + ref_epoch=date(1994, 1, 1), + tx=0.02454, + ty=-0.03643, + tz=-0.06812, sc=0.006901, - rx=-0.0027359, ry=-0.0020431, rz=0.0003731, - d_tx=-0.02180, d_ty=0.00471, d_tz=0.02627, + rx=-0.0027359, + ry=-0.0020431, + rz=0.0003731, + d_tx=-0.02180, + d_ty=0.00471, + d_tz=0.02627, d_sc=0.000388, - d_rx=0.0020203, d_ry=0.0021735, d_rz=0.0016290, - tf_sd=itrf96_to_gda94_sd) + d_rx=0.0020203, + d_ry=0.0021735, + d_rz=0.0016290, + tf_sd=itrf96_to_gda94_sd, +) agd84_to_gda94 = Transformation( - from_datum='AGD84', to_datum='GDA94', ref_epoch=0, - tx=-117.763, ty=-51.510, tz=139.061, + from_datum="AGD84", + to_datum="GDA94", + ref_epoch=0, + tx=-117.763, + ty=-51.510, + tz=139.061, sc=-0.191, - rx=-0.292, ry=-0.443, rz=-0.277) + rx=-0.292, + ry=-0.443, + rz=-0.277, +) agd66_to_gda94 = Transformation( - from_datum='AGD66', to_datum='GDA94', ref_epoch=0, - tx=-117.808, ty=-51.536, tz=137.784, + from_datum="AGD66", + to_datum="GDA94", + ref_epoch=0, + tx=-117.808, + ty=-51.536, + tz=137.784, sc=-0.290, - rx=-0.303, ry=-0.446, rz=-0.234) + rx=-0.303, + ry=-0.446, + rz=-0.234, +) agd66_to_gda94_act = Transformation( - from_datum='AGD66', to_datum='GDA94', ref_epoch=0, - tx=-129.193, ty=-41.212, tz=130.730, + from_datum="AGD66", + to_datum="GDA94", + ref_epoch=0, + tx=-129.193, + ty=-41.212, + tz=130.730, sc=-2.955, - rx=-0.246, ry=-0.374, rz=-0.329) + rx=-0.246, + ry=-0.374, + rz=-0.329, +) agd66_to_gda94_tas = Transformation( - from_datum='AGD66', to_datum='GDA94', ref_epoch=0, - tx=-120.271, ty=-64.543, tz=161.632, + from_datum="AGD66", + to_datum="GDA94", + ref_epoch=0, + tx=-120.271, + ty=-64.543, + tz=161.632, sc=2.499, - rx=-0.217, ry=0.067, rz=0.129) + rx=-0.217, + ry=0.067, + rz=0.129, +) agd66_to_gda94_vicnsw = Transformation( - from_datum='AGD66', to_datum='GDA94', ref_epoch=0, - tx=-119.353, ty=-48.301, tz=139.484, + from_datum="AGD66", + to_datum="GDA94", + ref_epoch=0, + tx=-119.353, + ty=-48.301, + tz=139.484, sc=-0.613, - rx=-0.415, ry=-0.260, rz=-0.437) + rx=-0.415, + ry=-0.260, + rz=-0.437, +) agd66_to_gda94_nt = Transformation( - from_datum='AGD66', to_datum='GDA94', ref_epoch=0, - tx=-124.133, ty=-42.003, tz=137.400, + from_datum="AGD66", + to_datum="GDA94", + ref_epoch=0, + tx=-124.133, + ty=-42.003, + tz=137.400, sc=-1.854, - rx=0.008, ry=-0.557, rz=-0.178) + rx=0.008, + ry=-0.557, + rz=-0.178, +) gda94_to_itrf2008 = -itrf2008_to_gda94 gda94_to_itrf2005 = -itrf2005_to_gda94 @@ -481,131 +851,285 @@ class parameters. # Ref: https://itrf.ign.fr/docs/solutions/itrf2020/Transfo-ITRF2020_TRFs.txt itrf2020_to_itrf2014_vel = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF2014', ref_epoch=date(2015, 1, 1), - tx=0.0, ty=-0.1, tz=0.2, + itrf_from="ITRF2020", + itrf_to="ITRF2014", + ref_epoch=date(2015, 1, 1), + tx=0.0, + ty=-0.1, + tz=0.2, sc=-0.42, - rx=0.0, ry=0.0, rz=0.0, - d_tx=0.0, d_ty=0.0, d_tz=0.0, + rx=0.0, + ry=0.0, + rz=0.0, + d_tx=0.0, + d_ty=0.0, + d_tz=0.0, d_sc=0.0, - d_rx=0.0, d_ry=0.0, d_rz=0.0) + d_rx=0.0, + d_ry=0.0, + d_rz=0.0, +) itrf2020_to_itrf2014 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF2014', ref_epoch=date(2015, 1, 1), - tx=-1.4, ty=-0.9, tz=1.4, + itrf_from="ITRF2020", + itrf_to="ITRF2014", + ref_epoch=date(2015, 1, 1), + tx=-1.4, + ty=-0.9, + tz=1.4, sc=-0.42, - rx=0.0, ry=0.0, rz=0.0, - d_tx=0.0, d_ty=-0.1, d_tz=0.2, + rx=0.0, + ry=0.0, + rz=0.0, + d_tx=0.0, + d_ty=-0.1, + d_tz=0.2, d_sc=0.0, - d_rx=0.0, d_ry=0.0, d_rz=0.0) + d_rx=0.0, + d_ry=0.0, + d_rz=0.0, +) itrf2020_to_itrf2008 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF2008', ref_epoch=date(2015, 1, 1), - tx=0.2, ty=1.0, tz=3.3, + itrf_from="ITRF2020", + itrf_to="ITRF2008", + ref_epoch=date(2015, 1, 1), + tx=0.2, + ty=1.0, + tz=3.3, sc=-0.29, - rx=0.0, ry=0.0, rz=0.0, - d_tx=0.0, d_ty=-0.1, d_tz=0.1, + rx=0.0, + ry=0.0, + rz=0.0, + d_tx=0.0, + d_ty=-0.1, + d_tz=0.1, d_sc=0.03, - d_rx=0.0, d_ry=0.0, d_rz=0.0) + d_rx=0.0, + d_ry=0.0, + d_rz=0.0, +) itrf2020_to_itrf2005 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF2005', ref_epoch=date(2015, 1, 1), - tx=2.7, ty=0.1, tz=-1.4, + itrf_from="ITRF2020", + itrf_to="ITRF2005", + ref_epoch=date(2015, 1, 1), + tx=2.7, + ty=0.1, + tz=-1.4, sc=0.65, - rx=0.0, ry=0.0, rz=0.0, - d_tx=0.3, d_ty=-0.1, d_tz=0.1, + rx=0.0, + ry=0.0, + rz=0.0, + d_tx=0.3, + d_ty=-0.1, + d_tz=0.1, d_sc=0.03, - d_rx=0.0, d_ry=0.0, d_rz=0.0) + d_rx=0.0, + d_ry=0.0, + d_rz=0.0, +) itrf2020_to_itrf2000 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF2000', ref_epoch=date(2015, 1, 1), - tx=-0.2, ty=0.8, tz=-34.2, + itrf_from="ITRF2020", + itrf_to="ITRF2000", + ref_epoch=date(2015, 1, 1), + tx=-0.2, + ty=0.8, + tz=-34.2, sc=2.25, - rx=0.0, ry=0.0, rz=0.0, - d_tx=0.1, d_ty=0.0, d_tz=-1.7, + rx=0.0, + ry=0.0, + rz=0.0, + d_tx=0.1, + d_ty=0.0, + d_tz=-1.7, d_sc=0.11, - d_rx=0.0, d_ry=0.0, d_rz=0.0) + d_rx=0.0, + d_ry=0.0, + d_rz=0.0, +) itrf2020_to_itrf97 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF97', ref_epoch=date(2015, 1, 1), - tx=6.5, ty=-3.9, tz=-77.9, + itrf_from="ITRF2020", + itrf_to="ITRF97", + ref_epoch=date(2015, 1, 1), + tx=6.5, + ty=-3.9, + tz=-77.9, sc=3.98, - rx=0.0, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.0, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) itrf2020_to_itrf96 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF96', ref_epoch=date(2015, 1, 1), - tx=6.5, ty=-3.9, tz=-77.9, + itrf_from="ITRF2020", + itrf_to="ITRF96", + ref_epoch=date(2015, 1, 1), + tx=6.5, + ty=-3.9, + tz=-77.9, sc=3.98, - rx=0.0, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.0, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) itrf2020_to_itrf94 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF94', ref_epoch=date(2015, 1, 1), - tx=6.5, ty=-3.9, tz=-77.9, + itrf_from="ITRF2020", + itrf_to="ITRF94", + ref_epoch=date(2015, 1, 1), + tx=6.5, + ty=-3.9, + tz=-77.9, sc=3.98, - rx=0.0, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.0, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) itrf2020_to_itrf93 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF93', ref_epoch=date(2015, 1, 1), - tx=-65.8, ty=1.9, tz=-71.3, + itrf_from="ITRF2020", + itrf_to="ITRF93", + ref_epoch=date(2015, 1, 1), + tx=-65.8, + ty=1.9, + tz=-71.3, sc=4.47, - rx=-3.36, ry=-4.33, rz=0.75, - d_tx=-2.8, d_ty=-0.2, d_tz=-2.3, + rx=-3.36, + ry=-4.33, + rz=0.75, + d_tx=-2.8, + d_ty=-0.2, + d_tz=-2.3, d_sc=0.12, - d_rx=-0.11, d_ry=-0.19, d_rz=0.07) + d_rx=-0.11, + d_ry=-0.19, + d_rz=0.07, +) itrf2020_to_itrf92 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF92', ref_epoch=date(2015, 1, 1), - tx=14.5, ty=-1.9, tz=-85.9, + itrf_from="ITRF2020", + itrf_to="ITRF92", + ref_epoch=date(2015, 1, 1), + tx=14.5, + ty=-1.9, + tz=-85.9, sc=3.27, - rx=0.0, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.0, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) itrf2020_to_itrf91 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF91', ref_epoch=date(2015, 1, 1), - tx=26.5, ty=12.1, tz=-91.9, + itrf_from="ITRF2020", + itrf_to="ITRF91", + ref_epoch=date(2015, 1, 1), + tx=26.5, + ty=12.1, + tz=-91.9, sc=4.67, - rx=0.0, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.0, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) itrf2020_to_itrf90 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF90', ref_epoch=date(2015, 1, 1), - tx=24.5, ty=8.1, tz=-107.9, + itrf_from="ITRF2020", + itrf_to="ITRF90", + ref_epoch=date(2015, 1, 1), + tx=24.5, + ty=8.1, + tz=-107.9, sc=4.97, - rx=0.0, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.0, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) itrf2020_to_itrf89 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF89', ref_epoch=date(2015, 1, 1), - tx=29.5, ty=32.1, tz=-145.9, + itrf_from="ITRF2020", + itrf_to="ITRF89", + ref_epoch=date(2015, 1, 1), + tx=29.5, + ty=32.1, + tz=-145.9, sc=8.37, - rx=0.0, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.0, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) itrf2020_to_itrf88 = iers2trans( - itrf_from='ITRF2020', itrf_to='ITRF88', ref_epoch=date(2015, 1, 1), - tx=24.5, ty=-3.9, tz=-169.9, + itrf_from="ITRF2020", + itrf_to="ITRF88", + ref_epoch=date(2015, 1, 1), + tx=24.5, + ty=-3.9, + tz=-169.9, sc=11.47, - rx=0.1, ry=0.0, rz=0.36, - d_tx=0.1, d_ty=-0.6, d_tz=-3.1, + rx=0.1, + ry=0.0, + rz=0.36, + d_tx=0.1, + d_ty=-0.6, + d_tz=-3.1, d_sc=0.12, - d_rx=0.0, d_ry=0.0, d_rz=0.02) - + d_rx=0.0, + d_ry=0.0, + d_rz=0.02, +) + itrf2014_to_itrf2020 = -itrf2020_to_itrf2014 itrf2008_to_itrf2020 = -itrf2020_to_itrf2008 itrf2005_to_itrf2020 = -itrf2020_to_itrf2005 @@ -619,7 +1143,7 @@ class parameters. itrf90_to_itrf2020 = -itrf2020_to_itrf90 itrf89_to_itrf2020 = -itrf2020_to_itrf89 itrf88_to_itrf2020 = -itrf2020_to_itrf88 - + # ITRF2014 parameters # Note: These parameter definitions use the units, rotation and delta-rotation @@ -630,112 +1154,244 @@ class parameters. # Ref: http://itrf.ign.fr/doc_ITRF/Transfo-ITRF2014_ITRFs.txt itrf2014_to_itrf2008 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF2008', ref_epoch=date(2010, 1, 1), - tx=1.6, ty=1.9, tz=2.4, + itrf_from="ITRF2014", + itrf_to="ITRF2008", + ref_epoch=date(2010, 1, 1), + tx=1.6, + ty=1.9, + tz=2.4, sc=-0.02, - rx=0, ry=0, rz=0, - d_tx=0.0, d_ty=0.0, d_tz=-0.1, + rx=0, + ry=0, + rz=0, + d_tx=0.0, + d_ty=0.0, + d_tz=-0.1, d_sc=0.03, - d_rx=0, d_ry=0, d_rz=0) + d_rx=0, + d_ry=0, + d_rz=0, +) itrf2014_to_itrf2005 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF2005', ref_epoch=date(2010, 1, 1), - tx=2.6, ty=1.0, tz=-2.3, + itrf_from="ITRF2014", + itrf_to="ITRF2005", + ref_epoch=date(2010, 1, 1), + tx=2.6, + ty=1.0, + tz=-2.3, sc=0.92, - rx=0, ry=0, rz=0, - d_tx=0.3, d_ty=0.0, d_tz=-0.1, + rx=0, + ry=0, + rz=0, + d_tx=0.3, + d_ty=0.0, + d_tz=-0.1, d_sc=0.03, - d_rx=0, d_ry=0, d_rz=0) + d_rx=0, + d_ry=0, + d_rz=0, +) itrf2014_to_itrf2000 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF2000', ref_epoch=date(2010, 1, 1), - tx=0.7, ty=1.2, tz=-26.1, + itrf_from="ITRF2014", + itrf_to="ITRF2000", + ref_epoch=date(2010, 1, 1), + tx=0.7, + ty=1.2, + tz=-26.1, sc=2.12, - rx=0, ry=0, rz=0, - d_tx=0.1, d_ty=0.1, d_tz=-1.9, + rx=0, + ry=0, + rz=0, + d_tx=0.1, + d_ty=0.1, + d_tz=-1.9, d_sc=0.11, - d_rx=0, d_ry=0, d_rz=0) + d_rx=0, + d_ry=0, + d_rz=0, +) itrf2014_to_itrf97 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF97', ref_epoch=date(2010, 1, 1), - tx=7.4, ty=-0.5, tz=-62.8, + itrf_from="ITRF2014", + itrf_to="ITRF97", + ref_epoch=date(2010, 1, 1), + tx=7.4, + ty=-0.5, + tz=-62.8, sc=3.80, - rx=0, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2014_to_itrf96 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF96', ref_epoch=date(2010, 1, 1), - tx=7.4, ty=-0.5, tz=-62.8, + itrf_from="ITRF2014", + itrf_to="ITRF96", + ref_epoch=date(2010, 1, 1), + tx=7.4, + ty=-0.5, + tz=-62.8, sc=3.80, - rx=0, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2014_to_itrf94 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF94', ref_epoch=date(2010, 1, 1), - tx=7.4, ty=-0.5, tz=-62.8, + itrf_from="ITRF2014", + itrf_to="ITRF94", + ref_epoch=date(2010, 1, 1), + tx=7.4, + ty=-0.5, + tz=-62.8, sc=3.80, - rx=0, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2014_to_itrf93 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF93', ref_epoch=date(2010, 1, 1), - tx=-50.4, ty=3.3, tz=-60.2, + itrf_from="ITRF2014", + itrf_to="ITRF93", + ref_epoch=date(2010, 1, 1), + tx=-50.4, + ty=3.3, + tz=-60.2, sc=4.29, - rx=-2.81, ry=-3.38, rz=0.40, - d_tx=-2.8, d_ty=-0.1, d_tz=-2.5, + rx=-2.81, + ry=-3.38, + rz=0.40, + d_tx=-2.8, + d_ty=-0.1, + d_tz=-2.5, d_sc=0.12, - d_rx=-0.11, d_ry=-0.19, d_rz=0.07) + d_rx=-0.11, + d_ry=-0.19, + d_rz=0.07, +) itrf2014_to_itrf92 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF92', ref_epoch=date(2010, 1, 1), - tx=15.4, ty=1.5, tz=-70.8, + itrf_from="ITRF2014", + itrf_to="ITRF92", + ref_epoch=date(2010, 1, 1), + tx=15.4, + ty=1.5, + tz=-70.8, sc=3.09, - rx=0, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2014_to_itrf91 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF91', ref_epoch=date(2010, 1, 1), - tx=27.4, ty=15.5, tz=-76.8, + itrf_from="ITRF2014", + itrf_to="ITRF91", + ref_epoch=date(2010, 1, 1), + tx=27.4, + ty=15.5, + tz=-76.8, sc=4.49, - rx=0, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2014_to_itrf90 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF90', ref_epoch=date(2010, 1, 1), - tx=25.4, ty=11.5, tz=-92.8, + itrf_from="ITRF2014", + itrf_to="ITRF90", + ref_epoch=date(2010, 1, 1), + tx=25.4, + ty=11.5, + tz=-92.8, sc=4.79, - rx=0, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2014_to_itrf89 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF89', ref_epoch=date(2010, 1, 1), - tx=30.4, ty=35.5, tz=-130.8, + itrf_from="ITRF2014", + itrf_to="ITRF89", + ref_epoch=date(2010, 1, 1), + tx=30.4, + ty=35.5, + tz=-130.8, sc=8.19, - rx=0, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2014_to_itrf88 = iers2trans( - itrf_from='ITRF2014', itrf_to='ITRF88', ref_epoch=date(2010, 1, 1), - tx=25.4, ty=-0.5, tz=-154.8, + itrf_from="ITRF2014", + itrf_to="ITRF88", + ref_epoch=date(2010, 1, 1), + tx=25.4, + ty=-0.5, + tz=-154.8, sc=11.29, - rx=0.1, ry=0, rz=0.26, - d_tx=0.1, d_ty=-0.5, d_tz=-3.3, + rx=0.1, + ry=0, + rz=0.26, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.3, d_sc=0.12, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf2014 = -itrf2014_to_itrf2008 itrf2005_to_itrf2014 = -itrf2014_to_itrf2005 @@ -760,103 +1416,224 @@ class parameters. # Ref: http://itrf.ign.fr/doc_ITRF/Transfo-ITRF2008_ITRFs.txt itrf2008_to_itrf2005 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF2005', ref_epoch=date(2000, 1, 1), - tx=-2.0, ty=-0.9, tz=-4.7, + itrf_from="ITRF2008", + itrf_to="ITRF2005", + ref_epoch=date(2000, 1, 1), + tx=-2.0, + ty=-0.9, + tz=-4.7, sc=0.94, - rx=0, ry=0, rz=0, - d_tx=0.3, d_ty=0.0, d_tz=0.0, + rx=0, + ry=0, + rz=0, + d_tx=0.3, + d_ty=0.0, + d_tz=0.0, d_sc=0.0, - d_rx=0, d_ry=0, d_rz=0) + d_rx=0, + d_ry=0, + d_rz=0, +) itrf2008_to_itrf2000 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF2000', ref_epoch=date(2000, 1, 1), - tx=-1.9, ty=-1.7, tz=-10.5, + itrf_from="ITRF2008", + itrf_to="ITRF2000", + ref_epoch=date(2000, 1, 1), + tx=-1.9, + ty=-1.7, + tz=-10.5, sc=1.34, - rx=0, ry=0, rz=0, - d_tx=0.1, d_ty=0.1, d_tz=-1.8, + rx=0, + ry=0, + rz=0, + d_tx=0.1, + d_ty=0.1, + d_tz=-1.8, d_sc=0.08, - d_rx=0, d_ry=0, d_rz=0) + d_rx=0, + d_ry=0, + d_rz=0, +) itrf2008_to_itrf97 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF97', ref_epoch=date(2000, 1, 1), - tx=4.8, ty=2.6, tz=-33.2, + itrf_from="ITRF2008", + itrf_to="ITRF97", + ref_epoch=date(2000, 1, 1), + tx=4.8, + ty=2.6, + tz=-33.2, sc=2.92, - rx=0, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf96 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF96', ref_epoch=date(2000, 1, 1), - tx=4.8, ty=2.6, tz=-33.2, + itrf_from="ITRF2008", + itrf_to="ITRF96", + ref_epoch=date(2000, 1, 1), + tx=4.8, + ty=2.6, + tz=-33.2, sc=2.92, - rx=0, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf94 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF94', ref_epoch=date(2000, 1, 1), - tx=4.8, ty=2.6, tz=-33.2, + itrf_from="ITRF2008", + itrf_to="ITRF94", + ref_epoch=date(2000, 1, 1), + tx=4.8, + ty=2.6, + tz=-33.2, sc=2.92, - rx=0, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf93 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF93', ref_epoch=date(2000, 1, 1), - tx=4-24.0, ty=2.4, tz=-38.6, + itrf_from="ITRF2008", + itrf_to="ITRF93", + ref_epoch=date(2000, 1, 1), + tx=4 - 24.0, + ty=2.4, + tz=-38.6, sc=3.41, - rx=-1.71, ry=-1.48, rz=-0.30, - d_tx=-2.8, d_ty=-0.1, d_tz=-2.4, + rx=-1.71, + ry=-1.48, + rz=-0.30, + d_tx=-2.8, + d_ty=-0.1, + d_tz=-2.4, d_sc=0.09, - d_rx=-0.11, d_ry=-0.19, d_rz=0.07) + d_rx=-0.11, + d_ry=-0.19, + d_rz=0.07, +) itrf2008_to_itrf92 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF92', ref_epoch=date(2000, 1, 1), - tx=12.8, ty=4.6, tz=-41.2, + itrf_from="ITRF2008", + itrf_to="ITRF92", + ref_epoch=date(2000, 1, 1), + tx=12.8, + ty=4.6, + tz=-41.2, sc=2.21, - rx=0, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf91 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF91', ref_epoch=date(2000, 1, 1), - tx=24.8, ty=18.6, tz=-47.2, + itrf_from="ITRF2008", + itrf_to="ITRF91", + ref_epoch=date(2000, 1, 1), + tx=24.8, + ty=18.6, + tz=-47.2, sc=3.61, - rx=0, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf90 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF90', ref_epoch=date(2000, 1, 1), - tx=22.8, ty=14.6, tz=-63.2, + itrf_from="ITRF2008", + itrf_to="ITRF90", + ref_epoch=date(2000, 1, 1), + tx=22.8, + ty=14.6, + tz=-63.2, sc=3.91, - rx=0, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf89 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF89', ref_epoch=date(2000, 1, 1), - tx=27.8, ty=38.6, tz=-101.2, + itrf_from="ITRF2008", + itrf_to="ITRF89", + ref_epoch=date(2000, 1, 1), + tx=27.8, + ty=38.6, + tz=-101.2, sc=7.31, - rx=0, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2008_to_itrf88 = iers2trans( - itrf_from='ITRF2008', itrf_to='ITRF88', ref_epoch=date(2000, 1, 1), - tx=22.8, ty=2.6, tz=-125.2, + itrf_from="ITRF2008", + itrf_to="ITRF88", + ref_epoch=date(2000, 1, 1), + tx=22.8, + ty=2.6, + tz=-125.2, sc=10.41, - rx=0.10, ry=0, rz=0.06, - d_tx=0.1, d_ty=-0.5, d_tz=-3.2, + rx=0.10, + ry=0, + rz=0.06, + d_tx=0.1, + d_ty=-0.5, + d_tz=-3.2, d_sc=0.09, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2005_to_itrf2008 = -itrf2008_to_itrf2005 itrf2000_to_itrf2008 = -itrf2008_to_itrf2000 @@ -880,13 +1657,24 @@ class parameters. # Ref: http://itrf.ensg.ign.fr/ITRF_solutions/2005/tp_05-00.php itrf2005_to_itrf2000 = iers2trans( - itrf_from='ITRF2005', itrf_to='ITRF2000', ref_epoch=date(2000, 1, 1), - tx=0.1, ty=-0.8, tz=-5.8, + itrf_from="ITRF2005", + itrf_to="ITRF2000", + ref_epoch=date(2000, 1, 1), + tx=0.1, + ty=-0.8, + tz=-5.8, sc=0.40, - rx=0, ry=0, rz=0, - d_tx=-0.2, d_ty=0.1, d_tz=-1.8, + rx=0, + ry=0, + rz=0, + d_tx=-0.2, + d_ty=0.1, + d_tz=-1.8, d_sc=0.08, - d_rx=0, d_ry=0, d_rz=0) + d_rx=0, + d_ry=0, + d_rz=0, +) itrf2000_to_itrf2005 = -itrf2005_to_itrf2000 @@ -897,85 +1685,184 @@ class parameters. # NOTE: All translations and rates of translation shown below have been # converted to millimetres. itrf2000_to_itrf97 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF97', ref_epoch=date(1997, 1, 1), - tx=6.7, ty=6.1, tz=-18.5, + itrf_from="ITRF2000", + itrf_to="ITRF97", + ref_epoch=date(1997, 1, 1), + tx=6.7, + ty=6.1, + tz=-18.5, sc=1.55, - rx=0, ry=0, rz=0, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0, + ry=0, + rz=0, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2000_to_itrf96 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF96', ref_epoch=date(1997, 1, 1), - tx=6.7, ty=6.1, tz=-18.5, + itrf_from="ITRF2000", + itrf_to="ITRF96", + ref_epoch=date(1997, 1, 1), + tx=6.7, + ty=6.1, + tz=-18.5, sc=1.55, - rx=0, ry=0, rz=0, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0, + ry=0, + rz=0, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2000_to_itrf94 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF94', ref_epoch=date(1997, 1, 1), - tx=6.7, ty=6.1, tz=-18.5, + itrf_from="ITRF2000", + itrf_to="ITRF94", + ref_epoch=date(1997, 1, 1), + tx=6.7, + ty=6.1, + tz=-18.5, sc=1.55, - rx=0, ry=0, rz=0, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0, + ry=0, + rz=0, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2000_to_itrf93 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF93', ref_epoch=date(1988, 1, 1), - tx=12.7, ty=6.5, tz=-20.9, + itrf_from="ITRF2000", + itrf_to="ITRF93", + ref_epoch=date(1988, 1, 1), + tx=12.7, + ty=6.5, + tz=-20.9, sc=1.95, - rx=-0.39, ry=0.80, rz=-1.14, - d_tx=-2.9, d_ty=-0.2, d_tz=-0.6, + rx=-0.39, + ry=0.80, + rz=-1.14, + d_tx=-2.9, + d_ty=-0.2, + d_tz=-0.6, d_sc=0.01, - d_rx=-0.11, d_ry=-0.19, d_rz=0.07) + d_rx=-0.11, + d_ry=-0.19, + d_rz=0.07, +) itrf2000_to_itrf92 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF92', ref_epoch=date(1988, 1, 1), - tx=14.7, ty=13.5, tz=-13.9, + itrf_from="ITRF2000", + itrf_to="ITRF92", + ref_epoch=date(1988, 1, 1), + tx=14.7, + ty=13.5, + tz=-13.9, sc=0.75, - rx=0, ry=0, rz=-0.18, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0, + ry=0, + rz=-0.18, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2000_to_itrf91 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF91', ref_epoch=date(1988, 1, 1), - tx=26.7, ty=27.5, tz=-19.9, + itrf_from="ITRF2000", + itrf_to="ITRF91", + ref_epoch=date(1988, 1, 1), + tx=26.7, + ty=27.5, + tz=-19.9, sc=2.15, - rx=0, ry=0, rz=-0.18, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0, + ry=0, + rz=-0.18, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2000_to_itrf90 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF90', ref_epoch=date(1988, 1, 1), - tx=24.7, ty=23.5, tz=-35.9, + itrf_from="ITRF2000", + itrf_to="ITRF90", + ref_epoch=date(1988, 1, 1), + tx=24.7, + ty=23.5, + tz=-35.9, sc=2.45, - rx=0, ry=0, rz=-0.18, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0, + ry=0, + rz=-0.18, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2000_to_itrf89 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF89', ref_epoch=date(1988, 1, 1), - tx=29.7, ty=47.5, tz=-73.9, + itrf_from="ITRF2000", + itrf_to="ITRF89", + ref_epoch=date(1988, 1, 1), + tx=29.7, + ty=47.5, + tz=-73.9, sc=5.85, - rx=0, ry=0, rz=-0.18, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0, + ry=0, + rz=-0.18, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf2000_to_itrf88 = iers2trans( - itrf_from='ITRF2000', itrf_to='ITRF88', ref_epoch=date(1988, 1, 1), - tx=24.7, ty=11.5, tz=-97.9, + itrf_from="ITRF2000", + itrf_to="ITRF88", + ref_epoch=date(1988, 1, 1), + tx=24.7, + ty=11.5, + tz=-97.9, sc=8.95, - rx=0.10, ry=0, rz=-0.18, - d_tx=0.0, d_ty=-0.6, d_tz=-1.4, + rx=0.10, + ry=0, + rz=-0.18, + d_tx=0.0, + d_ty=-0.6, + d_tz=-1.4, d_sc=0.01, - d_rx=0, d_ry=0, d_rz=0.02) + d_rx=0, + d_ry=0, + d_rz=0.02, +) itrf97_to_itrf2000 = -itrf2000_to_itrf97 itrf96_to_itrf2000 = -itrf2000_to_itrf96 @@ -989,20 +1876,20 @@ class parameters. # The locations of files used in the height module -aws_server = '/vsicurl/https://geoid.s3-ap-southeast-2.amazonaws.com/' -file_DOV_PV = aws_server + 'AGQG/DOV_PV.tif' -file_DOV_PM = aws_server + 'AGQG/DOV_PM.tif' -file_AG2020 = aws_server + 'AUSGeoid/AUSGeoid2020_RELEASEV20170908.tif' -file_AG2020_STD = aws_server + 'AUSGeoid/AUSGeoid2020_RELEASEV20170908_err.tif' -file_AVWS = aws_server + 'AGQG/AGQG_20201120.tif' -file_AVWS_STD = aws_server + 'AGQG/AGQG_uncertainty_20201120.tif' -file_GRAV_BA = aws_server + 'AGQG/Bouguer_Grav_RELEASE20191107.tif' -file_AG98=aws_server+'AUSGeoid/AUSGeoid98.tif' -file_AG09=aws_server+'AUSGeoid/AUSGeoid09_V1.01.tif' -file_AG98_DOV_PV=aws_server+'AUSGeoid/AUSGeoid98_DOV_PV.tif' -file_AG98_DOV_PM=aws_server+'AUSGeoid/AUSGeoid98_DOV_PM.tif' -file_AG09_DOV_PV=aws_server+'AUSGeoid/AUSGeoid09_DOV_PV_V1.01.tif' -file_AG09_DOV_PM=aws_server+'AUSGeoid/AUSGeoid09_DOV_PM_V1.01.tif' +aws_server = "/vsicurl/https://geoid.s3-ap-southeast-2.amazonaws.com/" +file_DOV_PV = aws_server + "AGQG/DOV_PV.tif" +file_DOV_PM = aws_server + "AGQG/DOV_PM.tif" +file_AG2020 = aws_server + "AUSGeoid/AUSGeoid2020_RELEASEV20170908.tif" +file_AG2020_STD = aws_server + "AUSGeoid/AUSGeoid2020_RELEASEV20170908_err.tif" +file_AVWS = aws_server + "AGQG/AGQG_20201120.tif" +file_AVWS_STD = aws_server + "AGQG/AGQG_uncertainty_20201120.tif" +file_GRAV_BA = aws_server + "AGQG/Bouguer_Grav_RELEASE20191107.tif" +file_AG98 = aws_server + "AUSGeoid/AUSGeoid98.tif" +file_AG09 = aws_server + "AUSGeoid/AUSGeoid09_V1.01.tif" +file_AG98_DOV_PV = aws_server + "AUSGeoid/AUSGeoid98_DOV_PV.tif" +file_AG98_DOV_PM = aws_server + "AUSGeoid/AUSGeoid98_DOV_PM.tif" +file_AG09_DOV_PV = aws_server + "AUSGeoid/AUSGeoid09_DOV_PV_V1.01.tif" +file_AG09_DOV_PM = aws_server + "AUSGeoid/AUSGeoid09_DOV_PM_V1.01.tif" # GRS80 normal gravity flattening [Moritz, 2000 Section 4] diff --git a/geodepy/convert.py b/geodepy/convert.py index 040e12f..30ffd63 100644 --- a/geodepy/convert.py +++ b/geodepy/convert.py @@ -5,30 +5,51 @@ Convert Module """ -from math import (sin, cos, atan2, radians, degrees, - sqrt, cosh, sinh, tan, atan, log) +from math import sin, cos, atan2, radians, degrees, sqrt, cosh, sinh, tan, atan, log import datetime import warnings from geodepy.constants import utm, isg, grs80, ans -from geodepy.angles import (DECAngle, HPAngle, GONAngle, DMSAngle, DDMAngle, - dec2hp, dec2hpa, dec2gon, dec2gona, - dec2dms, dec2ddm, - hp2dec, hp2deca, hp2gon, hp2gona, - hp2dms, hp2ddm, hp2rad, hp2dec_v, - gon2dec, gon2deca, gon2hp, gon2hpa, - gon2dms, gon2ddm, gon2rad, - dd2sec, angular_typecheck) +from geodepy.angles import ( + DECAngle, + HPAngle, + GONAngle, + DMSAngle, + DDMAngle, + dec2hp, + dec2hpa, + dec2gon, + dec2gona, + dec2dms, + dec2ddm, + hp2dec, + hp2deca, + hp2gon, + hp2gona, + hp2dms, + hp2ddm, + hp2rad, + hp2dec_v, + gon2dec, + gon2deca, + gon2hp, + gon2hpa, + gon2dms, + gon2ddm, + gon2rad, + dd2sec, + angular_typecheck, +) def polar2rect(r, theta): """ Converts point in polar coordinates to corresponding rectangular coordinates - Theta is degrees and is measured clockwise from the positive y axis - (i.e. north) + :param r: Radius - :param theta: Angle (decimal degrees) + :param theta: Angle (decimal degrees) from postive y axis (north) :type theta: Float (decimal degrees), DMSAngle or DDMAngle :return: Rectangular Coordinates X, Y + """ theta = angular_typecheck(theta) x = r * sin(radians(theta)) @@ -41,13 +62,14 @@ def rect2polar(x, y): Converts point in rectangular coordinates to corresponding polar coordinates Angular component of polar coordinates (theta) is in decimal degrees and is measured clockwise from the positive y axis (i.e. north) + :param x: Rectangular Coordinate X :param y: Rectangular Coordinate Y - :return r: Radius - :return theta: Angle (decimal degrees) - :rtype theta: float (decimal degrees) + :return: + - Radius + - Angle (decimal degrees) """ - r = sqrt(x ** 2 + y ** 2) + r = sqrt(x**2 + y**2) theta = atan2(x, y) if theta < 0: theta = degrees(theta) + 360 @@ -60,101 +82,118 @@ def rect_radius(ellipsoid): """ Computes the Rectifying Radius of an Ellipsoid with specified Inverse Flattening (See Ref 2 Equation 3) + :param ellipsoid: Ellipsoid Object :type ellipsoid: Ellipsoid :return: Ellipsoid Rectifying Radius """ - nval = (1 / float(ellipsoid.inversef)) /\ - (2 - (1 / float(ellipsoid.inversef))) + nval = (1 / float(ellipsoid.inversef)) / (2 - (1 / float(ellipsoid.inversef))) nval2 = nval**2 - return (ellipsoid.semimaj / (1 + nval) * ((nval2 * - (nval2 * - (nval2 * - (25 * nval2 + 64) - + 256) - + 4096) - + 16384) - / 16384.)) + return ( + ellipsoid.semimaj + / (1 + nval) + * ( + (nval2 * (nval2 * (nval2 * (25 * nval2 + 64) + 256) + 4096) + 16384) + / 16384.0 + ) + ) def alpha_coeff(ellipsoid): """ Computes the set of Alpha coefficients of an Ellipsoid with specified Inverse Flattening (See Ref 2 Equation 5) + :param ellipsoid: Ellipsoid Object :type ellipsoid: Ellipsoid :return: Alpha coefficients a2, a4 ... a16 :rtype: tuple """ nval = ellipsoid.n - a2 = ((nval * - (nval * - (nval * - (nval * - (nval * - (nval * - ((37884525 - 75900428 * nval) - * nval + 42422016) - - 89611200) - + 46287360) - + 63504000) - - 135475200) - + 101606400)) - / 203212800.) - - a4 = ((nval ** 2 * - (nval * - (nval * - (nval * - (nval * - (nval * - (148003883 * nval + 83274912) - - 178508970) - + 77690880) - + 67374720) - - 104509440) - + 47174400)) - / 174182400.) - - a6 = ((nval ** 3 * - (nval * - (nval * - (nval * - (nval * - (318729724 * nval - 738126169) - + 294981280) - + 178924680) - - 234938880) - + 81164160)) - / 319334400.) - - a8 = ((nval ** 4 * - (nval * - (nval * - ((14967552000 - 40176129013 * nval) * nval + 6971354016) - - 8165836800) - + 2355138720)) - / 7664025600.) - - a10 = ((nval ** 5 * - (nval * - (nval * - (10421654396 * nval + 3997835751) - - 4266773472) - + 1072709352)) - / 2490808320.) - - a12 = ((nval ** 6 * - (nval * - (175214326799 * nval - 171950693600) - + 38652967262)) - / 58118860800.) - - a14 = ((nval ** 7 * - (13700311101 - 67039739596 * nval)) - / 12454041600.) - - a16 = (1424729850961 * nval ** 8) / 743921418240. + a2 = ( + nval + * ( + nval + * ( + nval + * ( + nval + * ( + nval + * ( + nval * ((37884525 - 75900428 * nval) * nval + 42422016) + - 89611200 + ) + + 46287360 + ) + + 63504000 + ) + - 135475200 + ) + + 101606400 + ) + ) / 203212800.0 + + a4 = ( + nval**2 + * ( + nval + * ( + nval + * ( + nval + * ( + nval * (nval * (148003883 * nval + 83274912) - 178508970) + + 77690880 + ) + + 67374720 + ) + - 104509440 + ) + + 47174400 + ) + ) / 174182400.0 + + a6 = ( + nval**3 + * ( + nval + * ( + nval + * ( + nval * (nval * (318729724 * nval - 738126169) + 294981280) + + 178924680 + ) + - 234938880 + ) + + 81164160 + ) + ) / 319334400.0 + + a8 = ( + nval**4 + * ( + nval + * ( + nval * ((14967552000 - 40176129013 * nval) * nval + 6971354016) + - 8165836800 + ) + + 2355138720 + ) + ) / 7664025600.0 + + a10 = ( + nval**5 + * (nval * (nval * (10421654396 * nval + 3997835751) - 4266773472) + 1072709352) + ) / 2490808320.0 + + a12 = ( + nval**6 * (nval * (175214326799 * nval - 171950693600) + 38652967262) + ) / 58118860800.0 + + a14 = (nval**7 * (13700311101 - 67039739596 * nval)) / 12454041600.0 + + a16 = (1424729850961 * nval**8) / 743921418240.0 return a2, a4, a6, a8, a10, a12, a14, a16 @@ -162,77 +201,87 @@ def beta_coeff(ellipsoid): """ Computes the set of Beta coefficients of an Ellipsoid with specified Inverse Flattening (See Ref 2 Equation 23) + :param ellipsoid: Ellipsoid Object :type ellipsoid: Ellipsoid :return: Alpha coefficients a2, a4 ... a16 :rtype: tuple """ nval = ellipsoid.n - b2 = ((nval * - (nval * - (nval * - (nval * - (nval * - (nval * - ((37845269 - 31777436 * nval) - 43097152) - + 42865200) - + 752640) - - 104428800) - + 180633600) - - 135475200)) - / 270950400.) - - b4 = ((nval ** 2 * - (nval * - (nval * - (nval * - (nval * - ((-24749483 * nval - 14930208) * nval + 100683990) - - 152616960) - + 105719040) - - 23224320) - - 7257600)) - / 348364800.) - - b6 = ((nval ** 3 * - (nval * - (nval * - (nval * - (nval * - (232468668 * nval - 101880889) - - 39205760) - + 29795040) - + 28131840) - - 22619520)) - / 638668800.) - - b8 = ((nval ** 4 * - (nval * - (nval * - ((-324154477 * nval - 1433121792) * nval + 876745056) - + 167270400) - - 208945440)) - / 7664025600.) - - b10 = ((nval ** 5 * - (nval * - (nval * - (312227409 - 457888660 * nval) - + 67920528) - - 70779852)) - / 2490808320.) - - b12 = ((nval ** 6 * - (nval * - (19841813847 * nval + 3665348512) - - 3758062126)) - / 116237721600.) - - b14 = ((nval ** 7 * - (1989295244 * nval - 1979471673)) - / 49816166400.) - - b16 = ((-191773887257 * nval ** 8) / 3719607091200.) + b2 = ( + nval + * ( + nval + * ( + nval + * ( + nval + * ( + nval + * (nval * ((37845269 - 31777436 * nval) - 43097152) + 42865200) + + 752640 + ) + - 104428800 + ) + + 180633600 + ) + - 135475200 + ) + ) / 270950400.0 + + b4 = ( + nval**2 + * ( + nval + * ( + nval + * ( + nval + * ( + nval * ((-24749483 * nval - 14930208) * nval + 100683990) + - 152616960 + ) + + 105719040 + ) + - 23224320 + ) + - 7257600 + ) + ) / 348364800.0 + + b6 = ( + nval**3 + * ( + nval + * ( + nval + * (nval * (nval * (232468668 * nval - 101880889) - 39205760) + 29795040) + + 28131840 + ) + - 22619520 + ) + ) / 638668800.0 + + b8 = ( + nval**4 + * ( + nval + * (nval * ((-324154477 * nval - 1433121792) * nval + 876745056) + 167270400) + - 208945440 + ) + ) / 7664025600.0 + + b10 = ( + nval**5 * (nval * (nval * (312227409 - 457888660 * nval) + 67920528) - 70779852) + ) / 2490808320.0 + + b12 = ( + nval**6 * (nval * (19841813847 * nval + 3665348512) - 3758062126) + ) / 116237721600.0 + + b14 = (nval**7 * (1989295244 * nval - 1979471673)) / 49816166400.0 + + b16 = (-191773887257 * nval**8) / 3719607091200.0 return b2, b4, b6, b8, b10, b12, b14, b16 @@ -240,6 +289,7 @@ def psfandgridconv(xi1, eta1, lat, lon, cm, conf_lat, ellipsoid=grs80, prj=utm): """ Calculates Point Scale Factor and Grid Convergence. Used in convert.geo2grid and convert.grid2geo + :param xi1: Transverse Mercator Ratio Xi :param eta1: Transverse Mercator Ratio Eta :param lat: Latitude @@ -263,20 +313,24 @@ def psfandgridconv(xi1, eta1, lat, lon, cm, conf_lat, ellipsoid=grs80, prj=utm): p = 1 q = 0 for r in range(1, 9): - p += 2*r * a[r-1] * cos(2*r * xi1) * cosh(2*r * eta1) - q += 2*r * a[r-1] * sin(2*r * xi1) * sinh(2*r * eta1) + p += 2 * r * a[r - 1] * cos(2 * r * xi1) * cosh(2 * r * eta1) + q += 2 * r * a[r - 1] * sin(2 * r * xi1) * sinh(2 * r * eta1) q = -q - psf = (float(prj.cmscale) - * (A / ellipsoid.semimaj) - * sqrt(q**2 + p**2) - * ((sqrt(1 + (tan(lat)**2)) - * sqrt(1 - ellipsoid.ecc1sq * (sin(lat)**2))) - / sqrt((tan(conf_lat)**2) + (cos(long_diff)**2)))) + psf = ( + float(prj.cmscale) + * (A / ellipsoid.semimaj) + * sqrt(q**2 + p**2) + * ( + (sqrt(1 + (tan(lat) ** 2)) * sqrt(1 - ellipsoid.ecc1sq * (sin(lat) ** 2))) + / sqrt((tan(conf_lat) ** 2) + (cos(long_diff) ** 2)) + ) + ) # Grid Convergence - grid_conv = degrees(atan(abs(q / p)) - + atan(abs(tan(conf_lat) * tan(long_diff)) - / sqrt(1 + tan(conf_lat)**2))) + grid_conv = degrees( + atan(abs(q / p)) + + atan(abs(tan(conf_lat) * tan(long_diff)) / sqrt(1 + tan(conf_lat) ** 2)) + ) if cm > lon and lat < 0: grid_conv = -grid_conv elif cm < lon and lat > 0: @@ -292,12 +346,13 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80, prj=utm): Scale Factor and Grid Convergence. Default Projection is Universal Transverse Mercator Projection using GRS80 Ellipsoid parameters. + :param lat: Latitude :type lat: Float (Decimal Degrees), DMSAngle or DDMAngle :param lon: Longitude :type lon: Float (Decimal Degrees, DMSAngle or DDMAngle :param zone: Optional Zone Number - Only required if calculating grid - co-ordinate outside zone boundaries + co-ordinate outside zone boundaries :param ellipsoid: Ellipsoid Object :type ellipsoid: Ellipsoid :return: hemisphere, zone, east (m), north (m), Point Scale Factor, @@ -312,19 +367,24 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80, prj=utm): zone = int(zone) if prj == isg: if zone not in (0, 541, 542, 543, 551, 552, 553, 561, 562, 563, 572): - raise ValueError('Invalid Zone - Choose from 541, 542, 543, 551, 552, 553, 561, 562, 563, 572') + raise ValueError( + "Invalid Zone - Choose from 541, 542, 543, 551, 552, 553, 561, 562, 563, 572" + ) else: if zone < 0 or zone > 60: - raise ValueError('Invalid Zone - Zones from 1 to 60') + raise ValueError("Invalid Zone - Zones from 1 to 60") if lat < -80 or lat > 84: - raise ValueError('Invalid Latitude - Latitudes from -80 to +84') + raise ValueError("Invalid Latitude - Latitudes from -80 to +84") if lon < -180 or lon > 180: - raise ValueError('Invalid Longitude - Longitudes from -180 to +180') + raise ValueError("Invalid Longitude - Longitudes from -180 to +180") if prj == isg and ellipsoid != ans: - warnings.warn(message='ISG projection should be used with ANS ellipsoid', category=UserWarning) + warnings.warn( + message="ISG projection should be used with ANS ellipsoid", + category=UserWarning, + ) A = rect_radius(ellipsoid) a = alpha_coeff(ellipsoid) @@ -332,23 +392,31 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80, prj=utm): # Calculate Zone if zone == 0: if prj == isg: - amgzone = (float(lon) - (prj.initialcm - (4.5 * prj.zonewidth))) / (prj.zonewidth * 3) + amgzone = (float(lon) - (prj.initialcm - (4.5 * prj.zonewidth))) / ( + prj.zonewidth * 3 + ) subzone = int((amgzone - int(amgzone)) * 3 + 1) amgzone = int(amgzone) - zone = int(f'{amgzone}{subzone}') + zone = int(f"{amgzone}{subzone}") else: - zone = int((float(lon) - (prj.initialcm - (1.5 * prj.zonewidth))) / prj.zonewidth) + zone = int( + (float(lon) - (prj.initialcm - (1.5 * prj.zonewidth))) / prj.zonewidth + ) if prj == isg: amgzone = int(str(zone)[:2]) subzone = int(str(zone)[2]) - cm = float((amgzone - 1) * prj.zonewidth * 3 + prj.initialcm + (subzone - 2) * prj.zonewidth) + cm = float( + (amgzone - 1) * prj.zonewidth * 3 + + prj.initialcm + + (subzone - 2) * prj.zonewidth + ) else: cm = float(zone * prj.zonewidth + prj.initialcm - prj.zonewidth) # Conformal Latitude sigx = (ellipsoid.ecc1 * tan(lat)) / sqrt(1 + (tan(lat) ** 2)) sig = sinh(ellipsoid.ecc1 * (0.5 * log((1 + sigx) / (1 - sigx)))) - conf_lat = tan(lat) * sqrt(1 + sig ** 2) - sig * sqrt(1 + (tan(lat) ** 2)) + conf_lat = tan(lat) * sqrt(1 + sig**2) - sig * sqrt(1 + (tan(lat) ** 2)) conf_lat = atan(conf_lat) # Longitude Difference @@ -356,14 +424,14 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80, prj=utm): # Gauss-Schreiber Ratios xi1 = atan(tan(conf_lat) / cos(long_diff)) eta1x = sin(long_diff) / (sqrt(tan(conf_lat) ** 2 + cos(long_diff) ** 2)) - eta1 = log(eta1x + sqrt(1 + eta1x ** 2)) + eta1 = log(eta1x + sqrt(1 + eta1x**2)) # Transverse Mercator Ratios eta = eta1 xi = xi1 for r in range(1, 9): - eta += a[r-1] * cos(2*r * xi1) * sinh(2*r * eta1) - xi += a[r-1] * sin(2*r * xi1) * cosh(2*r * eta1) + eta += a[r - 1] * cos(2 * r * xi1) * sinh(2 * r * eta1) + xi += a[r - 1] * sin(2 * r * xi1) * cosh(2 * r * eta1) # Transverse Mercator Co-ordinates x = A * eta @@ -372,28 +440,33 @@ def geo2grid(lat, lon, zone=0, ellipsoid=grs80, prj=utm): # Hemisphere-dependent UTM Projection Co-ordinates east = prj.cmscale * x + prj.falseeast if y < 0: - hemisphere = 'South' + hemisphere = "South" north = prj.cmscale * y + prj.falsenorth else: - hemisphere = 'North' + hemisphere = "North" falsenorth = 0 north = prj.cmscale * y + falsenorth # Point Scale Factor and Grid Convergence psf, grid_conv = psfandgridconv(xi1, eta1, degrees(lat), lon, cm, conf_lat) - return (hemisphere, zone, - round(float(east), 4), - round(float(north), 4), - round(psf, 8), grid_conv) + return ( + hemisphere, + zone, + round(float(east), 4), + round(float(north), 4), + round(psf, 8), + grid_conv, + ) -def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80, prj=utm): +def grid2geo(zone, east, north, hemisphere="south", ellipsoid=grs80, prj=utm): """ Takes a Transverse Mercator grid co-ordinate (Zone, Easting, Northing, Hemisphere) and returns its corresponding Geographic Latitude and Longitude, Point Scale Factor and Grid Convergence. Default Projection is Universal Transverse Mercator Projection using GRS80 Ellipsoid parameters. + :param zone: Zone Number - 1 to 60 :param east: Easting (m, within 3330km of Central Meridian) :param north: Northing (m, 0 to 10,000,000m) @@ -408,30 +481,36 @@ def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80, prj=utm): zone = int(zone) if prj == isg: if zone not in (541, 542, 543, 551, 552, 553, 561, 562, 563, 572): - raise ValueError('Invalid Zone - Choose from 541, 542, 543, 551, 552, 553, 561, 562, 563, 572') + raise ValueError( + "Invalid Zone - Choose from 541, 542, 543, 551, 552, 553, 561, 562, 563, 572" + ) else: if zone < 0 or zone > 60: - raise ValueError('Invalid Zone - Zones from 1 to 60') + raise ValueError("Invalid Zone - Zones from 1 to 60") if east < -2830000 or east > 3830000: - raise ValueError('Invalid Easting - Must be within' - '3330km of Central Meridian') + raise ValueError( + "Invalid Easting - Must be within" "3330km of Central Meridian" + ) if north < 0 or north > 10000000: - raise ValueError('Invalid Northing - Must be between 0 and 10,000,000m') + raise ValueError("Invalid Northing - Must be between 0 and 10,000,000m") h = hemisphere.lower() - if h != 'north' and h != 'south': - raise ValueError('Invalid Hemisphere - String, either North or South') + if h != "north" and h != "south": + raise ValueError("Invalid Hemisphere - String, either North or South") if prj == isg and ellipsoid != ans: - warnings.warn(message='ISG projection should be used with ANS ellipsoid', category=UserWarning) + warnings.warn( + message="ISG projection should be used with ANS ellipsoid", + category=UserWarning, + ) A = rect_radius(ellipsoid) b = beta_coeff(ellipsoid) # Transverse Mercator Co-ordinates x = (east - float(prj.falseeast)) / float(prj.cmscale) - if hemisphere.lower() == 'north': + if hemisphere.lower() == "north": y = -(north / float(prj.cmscale)) hemisign = -1 else: @@ -446,8 +525,8 @@ def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80, prj=utm): eta1 = eta xi1 = xi for r in range(1, 9): - eta1 += b[r-1] * cos(2*r * xi) * sinh(2*r * eta) - xi1 += b[r-1] * sin(2*r * xi) * cosh(2*r * eta) + eta1 += b[r - 1] * cos(2 * r * xi) * sinh(2 * r * eta) + xi1 += b[r - 1] * sin(2 * r * xi) * cosh(2 * r * eta) # Conformal Latitude conf_lat = (sin(xi1)) / (sqrt((sinh(eta1)) ** 2 + (cos(xi1)) ** 2)) @@ -456,20 +535,28 @@ def grid2geo(zone, east, north, hemisphere='south', ellipsoid=grs80, prj=utm): # Finding t using Newtons Method def sigma(tn, ecc1): - return (sinh(ecc1 - * 0.5 - * log((1 + ((ecc1 * tn) / (sqrt(1 + tn ** 2)))) - / (1 - ((ecc1 * tn) / (sqrt(1 + tn ** 2))))))) + return sinh( + ecc1 + * 0.5 + * log( + (1 + ((ecc1 * tn) / (sqrt(1 + tn**2)))) + / (1 - ((ecc1 * tn) / (sqrt(1 + tn**2)))) + ) + ) def ftn(tn, ecc1): - return (t * sqrt(1 + (sigma(tn, ecc1)) ** 2) - - sigma(tn, ecc1) * sqrt(1 + tn ** 2) - t1) + return ( + t * sqrt(1 + (sigma(tn, ecc1)) ** 2) + - sigma(tn, ecc1) * sqrt(1 + tn**2) + - t1 + ) def f1tn(tn, ecc1, ecc1sq): - return ((sqrt(1 + (sigma(tn, ecc1)) ** 2) * sqrt(1 + tn ** 2) - - sigma(tn, ecc1) * tn) - * (((1 - float(ecc1sq)) * sqrt(1 + t ** 2)) - / (1 + (1 - float(ecc1sq)) * t ** 2))) + return ( + sqrt(1 + (sigma(tn, ecc1)) ** 2) * sqrt(1 + tn**2) - sigma(tn, ecc1) * tn + ) * ( + ((1 - float(ecc1sq)) * sqrt(1 + t**2)) / (1 + (1 - float(ecc1sq)) * t**2) + ) diff = 1 t = t1 @@ -477,8 +564,7 @@ def f1tn(tn, ecc1, ecc1sq): while diff > 1e-15 and itercount < 100: itercount += 1 t_before = t - t = t - (ftn(t, ellipsoid.ecc1) - / f1tn(t, ellipsoid.ecc1, ellipsoid.ecc1sq)) + t = t - (ftn(t, ellipsoid.ecc1) / f1tn(t, ellipsoid.ecc1, ellipsoid.ecc1sq)) diff = abs(t - t_before) lat = degrees(atan(t)) @@ -486,7 +572,11 @@ def f1tn(tn, ecc1, ecc1sq): if prj == isg: amgzone = int(str(zone)[:2]) subzone = int(str(zone)[2]) - cm = float((amgzone - 1) * prj.zonewidth * 3 + prj.initialcm + (subzone - 2) * prj.zonewidth) + cm = float( + (amgzone - 1) * prj.zonewidth * 3 + + prj.initialcm + + (subzone - 2) * prj.zonewidth + ) else: cm = float((zone * prj.zonewidth) + prj.initialcm - prj.zonewidth) long_diff = degrees(atan(sinh(eta1) / cos(xi1))) @@ -495,37 +585,41 @@ def f1tn(tn, ecc1, ecc1sq): # Point Scale Factor and Grid Convergence psf, grid_conv = psfandgridconv(xi1, eta1, lat, long, cm, conf_lat) - return (hemisign * round(lat, 11), - round(long, 11), round(psf, 8), - hemisign * grid_conv) + return ( + hemisign * round(lat, 11), + round(long, 11), + round(psf, 8), + hemisign * grid_conv, + ) def xyz2llh(x, y, z, ellipsoid=grs80): """ Converts Cartesian X, Y, Z coordinate to Geographic Latitude, Longitude and Ellipsoid Height. Default Ellipsoid parameters used are GRS80. + :param x: Cartesian X Coordinate (metres) :param y: Cartesian Y Coordinate (metres) :param z: Cartesian Z Coordinate (metres) :param ellipsoid: Ellipsoid Object :type ellipsoid: Ellipsoid :return: Geographic Latitude (Decimal Degrees), Longitude (Decimal Degrees) - and Ellipsoid Height (metres) + and Ellipsoid Height (metres) :rtype: tuple """ # Calculate Longitude long = atan2(y, x) # Calculate Latitude p = sqrt(x**2 + y**2) - latinit = atan((z*(1+ellipsoid.ecc2sq))/p) + latinit = atan((z * (1 + ellipsoid.ecc2sq)) / p) lat = latinit itercheck = 1 while abs(itercheck) > 1e-10: - nu = ellipsoid.semimaj/(sqrt(1 - ellipsoid.ecc1sq * (sin(lat))**2)) - itercheck = lat - atan((z + nu * ellipsoid.ecc1sq * sin(lat))/p) - lat = atan((z + nu * ellipsoid.ecc1sq * sin(lat))/p) - nu = ellipsoid.semimaj/(sqrt(1 - ellipsoid.ecc1sq * (sin(lat))**2)) - ellht = p/(cos(lat)) - nu + nu = ellipsoid.semimaj / (sqrt(1 - ellipsoid.ecc1sq * (sin(lat)) ** 2)) + itercheck = lat - atan((z + nu * ellipsoid.ecc1sq * sin(lat)) / p) + lat = atan((z + nu * ellipsoid.ecc1sq * sin(lat)) / p) + nu = ellipsoid.semimaj / (sqrt(1 - ellipsoid.ecc1sq * (sin(lat)) ** 2)) + ellht = p / (cos(lat)) - nu # Convert Latitude and Longitude to Degrees lat = degrees(lat) long = degrees(long) @@ -536,6 +630,7 @@ def llh2xyz(lat, lon, ellht=0, ellipsoid=grs80): """ Converts Geographic Latitude, Longitude and Ellipsoid Height to Cartesian X, Y and Z Coordinates. Default Ellipsoid parameters used are GRS80. + :param lat: Geographic Latitude :type lat: Float (Decimal Degrees), DMSAngle or DDMAngle :param lon: Geographic Longitude @@ -553,7 +648,7 @@ def llh2xyz(lat, lon, ellht=0, ellipsoid=grs80): if lat == 0: nu = grs80.semimaj else: - nu = ellipsoid.semimaj/(sqrt(1 - ellipsoid.ecc1sq * (sin(lat)**2))) + nu = ellipsoid.semimaj / (sqrt(1 - ellipsoid.ecc1sq * (sin(lat) ** 2))) # Calculate x, y, z x = (nu + ellht) * cos(lat) * cos(lon) y = (nu + ellht) * cos(lat) * sin(lon) @@ -566,16 +661,18 @@ def date_to_yyyydoy(date): Convert a datetime.date object to a string in the form 'yyyy.doy', where yyyy is the 4 character year number and doy is the 3 character day of year + :param date: datetime.date object :type date: datetime.date :return: string with date in the form 'yyyy.doy' :rtype: str """ try: - return (str(date.timetuple().tm_year) + '.' + - str(date.timetuple().tm_yday).zfill(3)) + return ( + str(date.timetuple().tm_year) + "." + str(date.timetuple().tm_yday).zfill(3) + ) except AttributeError: - raise AttributeError('Invalid date: date must be datetime.date object') + raise AttributeError("Invalid date: date must be datetime.date object") def yyyydoy_to_date(yyyydoy): @@ -583,20 +680,21 @@ def yyyydoy_to_date(yyyydoy): Convert a string in the form of either 'yyyydoy' or 'yyyy.doy' to a datetime.date object, where yyyy is the 4 character year number and doy is the 3 character day of year + :param yyyydoy: string with date in the form 'yyyy.doy' or 'yyyydoy' :return: datetime.date object :rtype: datetime.date """ try: - if '.' in yyyydoy: + if "." in yyyydoy: if len(yyyydoy) != 8: - raise ValueError('Invalid string: must be yyyydoy or yyyy.doy') - yyyy, doy = yyyydoy.split('.') + raise ValueError("Invalid string: must be yyyydoy or yyyy.doy") + yyyy, doy = yyyydoy.split(".") else: if len(yyyydoy) != 7: - raise ValueError('Invalid string: must be yyyydoy or yyyy.doy') + raise ValueError("Invalid string: must be yyyydoy or yyyy.doy") yyyy = yyyydoy[0:4] doy = yyyydoy[4:7] return datetime.date(int(yyyy), 1, 1) + datetime.timedelta(int(doy) - 1) except ValueError: - raise ValueError('Invalid string: must be yyyydoy or yyyy.doy') + raise ValueError("Invalid string: must be yyyydoy or yyyy.doy") diff --git a/geodepy/coord.py b/geodepy/coord.py index 6540bb1..1463e64 100644 --- a/geodepy/coord.py +++ b/geodepy/coord.py @@ -6,33 +6,44 @@ """ from geodepy.constants import Projection, utm, grs80 -from geodepy.angles import (DECAngle, HPAngle, GONAngle, DMSAngle, DDMAngle, - dec2hpa, dec2gona, dec2dms, dec2ddm, - angular_typecheck) +from geodepy.angles import ( + DECAngle, + HPAngle, + GONAngle, + DMSAngle, + DDMAngle, + dec2hpa, + dec2gona, + dec2dms, + dec2ddm, + angular_typecheck, +) from geodepy.convert import xyz2llh, llh2xyz, grid2geo, geo2grid class CoordCart(object): """ Cartesian Coordinate Class + Used for working with coordinates representing points in a 3 dimensional earth-centred earth-fixed system. N Value (optional) is the distance (m) at the coordinate that a reference surface (typically a geoid) is above - or below an ellipsoid + or below an ellipsoid. """ + def __init__(self, xaxis, yaxis, zaxis, nval=None): """ :param xaxis: Distance (m) along X Axis (positive when passing through - intersection of equator and prime meridian) + intersection of equator and prime meridian) :type xaxis: float :param yaxis: Distance (m) along Y Axis (positive when passing through - intersection of equator and 90 degrees east longitude) + intersection of equator and 90 degrees east longitude) :type yaxis: float :param zaxis: Distance (m) along Z Axis (positive when passing through - north pole) + north pole) :type zaxis: float :param nval: Distance (m) between a reference surface (typically a - geoid) and an ellipsoid (positive when surface is above ellipsoid) + geoid) and an ellipsoid (positive when surface is above ellipsoid) :type nval: float """ self.xaxis = float(xaxis) @@ -44,36 +55,46 @@ def __init__(self, xaxis, yaxis, zaxis, nval=None): self.nval = float(nval) def __repr__(self): - return (f'CoordCart: X: {self.xaxis} Y: {self.yaxis} Z: {self.zaxis} ' - f'NVal: {self.nval}') + return ( + f"CoordCart: X: {self.xaxis} Y: {self.yaxis} Z: {self.zaxis} " + f"NVal: {self.nval}" + ) def __eq__(self, other): if isinstance(other, CoordCart): return vars(self) == vars(other) else: - raise ValueError(f"Can't compare {self} to {other}. If other object" - f" is coord, convert to CoordCart first.") + raise ValueError( + f"Can't compare {self} to {other}. If other object" + f" is coord, convert to CoordCart first." + ) def __round__(self, n=None): if self.nval is None: - return CoordCart(round(self.xaxis, n), round(self.yaxis, n), - round(self.zaxis, n)) + return CoordCart( + round(self.xaxis, n), round(self.yaxis, n), round(self.zaxis, n) + ) else: - return CoordCart(round(self.xaxis, n), round(self.yaxis, n), - round(self.zaxis, n), round(self.nval, n)) + return CoordCart( + round(self.xaxis, n), + round(self.yaxis, n), + round(self.zaxis, n), + round(self.nval, n), + ) def geo(self, ellipsoid=grs80, notation=DECAngle): """ Convert coordinates to Geographic + Note: If no N Value, no Orthometric Height set. + :param ellipsoid: geodepy.constants.Ellipsoid Object (default: grs80) :param notation: Latitude and Longitude Angle Notation format :type notation: geodepy.angle class or float :return: Geographic Coordinate :rtype: CoordGeo """ - lat, lon, ell_ht = xyz2llh(self.xaxis, self.yaxis, - self.zaxis, ellipsoid) + lat, lon, ell_ht = xyz2llh(self.xaxis, self.yaxis, self.zaxis, ellipsoid) if notation is DECAngle: lat = DECAngle(lat) lon = DECAngle(lon) @@ -92,20 +113,22 @@ def geo(self, ellipsoid=grs80, notation=DECAngle): elif notation is float: pass # geodepy.convert.grid2geo returns float dec degrees else: - raise ValueError(f'CoordCart.geo() notation requires class float or' - f' class from geodepy.angles module. ' - f'Supplied: {notation}') + raise ValueError( + f"CoordCart.geo() notation requires class float or" + f" class from geodepy.angles module. " + f"Supplied: {notation}" + ) if self.nval is None: return CoordGeo(lat, lon, ell_ht) else: - return CoordGeo(lat, lon, ell_ht, - ell_ht - self.nval) + return CoordGeo(lat, lon, ell_ht, ell_ht - self.nval) # TODO: Add functionality to utilise different TM projections def tm(self, ellipsoid=grs80, projection=utm): """ Convert coordinates to Transverse Mercator + :param ellipsoid: geodepy.constants.Ellipsoid Object (default: grs80) :param projection: geodepy.constants.Projection Object (default: utm) :return: Transverse Mercator Projection Coordinate @@ -117,18 +140,20 @@ def tm(self, ellipsoid=grs80, projection=utm): class CoordGeo(object): """ Geographic Coordinate Class + Used for working with coordinates representing points in an ellipsoidal system with ellipsoid heights (m) relative to the surface of the ellipsoid. Orthometric heights (m) are relative to a different reference surface (typically a geoid). """ + def __init__(self, lat, lon, ell_ht=None, orth_ht=None): """ :param lat: Geographic Latitude (angle between +90 and -90 degrees, - positive values are north of equator) + positive values are north of equator) :type lat: float (decimal degrees) or any Angle object in geodepy.angles :param lon: Geographic Longitude (angle between +180 and -180 degrees, - positive values are east of central meridian) + positive values are east of central meridian) :type lon: float (decimal degrees) or any Angle object in geodepy.angles :param ell_ht: Ellipsoid Height (m, positive is outside ellipsoid) :type ell_ht: float @@ -138,14 +163,18 @@ def __init__(self, lat, lon, ell_ht=None, orth_ht=None): # Check latitude and longitude are supported types, both same type type_list = [float, DECAngle, HPAngle, GONAngle, DMSAngle, DDMAngle] if not all(x in type_list for x in [type(lat), type(lon)]): - raise TypeError(f'CoordGeo Latitude and Longitude must be type: ' - f'float (decimal degrees) or geodepy.angles class ' - f'Lat: {type(lat)}, Lon: {type(lon)}') + raise TypeError( + f"CoordGeo Latitude and Longitude must be type: " + f"float (decimal degrees) or geodepy.angles class " + f"Lat: {type(lat)}, Lon: {type(lon)}" + ) if type(lat) != type(lon): - raise TypeError(f'CoordGeo Latitude and Longitude must have the ' - f'same notation type: float (decimal degrees) or ' - f'geodepy.angles class. Lat: {type(lat)}, ' - f'Lon: {type(lon)}') + raise TypeError( + f"CoordGeo Latitude and Longitude must have the " + f"same notation type: float (decimal degrees) or " + f"geodepy.angles class. Lat: {type(lat)}, " + f"Lon: {type(lon)}" + ) self.lat = lat self.lon = lon if ell_ht is None: @@ -158,28 +187,38 @@ def __init__(self, lat, lon, ell_ht=None, orth_ht=None): self.orth_ht = float(orth_ht) def __repr__(self): - return (f'CoordGeo: Lat: {self.lat} Lon: {self.lon} ' - f'Ell_Ht: {self.ell_ht} Orth_Ht: {self.orth_ht}') + return ( + f"CoordGeo: Lat: {self.lat} Lon: {self.lon} " + f"Ell_Ht: {self.ell_ht} Orth_Ht: {self.orth_ht}" + ) def __eq__(self, other): if isinstance(other, CoordGeo): return vars(self) == vars(other) else: - raise ValueError(f"Can't compare {self} to {other}. If other object" - f" is coord, convert to CoordGeo first.") + raise ValueError( + f"Can't compare {self} to {other}. If other object" + f" is coord, convert to CoordGeo first." + ) def __round__(self, n=None): if self.ell_ht is None and self.orth_ht is None: return CoordGeo(round(self.lat, n), round(self.lon, n)) elif self.ell_ht is None: - return CoordGeo(round(self.lat, n), round(self.lon, n), - None, round(self.orth_ht, n)) + return CoordGeo( + round(self.lat, n), round(self.lon, n), None, round(self.orth_ht, n) + ) elif self.orth_ht is None: - return CoordGeo(round(self.lat, n), round(self.lon, n), - round(self.ell_ht, n), None) + return CoordGeo( + round(self.lat, n), round(self.lon, n), round(self.ell_ht, n), None + ) else: - return CoordGeo(round(self.lat, n), round(self.lon, n), - round(self.ell_ht, n), round(self.orth_ht, n)) + return CoordGeo( + round(self.lat, n), + round(self.lon, n), + round(self.ell_ht, n), + round(self.orth_ht, n), + ) def notation(self, notation): if type(self.lat) == float: # Decimal Degrees (float) @@ -203,11 +242,11 @@ def notation(self, notation): new_lon = dec2ddm(self.lon) else: raise ValueError( - f'CoordGeo.notation() notation requires class float or ' - f'class from geodepy.angles module. ' - f'Supplied: {notation}') - elif type(self.lat) in [DECAngle, HPAngle, GONAngle, - DMSAngle, DDMAngle]: + f"CoordGeo.notation() notation requires class float or " + f"class from geodepy.angles module. " + f"Supplied: {notation}" + ) + elif type(self.lat) in [DECAngle, HPAngle, GONAngle, DMSAngle, DDMAngle]: # Use methods to convert from geodepy.angles classes if notation == float: new_lat = self.lat.dec() @@ -229,15 +268,18 @@ def notation(self, notation): new_lon = self.lon.ddm() else: raise ValueError( - f'CoordGeo.notation() notation requires class float or ' - f'class from geodepy.angles module. ' - f'Supplied: {notation}') + f"CoordGeo.notation() notation requires class float or " + f"class from geodepy.angles module. " + f"Supplied: {notation}" + ) return CoordGeo(new_lat, new_lon, self.ell_ht, self.orth_ht) def cart(self, ellipsoid=grs80): """ Convert coordinates to Cartesian + Note: If no ellipsoid height set, uses 0m. No N Value output + :param ellipsoid: geodepy.constants.Ellipsoid Object (default: grs80) :return: Cartesian Coordinate :rtype: CoordCart @@ -257,27 +299,29 @@ def cart(self, ellipsoid=grs80): def tm(self, ellipsoid=grs80, projection=utm): """ Convert coordinates to Universal Transverse Mercator Projection + Note: Heights are not used in calculation + :param ellipsoid: geodepy.constants.Ellipsoid Object (default: grs80) :param projection: geodepy.constants.Projection Object (default: utm) :return: Transverse Mercator Projection Coordinate :rtype: CoordTM """ - hemi, zone, east, north, psf, gc = geo2grid(self.lat, self.lon, - 0, ellipsoid) - if hemi == 'North': + hemi, zone, east, north, psf, gc = geo2grid(self.lat, self.lon, 0, ellipsoid) + if hemi == "North": hemi_north = True else: # hemi == 'South' hemi_north = False - return CoordTM(zone, east, north, - self.ell_ht, self.orth_ht, - hemi_north, projection) + return CoordTM( + zone, east, north, self.ell_ht, self.orth_ht, hemi_north, projection + ) class CoordTM(object): """ Transverse Mercator Coordinate Class + Used for working with coordinates representing points in a Transverse Mercator projected planar system. Coordinates are related to an ellipsoid via a projection (default is Universal Transverse Mercator, see @@ -285,8 +329,17 @@ class CoordTM(object): are relative to the surface of the ellipsoid. Orthometric heights (m) are relative to a different reference surface (typically a geoid). """ - def __init__(self, zone, east, north, ell_ht=None, orth_ht=None, - hemi_north=False, projection=utm): + + def __init__( + self, + zone, + east, + north, + ell_ht=None, + orth_ht=None, + hemi_north=False, + projection=utm, + ): """ :param zone: Transverse Mercator Zone Number - 1 to 60 :type zone: int @@ -299,12 +352,12 @@ def __init__(self, zone, east, north, ell_ht=None, orth_ht=None, :param orth_ht: Orthometric Height (m) :type orth_ht: float :param hemi_north: True if coordinate in Northern Hemisphere, False if - coordinate in Southern Hemisphere (default) + coordinate in Southern Hemisphere (default) :type hemi_north: bool :param projection: Information defining relationship between projected - coordinate and the ellipsoid + coordinate and the ellipsoid :type projection: geodepy.constants.Projection object - (default: geodepy.constants.utm) + (default: geodepy.constants.utm) """ self.zone = int(zone) self.east = float(east) @@ -320,50 +373,84 @@ def __init__(self, zone, east, north, ell_ht=None, orth_ht=None, if isinstance(hemi_north, bool): self.hemi_north = hemi_north else: - raise TypeError(f'CoordTM: invalid hemi_north, must be bool (True ' - f'for Northern Hemisphere, False for Southern). ' - f'hemi_north entered: {hemi_north}') + raise TypeError( + f"CoordTM: invalid hemi_north, must be bool (True " + f"for Northern Hemisphere, False for Southern). " + f"hemi_north entered: {hemi_north}" + ) if isinstance(projection, Projection): self.projection = projection else: - raise TypeError(f'CoordTM: invalid projection, must be geodepy.' - f'constants.Projection object (default is geodepy.' - f'constants.utm. projection entered: {projection}') + raise TypeError( + f"CoordTM: invalid projection, must be geodepy." + f"constants.Projection object (default is geodepy." + f"constants.utm. projection entered: {projection}" + ) def __repr__(self): if self.hemi_north: - return (f'CoordTM: Zone: {self.zone} East: {self.east} ' - f'North: {self.north} Ell_Ht: {self.ell_ht} ' - f'Orth_Ht: {self.orth_ht} Hemisphere: North') + return ( + f"CoordTM: Zone: {self.zone} East: {self.east} " + f"North: {self.north} Ell_Ht: {self.ell_ht} " + f"Orth_Ht: {self.orth_ht} Hemisphere: North" + ) else: # not self.hemi_north - return (f'CoordTM: Zone: {self.zone} East: {self.east} ' - f'North: {self.north} Ell_Ht: {self.ell_ht} ' - f'Orth_Ht: {self.orth_ht} Hemisphere: South') + return ( + f"CoordTM: Zone: {self.zone} East: {self.east} " + f"North: {self.north} Ell_Ht: {self.ell_ht} " + f"Orth_Ht: {self.orth_ht} Hemisphere: South" + ) def __eq__(self, other): if isinstance(other, CoordTM): return vars(self) == vars(other) else: - raise ValueError(f"Can't compare {self} to {other}. If other object" - f" is coord, convert to CoordTM first.") + raise ValueError( + f"Can't compare {self} to {other}. If other object" + f" is coord, convert to CoordTM first." + ) def __round__(self, n=None): if self.ell_ht is None and self.orth_ht is None: - return CoordTM(self.zone, round(self.east, n), round(self.north, n), - None, None, - self.hemi_north, self.projection) + return CoordTM( + self.zone, + round(self.east, n), + round(self.north, n), + None, + None, + self.hemi_north, + self.projection, + ) elif self.ell_ht is None: - return CoordTM(self.zone, round(self.east, n), round(self.north, n), - None, round(self.orth_ht, n), - self.hemi_north, self.projection) + return CoordTM( + self.zone, + round(self.east, n), + round(self.north, n), + None, + round(self.orth_ht, n), + self.hemi_north, + self.projection, + ) elif self.orth_ht is None: - return CoordTM(self.zone, round(self.east, n), round(self.north, n), - round(self.ell_ht, n), None, - self.hemi_north, self.projection) + return CoordTM( + self.zone, + round(self.east, n), + round(self.north, n), + round(self.ell_ht, n), + None, + self.hemi_north, + self.projection, + ) else: - return CoordTM(self.zone, round(self.east, n), round(self.north, n), - round(self.ell_ht, n), round(self.orth_ht, n), - self.hemi_north, self.projection) + return CoordTM( + self.zone, + round(self.east, n), + round(self.north, n), + round(self.ell_ht, n), + round(self.orth_ht, n), + self.hemi_north, + self.projection, + ) def geo(self, ellipsoid=grs80, notation=DECAngle): """ @@ -371,14 +458,16 @@ def geo(self, ellipsoid=grs80, notation=DECAngle): :param ellipsoid: geodepy.constants.Ellipsoid Object (default: grs80) :param notation: Latitude and Longitude Angle Notation format :type notation: geodepy.angle class or float - :return: + :return: Geographic Coordinate + :rtype: CoordGeo """ if self.hemi_north: - hemi_str = 'north' + hemi_str = "north" else: - hemi_str = 'south' - lat, lon, psf, grid_conv = grid2geo(self.zone, self.east, self.north, - hemi_str, ellipsoid) + hemi_str = "south" + lat, lon, psf, grid_conv = grid2geo( + self.zone, self.east, self.north, hemi_str, ellipsoid + ) if notation is DECAngle: lat = DECAngle(lat) lon = DECAngle(lon) @@ -397,9 +486,11 @@ def geo(self, ellipsoid=grs80, notation=DECAngle): elif notation is float: pass # geodepy.convert.grid2geo returns float dec degrees else: - raise ValueError(f'CoordTM.geo() notation requires class float or ' - f'class from geodepy.angles module. ' - f'Supplied: {notation}') + raise ValueError( + f"CoordTM.geo() notation requires class float or " + f"class from geodepy.angles module. " + f"Supplied: {notation}" + ) return CoordGeo(lat, lon, self.ell_ht, self.orth_ht) # TODO: Add functionality to utilise different TM projections @@ -407,7 +498,9 @@ def geo(self, ellipsoid=grs80, notation=DECAngle): def cart(self, ellipsoid=grs80): """ Convert coordinates to Cartesian + Note: If no ellipsoid height set, uses 0m. No N Value output + :param ellipsoid: geodepy.constants.Ellipsoid Object (default: grs80) :return: Cartesian Coordinate :rtype: CoordCart diff --git a/geodepy/fileio.py b/geodepy/fileio.py index 8a0c4f2..8e7d63a 100644 --- a/geodepy/fileio.py +++ b/geodepy/fileio.py @@ -9,8 +9,25 @@ class DNACoord(object): - def __init__(self, pointid, const, easting, northing, zone, lat, - long, ortho_ht, ell_ht, x, y, z, x_sd, y_sd, z_sd, desc): + def __init__( + self, + pointid, + const, + easting, + northing, + zone, + lat, + long, + ortho_ht, + ell_ht, + x, + y, + z, + x_sd, + y_sd, + z_sd, + desc, + ): self.pointid = pointid self.const = const self.easting = easting @@ -35,7 +52,7 @@ def converthptodd(self): def read_dnacoord(fn): coord_list = [] - with open(fn, 'r') as file: + with open(fn, "r") as file: dnadata = file.readlines() for line in dnadata: pointid = line[0:20] @@ -54,8 +71,23 @@ def read_dnacoord(fn): y_sd = float(line[172:181]) z_sd = float(line[182:191]) desc = line[192:-1] - record = DNACoord(pointid.strip(), const.strip(), easting, - northing, zone, lat, long, ortho_ht, ell_ht, x, - y, z, x_sd, y_sd, z_sd, desc.strip()) + record = DNACoord( + pointid.strip(), + const.strip(), + easting, + northing, + zone, + lat, + long, + ortho_ht, + ell_ht, + x, + y, + z, + x_sd, + y_sd, + z_sd, + desc.strip(), + ) coord_list.append(record) return coord_list diff --git a/geodepy/geodesy.py b/geodepy/geodesy.py index b20b891..237f73a 100644 --- a/geodepy/geodesy.py +++ b/geodepy/geodesy.py @@ -16,6 +16,7 @@ def enu2xyz(lat, lon, east, north, up): """Convert a column vector in the local reference frame to a column vector in the Cartesian reference frame. + :param lat: latitude in decimal degrees :param lon: longitude in decimal degrees :param east: in metres @@ -35,6 +36,7 @@ def enu2xyz(lat, lon, east, north, up): def xyz2enu(lat, lon, x, y, z): """Convert a column vector in the Cartesian reference frame to a column vector in the local reference frame. + :param lat: latitude in decimal degrees :param lon: longitude in decimal degrees :param x: in metres @@ -54,6 +56,7 @@ def xyz2enu(lat, lon, x, y, z): def vincdir(lat1, lon1, azimuth1to2, ell_dist, ellipsoid=grs80): """ Vincenty's Direct Formula + :param lat1: Latitude of Point 1 (decimal degrees) :type lat1: float (decimal degrees), DMSAngle or DDMAngle :param lon1: Longitude of Point 1 (decimal degrees) @@ -62,11 +65,11 @@ def vincdir(lat1, lon1, azimuth1to2, ell_dist, ellipsoid=grs80): :type azimuth1to2: float (decimal degrees), DMSAngle or DDMAngle :param ell_dist: Ellipsoidal Distance between Points 1 and 2 (metres) :param ellipsoid: Ellipsoid Object - :return: lat2: Latitude of Point 2 (Decimal Degrees), - lon2: Longitude of Point 2 (Decimal Degrees), - azimuth2to1: Azimuth from Point 2 to 1 (Decimal Degrees) + :return: + - lat2 - Latitude of Point 2 (Decimal Degrees), + - lon2 - Longitude of Point 2 (Decimal Degrees), + - azimuth2to1 - Azimuth from Point 2 to 1 (Decimal Degrees) - Code review: 14-08-2018 Craig Harrison """ # Convert Angles to Decimal Degrees (if required) @@ -86,17 +89,21 @@ def vincdir(lat1, lon1, azimuth1to2, ell_dist, ellipsoid=grs80): alpha = asin(cos(u1) * sin(azimuth1to2)) # Eq. 91 - u_squared = cos(alpha)**2 \ - * (ellipsoid.semimaj**2 - ellipsoid.semimin**2) \ + u_squared = ( + cos(alpha) ** 2 + * (ellipsoid.semimaj**2 - ellipsoid.semimin**2) / ellipsoid.semimin**2 + ) # Eq. 92 - a = 1 + (u_squared / 16384) \ - * (4096 + u_squared * (-768 + u_squared * (320 - 175 * u_squared))) + a = 1 + (u_squared / 16384) * ( + 4096 + u_squared * (-768 + u_squared * (320 - 175 * u_squared)) + ) # Eq. 93 - b = (u_squared / 1024) \ - * (256 + u_squared * (-128 + u_squared * (74 - 47 * u_squared))) + b = (u_squared / 1024) * ( + 256 + u_squared * (-128 + u_squared * (74 - 47 * u_squared)) + ) # Eq. 94 sigma = ell_dist / (ellipsoid.semimin * a) @@ -107,15 +114,24 @@ def vincdir(lat1, lon1, azimuth1to2, ell_dist, ellipsoid=grs80): for i in range(1000): # Eq. 95 - two_sigma_m = 2*sigma1 + sigma + two_sigma_m = 2 * sigma1 + sigma # Eq. 96 - delta_sigma = b * sin(sigma) * (cos(two_sigma_m) + (b/4) - * (cos(sigma) - * (-1 + 2 * cos(two_sigma_m)**2) - - (b/6) * cos(two_sigma_m) - * (-3 + 4 * sin(sigma)**2) - * (-3 + 4 * cos(two_sigma_m)**2))) + delta_sigma = ( + b + * sin(sigma) + * ( + cos(two_sigma_m) + + (b / 4) + * ( + cos(sigma) * (-1 + 2 * cos(two_sigma_m) ** 2) + - (b / 6) + * cos(two_sigma_m) + * (-3 + 4 * sin(sigma) ** 2) + * (-3 + 4 * cos(two_sigma_m) ** 2) + ) + ) + ) new_sigma = (ell_dist / (ellipsoid.semimin * a)) + delta_sigma sigma_change = new_sigma - sigma sigma = new_sigma @@ -125,32 +141,51 @@ def vincdir(lat1, lon1, azimuth1to2, ell_dist, ellipsoid=grs80): # Calculate the Latitude of Point 2 # Eq. 98 - lat2 = atan2(sin(u1)*cos(sigma) + cos(u1)*sin(sigma)*cos(azimuth1to2), - (1 - ellipsoid.f) - * sqrt(sin(alpha)**2 + (sin(u1)*sin(sigma) - - cos(u1)*cos(sigma)*cos(azimuth1to2))**2)) + lat2 = atan2( + sin(u1) * cos(sigma) + cos(u1) * sin(sigma) * cos(azimuth1to2), + (1 - ellipsoid.f) + * sqrt( + sin(alpha) ** 2 + + (sin(u1) * sin(sigma) - cos(u1) * cos(sigma) * cos(azimuth1to2)) ** 2 + ), + ) lat2 = degrees(lat2) # Calculate the Longitude of Point 2 # Eq. 99 - lon = atan2(sin(sigma)*sin(azimuth1to2), - cos(u1)*cos(sigma) - sin(u1)*sin(sigma)*cos(azimuth1to2)) + lon = atan2( + sin(sigma) * sin(azimuth1to2), + cos(u1) * cos(sigma) - sin(u1) * sin(sigma) * cos(azimuth1to2), + ) # Eq. 100 - c = (ellipsoid.f/16)*cos(alpha)**2 \ - * (4 + ellipsoid.f*(4 - 3*cos(alpha)**2)) + c = ( + (ellipsoid.f / 16) + * cos(alpha) ** 2 + * (4 + ellipsoid.f * (4 - 3 * cos(alpha) ** 2)) + ) # Eq. 101 - omega = lon - (1-c)*ellipsoid.f*sin(alpha) \ - * (sigma + c*sin(sigma)*(cos(two_sigma_m) + c*cos(sigma) - * (-1 + 2*cos(two_sigma_m)**2))) + omega = lon - (1 - c) * ellipsoid.f * sin(alpha) * ( + sigma + + c + * sin(sigma) + * (cos(two_sigma_m) + c * cos(sigma) * (-1 + 2 * cos(two_sigma_m) ** 2)) + ) # Eq. 102 lon2 = float(lon1) + degrees(omega) # Calculate the Reverse Azimuth - azimuth2to1 = degrees(atan2(sin(alpha), -sin(u1)*sin(sigma) - + cos(u1)*cos(sigma)*cos(azimuth1to2))) + 180 + azimuth2to1 = ( + degrees( + atan2( + sin(alpha), + -sin(u1) * sin(sigma) + cos(u1) * cos(sigma) * cos(azimuth1to2), + ) + ) + + 180 + ) return round(lat2, 11), round(lon2, 11), round(azimuth2to1, 9) @@ -158,6 +193,7 @@ def vincdir(lat1, lon1, azimuth1to2, ell_dist, ellipsoid=grs80): def vincinv(lat1, lon1, lat2, lon2, ellipsoid=grs80): """ Vincenty's Inverse Formula + :param lat1: Latitude of Point 1 (decimal degrees) :type lat1: float (decimal degrees), DMSAngle or DDMAngle :param lon1: Longitude of Point 1 (decimal degrees) @@ -167,10 +203,11 @@ def vincinv(lat1, lon1, lat2, lon2, ellipsoid=grs80): :param lon2: Longitude of Point 2 (decimal degrees) :type lon2: float (decimal degrees), DMSAngle or DDMAngle :param ellipsoid: Ellipsoid Object - :return: ell_dist: Ellipsoidal Distance between Points 1 and 2 (m), - azimuth1to2: Azimuth from Point 1 to 2 (Decimal Degrees), - azimuth2to1: Azimuth from Point 2 to 1 (Decimal Degrees) - Code review: 14-08-2018 Craig Harrison + :return: + - ell_dist - Ellipsoidal Distance between Points 1 and 2 (m), + - azimuth1to2 - Azimuth from Point 1 to 2 (Decimal Degrees), + - azimuth2to1 - Azimuth from Point 2 to 1 (Decimal Degrees) + """ # Convert Angles to Decimal Degrees (if required) @@ -204,29 +241,37 @@ def vincinv(lat1, lon1, lat2, lon2, ellipsoid=grs80): for i in range(1000): # Eq. 74 - sin_sigma = sqrt((cos(u2)*sin(lon))**2 - + (cos(u1)*sin(u2) - sin(u1)*cos(u2)*cos(lon))**2) + sin_sigma = sqrt( + (cos(u2) * sin(lon)) ** 2 + + (cos(u1) * sin(u2) - sin(u1) * cos(u2) * cos(lon)) ** 2 + ) # Eq. 75 - cos_sigma = sin(u1)*sin(u2) + cos(u1)*cos(u2)*cos(lon) + cos_sigma = sin(u1) * sin(u2) + cos(u1) * cos(u2) * cos(lon) # Eq. 76 sigma = atan2(sin_sigma, cos_sigma) # Eq. 77 - alpha = asin((cos(u1)*cos(u2)*sin(lon)) / sin_sigma) + alpha = asin((cos(u1) * cos(u2) * sin(lon)) / sin_sigma) # Eq. 78 - cos_two_sigma_m = cos(sigma) - (2*sin(u1)*sin(u2) / cos(alpha)**2) + cos_two_sigma_m = cos(sigma) - (2 * sin(u1) * sin(u2) / cos(alpha) ** 2) # Eq. 79 - c = (ellipsoid.f / 16) * cos(alpha)**2 * (4 + ellipsoid.f - * (4 - 3*cos(alpha)**2)) + c = ( + (ellipsoid.f / 16) + * cos(alpha) ** 2 + * (4 + ellipsoid.f * (4 - 3 * cos(alpha) ** 2)) + ) # Eq. 80 - new_lon = omega + (1 - c) * ellipsoid.f * sin(alpha) * (sigma + c*sin(sigma) - * (cos_two_sigma_m + c * cos(sigma) - * (-1 + 2*(cos_two_sigma_m**2)))) + new_lon = omega + (1 - c) * ellipsoid.f * sin(alpha) * ( + sigma + + c + * sin(sigma) + * (cos_two_sigma_m + c * cos(sigma) * (-1 + 2 * (cos_two_sigma_m**2))) + ) delta_lon = new_lon - lon lon = new_lon @@ -235,61 +280,83 @@ def vincinv(lat1, lon1, lat2, lon2, ellipsoid=grs80): break # Eq. 81 - u_squared = cos(alpha)**2 \ - * (ellipsoid.semimaj**2 - ellipsoid.semimin**2) \ + u_squared = ( + cos(alpha) ** 2 + * (ellipsoid.semimaj**2 - ellipsoid.semimin**2) / ellipsoid.semimin**2 + ) # Eq. 82 - a = 1 + (u_squared / 16384) \ - * (4096 + u_squared * (-768 + u_squared * (320 - 175 * u_squared))) + a = 1 + (u_squared / 16384) * ( + 4096 + u_squared * (-768 + u_squared * (320 - 175 * u_squared)) + ) # Eq. 83 - b = (u_squared / 1024) \ - * (256 + u_squared * (-128 + u_squared * (74 - 47 * u_squared))) + b = (u_squared / 1024) * ( + 256 + u_squared * (-128 + u_squared * (74 - 47 * u_squared)) + ) # Eq. 84 - delta_sigma = b*sin(sigma) * (cos_two_sigma_m + (b / 4) - * (cos(sigma) * (-1 + 2*cos_two_sigma_m**2) - - (b / 6)*cos_two_sigma_m - * (-3 + 4*sin(sigma)**2) - * (-3 + 4*cos_two_sigma_m**2))) + delta_sigma = ( + b + * sin(sigma) + * ( + cos_two_sigma_m + + (b / 4) + * ( + cos(sigma) * (-1 + 2 * cos_two_sigma_m**2) + - (b / 6) + * cos_two_sigma_m + * (-3 + 4 * sin(sigma) ** 2) + * (-3 + 4 * cos_two_sigma_m**2) + ) + ) + ) # Calculate the ellipsoidal distance # Eq. 85 - ell_dist = ellipsoid.semimin*a * (sigma - delta_sigma) + ell_dist = ellipsoid.semimin * a * (sigma - delta_sigma) # Calculate the azimuth from point 1 to point 2 - azimuth1to2 = degrees(atan2((cos(u2)*sin(lon)), - (cos(u1)*sin(u2) - - sin(u1)*cos(u2)*cos(lon)))) + azimuth1to2 = degrees( + atan2((cos(u2) * sin(lon)), (cos(u1) * sin(u2) - sin(u1) * cos(u2) * cos(lon))) + ) if azimuth1to2 < 0: azimuth1to2 = azimuth1to2 + 360 # Calculate the azimuth from point 2 to point 1 - azimuth2to1 = degrees(atan2(cos(u1)*sin(lon), - (-sin(u1)*cos(u2) - + cos(u1)*sin(u2)*cos(lon)))) + 180 + azimuth2to1 = ( + degrees( + atan2( + cos(u1) * sin(lon), (-sin(u1) * cos(u2) + cos(u1) * sin(u2) * cos(lon)) + ) + ) + + 180 + ) # Meridian Critical Case Tests - #if lon1 == lon2 and lat1 > lat2: + # if lon1 == lon2 and lat1 > lat2: # azimuth1to2 = 180 # azimuth2to1 = 0 - #elif lon1 == lon2 and lat1 < lat2: + # elif lon1 == lon2 and lat1 < lat2: # azimuth1to2 = 0 # azimuth2to1 = 180 return round(ell_dist, 3), round(azimuth1to2, 9), round(azimuth2to1, 9) -def vincdir_utm(zone1, east1, north1, grid1to2, grid_dist, - hemisphere='south', ellipsoid=grs80): +def vincdir_utm( + zone1, east1, north1, grid1to2, grid_dist, hemisphere="south", ellipsoid=grs80 +): """ Perform Vincenty's Direct Computation using UTM Grid Coordinates, a grid bearing and grid distance. + Note: Point 2 UTM Coordinates use the Zone specified for Point 1, even if Point 2 would typically be computed in a different zone. This keeps the grid bearings and line scale factor all relative to the same UTM Zone. + :param zone1: Point 1 Zone Number - 1 to 60 :param east1: Point 1 Easting (m, within 3330km of Central Meridian) :param north1: Point 1 Northing (m, 0 to 10,000,000m) @@ -297,25 +364,24 @@ def vincdir_utm(zone1, east1, north1, grid1to2, grid_dist, :param grid_dist: UTM Grid Distance between Points 1 and 2 (m) :param hemisphere: String - 'North' or 'South'(default) :param ellipsoid: Ellipsoid Object (default: GRS80) - :return: zone2: Point 2 Zone Number - 1 to 60 - east2: Point 2 Easting (m, within 3330km of Central Meridian) - north2: Point 2 Northing (m, 0 to 10,000,000m) - grid2to1: Grid Bearing from Point 2 to 1 (decimal degrees) - lsf: Line Scale Factor (for Point 1 Zone) + :return: + - zone2 - Point 2 Zone Number - 1 to 60 + - east2 - Point 2 Easting (m, within 3330km of Central Meridian) + - north2 - Point 2 Northing (m, 0 to 10,000,000m) + - grid2to1 - Grid Bearing from Point 2 to 1 (decimal degrees) + - lsf - Line Scale Factor (for Point 1 Zone) """ # Convert angle to decimal degrees (if required) grid1to2 = angular_typecheck(grid1to2) # Convert UTM Coords to Geographic - lat1, lon1, psf1, gridconv1 = grid2geo(zone1, east1, north1, - hemisphere, ellipsoid) + lat1, lon1, psf1, gridconv1 = grid2geo(zone1, east1, north1, hemisphere, ellipsoid) # Convert Grid Bearing to Geodetic Azimuth az1to2 = grid1to2 - gridconv1 # Estimate Line Scale Factor (LSF) - zone2, east2, north2 = (zone1, *radiations(east1, north1, - grid1to2, grid_dist)) + zone2, east2, north2 = (zone1, *radiations(east1, north1, grid1to2, grid_dist)) lsf = line_sf(zone1, east1, north1, zone2, east2, north2) # Iteratively estimate Pt 2 Coordinates, refining LSF each time @@ -323,31 +389,30 @@ def vincdir_utm(zone1, east1, north1, grid1to2, grid_dist, max_iter = 10 while lsf_diff > 1e-9: lsf_previous = lsf - lat2, lon2, az2to1 = vincdir(lat1, lon1, az1to2, - grid_dist / lsf, ellipsoid) - (hemisphere2, zone2, east2, - north2, psf2, gridconv2) = geo2grid(lat2, lon2, - zone1, ellipsoid) - lsf = line_sf(zone1, east1, north1, - zone2, east2, north2, - hemisphere, ellipsoid) + lat2, lon2, az2to1 = vincdir(lat1, lon1, az1to2, grid_dist / lsf, ellipsoid) + (hemisphere2, zone2, east2, north2, psf2, gridconv2) = geo2grid( + lat2, lon2, zone1, ellipsoid + ) + lsf = line_sf(zone1, east1, north1, zone2, east2, north2, hemisphere, ellipsoid) lsf_diff = abs(lsf_previous - lsf) # Compute Grid Bearing for Station 2 - lat2, lon2, psf2, gridconv2 = grid2geo(zone2, east2, north2, - hemisphere, ellipsoid) + lat2, lon2, psf2, gridconv2 = grid2geo(zone2, east2, north2, hemisphere, ellipsoid) grid2to1 = az2to1 + gridconv2 return zone2, east2, north2, grid2to1, lsf -def vincinv_utm(zone1, east1, north1, zone2, east2, north2, - hemisphere='south', ellipsoid=grs80): +def vincinv_utm( + zone1, east1, north1, zone2, east2, north2, hemisphere="south", ellipsoid=grs80 +): """ - Perform Vincentys Inverse Computation using UTM Grid Coordinates + Perform Vincentys Inverse Computation using UTM Grid Coordinates. + Note: Where coordinates from different zones are used, UTM Grid Distance is relative to Point 1's Zone. Grid Bearings of Points 1 and 2 are relative to each of their respective Zones. + :param zone1: Point 1 Zone Number - 1 to 60 :param east1: Point 1 Easting (m, within 3330km of Central Meridian) :param north1: Point 1 Northing (m, 0 to 10,000,000m) @@ -356,20 +421,19 @@ def vincinv_utm(zone1, east1, north1, zone2, east2, north2, :param north2: Point 2 Northing (m, 0 to 10,000,000m) :param hemisphere: String - 'North' or 'South'(default) :param ellipsoid: Ellipsoid Object (default: GRS80) - :return: grid_dist: UTM Grid Distance between Points 1 and 2 (m), - grid1to2: Grid Bearing from Point 1 to 2 (decimal degrees), - grid2to1: Grid Bearing from Point 2 to 1 (decimal degrees) - lsf: Line Scale Factor (for Point 1 Zone) + :return: + - grid_dist - UTM Grid Distance between Points 1 and 2 (m), + - grid1to2 - Grid Bearing from Point 1 to 2 (decimal degrees), + - grid2to1 - Grid Bearing from Point 2 to 1 (decimal degrees) + - lsf - Line Scale Factor (for Point 1 Zone) """ # Convert utm to geographic pt1 = grid2geo(zone1, east1, north1, hemisphere, ellipsoid) pt2 = grid2geo(zone2, east2, north2, hemisphere, ellipsoid) - ell_dist, az1to2, az2to1 = vincinv(pt1[0], pt1[1], - pt2[0], pt2[1], ellipsoid) + ell_dist, az1to2, az2to1 = vincinv(pt1[0], pt1[1], pt2[0], pt2[1], ellipsoid) # Compute Grid Distance using Line Scale Factor - lsf = line_sf(zone1, east1, north1, - zone2, east2, north2, hemisphere, ellipsoid) + lsf = line_sf(zone1, east1, north1, zone2, east2, north2, hemisphere, ellipsoid) grid_dist = ell_dist * lsf # Compute Grid Bearings using Grid Convergence @@ -379,13 +443,24 @@ def vincinv_utm(zone1, east1, north1, zone2, east2, north2, return grid_dist, grid1to2, grid2to1, lsf -def line_sf(zone1, east1, north1, zone2, east2, north2, - hemisphere='south', ellipsoid=grs80, projection=utm): +def line_sf( + zone1, + east1, + north1, + zone2, + east2, + north2, + hemisphere="south", + ellipsoid=grs80, + projection=utm, +): """ - Computes Line Scale Factor for a pair of Transverse Mercator Coordinates + Computes Line Scale Factor for a pair of Transverse Mercator Coordinates. + Ref: Deakin 2010, Traverse Computations on the Ellipsoid and on the Universal Transverse Mercator Projection, pp 35 http://www.mygeodesy.id.au/documents/Trav_Comp_V2.1.pdf + :param zone1: Station 1 Zone Number - 1 to 60 :param east1: Station 1 Easting (m, within 3330km of Central Meridian) :param north1: Station 1 Northing (m, 0 to 10,000,000m) @@ -396,7 +471,7 @@ def line_sf(zone1, east1, north1, zone2, east2, north2, :param ellipsoid: Ellipsoid Object (default GRS80) :param projection: Projection Object (default Universal Transverse Mercator) :return: Line Scale Factor (relative to Zone 1 if different zones - are entered) + are entered) """ # Re-project cross-zone coordinate to same UTM zone if zone1 != zone2: @@ -417,13 +492,9 @@ def line_sf(zone1, east1, north1, zone2, east2, north2, lat_mean = (lat1 + lat2) / 2 # Compute Line Scale Factor (Deakin 2010 Eq. 13) - r_sq_m = (rho(lat_mean, ellipsoid) * - nu(lat_mean, ellipsoid) * - projection.cmscale ** 2) - k1 = ((eastofcm1 ** 2 + eastofcm1 * eastofcm2 + eastofcm2 ** 2) / - (6 * r_sq_m)) - k2 = ((eastofcm1 ** 2 + eastofcm1 * eastofcm2 + eastofcm2 ** 2) / - (36 * r_sq_m)) + r_sq_m = rho(lat_mean, ellipsoid) * nu(lat_mean, ellipsoid) * projection.cmscale**2 + k1 = (eastofcm1**2 + eastofcm1 * eastofcm2 + eastofcm2**2) / (6 * r_sq_m) + k2 = (eastofcm1**2 + eastofcm1 * eastofcm2 + eastofcm2**2) / (36 * r_sq_m) return projection.cmscale * (1 + k1 * (1 + k2)) @@ -431,21 +502,23 @@ def rho(lat, ellipsoid=grs80): """ Return the radius of curvature of the ellipsoid in the meridian plane (rho) at a given latitude + :param lat: latitude in decimal degrees :param ellipsoid: Ellipsoid Object :return: rho at specified latitude """ - return ((ellipsoid.semimaj * (1 - ellipsoid.ecc1sq)) / - (1 - ellipsoid.ecc1sq * (sin(radians(lat)) ** 2)) ** 1.5) + return (ellipsoid.semimaj * (1 - ellipsoid.ecc1sq)) / ( + 1 - ellipsoid.ecc1sq * (sin(radians(lat)) ** 2) + ) ** 1.5 def nu(lat, ellipsoid=grs80): """ Return the radius of curvature of the ellipsoid in the prime vertical plane (nu) at a given latitude + :param lat: latitude in decimal degrees :param ellipsoid: Ellipsoid Object :return: nu at specified latitude """ - return (ellipsoid.semimaj / - sqrt(1 - ellipsoid.ecc1sq * (sin(radians(lat)) ** 2))) + return ellipsoid.semimaj / sqrt(1 - ellipsoid.ecc1sq * (sin(radians(lat)) ** 2)) diff --git a/geodepy/gnss.py b/geodepy/gnss.py index 3d19547..65903a8 100644 --- a/geodepy/gnss.py +++ b/geodepy/gnss.py @@ -12,10 +12,10 @@ - remove_velocity_sinex() - remove_matrixzeros_sinex() -General functions for reading from SINEX: +General functions for reading from SINEX: - list_sinex_blocks() - - read_sinex_comments() + - read_sinex_comments() - read_sinex_header_line() - read_sinex_custom() - read_sinex_estimate() @@ -43,7 +43,7 @@ - sinex2dataframe_solution_matrix_estimate() General functions for writing to SINEX: - + - print_sinex_comments() - set_creation_time() - dataframe2sinex_solution_estimate() @@ -52,7 +52,7 @@ - dataframe2matrix_solution_matrix_estimate() - dataframe2sinex_solution_matrix_estimate() - writeSINEX() - + """ from datetime import datetime @@ -64,20 +64,22 @@ def list_sinex_blocks(file): - """This script lists the blocks in a SINEX file + """ + This script lists the blocks in a SINEX file :param str file: the input SINEX file """ blocks = [] with open(file) as f: for line in f.readlines(): - if line.startswith('+'): - col = line.split(' ') - block = col[0].replace('+', '') + if line.startswith("+"): + col = line.split(" ") + block = col[0].replace("+", "") blocks.append(block.strip()) for block in blocks: print(block) + def print_sinex_comments(file): """This script prints comments in a SINEX file. @@ -86,13 +88,14 @@ def print_sinex_comments(file): go = False with open(file) as f: for line in f.readlines(): - if line.startswith('+FILE/COMMENT'): + if line.startswith("+FILE/COMMENT"): go = True if go: print(line.strip()) - if line.startswith('-FILE/COMMENT'): + if line.startswith("-FILE/COMMENT"): go = False + def read_sinex_comments(file): """This function reads comments in a SINEX file. @@ -104,17 +107,21 @@ def read_sinex_comments(file): go = False with open(file) as f: for line in f.readlines(): - if line.startswith('+FILE/COMMENT'): + if line.startswith("+FILE/COMMENT"): go = True if go: comments.append(line.strip()) - if line.startswith('-FILE/COMMENT'): + if line.startswith("-FILE/COMMENT"): go = False - comments.insert(-1,f"* File created by Geodepy.gnss.py at {datetime.now().strftime('%d-%m-%Y, %H:%M')}") + comments.insert( + -1, + f"* File created by Geodepy.gnss.py at {datetime.now().strftime('%d-%m-%Y, %H:%M')}", + ) return comments + def set_creation_time(): """This function sets the creation time, in format YY:DDD:SSSSS, for use in the SINEX header line @@ -126,15 +133,17 @@ def set_creation_time(): time_tup = now.timetuple() year = str(time_tup.tm_year)[2:] doy = time_tup.tm_yday - doy = '{:03d}'.format(doy) - seconds = (now - now.replace(hour=0, minute=0, second=0, microsecond=0))\ - .total_seconds() - #seconds = '{:.0f}'.format(seconds) - seconds = '{:05.0f}'.format(seconds) - creation_time = year + ':' + doy + ':' + seconds + doy = "{:03d}".format(doy) + seconds = ( + now - now.replace(hour=0, minute=0, second=0, microsecond=0) + ).total_seconds() + # seconds = '{:.0f}'.format(seconds) + seconds = "{:05.0f}".format(seconds) + creation_time = year + ":" + doy + ":" + seconds return creation_time + def read_sinex_header_line(file): """This function reads the header line of a SINEX file into a string @@ -147,13 +156,14 @@ def read_sinex_header_line(file): return header_line + def read_sinex_custom(fp, start_line, end_line): - """Read custom line range from SINEX. Useful for - SINEX with irregular formatting or block that has + """Read custom line range from SINEX. Useful for + SINEX with irregular formatting or block that has not been accounted for. :param str fp: Path to SINEX file. - :param int start_line: Beginning line number. + :param int start_line: Beginning line number. :param int end_line: Finishing line number. :return list custom: List of string(s). """ @@ -165,37 +175,37 @@ def read_sinex_custom(fp, start_line, end_line): custom = [] with open(fp) as f: - for line in f.readlines()[i-1:j]: - custom.append(line.strip()) + for line in f.readlines()[i - 1 : j]: + custom.append(line.strip()) # Return return custom + def read_sinex_estimate(file): """This function reads in the SOLUTION/ESTIMATE block of a SINEX file. It returns estimate, a list of tuples: - estimate = [(code, soln, refEpoch, staX, staY, staZ, staX_sd, staY_sd, - staZ_sd[, velX, velY, velZ, velX_sd, velY_sd, velZ_sd])...] + estimate = [(code, soln, refEpoch, staX, staY, staZ, staX_sd, staY_sd, staZ_sd[, velX, velY, velZ, velX_sd, velY_sd, velZ_sd])...] where: * code is the stations's 4-character ID * soln is the segment of the stations's time series * refEpoch is the epoch of the solution in the form YY:DOY:SSSSS (YY - is the two digit year, DOY is day of year, and SSSSS is the time of - day in seconds + is the two digit year, DOY is day of year, and SSSSS is the time of + day in seconds * sta[XYZ] is the station coordinates in the Cartesian reference frame * sta[XYZ]_sd is the standard deviation of the station coordinates in - the Cartesian reference frame + the Cartesian reference frame * vel[XYZ] is the station velocity in the Cartesian reference frame * vel[XYZ]_sd is the standard deviation of the station velocity in the - Cartesian reference frame + Cartesian reference frame Velocities are not included in all SINEX files and so are only returned if present. :param file: the input SINEX file - :return: estimate + :return: Sinex estimates as list of lists """ # Create data structures and set variables @@ -203,77 +213,90 @@ def read_sinex_estimate(file): estimate = [] velocities = False go = False - code = '' - soln = '' - epoch = '' - stax = '' - stay = '' - staz = '' - stax_sd = '' - stay_sd = '' - staz_sd = '' - velx = '' - vely = '' - velz = '' - velx_sd = '' - vely_sd = '' - velz_sd = '' + code = "" + soln = "" + epoch = "" + stax = "" + stay = "" + staz = "" + stax_sd = "" + stay_sd = "" + staz_sd = "" + velx = "" + vely = "" + velz = "" + velx_sd = "" + vely_sd = "" + velz_sd = "" # Read the SOLUTION/ESTIMATE block into a list and determine if there is # any velocity information with open(file) as f: for line in f: - if line[:18] == '-SOLUTION/ESTIMATE': + if line[:18] == "-SOLUTION/ESTIMATE": break - if go and line[:11] == '*INDEX TYPE': + if go and line[:11] == "*INDEX TYPE": pass elif go: - if line[7:10] == 'VEL': + if line[7:10] == "VEL": velocities = True lines.append(line) - if line[:18] == '+SOLUTION/ESTIMATE': + if line[:18] == "+SOLUTION/ESTIMATE": go = True for line in lines: typ = line[7:11] - if typ == 'STAX': + if typ == "STAX": code = line[14:18] soln = line[23:26].lstrip() epoch = line[27:39] stax = float(line[47:68]) stax_sd = float(line[69:80]) - elif typ == 'STAY': + elif typ == "STAY": stay = float(line[47:68]) stay_sd = float(line[69:80]) - elif typ == 'STAZ': + elif typ == "STAZ": staz = float(line[47:68]) staz_sd = float(line[69:80]) if not velocities: - info = (code, soln, epoch, stax, stay, staz, stax_sd, stay_sd, - staz_sd) + info = (code, soln, epoch, stax, stay, staz, stax_sd, stay_sd, staz_sd) estimate.append(info) - elif typ == 'VELX': + elif typ == "VELX": velx = float(line[47:68]) velx_sd = float(line[69:80]) - elif typ == 'VELY': + elif typ == "VELY": vely = float(line[47:68]) vely_sd = float(line[69:80]) - elif typ == 'VELZ': + elif typ == "VELZ": velz = float(line[47:68]) velz_sd = float(line[69:80]) - info = (code, soln, epoch, stax, stay, staz, stax_sd, stay_sd, - staz_sd, velx, vely, velz, velx_sd, vely_sd, velz_sd) + info = ( + code, + soln, + epoch, + stax, + stay, + staz, + stax_sd, + stay_sd, + staz_sd, + velx, + vely, + velz, + velx_sd, + vely_sd, + velz_sd, + ) estimate.append(info) return estimate + def read_sinex_matrix(file): """This function reads in the SOLUTION/MATRIX_ESTIMATE block of a SINEX file. It returns matrix, a list of tuples: - matrix = [(code, soln, var_x, covar_xy, covar_xz, var_y, covar_yz, - var_z[, var_v_x, covar_v_xy, covar_v_xz, var_v_y, covar_v_yz, - var_v_z])...] + matrix = [(code, soln, var_x, covar_xy, covar_xz, var_y, covar_yz, var_z[, var_v_x, covar_v_xy, covar_v_xz, var_v_y, covar_v_yz, var_v_z])...] where: * code is the stations's 4-character ID @@ -294,15 +317,15 @@ def read_sinex_matrix(file): Velocities are not included in all SINEX files and so their VCV information is only returned if they are present. - ToDo: - 1. The above order is only valid if the matrix is upper triangle. If it is - lower triangle, then the covar_xy is actually the var_y. Will need to fix - this when time permits. - :param file: the input SINEX file - :return: matrix + :return: Sinex matrix as list of lists """ - + ''' + ToDo: + 1. The above order is only valid if the matrix is upper triangle. If it is + lower triangle, then the covar_xy is actually the var_y. Will need to fix + this when time permits. + ''' # Read in the codes (station names) and solutions, and check for velocities data = read_sinex_estimate(file) code = [] @@ -321,14 +344,14 @@ def read_sinex_matrix(file): go = False with open(file) as f: for line in f: - if line[:25] == '-SOLUTION/MATRIX_ESTIMATE': + if line[:25] == "-SOLUTION/MATRIX_ESTIMATE": break - if go and line[:12] == '*PARA1 PARA2': + if go and line[:12] == "*PARA1 PARA2": pass elif go: lines.append(line) - if line[:25] == '+SOLUTION/MATRIX_ESTIMATE': - if line[26] == 'L': + if line[:25] == "+SOLUTION/MATRIX_ESTIMATE": + if line[26] == "L": lower_triangular = True go = True @@ -346,54 +369,73 @@ def read_sinex_matrix(file): if velocities: if lower_triangular: for i in range(len(code)): - info = (code[i], soln[i], element[6 * i][6 * i], - element[6 * i + 1][6 * i], - element[6 * i + 1][6 * i + 1], - element[6 * i + 2][6 * i], - element[6 * i + 2][6 * i + 1], - element[6 * i + 2][6 * i + 2], - element[6 * i + 3][6 * i + 3], - element[6 * i + 4][6 * i + 3], - element[6 * i + 4][6 * i + 4], - element[6 * i + 5][6 * i + 3], - element[6 * i + 5][6 * i + 4], - element[6 * i + 5][6 * i + 5]) + info = ( + code[i], + soln[i], + element[6 * i][6 * i], + element[6 * i + 1][6 * i], + element[6 * i + 1][6 * i + 1], + element[6 * i + 2][6 * i], + element[6 * i + 2][6 * i + 1], + element[6 * i + 2][6 * i + 2], + element[6 * i + 3][6 * i + 3], + element[6 * i + 4][6 * i + 3], + element[6 * i + 4][6 * i + 4], + element[6 * i + 5][6 * i + 3], + element[6 * i + 5][6 * i + 4], + element[6 * i + 5][6 * i + 5], + ) matrix.append(info) else: for i in range(len(code)): - info = (code[i], soln[i], element[6 * i][6 * i], - element[6 * i][6 * i + 1], element[6 * i][6 * i + 2], - element[6 * i + 1][6 * i + 1], - element[6 * i + 1][6 * i + 2], - element[6 * i + 2][6 * i + 2], - element[6 * i + 3][6 * i + 3], - element[6 * i + 3][6 * i + 4], - element[6 * i + 3][6 * i + 5], - element[6 * i + 4][6 * i + 4], - element[6 * i + 4][6 * i + 5], - element[6 * i + 5][6 * i + 5]) + info = ( + code[i], + soln[i], + element[6 * i][6 * i], + element[6 * i][6 * i + 1], + element[6 * i][6 * i + 2], + element[6 * i + 1][6 * i + 1], + element[6 * i + 1][6 * i + 2], + element[6 * i + 2][6 * i + 2], + element[6 * i + 3][6 * i + 3], + element[6 * i + 3][6 * i + 4], + element[6 * i + 3][6 * i + 5], + element[6 * i + 4][6 * i + 4], + element[6 * i + 4][6 * i + 5], + element[6 * i + 5][6 * i + 5], + ) matrix.append(info) else: if lower_triangular: for i in range(len(code)): - info = (code[i], soln[i], element[3 * i][3 * i], - element[3 * i + 1][3 * i], - element[3 * i + 1][3 * i + 1], - element[3 * i + 2][3 * i], - element[3 * i + 2][3 * i + 1], - element[3 * i + 2][3 * i + 2]) + info = ( + code[i], + soln[i], + element[3 * i][3 * i], + element[3 * i + 1][3 * i], + element[3 * i + 1][3 * i + 1], + element[3 * i + 2][3 * i], + element[3 * i + 2][3 * i + 1], + element[3 * i + 2][3 * i + 2], + ) matrix.append(info) else: for i in range(len(code)): - info = (code[i], soln[i], element[3 * i][3 * i], - element[3 * i][3 * i + 1], element[3 * i][3 * i + 2], - element[3 * i + 1][3 * i + 1], - element[3 * i + 1][3 * i + 2], - element[3 * i + 2][3 * i + 2]) + info = ( + code[i], + soln[i], + element[3 * i][3 * i], + element[3 * i][3 * i + 1], + element[3 * i][3 * i + 2], + element[3 * i + 1][3 * i + 1], + element[3 * i + 1][3 * i + 2], + element[3 * i + 2][3 * i + 2], + ) matrix.append(info) return matrix + def read_sinex_sites(file): """This function reads in the SITE/ID block of a SINEX file. It returns sites, a list of tuples: @@ -419,13 +461,13 @@ def read_sinex_sites(file): go = False with open(file) as f: for line in f: - if line[:8] == '-SITE/ID': + if line[:8] == "-SITE/ID": break - if go and line[:8] == '*CODE PT': + if go and line[:8] == "*CODE PT": pass elif go: lines.append(line) - if line[:8] == '+SITE/ID': + if line[:8] == "+SITE/ID": go = True sites = [] for line in lines: @@ -442,6 +484,7 @@ def read_sinex_sites(file): return sites + def read_disconts(file): """This function reads in the SOLUTION/DISCONTINUITY block of a SINEX file. It returns disconts , a list of tuples: @@ -456,7 +499,7 @@ def read_disconts(file): * start is the start time for the point code in YY:DOY:SECOD * end is the end time for the point code in YY:DOY:SECOD * type is the type of discontinuity; P for position or V for - velocity + velocity I could not find the format description for this block. @@ -469,11 +512,11 @@ def read_disconts(file): go = False with open(file) as f: for line in f: - if line[:23] == '-SOLUTION/DISCONTINUITY': + if line[:23] == "-SOLUTION/DISCONTINUITY": break elif go: lines.append(line) - if line[:23] == '+SOLUTION/DISCONTINUITY': + if line[:23] == "+SOLUTION/DISCONTINUITY": go = True disconts = [] for line in lines: @@ -489,6 +532,7 @@ def read_disconts(file): return disconts + def read_solution_epochs(file): """This function reads in the SOLUTION/EPOCHS block of a SINEX file. It returns epochs, a list of tuples: @@ -513,13 +557,13 @@ def read_solution_epochs(file): go = False with open(file) as f: for line in f: - if line[:16] == '-SOLUTION/EPOCHS': + if line[:16] == "-SOLUTION/EPOCHS": break - if go and line[:8] == '*Code PT': + if go and line[:8] == "*Code PT": pass elif go: lines.append(line) - if line[:16] == '+SOLUTION/EPOCHS': + if line[:16] == "+SOLUTION/EPOCHS": go = True epochs = [] # Parse each line, create a tuple and add it to the list @@ -536,341 +580,371 @@ def read_solution_epochs(file): return epochs + def read_sinex_header_block(sinex): - """This function reads in the header block information + """ + This function reads in the header block information of a SINEX file (All lines before the SITE/ID block). - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] - with open(sinex, 'r') as f: + with open(sinex, "r") as f: next(f) line = f.readline() while line: block.append(line.rstrip()) line = f.readline() - if line.startswith('+SITE/ID'): + if line.startswith("+SITE/ID"): break return block + def read_sinex_file_reference_block(sinex): - """This function reads in the +FILE/REFERENCE block + """ + This function reads in the +FILE/REFERENCE block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+FILE/REFERENCE'): + if line.startswith("+FILE/REFERENCE"): go = True if go: block.append(line.rstrip()) - if line.startswith('-FILE/REFERENCE'): + if line.startswith("-FILE/REFERENCE"): break line = f.readline() - + return block + def read_sinex_input_acknowledgments_block(sinex): - """This function reads in the +INPUT/ACKNOWLEDGMENTS + """ + This function reads in the +INPUT/ACKNOWLEDGMENTS block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+INPUT/ACKNOWLEDGMENTS'): + if line.startswith("+INPUT/ACKNOWLEDGMENTS"): go = True if go: block.append(line.rstrip()) - if line.startswith('-INPUT/ACKNOWLEDGMENTS'): + if line.startswith("-INPUT/ACKNOWLEDGMENTS"): break line = f.readline() return block + def read_sinex_solution_statistics_block(sinex): - """This function reads in the +SOLUTION/STATISTICS + """ + This function reads in the +SOLUTION/STATISTICS block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SOLUTION/STATISTICS'): + if line.startswith("+SOLUTION/STATISTICS"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SOLUTION/STATISTICS'): + if line.startswith("-SOLUTION/STATISTICS"): break line = f.readline() return block + def read_sinex_site_receiver_block(sinex): - """This function reads in the +SITE/RECEIVER + """ + This function reads in the +SITE/RECEIVER block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SITE/RECEIVER'): + if line.startswith("+SITE/RECEIVER"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SITE/RECEIVER'): + if line.startswith("-SITE/RECEIVER"): break line = f.readline() - + return block + def read_sinex_site_antenna_block(sinex): - """This function reads in the +SITE/ANTENNA + """ + This function reads in the +SITE/ANTENNA block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SITE/ANTENNA'): + if line.startswith("+SITE/ANTENNA"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SITE/ANTENNA'): + if line.startswith("-SITE/ANTENNA"): break line = f.readline() - + return block + def read_sinex_site_gps_phase_center_block(sinex): - """This function reads in the +SITE/GPS_PHASE_CENTER + """ + This function reads in the +SITE/GPS_PHASE_CENTER block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SITE/GPS_PHASE_CENTER'): + if line.startswith("+SITE/GPS_PHASE_CENTER"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SITE/GPS_PHASE_CENTER'): + if line.startswith("-SITE/GPS_PHASE_CENTER"): break line = f.readline() - + return block + def read_sinex_site_eccentricity_block(sinex): - """This function reads in the +SITE/ECCENTRICITY + """ + This function reads in the +SITE/ECCENTRICITY block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SITE/ECCENTRICITY'): + if line.startswith("+SITE/ECCENTRICITY"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SITE/ECCENTRICITY'): + if line.startswith("-SITE/ECCENTRICITY"): break line = f.readline() - + return block + def read_sinex_site_id_block(sinex): - """This function reads in the SITE/ID block of a SINEX file + """ + This function reads in the SITE/ID block of a SINEX file into list of strings. :param str sinex: input SINEX file - return: block + :return: block """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SITE/ID'): + if line.startswith("+SITE/ID"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SITE/ID'): + if line.startswith("-SITE/ID"): break line = f.readline() return block + def read_sinex_solution_epochs_block(sinex): - """This function reads in the SOLUTION/EPOCHS block of a SINEX file + """ + This function reads in the SOLUTION/EPOCHS block of a SINEX file into list of strings. :param str sinex: input SINEX file - return: block + :return: block """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SOLUTION/EPOCHS'): + if line.startswith("+SOLUTION/EPOCHS"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SOLUTION/EPOCHS'): + if line.startswith("-SOLUTION/EPOCHS"): break line = f.readline() return block + def read_sinex_solution_estimate_block(sinex): - """This function reads in the SOLUTION/ESTIMATE block of a SINEX + """ + This function reads in the SOLUTION/ESTIMATE block of a SINEX file into list of strings. :param str sinex: input SINEX file - return: block + :return: block """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SOLUTION/ESTIMATE'): + if line.startswith("+SOLUTION/ESTIMATE"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SOLUTION/ESTIMATE'): + if line.startswith("-SOLUTION/ESTIMATE"): break line = f.readline() return block + def read_sinex_solution_apriori_block(sinex): - """This function reads in the +SOLUTION/APRIORI + """ + This function reads in the +SOLUTION/APRIORI block of a SINEX file. - :param str sinex: input SINEX file - return: block - """ + :param str sinex: input SINEX file + :return: block + """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SOLUTION/APRIORI'): + if line.startswith("+SOLUTION/APRIORI"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SOLUTION/APRIORI'): + if line.startswith("-SOLUTION/APRIORI"): break line = f.readline() - + return block + def read_sinex_solution_matrix_estimate_block(sinex): - """This function reads in the SOLUTION/MATRIX_ESTIMATE block of a SINEX + """ + This function reads in the SOLUTION/MATRIX_ESTIMATE block of a SINEX file into list of strings. :param str sinex: input SINEX file - return: block + :return: block """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SOLUTION/MATRIX_ESTIMATE'): + if line.startswith("+SOLUTION/MATRIX_ESTIMATE"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SOLUTION/MATRIX_ESTIMATE'): + if line.startswith("-SOLUTION/MATRIX_ESTIMATE"): break line = f.readline() return block + def read_sinex_solution_matrix_apriori_block(sinex): - """This function reads in the SOLUTION/MATRIX_APRIORI block + """ + This function reads in the SOLUTION/MATRIX_APRIORI block of a SINEX file into list of strings. :param str sinex: input SINEX file - return: block + :return: block """ block = [] go = False - with open(sinex, 'r') as f: + with open(sinex, "r") as f: line = f.readline() while line: - if line.startswith('+SOLUTION/MATRIX_APRIORI'): + if line.startswith("+SOLUTION/MATRIX_APRIORI"): go = True if go: block.append(line.rstrip()) - if line.startswith('-SOLUTION/MATRIX_APRIORI'): + if line.startswith("-SOLUTION/MATRIX_APRIORI"): break line = f.readline() - + return block + def sinex2dataframe_solution_estimate(fp): - """This function reads in a SINEX file and returns - a dataframe of SOLUTION/ESTIMATE block only. + """ + This function reads in a SINEX file and returns + a dataframe of SOLUTION/ESTIMATE block only. :param str sinex: path of input SINEX file - return: df + :return: df """ - + # Get lines lines = read_sinex_solution_estimate_block(fp) # Remove non-data lines for l in lines[:]: - if l.startswith('*'): + if l.startswith("*"): lines.remove(l) - if l.startswith('+'): + if l.startswith("+"): lines.remove(l) - if l.startswith('-'): + if l.startswith("-"): lines.remove(l) # Split by column @@ -890,40 +964,42 @@ def sinex2dataframe_solution_estimate(fp): # Organise into DataFrame dict_temp = { - "row":row, - "par":par, - "code":code, - "pt":pt, - "soln":soln, - "refEpoch":refEpoch, - "unit":unit, - "s":s, - "est":est, - "sigma":sigma, + "row": row, + "par": par, + "code": code, + "pt": pt, + "soln": soln, + "refEpoch": refEpoch, + "unit": unit, + "s": s, + "est": est, + "sigma": sigma, } df = pd.DataFrame(dict_temp) # Return return df + def sinex2dataframe_solution_apriori(fp): - """This function reads in a SINEX file and returns - a dataframe of SOLUTION/APRIORI block only. + """ + This function reads in a SINEX file and returns + a dataframe of SOLUTION/APRIORI block only. :param str sinex: path of input SINEX file - return: df + :return: df """ - + # Get lines lines = read_sinex_solution_apriori_block(fp) # Remove non-data lines for l in lines[:]: - if l.startswith('*'): + if l.startswith("*"): lines.remove(l) - if l.startswith('+'): + if l.startswith("+"): lines.remove(l) - if l.startswith('-'): + if l.startswith("-"): lines.remove(l) # Split by column @@ -943,28 +1019,30 @@ def sinex2dataframe_solution_apriori(fp): # Organise into DataFrame dict_temp = { - "row":row, - "par":par, - "code":code, - "pt":pt, - "soln":soln, - "refEpoch":refEpoch, - "unit":unit, - "s":s, - "est":est, - "sigma":sigma, + "row": row, + "par": par, + "code": code, + "pt": pt, + "soln": soln, + "refEpoch": refEpoch, + "unit": unit, + "s": s, + "est": est, + "sigma": sigma, } df = pd.DataFrame(dict_temp) # Return return df + def sinex2dataframe_solution_matrix_estimate(fp): - """This function reads in a SINEX file and returns - a dataframe of SOLUTION/MATRIX_ESTIMATE block only. + """ + This function reads in a SINEX file and returns + a dataframe of SOLUTION/MATRIX_ESTIMATE block only. :param str sinex: path of input SINEX file - return: df + :return: df """ # Get lines @@ -972,11 +1050,11 @@ def sinex2dataframe_solution_matrix_estimate(fp): # Remove non-data lines for l in lines[:]: - if l.startswith('*'): + if l.startswith("*"): lines.remove(l) - if l.startswith('+'): + if l.startswith("+"): lines.remove(l) - if l.startswith('-'): + if l.startswith("-"): lines.remove(l) # Split by column @@ -984,14 +1062,14 @@ def sinex2dataframe_solution_matrix_estimate(fp): # Pad out lines with NaN for i in range(len(lines)): - if len(lines[i])==5: + if len(lines[i]) == 5: continue if len(lines[i]) == 4: lines[i].append(np.nan) if len(lines[i]) == 3: lines[i].append(np.nan) lines[i].append(np.nan) - + # Isolate into vectors row = np.int_(list(zip(*lines))[0]) col = np.int_(list(zip(*lines))[1]) @@ -1001,84 +1079,118 @@ def sinex2dataframe_solution_matrix_estimate(fp): # Organise into DataFrame dict_temp = { - "row":row, - "col":col, - "q1":q1, - "q2":q2, - "q3":q3, + "row": row, + "col": col, + "q1": q1, + "q2": q2, + "q3": q3, } df = pd.DataFrame(dict_temp) # Return return df + def dataframe2sinex_solution_estimate(df): - """This function reads in a dataframe of the + """ + This function reads in a dataframe of the SOLUTIONS/ESTIMATE block from a SINEX, then - converts each row to a string in a list + converts each row to a string in a list ready for writing to SINEX. :param dataframe df: dataframe of SOLUTION/ESTIMATE block - return: list of strings + :return: list of strings """ - lines =lines = ["+SOLUTION/ESTIMATE"] - lines.append("*INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __ESTIMATED VALUE____ _STD_DEV___") + lines = lines = ["+SOLUTION/ESTIMATE"] + lines.append( + "*INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __ESTIMATED VALUE____ _STD_DEV___" + ) for i in range(len(df.code)): - l = " %5i %s %s %s %s %s %-3s %s %21.14e %.5e" % (df.row.iloc[i], df.par.iloc[i], df.code.iloc[i], df.pt.iloc[i], df.soln.iloc[i], df.refEpoch.iloc[i], df.unit.iloc[i], df.s.iloc[i], df.est.iloc[i], df.sigma.iloc[i]) + l = " %5i %s %s %s %s %s %-3s %s %21.14e %.5e" % ( + df.row.iloc[i], + df.par.iloc[i], + df.code.iloc[i], + df.pt.iloc[i], + df.soln.iloc[i], + df.refEpoch.iloc[i], + df.unit.iloc[i], + df.s.iloc[i], + df.est.iloc[i], + df.sigma.iloc[i], + ) lines.append(l) - + lines.append("-SOLUTION/ESTIMATE") # Return return lines + def dataframe2sinex_solution_apriori(df): - """This function reads in a dataframe of the + """ + This function reads in a dataframe of the SOLUTION/APRIORI block from a SINEX, then - converts each row to a string in a list + converts each row to a string in a list ready for writing to SINEX. :param dataframe df: dataframe of SOLUTION/APRIORI block - return: list of strings + :return: list of strings """ - lines =lines = ["+SOLUTION/APRIORI"] - lines.append("*INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __ESTIMATED VALUE____ _STD_DEV___") + lines = lines = ["+SOLUTION/APRIORI"] + lines.append( + "*INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __ESTIMATED VALUE____ _STD_DEV___" + ) for i in range(len(df.code)): - l = " %5i %s %s %s %s %s %-3s %s %21.14e %.5e" % (df.row.iloc[i], df.par.iloc[i], df.code.iloc[i], df.pt.iloc[i], df.soln.iloc[i], df.refEpoch.iloc[i], df.unit.iloc[i], df.s.iloc[i], df.est.iloc[i], df.sigma.iloc[i]) + l = " %5i %s %s %s %s %s %-3s %s %21.14e %.5e" % ( + df.row.iloc[i], + df.par.iloc[i], + df.code.iloc[i], + df.pt.iloc[i], + df.soln.iloc[i], + df.refEpoch.iloc[i], + df.unit.iloc[i], + df.s.iloc[i], + df.est.iloc[i], + df.sigma.iloc[i], + ) lines.append(l) - + lines.append("-SOLUTION/APRIORI") # Return return lines + def dataframe2matrix_snx_vcv(df, numPar=3): - """This function converts a dataframe created from a - SINEX VCV based from read_sinex_matrix(), and converts + """ + This function converts a dataframe created from a + SINEX VCV based from read_sinex_matrix(), and converts it to a full VCV without covariances between sites. - i.e. xx xy xz 0 0 0 - xy yy yz . . . 0 0 0 - xz yz zz 0 0 0 - . . . - . . . - . . . - 0 0 0 xx xy xz - 0 0 0 . . . xy yy yz - 0 0 0 xz yz zz + .. code-block:: text + + i.e. xx xy xz 0 0 0 + xy yy yz . . . 0 0 0 + xz yz zz 0 0 0 + . . . + . . . + . . . + 0 0 0 xx xy xz + 0 0 0 . . . xy yy yz + 0 0 0 xz yz zz :param DataFrame: dataframe formed from read_sinex_matrix(): - return: Numpy matrix of VCV with no covariances between sites. + :return: Numpy matrix of VCV with no covariances between sites. """ - + # Matrix dimensions # - To Do: Handle check for velocities (?) - par = numPar # (is there a way to automatically determine if velocitie exist?) - n = len(df.code)*par + par = numPar # (is there a way to automatically determine if velocitie exist?) + n = len(df.code) * par Q = np.zeros((n, n)) # Matrix elements and rows of dataframe @@ -1088,36 +1200,37 @@ def dataframe2matrix_snx_vcv(df, numPar=3): while r < len(df.code): # variances - Q[i+0, j+0] = df.xx[r] - Q[i+1, j+1] = df.yy[r] - Q[i+2, j+2] = df.zz[r] + Q[i + 0, j + 0] = df.xx[r] + Q[i + 1, j + 1] = df.yy[r] + Q[i + 2, j + 2] = df.zz[r] # covariances - Q[i+1, j+0] = df.xy[r] - Q[i+0, j+1] = df.xy[r] - Q[i+2, j+0] = df.xz[r] - Q[i+0, j+2] = df.xz[r] - Q[i+2, j+1] = df.yz[r] - Q[i+1, j+2] = df.yz[r] - + Q[i + 1, j + 0] = df.xy[r] + Q[i + 0, j + 1] = df.xy[r] + Q[i + 2, j + 0] = df.xz[r] + Q[i + 0, j + 2] = df.xz[r] + Q[i + 2, j + 1] = df.yz[r] + Q[i + 1, j + 2] = df.yz[r] # Counter - r = r+1 - i = i+3 - j = j+3 + r = r + 1 + i = i + 3 + j = j + 3 # Return return Q - + + def dataframe2matrix_solution_matrix_estimate(df, tri="L"): - """This function reads in a dataframe of the SINEX - SOLUTION/MATRIX_ESTIMATE block formed from - sinex2dataframe_solution_matrix_estimate(), and then - forms the full VCV matrix from that dataframe. + """ + This function reads in a dataframe of the SINEX + SOLUTION/MATRIX_ESTIMATE block formed from + sinex2dataframe_solution_matrix_estimate(), and then + forms the full VCV matrix from that dataframe. :param DataFrame df: dataframe from sinex2dataframe_solution_matrix_estimate(). - :param String tri: String to indicate "upper" or "lower" triagle matrix. - return: Numpy matrix of full VCV. + :param String tri: String to indicate "upper" or "lower" triagle matrix. + :return: Numpy matrix of full VCV. """ # Triangularity of matrix @@ -1130,26 +1243,26 @@ def dataframe2matrix_solution_matrix_estimate(df, tri="L"): if triangle == "L": for i in range(len(df.row)): - + # Get matrix indices row = int(df.row[i]) - 1 col = int(df.col[i]) - 1 - + # Fill PARA2+0 - q1 = df.q1[i] + q1 = df.q1[i] Q[row, col] = q1 Q[col, row] = q1 - + # Fill PARA2+1 q2 = df.q2[i] - Q[row, col+1] = q2 - Q[col+1, row] = q2 - + Q[row, col + 1] = q2 + Q[col + 1, row] = q2 + # Fill PARA2+2 q3 = df.q3[i] - Q[row, col+2] = q3 - Q[col+2, row] = q3 - + Q[row, col + 2] = q3 + Q[col + 2, row] = q3 + if triangle == "U": for i in range(len(df.row)): @@ -1158,53 +1271,56 @@ def dataframe2matrix_solution_matrix_estimate(df, tri="L"): row = int(df.row[i]) - 1 col = int(df.col[i]) - 1 - if df.col[i] < n-1: - + if df.col[i] < n - 1: + # Fill PARA2+0 - q1 = df.q1[i] + q1 = df.q1[i] Q[row, col] = q1 Q[col, row] = q1 - + # Fill PARA2+1 q2 = df.q2[i] - Q[row, col+1] = q2 - Q[col+1, row] = q2 - + Q[row, col + 1] = q2 + Q[col + 1, row] = q2 + # Fill PARA2+2 q3 = df.q3[i] - Q[row, col+2] = q3 - Q[col+2, row] = q3 - - if df.col[i] == n-1: - + Q[row, col + 2] = q3 + Q[col + 2, row] = q3 + + if df.col[i] == n - 1: + # Fill PARA2+0 - q1 = df.q1[i] + q1 = df.q1[i] Q[row, col] = q1 Q[col, row] = q1 - + # Fill PARA2+1 q2 = df.q2[i] - Q[row, col+1] = q2 - Q[col+1, row] = q2 + Q[row, col + 1] = q2 + Q[col + 1, row] = q2 if df.col[i] == n: - + # Fill PARA2+0 - q1 = df.q1[i] + q1 = df.q1[i] Q[row, col] = q1 Q[col, row] = q1 # Return return Q + def matrix2dataframe_solution_matrix_estimate(m, tri="L"): - """This function reads a VCV in matrix format and writes it - to dataframe for SINEX SOLUTION/MATRIX_ESTIMATE format. It + """ + This function reads a VCV in matrix format and writes it + to dataframe for SINEX SOLUTION/MATRIX_ESTIMATE format. It is the format produced from sinex2dataframe_solution_matrix_estimate(). - :param numpy.array() m: A numpy array of the VCV matrix. - :param String tri: String to indicate "upper" or "lower" triagle matrix. - return: DataFrame df: A dataframe of SOLUTION/MATRIX_ESTIMATE. + :param numpy.array() m: A numpy array of the VCV matrix. + :param str tri: String to indicate "upper" or "lower" triagle matrix. + :return: A dataframe of SOLUTION/MATRIX_ESTIMATE. + :rtype: DataFrame """ # Matrix dimensions @@ -1226,11 +1342,11 @@ def matrix2dataframe_solution_matrix_estimate(m, tri="L"): j = 0 while i < n: while j <= i: - row.append(i+1) - col.append(j+1) + row.append(i + 1) + col.append(j + 1) q1.append(Q[i, j]) - q2.append(Q[i, j+1]) - q3.append(Q[i, j+2]) + q2.append(Q[i, j + 1]) + q3.append(Q[i, j + 2]) j += 3 j = 0 i += 1 @@ -1249,60 +1365,64 @@ def matrix2dataframe_solution_matrix_estimate(m, tri="L"): j = 0 while i < n: while j < n: - if j < n-2: - row.append(i+1) - col.append(j+1) + if j < n - 2: + row.append(i + 1) + col.append(j + 1) q1.append(Q[i, j]) - q2.append(Q[i, j+1]) - q3.append(Q[i, j+2]) + q2.append(Q[i, j + 1]) + q3.append(Q[i, j + 2]) j += 3 - if j == n-2: - row.append(i+1) - col.append(j+1) + if j == n - 2: + row.append(i + 1) + col.append(j + 1) q1.append(Q[i, j]) - q2.append(Q[i, j+1]) + q2.append(Q[i, j + 1]) q3.append(np.nan) j += 3 - if j == n-1: - row.append(i+1) - col.append(j+1) + if j == n - 1: + row.append(i + 1) + col.append(j + 1) q1.append(Q[i, j]) q2.append(np.nan) q3.append(np.nan) j += 3 - + i += 1 j = i dict_temp = { - "row":row, - "col":col, - "q1":q1, - "q2":q2, - "q3":q3, + "row": row, + "col": col, + "q1": q1, + "q2": q2, + "q3": q3, } df = pd.DataFrame(dict_temp) # Return return df + def dataframe2sinex_solution_matrix_estimate(df, tri="L"): - """This function reads in a dataframe of the + """ + This function reads in a dataframe of the SOLUTIONS/MATRIX_ESTIMATE block from a SINEX, the - converts each row to a string in a list + converts each row to a string in a list ready for writing to SINEX. :param dataframe df: dataframe of SOLUTION/ESTIMATE block - return: list of strings + :return: list of strings """ - + # Upper or lower triangle triangle = tri lines = [f"+SOLUTION/MATRIX_ESTIMATE {triangle} COVA"] - lines.append("*PARA1 PARA2 ____PARA2+0__________ ____PARA2+1__________ ____PARA2+2__________") + lines.append( + "*PARA1 PARA2 ____PARA2+0__________ ____PARA2+1__________ ____PARA2+2__________" + ) for i in range(len(df.row)): p1 = df.row.iloc[i] @@ -1310,62 +1430,64 @@ def dataframe2sinex_solution_matrix_estimate(df, tri="L"): q1 = df.q1.iloc[i] q2 = df.q2.iloc[i] q3 = df.q3.iloc[i] - l = ' {:5n} {:5n} {:>21.14e} {:>21.14e} {:>21.14e}'.format(p1, p2, q1, q2, q3) - l = l.replace('nan', '') + l = " {:5n} {:5n} {:>21.14e} {:>21.14e} {:>21.14e}".format(p1, p2, q1, q2, q3) + l = l.replace("nan", "") lines.append(l) - + lines.append(f"-SOLUTION/MATRIX_ESTIMATE {triangle} COVA") # Return return lines -def writeSINEX(fp, - header=None, - comment=None, - siteID=None, - solutionEpochs=None, - solutionEstimate=None, - solutionMatrixEstimate=None, - fileReference=None, - inputAcknowledgments=None, - solutionStatistics=None, - siteReceiver=None, - siteAntenna=None, - siteGpsPhaseCenter=None, - siteEccentricity=None, - solutionApriori=None, - solutionMatrixApriori=None): - """This function writes out SINEX blocks to a new SINEX file. The - SINEX blocks can be obtained from many of the read_sinex_...() - functions when writing the same input to output. Or can use the - dataframe2sinex_...() functions when SINEX manipulations have + +def writeSINEX( + fp, + header=None, + comment=None, + siteID=None, + solutionEpochs=None, + solutionEstimate=None, + solutionMatrixEstimate=None, + fileReference=None, + inputAcknowledgments=None, + solutionStatistics=None, + siteReceiver=None, + siteAntenna=None, + siteGpsPhaseCenter=None, + siteEccentricity=None, + solutionApriori=None, + solutionMatrixApriori=None, +): + """ + This function writes out SINEX blocks to a new SINEX file. The + SINEX blocks can be obtained from many of the :literal:`read_sinex_...()` + functions when writing the same input to output. Or can use the + :literal:`dataframe2sinex_...()` functions when SINEX manipulations have occurred on that specific block. Input arguments are set to 'None' by default. This allows user to only write out the required blocks. - Parameters: - fp (string): Full filepath to output SINEX. - header (string): Single header line. Can get from read_sinex_header_line(). - comment (list of strings): +FILE/COMMENT block. Can get from read_sinex_comments(). - SiteID (list of strings): +SITE/ID block. Can get from read_sinex_site_id_block(). - SolutionEpochs (list of strings): +SOLUTION/EPOCHS block. Can get from read_sinex_solution_epochs_block(). - SolutionEstimatee (list of strings): +SOLUTION/ESTIMATE block. Can get from read_sinex_solution_estimate_block(). - SolutionMatricEstimate (list of strings): +SOLUTION/MATRIX_ESTIMATE block. Can get from read_sinex_solution_matrix_estimate_block(). - fileReference (list of strings): +FILE/REFERENCE block. Can get from read_sinex_file_reference_block(). - inputAcknowledgments (list of strings): +INPUT/ACKNOWLEDGEMENTS block. Can get from read_sinex_input_acknowledgments_block(). - siteReceiver (list of strings): +SITE/RECEIVER block. Can get from read_sinex_site_receiver_block(). - siteAntenna (list of strings): +SITE/ANTENNA block. Can get from read_sinex_site_antenna_block(). - siteGpsPhaseCenter (list of strings): +SITE/GPS_PHASE_CENTER block. Can get from read_site_gps_phase_center_block(). - siteEccentricity (list of strings): +SITE/ECCENTRICITY block. Can get from read_sinex_site_eccentricity_block(). - solutionApriori (list of strings): +SOLUTION/APRIORI block. Can get from read_sinex_solution_apriori_block(). - solutionMatrixApriori (list of strings): +SOLUTION/MATRIX_APRIORI block. Can get from read_sinex_solution_matric_apriori_block(). - - Return: - No return. But a new SINEX file will be written out to the file path (fp). + :param str fp: Full filepath to output SINEX. + :param str header: Single header line. Can get from read_sinex_header_line(). + :param list of str comment: +FILE/COMMENT block. Can get from read_sinex_comments(). + :param list of str SiteID: +SITE/ID block. Can get from read_sinex_site_id_block(). + :param list of str SolutionEpochs: +SOLUTION/EPOCHS block. Can get from read_sinex_solution_epochs_block(). + :param list of str SolutionEstimate: +SOLUTION/ESTIMATE block. Can get from read_sinex_solution_estimate_block(). + :param list of str SolutionMatricEstimate: +SOLUTION/MATRIX_ESTIMATE block. Can get from read_sinex_solution_matrix_estimate_block(). + :param list of str fileReference: +FILE/REFERENCE block. Can get from read_sinex_file_reference_block(). + :param list of str inputAcknowledgments: +INPUT/ACKNOWLEDGEMENTS block. Can get from read_sinex_input_acknowledgments_block(). + :param list of str siteReceiver: +SITE/RECEIVER block. Can get from read_sinex_site_receiver_block(). + :param list of str siteAntenna: +SITE/ANTENNA block. Can get from read_sinex_site_antenna_block(). + :param list of str siteGpsPhaseCenter: +SITE/GPS_PHASE_CENTER block. Can get from read_site_gps_phase_center_block(). + :param list of str siteEccentricity: +SITE/ECCENTRICITY block. Can get from read_sinex_site_eccentricity_block(). + :param list of str solutionApriori: +SOLUTION/APRIORI block. Can get from read_sinex_solution_apriori_block(). + :param list of str solutionMatrixApriori: +SOLUTION/MATRIX_APRIORI block. Can get from read_sinex_solution_matric_apriori_block(). + + :return: No return. But a new SINEX file will be written out to the file path (fp). """ # Open File - with open(fp, 'w') as f: + with open(fp, "w") as f: # Header if header == None: @@ -1373,7 +1495,9 @@ def writeSINEX(fp, else: f.write("{}".format(header)) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # Comment if comment == None: @@ -1382,8 +1506,10 @@ def writeSINEX(fp, for i in range(len(comment)): f.write("{}\n".format(comment[i])) - f.write("*-------------------------------------------------------------------------------\n") - + f.write( + "*-------------------------------------------------------------------------------\n" + ) + # FILE/REFERENCE if fileReference == None: pass @@ -1391,7 +1517,9 @@ def writeSINEX(fp, for i in range(len(fileReference)): f.write("{}\n".format(fileReference[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # INPUT/ACKNOWLEDGMENTS if inputAcknowledgments == None: @@ -1400,7 +1528,9 @@ def writeSINEX(fp, for i in range(len(inputAcknowledgments)): f.write("{}\n".format(inputAcknowledgments[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SOLUTION/STATISTICS if solutionStatistics == None: @@ -1409,16 +1539,20 @@ def writeSINEX(fp, for i in range(len(solutionStatistics)): f.write("{}\n".format(solutionStatistics[i])) - f.write("*-------------------------------------------------------------------------------\n") - - #SITE/ID + f.write( + "*-------------------------------------------------------------------------------\n" + ) + + # SITE/ID if siteID == None: pass else: for i in range(len(siteID)): f.write("{}\n".format(siteID[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SITE/RECEIVER if siteReceiver == None: @@ -1427,7 +1561,9 @@ def writeSINEX(fp, for i in range(len(siteReceiver)): f.write("{}\n".format(siteReceiver[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SITE/ANTENNA if siteAntenna == None: @@ -1436,7 +1572,9 @@ def writeSINEX(fp, for i in range(len(siteAntenna)): f.write("{}\n".format(siteAntenna[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SITE/GPS_PHASE_CENTER if siteGpsPhaseCenter == None: @@ -1445,7 +1583,9 @@ def writeSINEX(fp, for i in range(len(siteGpsPhaseCenter)): f.write("{}\n".format(siteGpsPhaseCenter[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SITE/ECCENTRICITY if siteEccentricity == None: @@ -1454,7 +1594,9 @@ def writeSINEX(fp, for i in range(len(siteEccentricity)): f.write("{}\n".format(siteEccentricity[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SOLUTION/EPOCHS if solutionEpochs == None: @@ -1463,7 +1605,9 @@ def writeSINEX(fp, for i in range(len(solutionEpochs)): f.write("{}\n".format(solutionEpochs[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SOLUTION/ESTIMATE if solutionEstimate == None: @@ -1472,7 +1616,9 @@ def writeSINEX(fp, for i in range(len(solutionEstimate)): f.write("{}\n".format(solutionEstimate[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SOLUTION/APRIORI if solutionApriori == None: @@ -1481,7 +1627,9 @@ def writeSINEX(fp, for i in range(len(solutionApriori)): f.write("{}\n".format(solutionApriori[i])) - f.write("*-------------------------------------------------------------------------------\n") + f.write( + "*-------------------------------------------------------------------------------\n" + ) # SOLUTION/MATRIX_ESTIMATE if solutionMatrixEstimate == None: @@ -1490,24 +1638,30 @@ def writeSINEX(fp, for i in range(len(solutionMatrixEstimate)): f.write("{}\n".format(solutionMatrixEstimate[i])) - f.write("*-------------------------------------------------------------------------------\n") - + f.write( + "*-------------------------------------------------------------------------------\n" + ) + # SOLUTION/MATRIX_APRIORI if solutionMatrixApriori == None: pass else: for i in range(len(solutionMatrixApriori)): f.write("{}\n".format(solutionMatrixApriori[i])) - - f.write("*-------------------------------------------------------------------------------\n") + + f.write( + "*-------------------------------------------------------------------------------\n" + ) # End Line f.write("%ENDSNX") f.close() + def remove_stns_sinex(sinex, sites): - """This function removes a list sites from a SINEX file + """ + This function removes a list sites from a SINEX file :param sinex: input SINEX file :param sites: list of the sites to be removed @@ -1515,7 +1669,7 @@ def remove_stns_sinex(sinex, sites): """ # Open the output file - with open('output.snx', 'w') as out: + with open("output.snx", "w") as out: # Get header line and update the creation time and the number of # parameter estimates. Write the updated header line to the new file @@ -1524,7 +1678,7 @@ def remove_stns_sinex(sinex, sites): creation_time = set_creation_time() header = header.replace(old_creation_time, creation_time) old_num_params = header[60:65] - if header[70:71] == 'V': + if header[70:71] == "V": num_stn_params = 6 else: num_stn_params = 3 @@ -1536,24 +1690,27 @@ def remove_stns_sinex(sinex, sites): num_stns_to_remove += 1 del solution_epochs num_params = int(old_num_params) - num_stn_params * num_stns_to_remove - num_params = '{:05d}'.format(num_params) + num_params = "{:05d}".format(num_params) header = header.replace(str(old_num_params), str(num_params)) out.write(header) - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the +FILE/COMMENT block and write to output file comments = read_sinex_comments(sinex) for i in comments: out.write(f"{i}\n") - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the site ID block and write out the sites not being removed site_id = read_sinex_site_id_block(sinex) for line in site_id: - if line.startswith('*') or line.startswith('+') or \ - line.startswith('-'): + if line.startswith("*") or line.startswith("+") or line.startswith("-"): out.write(f"{line}\n") else: site = line[1:5] @@ -1561,14 +1718,15 @@ def remove_stns_sinex(sinex, sites): out.write(f"{line}\n") del site_id - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the solution epochs block and write out the epochs of the # sites not being removed solution_epochs = read_sinex_solution_epochs_block(sinex) for line in solution_epochs: - if line.startswith('*') or line.startswith('+') or \ - line.startswith('-'): + if line.startswith("*") or line.startswith("+") or line.startswith("-"): out.write(f"{line}\n") else: site = line[1:5] @@ -1576,7 +1734,9 @@ def remove_stns_sinex(sinex, sites): out.write(f"{line}\n") del solution_epochs - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the solution estimate block and write out the estimates of # the sites not being removed @@ -1584,8 +1744,7 @@ def remove_stns_sinex(sinex, sites): estimate_number = 0 solution_estimate = read_sinex_solution_estimate_block(sinex) for line in solution_estimate: - if line.startswith('*') or line.startswith('+') or \ - line.startswith('-'): + if line.startswith("*") or line.startswith("+") or line.startswith("-"): out.write(f"{line}\n") else: site = line[14:18] @@ -1594,27 +1753,28 @@ def remove_stns_sinex(sinex, sites): skip.append(num) else: estimate_number += 1 - number = '{:5d}'.format(estimate_number) - line = ' ' + number + line[6:] + number = "{:5d}".format(estimate_number) + line = " " + number + line[6:] out.write(f"{line}\n") del solution_estimate - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the matrix estimate block and write out minus the sites # being removed vcv = {} - solution_matrix_estimate = \ - read_sinex_solution_matrix_estimate_block(sinex) - if solution_matrix_estimate[0][26:27] == 'L': - matrix = 'lower' - elif solution_matrix_estimate[0][26:27] == 'U': - matrix = 'upper' + solution_matrix_estimate = read_sinex_solution_matrix_estimate_block(sinex) + if solution_matrix_estimate[0][26:27] == "L": + matrix = "lower" + elif solution_matrix_estimate[0][26:27] == "U": + matrix = "upper" out.write(f"{solution_matrix_estimate[0]}\n") - if solution_matrix_estimate[1].startswith('*'): + if solution_matrix_estimate[1].startswith("*"): out.write(f"{solution_matrix_estimate[1]}\n") for line in solution_matrix_estimate: - if line.startswith(' '): + if line.startswith(" "): cols = line.split() row = cols[0] for i in range(2, len(cols)): @@ -1627,71 +1787,75 @@ def remove_stns_sinex(sinex, sites): del solution_matrix_estimate sub_vcv = {} sub_row = 0 - for i in range(1, len(vcv)+1): + for i in range(1, len(vcv) + 1): if i not in skip: sub_row += 1 - if matrix == 'lower': + if matrix == "lower": for j in range(i): - if j+1 not in skip: + if j + 1 not in skip: try: sub_vcv[str(sub_row)].append(vcv[str(i)][j]) except KeyError: sub_vcv[str(sub_row)] = [] sub_vcv[str(sub_row)].append(vcv[str(i)][j]) - if matrix == 'upper': - for j in range(len(vcv)-(i-1)): - if j+i not in skip: + if matrix == "upper": + for j in range(len(vcv) - (i - 1)): + if j + i not in skip: try: sub_vcv[str(sub_row)].append(vcv[str(i)][j]) except KeyError: sub_vcv[str(sub_row)] = [] sub_vcv[str(sub_row)].append(vcv[str(i)][j]) - for i in range(1, len(sub_vcv)+1): - para1 = '{:5d}'.format(i) - if matrix == 'lower': + for i in range(1, len(sub_vcv) + 1): + para1 = "{:5d}".format(i) + if matrix == "lower": j = -2 - elif matrix == 'upper': + elif matrix == "upper": j = i - 3 while sub_vcv[str(i)]: j += 3 - para2 = '{:5d}'.format(j) - line = ' ' + para1 + ' ' + para2 + para2 = "{:5d}".format(j) + line = " " + para1 + " " + para2 n = min([3, len(sub_vcv[str(i)])]) for k in range(n): - val = '{:21.14e}'.format(float(sub_vcv[str(i)].pop(0))) - line += ' ' + str(val) - out.write(line + '\n') + val = "{:21.14e}".format(float(sub_vcv[str(i)].pop(0))) + line += " " + str(val) + out.write(line + "\n") out.write(block_end) # Write out the trailer line - out.write('%ENDSNX\n') + out.write("%ENDSNX\n") return + def remove_velocity_sinex(sinex): - """This function reads in a SINEX file and removes the + """ + This function reads in a SINEX file and removes the velocity parameters, including the zeros of the SOLUTION/MATRIX_ESTIMATE block, :param str sinex: input SINEX file - return: SINEX file output.snx + :return: SINEX file output.snx """ # From header, confirm that the SINEX has velocity parameters header = read_sinex_header_line(sinex) header = header.strip() - if header[-1] == 'V': + if header[-1] == "V": pass else: - print("Not removing velocities because SINEX file does not have velocity parameters.") + print( + "Not removing velocities because SINEX file does not have velocity parameters." + ) exit() # Open the output file - with open('output.snx', 'w') as out: + with open("output.snx", "w") as out: - # With header line: + # With header line: # - update the creation time # - update number of parameter estimates - # - remove 'V' from parameter list + # - remove 'V' from parameter list # - then write to file old_creation_time = header[15:27] creation_time = set_creation_time() @@ -1699,61 +1863,71 @@ def remove_velocity_sinex(sinex): old_num_params = int(header[60:65]) num_params = int(old_num_params / 2) header = header.replace(str(old_num_params), str(num_params)) - header = header.replace('V', '') + header = header.replace("V", "") out.write(header) out.write("\n") del header - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the +FILE/COMMENT block and write to output file comments = read_sinex_comments(sinex) for i in comments: out.write(f"{i}\n") - out.write("*-------------------------------------------------------------------------------\n") - + out.write( + "*-------------------------------------------------------------------------------\n" + ) + # Read in the +SITE/ID block and write to file site_id = read_sinex_site_id_block(sinex) for line in site_id: - out.write(f"{line}\n") + out.write(f"{line}\n") del site_id - out.write("*-------------------------------------------------------------------------------\n") - + out.write( + "*-------------------------------------------------------------------------------\n" + ) + # Read in the +SOLUTION/EPOCHS block and write to file # - also collect count on number of sites for use later numSites = 0 solution_epochs = read_sinex_solution_epochs_block(sinex) for line in solution_epochs: out.write(f"{line}\n") - if line[0]!="+" and line[0]!="*" and line[0]!="-": - numSites+=1 + if line[0] != "+" and line[0] != "*" and line[0] != "-": + numSites += 1 del solution_epochs - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) - # Read in the +SOLUTION/ESTIMATE block: - # - gather velocity indices - # - remove velocity rows + # Read in the +SOLUTION/ESTIMATE block: + # - gather velocity indices + # - remove velocity rows # - change indices to account for removed velocities # - write to file vel_indices = [] estimate_number = 0 solution_estimate = read_sinex_solution_estimate_block(sinex) for line in solution_estimate: - if line[7:10]=="VEL": + if line[7:10] == "VEL": vel_indices.append(int(line[0:6])) - elif line[0]=="+" or line[0]=="*" or line[0]=="-": + elif line[0] == "+" or line[0] == "*" or line[0] == "-": out.write(f"{line}\n") else: - estimate_number+=1 - number = '{:5d}'.format(estimate_number) - line = ' ' + number + line[6:] + estimate_number += 1 + number = "{:5d}".format(estimate_number) + line = " " + number + line[6:] out.write(f"{line}\n") del solution_estimate - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the +SOLUTION/MATRIX_ESTIMATE block: # - identify if matrix is upper or lower triangle form @@ -1761,49 +1935,49 @@ def remove_velocity_sinex(sinex): # - remove all rows/columns associated with velocity # - write to file with new matrix indices (para1, para2) solution_matrix_estimate = read_sinex_solution_matrix_estimate_block(sinex) - if solution_matrix_estimate[0][26:27] == 'L': - matrix = 'lower' - elif solution_matrix_estimate[0][26:27] == 'U': - matrix = 'upper' + if solution_matrix_estimate[0][26:27] == "L": + matrix = "lower" + elif solution_matrix_estimate[0][26:27] == "U": + matrix = "upper" # Size of original matrix - matrix_dim = numSites*6 + matrix_dim = numSites * 6 # Setup matrix of zeros Q = zeros((matrix_dim, matrix_dim)) # Write initial comment line(s), save last comment line, and form matrix for line in solution_matrix_estimate: - if line[0]=="+": + if line[0] == "+": out.write(f"{line}\n") continue - if line[0]=="*": + if line[0] == "*": out.write(f"{line}\n") continue - if line[0]=="-": + if line[0] == "-": block_end = line else: - lineMAT = re.split('\s+', line) + lineMAT = re.split("\s+", line) lineMAT = list(filter(None, lineMAT)) row = int(lineMAT[0]) col = int(lineMAT[1]) if len(lineMAT) >= 3: q1 = float(lineMAT[2]) - Q[row-1, col-1] = q1 - Q[col-1, row-1] = q1 + Q[row - 1, col - 1] = q1 + Q[col - 1, row - 1] = q1 if len(lineMAT) >= 4: q2 = float(lineMAT[3]) - Q[row-1, col] = q2 - Q[col, row-1] = q2 + Q[row - 1, col] = q2 + Q[col, row - 1] = q2 if len(lineMAT) >= 5: q3 = float(lineMAT[4]) - Q[row-1, col+1] = q3 - Q[col+1, row-1] = q3 + Q[row - 1, col + 1] = q3 + Q[col + 1, row - 1] = q3 # Remove velocity rows/columns from matrix num_removed = 0 for i in vel_indices: - Q = delete(Q, i-1-num_removed, 0) - Q = delete(Q, i-1-num_removed, 1) + Q = delete(Q, i - 1 - num_removed, 0) + Q = delete(Q, i - 1 - num_removed, 1) num_removed += 1 # Write matrix to SINEX (lower triangle) - if matrix == 'lower': + if matrix == "lower": i = 0 j = 0 while i < len(Q): @@ -1820,10 +1994,10 @@ def remove_velocity_sinex(sinex): j = 0 i += 1 # Write matrix to SINEX (upper triangle) - if matrix == 'upper': + if matrix == "upper": j = 0 for i in range(len(Q)): - j = i + j = i while j < len(Q): out.write(f" {i+1:5d} {j+1:5d} {Q[i,j]:21.14E} ") j += 1 @@ -1840,23 +2014,25 @@ def remove_velocity_sinex(sinex): del Q # Write out the trailer line - out.write('%ENDSNX\n') + out.write("%ENDSNX\n") return + def remove_matrixzeros_sinex(sinex): - """This function reads in a SINEX file and removes the - zeros from the SOLUTION/MATRIX_ESTIMATE block only. + """ + This function reads in a SINEX file and removes the + zeros from the SOLUTION/MATRIX_ESTIMATE block only. :param str sinex: input SINEX file - return: SINEX file output.snx + :return: SINEX file output.snx """ - + # Open the output file - with open('output.snx', 'w') as out: + with open("output.snx", "w") as out: # With header line: - # - update the creation time + # - update the creation time # - then write to file header = read_sinex_header_line(sinex) old_creation_time = header[15:27] @@ -1865,38 +2041,48 @@ def remove_matrixzeros_sinex(sinex): out.write(header) del header - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the +FILE/COMMENT block and write to output file comments = read_sinex_comments(sinex) for i in comments: out.write(f"{i}\n") - out.write("*-------------------------------------------------------------------------------\n") - + out.write( + "*-------------------------------------------------------------------------------\n" + ) + # Read in the +SITE/ID block and write to file site_id = read_sinex_site_id_block(sinex) for line in site_id: - out.write(f"{line}\n") + out.write(f"{line}\n") del site_id - out.write("*-------------------------------------------------------------------------------\n") - + out.write( + "*-------------------------------------------------------------------------------\n" + ) + # Read in the +SOLUTION/EPOCHS block and write to file solution_epochs = read_sinex_solution_epochs_block(sinex) for line in solution_epochs: out.write(f"{line}\n") del solution_epochs - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the +SOLUTION/ESTIMATE block solution_estimate = read_sinex_solution_estimate_block(sinex) for line in solution_estimate: - out.write(f"{line}\n") + out.write(f"{line}\n") del solution_estimate - out.write("*-------------------------------------------------------------------------------\n") + out.write( + "*-------------------------------------------------------------------------------\n" + ) # Read in the +SOLUTION/MATRIX_ESTIMATE block: # - Remove lines that contain only zeros @@ -1904,19 +2090,26 @@ def remove_matrixzeros_sinex(sinex): for line in solution_matrix_estimate: col = line.split() numCol = len(col) - if numCol==3: - if col[2]=="0.00000000000000e+00": + if numCol == 3: + if col[2] == "0.00000000000000e+00": continue - if numCol==4: - if col[2]=="0.00000000000000e+00" and col[3]=="0.00000000000000e+00": + if numCol == 4: + if ( + col[2] == "0.00000000000000e+00" + and col[3] == "0.00000000000000e+00" + ): continue - if numCol==5: - if col[2]=="0.00000000000000e+00" and col[3]=="0.00000000000000e+00" and col[4]=="0.00000000000000e+00": + if numCol == 5: + if ( + col[2] == "0.00000000000000e+00" + and col[3] == "0.00000000000000e+00" + and col[4] == "0.00000000000000e+00" + ): continue out.write(line) del solution_matrix_estimate # Write out the trailer line - out.write('%ENDSNX\n') + out.write("%ENDSNX\n") return diff --git a/geodepy/height.py b/geodepy/height.py index bc5f8d4..c5856c7 100644 --- a/geodepy/height.py +++ b/geodepy/height.py @@ -1,167 +1,374 @@ -#___________________________________________________________________________# +# ___________________________________________________________________________# # Some notes: # Written by Jack McCubbine of Geoscience Australia, date: 08/11/2019 -# This code contains functions to handle tranformations between GPS and -# AWVS/AHD and Vice Versa +# This code contains functions to handle tranformations between GPS and +# AWVS/AHD and Vice Versa # Gridded data used for the varisous reference surfaces are geotiff files # These allow direct access remotely using "gdal" -#___________________________________________________________________________# -# Import dependencies +# ___________________________________________________________________________# +# Import dependencies import geodepy.constants as cons import geodepy.geodesy as gg from osgeo import gdal import numpy as np from scipy.interpolate import griddata import math as m -#___________________________________________________________________________# + + +# ___________________________________________________________________________# # Interpolation functions -def interp_file(Lat,Long,file): +def interp_file(Lat, Long, file): + """ + Interpolates files at specific Latitude and Longitude. Uses files found in + geodepy.constants and vsicurl to access only part of file. + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param file: Grid file to be interpolated + :return: Interpolated value + """ # Import the DOVPM file f = gdal.Open(file) # load band (akin to a variable in dataset) - band = f.GetRasterBand(1) + band = f.GetRasterBand(1) # get the pixel width, height, etc. transform = f.GetGeoTransform() # Grid resolution (known) - res=transform[1] + res = transform[1] # convert lat,lon to row,col column = (Long - transform[0]) / transform[1] row = (Lat - transform[3]) / transform[5] # get pixel values surrounding data point - Surrounding_data=(band.ReadAsArray(np.floor(column-2), np.floor(row-2), 5, 5)) + Surrounding_data = band.ReadAsArray(np.floor(column - 2), np.floor(row - 2), 5, 5) # convert row,col back to north,east Long_c = transform[0] + np.floor(column) * res Lat_c = transform[3] - np.floor(row) * res # set up matrices for interpolation - count=-1 - pos=np.zeros((25,2)) - Surrounding_data_v=np.zeros((25,1)) - for k in range(-2,3): - for j in range(-2,3): - count=count+1 - pos[count]=(Long_c+j*res,Lat_c-k*res) - Surrounding_data_v[count]=Surrounding_data[k+2,j+2] - interp_val=griddata(pos,Surrounding_data_v,(Long,Lat),method='cubic') + count = -1 + pos = np.zeros((25, 2)) + Surrounding_data_v = np.zeros((25, 1)) + for k in range(-2, 3): + for j in range(-2, 3): + count = count + 1 + pos[count] = (Long_c + j * res, Lat_c - k * res) + Surrounding_data_v[count] = Surrounding_data[k + 2, j + 2] + interp_val = griddata(pos, Surrounding_data_v, (Long, Lat), method="cubic") return interp_val -#___________________________________________________________________________# + +# ___________________________________________________________________________# # Functions to handle the conversions from one height to another -def GPS_to_AVWS(Lat,Long,GPS_H): - zeta=interp_file(Lat, Long, cons.file_AVWS) # AVWS file - zeta_std=interp_file(Lat, Long, cons.file_AVWS_STD) # AVWS STD file - NORMAL_H=GPS_H-zeta - return [NORMAL_H,zeta_std] - -def AVWS_to_GPS(Lat,Long,AVWS_H): - zeta=interp_file(Lat, Long, cons.file_AVWS) # AVWS file - zeta_std=interp_file(Lat, Long, cons.file_AVWS_STD) # AVWS STD file - GPS_H=AVWS_H+zeta - return [GPS_H,zeta_std] - -def AHD_to_AVWS(Lat,Long,AHD_H): +def GPS_to_AVWS(Lat, Long, GPS_H): + """ + Convert from ellipsoidal height to AVWS height + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param GPS_H: Ellipsoidal height on GRS80 (m) + :return: - AVWS height (m) + - Geoid std (m) + """ + zeta = interp_file(Lat, Long, cons.file_AVWS) # AVWS file + zeta_std = interp_file(Lat, Long, cons.file_AVWS_STD) # AVWS STD file + NORMAL_H = GPS_H - zeta + return [NORMAL_H, zeta_std] + + +def AVWS_to_GPS(Lat, Long, AVWS_H): + """ + Convert from AVWS height to ellipsoidal height + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param AVWS_H: AVWS height (m) + :return: - Ellipsoidal height on GRS80 (m) + - Geoid std (m) + """ + zeta = interp_file(Lat, Long, cons.file_AVWS) # AVWS file + zeta_std = interp_file(Lat, Long, cons.file_AVWS_STD) # AVWS STD file + GPS_H = AVWS_H + zeta + return [GPS_H, zeta_std] + + +def AHD_to_AVWS(Lat, Long, AHD_H): + """ + Convert from AHD to AVWS + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param AHD_H: AHD height (m) + :return: AVWS height (m) + """ # Convert to GPS - GPS_H=AHD_H+interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file + GPS_H = AHD_H + interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file # Convert to AVWS - Normal_H=GPS_H-interp_file(Lat, Long, cons.file_AVWS) # AVWS file + Normal_H = GPS_H - interp_file(Lat, Long, cons.file_AVWS) # AVWS file return [Normal_H] -def GPS_to_AHD(Lat,Long,GPS_H): - N=interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file - N_std=interp_file(Lat, Long, cons.file_AG2020_STD) # AUSGEOID2020 STD file - AHD_H=GPS_H-N - return [AHD_H,N_std] - -def AHD_to_GPS(Lat,Long,AHD_H): - N=interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file - N_std=interp_file(Lat, Long, cons.file_AG2020_STD) # AUSGEOID2020 STD file - GPS_H=AHD_H+N - return [GPS_H,N_std] - -def AVWS_to_AHD(Lat,Long,Normal_H): - # Convert to GPS - GPS_H=Normal_H+interp_file(Lat, Long, cons.file_AVWS) # AVWS file + +def GPS_to_AHD(Lat, Long, GPS_H): + """ + Convert from ellipsoidal height to AHD + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param GPS_H: Ellipsoidal height on GRS80 (m) + :return: - AHD (m) + - Geoid STD + """ + N = interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file + N_std = interp_file(Lat, Long, cons.file_AG2020_STD) # AUSGEOID2020 STD file + AHD_H = GPS_H - N + return [AHD_H, N_std] + + +def AHD_to_GPS(Lat, Long, AHD_H): + """ + Convert from AHD to ellipsoidal height + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param AHD_H: AHD (m) + :return: - Ellipsoidal height (m) + - Geoid STD + """ + N = interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file + N_std = interp_file(Lat, Long, cons.file_AG2020_STD) # AUSGEOID2020 STD file + GPS_H = AHD_H + N + return [GPS_H, N_std] + + +def AVWS_to_AHD(Lat, Long, AVWS_H): + """ + Convert from AVWS to AHD + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param AVWS_H: AVWS height (m) + :return: AHD height (m) + """ + # Convert to GPS + GPS_H = AVWS_H + interp_file(Lat, Long, cons.file_AVWS) # AVWS file # Convert to AHD - AHD_H=GPS_H-interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file + AHD_H = GPS_H - interp_file(Lat, Long, cons.file_AG2020) # AUSGEOID2020 file return [AHD_H] -def DOV(Lat,Long): - # Convert to GPS - DOV_PM=interp_file(Lat, Long, cons.file_DOV_PM) # AVWS file + +def DOV(Lat, Long): + """ + Deflection of the vertical in the north-south and east west direction from AUSGeoid2020 + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :return: - DOV in north-south + - DOV in east-west + """ + # Convert to GPS + DOV_PM = interp_file(Lat, Long, cons.file_DOV_PM) # AVWS file # Convert to AHD - DOV_PV=interp_file(Lat, Long, cons.file_DOV_PV) # AUSGEOID2020 file - return [DOV_PM,DOV_PV] + DOV_PV = interp_file(Lat, Long, cons.file_DOV_PV) # AUSGEOID2020 file + return [DOV_PM, DOV_PV] -def GPS_to_AUSGeoid98(Lat,Long,GPS_H): - N=interp_file(Lat,Long,cons.file_AG98) # AUSGEOID98 file - AHD_H=GPS_H-N + +def GPS_to_AUSGeoid98(Lat, Long, GPS_H): + """ + Convert from ellipsoidal height to heights using AUSGeoid98 + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param GPS_H: Ellipsoidal height (m) + :return: AHD using AUSGeoid98 (m) + """ + N = interp_file(Lat, Long, cons.file_AG98) # AUSGEOID98 file + AHD_H = GPS_H - N return [AHD_H] -def AUSGeoid98_to_GPS(Lat,Long,AHD_H): - N=interp_file(Lat,Long,cons.file_AG98) # AUSGEOID98 file - GPS_H=AHD_H+N + +def AUSGeoid98_to_GPS(Lat, Long, AHD_H): + """ + Convert from height using AUSGeoid98 to ellipsoidal height + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param AHD_H: height using AUSGEOID98 (m) + :return: Ellipsoidal height (m) + """ + N = interp_file(Lat, Long, cons.file_AG98) # AUSGEOID98 file + GPS_H = AHD_H + N return [GPS_H] -def GPS_to_AUSGeoid09(Lat,Long,GPS_H): - N=interp_file(Lat,Long,cons.file_AG09) # AUSGEOID09 file - AHD_H=GPS_H-N + +def GPS_to_AUSGeoid09(Lat, Long, GPS_H): + """ + Convert from ellipsoidal height to AHD using AUSGeoid09 + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param AHD_H: ellipsoidal height (m) + :return: AHD using AUSGeoid09 (m) + """ + N = interp_file(Lat, Long, cons.file_AG09) # AUSGEOID09 file + AHD_H = GPS_H - N return [AHD_H] -def AUSGeoid09_to_GPS(Lat,Long,AHD_H): - N=interp_file(Lat,Long,cons.file_AG09) # AUSGEOID09 file - GPS_H=AHD_H+N + +def AUSGeoid09_to_GPS(Lat, Long, AHD_H): + """ + Convert from AHD using AUSGeoid09 to ellipsoidal height + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :param AHD_H: AHD using AUSGEOID98 (m) + :return: Ellipsoidal height (m) + """ + N = interp_file(Lat, Long, cons.file_AG09) # AUSGEOID09 file + GPS_H = AHD_H + N return [GPS_H] -def DOV_09(Lat,Long): + +def DOV_09(Lat, Long): + """ + Deflection of the vertical in the north-south and east west direction from AUSGeoid09 + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :return: - DOV in north-south + - DOV in east-west + """ # Interp PM - DOV_PM=interp_file(Lat,Long,cons.file_AG09_DOV_PM) # AGQG09 DOV file + DOV_PM = interp_file(Lat, Long, cons.file_AG09_DOV_PM) # AGQG09 DOV file # Interp PV - DOV_PV=interp_file(Lat,Long,cons.file_AG09_DOV_PV) # AGQG09 DOV file - return [DOV_PM,DOV_PV] + DOV_PV = interp_file(Lat, Long, cons.file_AG09_DOV_PV) # AGQG09 DOV file + return [DOV_PM, DOV_PV] + -def DOV_98(Lat,Long): - # Interp PM - DOV_PM=interp_file(Lat,Long,cons.file_AG98_DOV_PM) # AGQG98 DOV file +def DOV_98(Lat, Long): + """ + Deflection of the vertical in the north-south and east west direction from AUSGeoid98 + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :return: - DOV in north-south + - DOV in east-west + """ + # Interp PM + DOV_PM = interp_file(Lat, Long, cons.file_AG98_DOV_PM) # AGQG98 DOV file # Interp PV - DOV_PV=interp_file(Lat,Long,cons.file_AG98_DOV_PV) # AGQG98 DOV file - return [DOV_PM,DOV_PV] + DOV_PV = interp_file(Lat, Long, cons.file_AG98_DOV_PV) # AGQG98 DOV file + return [DOV_PM, DOV_PV] + -def mean_normal_grav(Lat,h): +def mean_normal_grav(Lat, h): + """ + Gives the mean gravity between the ellipsoid and the height above ellipsoid given. + + :param Lat: Latitude in decimal degrees + :param h: Height above ellipsoid (m) + :return: Average gravity value from ellipsoid to h + """ # GRS 80 constants - a=6378137 - b=6356752.3141 - omega=7292115*(10**-11) - e2=0.00669438002290 - GM=3986005*10**8 - k=0.001931851353 + a = 6378137 + b = 6356752.3141 + omega = 7292115 * (10**-11) + e2 = 0.00669438002290 + GM = 3986005 * 10**8 + k = 0.001931851353 # GRS80 normal gravity - EllGrav=(10**5)*9.7803267715*(1+k*(np.sin(Lat*np.pi/180)**2))/np.sqrt(1-e2*(np.sin(Lat*np.pi/180)**2)) - FA=-((2*(EllGrav/a)*(1+(a-b)/a + omega**2*a**2*b/GM - 2*(a-b)/a*(np.sin(Lat*np.pi/180)**2))*(h**2)/2-3*(EllGrav/a**2)*(h**3)/3)/h) - mean_normal_g=(EllGrav+FA)*(10**-5) + EllGrav = ( + (10**5) + * 9.7803267715 + * (1 + k * (np.sin(Lat * np.pi / 180) ** 2)) + / np.sqrt(1 - e2 * (np.sin(Lat * np.pi / 180) ** 2)) + ) + FA = -( + ( + 2 + * (EllGrav / a) + * ( + 1 + + (a - b) / a + + omega**2 * a**2 * b / GM + - 2 * (a - b) / a * (np.sin(Lat * np.pi / 180) ** 2) + ) + * (h**2) + / 2 + - 3 * (EllGrav / a**2) * (h**3) / 3 + ) + / h + ) + mean_normal_g = (EllGrav + FA) * (10**-5) return mean_normal_g -def normal_grav(Lat,h): + +def normal_grav(Lat, h): + """ + Gives the normal gravity at height above ellipsoid. + + :param Lat: Latitude in decimal degrees + :param h: Height above ellipsoid (m) + :return: Normal gravity at h above ellipsoid + """ # GRS 80 constants - a=6378137 - b=6356752.3141 - omega=7292115*(10**-11) - e2=0.00669438002290 - GM=3986005*10**8 - k=0.001931851353 + a = 6378137 + b = 6356752.3141 + omega = 7292115 * (10**-11) + e2 = 0.00669438002290 + GM = 3986005 * 10**8 + k = 0.001931851353 # GRS80 normal gravity - EllGrav=(10**5)*9.7803267715*(1+k*(np.sin(Lat*np.pi/180)**2))/np.sqrt(1-e2*(np.sin(Lat*np.pi/180)**2)) - FA=-(2*EllGrav*h/a)*(1+(a-b)/a+omega**2*a**2*b/GM-2*(a-b)/a*(np.sin(Lat*np.pi/180)**2))+3*(EllGrav*h**2)/(a**2) - normal_g=(EllGrav+FA)*(10**-5) + EllGrav = ( + (10**5) + * 9.7803267715 + * (1 + k * (np.sin(Lat * np.pi / 180) ** 2)) + / np.sqrt(1 - e2 * (np.sin(Lat * np.pi / 180) ** 2)) + ) + FA = -(2 * EllGrav * h / a) * ( + 1 + + (a - b) / a + + omega**2 * a**2 * b / GM + - 2 * (a - b) / a * (np.sin(Lat * np.pi / 180) ** 2) + ) + 3 * (EllGrav * h**2) / (a**2) + normal_g = (EllGrav + FA) * (10**-5) return normal_g -def mean_surface_grav(Lat_A,Long_A,H_A,Lat_B,Long_B,H_B): - Surf_Grav_A=interp_grav(Lat_A,Long_A)*(10**-5)+normal_grav(Lat_A,H_A)+0.0419*2.67*H_A*(10**-5) - Surf_Grav_B=interp_grav(Lat_B,Long_B)*(10**-5)+normal_grav(Lat_B,H_B)+0.0419*2.67*H_B*(10**-5) - mean_g=(Surf_Grav_A+Surf_Grav_B)/2 + +def mean_surface_grav(Lat_A, Long_A, H_A, Lat_B, Long_B, H_B): + """ + Mean surface gravity between two points. Uses anomalies, normal theoretical + gravity and the Bouguer slab correction. + + :param Lat_A: Latitude of point A in decimal degrees + :param Long_A: Longitude of point A in decimal degrees + :param H_A: AHD of point A + :param Lat_B: Latitude of point B in decimal degrees + :param Long_B: Longitude of point B in decimal degrees + :param H_B: AHD of point B + :return: Mean surface gravity between two points + """ + Surf_Grav_A = ( + interp_grav(Lat_A, Long_A) * (10**-5) + + normal_grav(Lat_A, H_A) + + 0.0419 * 2.67 * H_A * (10**-5) + ) + Surf_Grav_B = ( + interp_grav(Lat_B, Long_B) * (10**-5) + + normal_grav(Lat_B, H_B) + + 0.0419 * 2.67 * H_B * (10**-5) + ) + mean_g = (Surf_Grav_A + Surf_Grav_B) / 2 return mean_g -def interp_grav(Lat,Long): + +def interp_grav(Lat, Long): + """ + Finds gravity anomalies at set lat and long. + + :param Lat: Latitude in decimal degrees + :param Long: Longitude in decimal degrees + :return: Gravity anomalies + """ # Grid resolution (known) - res=1.0/60 + res = 1.0 / 60 # open geotiff file f = gdal.Open(cons.file_GRAV_BA) # load band (akin to a variable in dataset) @@ -172,35 +379,52 @@ def interp_grav(Lat,Long): column = (Long - transform[0]) / transform[1] row = (Lat - transform[3]) / transform[5] # get pixel values surrounding data point - Surrounding_data=(band.ReadAsArray(np.floor(column-2), np.floor(row-2), 5, 5)) + Surrounding_data = band.ReadAsArray(np.floor(column - 2), np.floor(row - 2), 5, 5) # convert row,col back to north,east Long_c = transform[0] + np.floor(column) * res Lat_c = transform[3] - np.floor(row) * res # set up matrices for interpolation - count=-1 - pos=np.zeros((25,2)) - Surrounding_data_v=np.zeros((25,1)) - for k in range(-2,3): - for j in range(-2,3): - count=count+1 - pos[count]=(Long_c+j*res,Lat_c-k*res) - Surrounding_data_v[count]=Surrounding_data[k+2,j+2] - interp_g=griddata(pos,Surrounding_data_v,(Long,Lat)) + count = -1 + pos = np.zeros((25, 2)) + Surrounding_data_v = np.zeros((25, 1)) + for k in range(-2, 3): + for j in range(-2, 3): + count = count + 1 + pos[count] = (Long_c + j * res, Lat_c - k * res) + Surrounding_data_v[count] = Surrounding_data[k + 2, j + 2] + interp_g = griddata(pos, Surrounding_data_v, (Long, Lat)) return interp_g -def normal_correction(Lat_A,Long_A,H_A,Lat_B,Long_B,H_B): + +def normal_correction(Lat_A, Long_A, H_A, Lat_B, Long_B, H_B): + """ + The normal gravity correction between two points. + + :param Lat_A: Latitude of point A in decimal degrees + :param Long_A: Longitude of point A in decimal degrees + :param H_A: AHD of point A + :param Lat_B: Latitude of point B in decimal degrees + :param Long_B: Longitude of point B in decimal degrees + :param H_B: AHD of point B + :return: - normal gravity correction + - Mean surface gravity between two points + """ # ellipsoidal gravity at 45 deg. Lat - Gamma_0=9.8061992115 + Gamma_0 = 9.8061992115 # Normal Gravity at the point - normal_g_A=mean_normal_grav(Lat_A,H_A) -# print normal_g_A - normal_g_B=mean_normal_grav(Lat_B,H_B) -# print normal_g_B - dn=H_B-H_A - g=mean_surface_grav(Lat_A,Long_A,H_A,Lat_B,Long_B,H_B) - # print g - NC=(dn*(g-Gamma_0)/Gamma_0)+H_A*(normal_g_A-Gamma_0)/Gamma_0-H_B*(normal_g_B-Gamma_0)/Gamma_0 - return NC,g + normal_g_A = mean_normal_grav(Lat_A, H_A) + # print normal_g_A + normal_g_B = mean_normal_grav(Lat_B, H_B) + # print normal_g_B + dn = H_B - H_A + g = mean_surface_grav(Lat_A, Long_A, H_A, Lat_B, Long_B, H_B) + # print g + NC = ( + (dn * (g - Gamma_0) / Gamma_0) + + H_A * (normal_g_A - Gamma_0) / Gamma_0 + - H_B * (normal_g_B - Gamma_0) / Gamma_0 + ) + return NC, g def normal_orthometric_correction(lat1, lon1, H1, lat2, lon2, H2): @@ -227,6 +451,8 @@ def normal_orthometric_correction(lat1, lon1, H1, lat2, lon2, H2): dist = vinc_inv[0] az = vinc_inv[1] - noc = - f_ng / m_rad * mid_height * m.sin(2.0 * mid_lat) * m.cos(m.radians(az)) * dist + noc = ( + -f_ng / m_rad * mid_height * m.sin(2.0 * mid_lat) * m.cos(m.radians(az)) * dist + ) return noc diff --git a/geodepy/inputoutput.py b/geodepy/inputoutput.py index 2232bfb..52daaf5 100644 --- a/geodepy/inputoutput.py +++ b/geodepy/inputoutput.py @@ -1,4 +1,3 @@ - __all__ = ["grid2geoio", "geo2gridio", "gdatrans7"] import geodepy.convert @@ -8,30 +7,28 @@ import geodepy.convert as cv import geodepy.constants as cs -''' +""" The inputoutput module acts as the backend for the GUI and manages the calls to the GeodePy functions. The functions are rewritten from the transform module but use pandas to improve efficiency. Functions placed here to not overcrowd the transform module. -''' +""" def grid2geoio(fn, fn_out, easting, northing, utmzone, geotypeout): """ - Input: - The CSV data must have headers. - Inputs passed the the function: - :param fn: input file path - :param fn_out: file output path - :param easting: is the column name in the csv where the eastings are stored - :param northing: is the column name in the csv where the northings are stored - :param utmzone: is the column name in the csv where the UTM Zone is stored - :param geotypeout: format of latitude and longitude output e.g. DD or DMS + The CSV data must have headers. + + :param fn: input file path + :param fn_out: file output path + :param easting: is the column name in the csv where the eastings are stored + :param northing: is the column name in the csv where the northings are stored + :param utmzone: is the column name in the csv where the UTM Zone is stored + :param geotypeout: format of latitude and longitude output e.g. DD or DMS - Output: - Data in the file is output to the fn_out location as a CSV. - """ + :return: Data is output to the fn_out location as a CSV. + """ # Check whether geotypeout value is from GUI or if function called within other code, then check that a valid # geotypeout value was provided @@ -44,12 +41,17 @@ def grid2geoio(fn, fn_out, easting, northing, utmzone, geotypeout): # Opens the CSV as a pandas dataframe csvdf = pd.read_csv(fn, low_memory=False) # Sends data to tf.grid2geo - returned = csvdf.apply(lambda x: geodepy.convert.grid2geo(x[utmzone], x[easting], x[northing]), axis=1) + returned = csvdf.apply( + lambda x: geodepy.convert.grid2geo(x[utmzone], x[easting], x[northing]), axis=1 + ) # Convert tuple returned to DafaFrame - resultdf = pd.DataFrame(list(returned), columns=['Latitude', 'Longitude', 'Point Scale Factor', 'Grid Convergence']) - if geotypeout == 'DMS': - resultdf['Latitude'] = resultdf['Latitude'].apply(cv.dec2hp) - resultdf['Longitude'] = resultdf['Longitude'].apply(cv.dec2hp) + resultdf = pd.DataFrame( + list(returned), + columns=["Latitude", "Longitude", "Point Scale Factor", "Grid Convergence"], + ) + if geotypeout == "DMS": + resultdf["Latitude"] = resultdf["Latitude"].apply(cv.dec2hp) + resultdf["Longitude"] = resultdf["Longitude"].apply(cv.dec2hp) # Adds the results to the original dataframe from the csv csvdf = pd.concat([csvdf, resultdf], axis=1) # Writes the dataframe to a csv @@ -58,19 +60,17 @@ def grid2geoio(fn, fn_out, easting, northing, utmzone, geotypeout): def geo2gridio(fn, fn_out, latitude, longitude, geotypein): """ - Input: - The CSV data must have headers. - Inputs passed the the function: - :param fn: input file path - :param fn_out: file output path - :param latitude: is the column name in the csv where the latitudes are stored - :param longitude: is the column name in the csv where the longitudes are stored - :param geotypein: format of latitude and longitude e.g. DD or DMS + The CSV data must have headers. - Output: - Data in the file is output to the fn_out location as a CSV. - """ + :param fn: input file path + :param fn_out: file output path + :param latitude: is the column name in the csv where the latitudes are stored + :param longitude: is the column name in the csv where the longitudes are stored + :param geotypein: format of latitude and longitude e.g. DD or DMS + + :return: Data is output to the fn_out location as a CSV. + """ # Check whether geotypein value is from GUI or if function called within other code, then check that a valid # geotypein value was provided @@ -84,17 +84,28 @@ def geo2gridio(fn, fn_out, latitude, longitude, geotypein): csvdf = pd.read_csv(fn, low_memory=False) # Converts DMS lat and long to Decimal Degrees if required - if geotypein == 'DMS': - csvdf['LatitudeDD'] = csvdf[latitude].apply(cv.hp2dec) - csvdf['LongitudeDD'] = csvdf[longitude].apply(cv.hp2dec) - latitude = 'LatitudeDD' - longitude = 'LongitudeDD' + if geotypein == "DMS": + csvdf["LatitudeDD"] = csvdf[latitude].apply(cv.hp2dec) + csvdf["LongitudeDD"] = csvdf[longitude].apply(cv.hp2dec) + latitude = "LatitudeDD" + longitude = "LongitudeDD" # Sends data to tf.geo2grid - returned = csvdf.apply(lambda x: geodepy.convert.geo2grid(x[latitude], x[longitude]), axis=1) + returned = csvdf.apply( + lambda x: geodepy.convert.geo2grid(x[latitude], x[longitude]), axis=1 + ) # Convert tuple returned from tf.gepo2grid into DafaFrame - resultdf = pd.DataFrame(list(returned), columns=['Hemisphere', 'UTMZone', 'Easting', 'Northing', - 'Point Scale Factor', 'Grid Convergence']) + resultdf = pd.DataFrame( + list(returned), + columns=[ + "Hemisphere", + "UTMZone", + "Easting", + "Northing", + "Point Scale Factor", + "Grid Convergence", + ], + ) # Adds the results to the original dataframe from the csv csvdf = pd.concat([csvdf, resultdf], axis=1) # Writes the dataframe to a csv @@ -102,12 +113,10 @@ def geo2gridio(fn, fn_out, latitude, longitude, geotypein): def gdatrans7(fn, fn_out, latitude, longitude, ellht, gdageotypein, direction): - """ - Input: + The CSV data must have headers. - Inputs passed the the function: :param fn: input file path :param fn_out: file output path :param latitude: is the column name in the csv where the latitudes are stored @@ -116,12 +125,7 @@ def gdatrans7(fn, fn_out, latitude, longitude, ellht, gdageotypein, direction): :param gdageotypein: format of latitude and longitude e.g. DD or DMS :param direction: either "94to2020" or "2020to94". Specifies the datum to transform from and to. - trans: Transformation parameter from the geodepy.constants module. The trans parameters are hard coded below. - Parameters were sourced from GDA2020 Technical Manual. Need to work on a way of entering and passing different - transformation parameters to the function. - - Output: - Data in the file is output to the fn_out location as a CSV. + :return: Data is output to the fn_out location as a CSV. """ # Check whether direction value is from GUI or if function called within other code, then check that a valid @@ -138,18 +142,40 @@ def gdatrans7(fn, fn_out, latitude, longitude, ellht, gdageotypein, direction): gdageotypein = gdageotypein.get() gdageotypein_options = ["DD", "DMS"] if gdageotypein not in gdageotypein_options: - raise ValueError("Invalid gdageotypein. Expected one of: %s" % gdageotypein_options) + raise ValueError( + "Invalid gdageotypein. Expected one of: %s" % gdageotypein_options + ) # Sets up the direction of the transformation and sets up the output column naming if direction == "94to2020": - trans = cs.Transformation('MGA94', 'MGA2020', '1994', 0.06155, -0.01087, -0.04019, -0.009994, -0.0394924, - -0.0327221, -0.0328979) + trans = cs.Transformation( + "MGA94", + "MGA2020", + "1994", + 0.06155, + -0.01087, + -0.04019, + -0.009994, + -0.0394924, + -0.0327221, + -0.0328979, + ) translat = "GDA2020Latitude" translong = "GDA2020Longitude" transellht = "GDA2020EllHt" else: - trans = cs.Transformation('MGA94', 'MGA2020', '1994', 0.06155, -0.01087, -0.04019, -0.009994, -0.0394924, - -0.0327221, -0.0328979).__neg__() + trans = cs.Transformation( + "MGA94", + "MGA2020", + "1994", + 0.06155, + -0.01087, + -0.04019, + -0.009994, + -0.0394924, + -0.0327221, + -0.0328979, + ).__neg__() translat = "GDA1994Latitude" translong = "GDA1994Longitude" transellht = "GDA1994EllHt" @@ -158,37 +184,62 @@ def gdatrans7(fn, fn_out, latitude, longitude, ellht, gdageotypein, direction): csvdf = pd.read_csv(fn, low_memory=False) # Converts DMS lat and long to Decimal Degrees if required - if gdageotypein == 'DMS': - csvdf['LatitudeDMS'] = csvdf[latitude] - csvdf['LongitudeDMS'] = csvdf[longitude] - csvdf['Latitude'] = csvdf[latitude].apply(cv.hp2dec) - csvdf['Longitude'] = csvdf[longitude].apply(cv.hp2dec) + if gdageotypein == "DMS": + csvdf["LatitudeDMS"] = csvdf[latitude] + csvdf["LongitudeDMS"] = csvdf[longitude] + csvdf["Latitude"] = csvdf[latitude].apply(cv.hp2dec) + csvdf["Longitude"] = csvdf[longitude].apply(cv.hp2dec) # Converts Lat, Long & ElipHt XYZ - returned = csvdf.apply(lambda x: geodepy.convert.llh2xyz(x[latitude], x[longitude], x[ellht]), axis=1) + returned = csvdf.apply( + lambda x: geodepy.convert.llh2xyz(x[latitude], x[longitude], x[ellht]), axis=1 + ) # Converts the results from llh2xyz into a dataframe - xyzresultdf = pd.DataFrame(list(returned), columns=['CartesianX', 'CartesianY', 'CartesianZ']) + xyzresultdf = pd.DataFrame( + list(returned), columns=["CartesianX", "CartesianY", "CartesianZ"] + ) # Combines the results with the original dataframe csvdf = pd.concat([csvdf, xyzresultdf], axis=1) # Transforms Lat, Long & ElipHt XYZ using a 7 parameter transformation - transformed = csvdf.apply(lambda x: tf.conform7(x.CartesianX, x.CartesianY, x.CartesianZ, trans), axis=1) + transformed = csvdf.apply( + lambda x: tf.conform7(x.CartesianX, x.CartesianY, x.CartesianZ, trans), axis=1 + ) # Converts the results from conform7 into a dataframe - transresultdf = pd.DataFrame(list(transformed), columns=['TransCartesianX', 'TransCartesianY', 'TransCartesianZ']) + transresultdf = pd.DataFrame( + list(transformed), + columns=["TransCartesianX", "TransCartesianY", "TransCartesianZ"], + ) # Combines the results with the original dataframe csvdf = pd.concat([csvdf, transresultdf], axis=1) # Converts the the transformed XYZ coordinates back to Lats, Longs and Ellipsoidal Heights - returned = csvdf.apply(lambda x: geodepy.convert.xyz2llh(x.TransCartesianX, x.TransCartesianY, x.TransCartesianZ), axis=1) + returned = csvdf.apply( + lambda x: geodepy.convert.xyz2llh( + x.TransCartesianX, x.TransCartesianY, x.TransCartesianZ + ), + axis=1, + ) # Converts the results from llh2xyz into a dataframe - llhresultdf = pd.DataFrame(list(returned), columns=[translat, translong, transellht]) + llhresultdf = pd.DataFrame( + list(returned), columns=[translat, translong, transellht] + ) # Combines the results with the original dataframe csvdf = pd.concat([csvdf, llhresultdf], axis=1) - csvdf = csvdf.drop(['CartesianX', 'CartesianY', 'CartesianZ', 'TransCartesianX', 'TransCartesianY', - 'TransCartesianZ'], axis=1) + csvdf = csvdf.drop( + [ + "CartesianX", + "CartesianY", + "CartesianZ", + "TransCartesianX", + "TransCartesianY", + "TransCartesianZ", + ], + axis=1, + ) # Writes the csv dataframe to a csv file csvdf.to_csv(fn_out, index=False) diff --git a/geodepy/ntv2reader.py b/geodepy/ntv2reader.py index 6a152ef..2ae9bf7 100644 --- a/geodepy/ntv2reader.py +++ b/geodepy/ntv2reader.py @@ -12,36 +12,95 @@ class NTv2Grid(object): - def __init__(self, num_orec, num_srec, num_file, gs_type, version, system_f, system_t, major_f, minor_f, major_t, - minor_t, file_path): - self.num_orec = num_orec # Number of header identifiers - self.num_srec = num_srec # Number of sub-header idents - self.num_file = num_file # Number of subgrids in file - self.gs_type = gs_type # grid shift type - self.version = version # grid file version - self.system_f = system_f # reference system - self.system_t = system_t # reference system - self.major_f = major_f # semi major of from system - self.minor_f = minor_f # semi minor of from system - self.major_t = major_t # semi major of to system - self.minor_t = minor_t # semi minor of to system + def __init__( + self, + num_orec, + num_srec, + num_file, + gs_type, + version, + system_f, + system_t, + major_f, + minor_f, + major_t, + minor_t, + file_path, + ): + ''' + NTv2 Grid Parameters + + :param num_orec: Number of header identifiers + :param num_srec: Number of sub-header idents + :param num_file: Number of subgrids in file + :param gs_type: grid shift type + :param version: grid file version + :param system_f: reference system + :param system_t: reference system + :param major_f: semi major of from system + :param minor_f: semi minor of from system + :param major_t: semi major of to system + :param minor_t: semi minor of to system + :param file_path: full path to ntv2 gsb file + + ''' + self.num_orec = num_orec # Number of header identifiers + self.num_srec = num_srec # Number of sub-header idents + self.num_file = num_file # Number of subgrids in file + self.gs_type = gs_type # grid shift type + self.version = version # grid file version + self.system_f = system_f # reference system + self.system_t = system_t # reference system + self.major_f = major_f # semi major of from system + self.minor_f = minor_f # semi minor of from system + self.major_t = major_t # semi major of to system + self.minor_t = minor_t # semi minor of to system self.subgrids = {} self.file_path = file_path class SubGrid(object): - def __init__(self, sub_name, parent, created, updated, s_lat, n_lat, e_long, w_long, lat_inc, long_inc, gs_count): - self.sub_name = sub_name # subgrid name - self.parent = parent # parent name - self.created = created # date created - self.updated = updated # date modified - self.s_lat = s_lat # south latitude extents - self.n_lat = n_lat # north latitude extents - self.e_long = e_long # east longitude extents - self.w_long = w_long # west longitude extents - self.lat_inc = lat_inc # latitude increment - self.long_inc = long_inc # longitude increment - self.gs_count = gs_count # total nodes in subgrid + def __init__( + self, + sub_name, + parent, + created, + updated, + s_lat, + n_lat, + e_long, + w_long, + lat_inc, + long_inc, + gs_count, + ): + ''' + Sub Grid Parameters + + :param sub_name: subgrid name + :param parent: parent name + :param created: date created + :param updated: date modified + :param s_lat: south latitude extents + :param n_lat: north latitude extents + :param e_long: east longitude extents + :param w_long: west longitude extents + :param lat_inc: latitude increment + :param long_inc: longitude increment + :param gs_count: total nodes in subgrid + + ''' + self.sub_name = sub_name # subgrid name + self.parent = parent # parent name + self.created = created # date created + self.updated = updated # date modified + self.s_lat = s_lat # south latitude extents + self.n_lat = n_lat # north latitude extents + self.e_long = e_long # east longitude extents + self.w_long = w_long # west longitude extents + self.lat_inc = lat_inc # latitude increment + self.long_inc = long_inc # longitude increment + self.gs_count = gs_count # total nodes in subgrid def ntv2_bilinear(self, lat, lon, num_cols, row, col, f, start_byte): """ @@ -55,7 +114,7 @@ def ntv2_bilinear(self, lat, lon, num_cols, row, col, f, start_byte): :param f: variable for open file NTv2 being read as binary :param start_byte: start index of subgrid - :return: four field tuple of interpolation results at point of interest. + :return: Four field tuple of interpolation results at point of interest. """ # | | @@ -83,7 +142,7 @@ def ntv2_bilinear(self, lat, lon, num_cols, row, col, f, start_byte): node_2 = read_node(f) # Navigate to beginning of node 3 - f.seek(16*(pos3 - pos2 - 1), 1) + f.seek(16 * (pos3 - pos2 - 1), 1) # Read in values for nodes 3 and 4 node_3 = read_node(f) @@ -97,10 +156,18 @@ def ntv2_bilinear(self, lat, lon, num_cols, row, col, f, start_byte): x = (lon - long1) / self.long_inc y = (lat - lat1) / self.lat_inc - field_1 = round(bilinear_interpolation(node_1[0], node_2[0], node_3[0], node_4[0], x, y), 6) - field_2 = round(bilinear_interpolation(node_1[1], node_2[1], node_3[1], node_4[1], x, y), 6) - field_3 = round(bilinear_interpolation(node_1[2], node_2[2], node_3[2], node_4[2], x, y), 6) - field_4 = round(bilinear_interpolation(node_1[3], node_2[3], node_3[3], node_4[3], x, y), 6) + field_1 = round( + bilinear_interpolation(node_1[0], node_2[0], node_3[0], node_4[0], x, y), 6 + ) + field_2 = round( + bilinear_interpolation(node_1[1], node_2[1], node_3[1], node_4[1], x, y), 6 + ) + field_3 = round( + bilinear_interpolation(node_1[2], node_2[2], node_3[2], node_4[2], x, y), 6 + ) + field_4 = round( + bilinear_interpolation(node_1[3], node_2[3], node_3[3], node_4[3], x, y), 6 + ) return field_1, field_2, field_3, field_4 @@ -116,7 +183,7 @@ def ntv2_bicubic(self, lat, lon, num_cols, row, col, f, start_byte): :param f: variable for open file NTv2 being read as binary :param start_byte: start index of subgrid - :return: four field tuple of interpolation results at point of interest. + :return: Four field tuple of interpolation results at point of interest. """ @@ -200,26 +267,98 @@ def ntv2_bicubic(self, lat, lon, num_cols, row, col, f, start_byte): y = round((lat - lat1) / self.lat_inc, 6) # Call bicubic interpolation function to interpolate ntv2 fields. - field_1 = round(bicubic_interpolation(node_1[0], node_2[0], node_3[0], node_4[0], - node_5[0], node_6[0], node_7[0], node_8[0], - node_9[0], node_10[0], node_11[0], node_12[0], - node_13[0], node_14[0], node_15[0], node_16[0], - x, y), 6) - field_2 = round(bicubic_interpolation(node_1[1], node_2[1], node_3[1], node_4[1], - node_5[1], node_6[1], node_7[1], node_8[1], - node_9[1], node_10[1], node_11[1], node_12[1], - node_13[1], node_14[1], node_15[1], node_16[1], - x, y), 6) - field_3 = round(bicubic_interpolation(node_1[2], node_2[2], node_3[2], node_4[2], - node_5[2], node_6[2], node_7[2], node_8[2], - node_9[2], node_10[2], node_11[2], node_12[2], - node_13[2], node_14[2], node_15[2], node_16[2], - x, y), 6) - field_4 = round(bicubic_interpolation(node_1[3], node_2[3], node_3[3], node_4[3], - node_5[3], node_6[3], node_7[3], node_8[3], - node_9[3], node_10[3], node_11[3], node_12[3], - node_13[3], node_14[3], node_15[3], node_16[3], - x, y), 6) + field_1 = round( + bicubic_interpolation( + node_1[0], + node_2[0], + node_3[0], + node_4[0], + node_5[0], + node_6[0], + node_7[0], + node_8[0], + node_9[0], + node_10[0], + node_11[0], + node_12[0], + node_13[0], + node_14[0], + node_15[0], + node_16[0], + x, + y, + ), + 6, + ) + field_2 = round( + bicubic_interpolation( + node_1[1], + node_2[1], + node_3[1], + node_4[1], + node_5[1], + node_6[1], + node_7[1], + node_8[1], + node_9[1], + node_10[1], + node_11[1], + node_12[1], + node_13[1], + node_14[1], + node_15[1], + node_16[1], + x, + y, + ), + 6, + ) + field_3 = round( + bicubic_interpolation( + node_1[2], + node_2[2], + node_3[2], + node_4[2], + node_5[2], + node_6[2], + node_7[2], + node_8[2], + node_9[2], + node_10[2], + node_11[2], + node_12[2], + node_13[2], + node_14[2], + node_15[2], + node_16[2], + x, + y, + ), + 6, + ) + field_4 = round( + bicubic_interpolation( + node_1[3], + node_2[3], + node_3[3], + node_4[3], + node_5[3], + node_6[3], + node_7[3], + node_8[3], + node_9[3], + node_10[3], + node_11[3], + node_12[3], + node_13[3], + node_14[3], + node_15[3], + node_16[3], + x, + y, + ), + 6, + ) return field_1, field_2, field_3, field_4 @@ -229,21 +368,21 @@ def read_node(f): Function to read in values of nodes :param f: variable for open file NTv2 being read as binary - :return: tuple containing the four ntv2 fields at the grid node. + :return: Tuple containing the four ntv2 fields at the grid node. """ # field_1: shift lat / geoid separation (N) byte = f.read(4) - field_1 = struct.unpack('f', byte)[0] + field_1 = struct.unpack("f", byte)[0] # field 2: shift lon / deflection in prime meridian value (Xi) byte = f.read(4) - field_2 = struct.unpack('f', byte)[0] + field_2 = struct.unpack("f", byte)[0] # field 3: reliability lat / deflection in prime vertical value (Eta) byte = f.read(4) - field_3 = struct.unpack('f', byte)[0] + field_3 = struct.unpack("f", byte)[0] # field 4: reliability lon / NA byte = f.read(4) - field_4 = struct.unpack('f', byte)[0] + field_4 = struct.unpack("f", byte)[0] return field_1, field_2, field_3, field_4 @@ -259,7 +398,7 @@ def bilinear_interpolation(n1, n2, n3, n4, x, y): :param x: interpolation scale factor for x axis :param y: interpolation scale factor for y axis - :return: value at node P + :return: Value at node P """ a0 = n1 @@ -270,7 +409,9 @@ def bilinear_interpolation(n1, n2, n3, n4, x, y): return p -def bicubic_interpolation(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, x, y): +def bicubic_interpolation( + n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, x, y +): """ Bicubic interpolation of value for point of interest (P). @@ -293,26 +434,30 @@ def bicubic_interpolation(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13 :param x: interpolation scale factor for x axis :param y: interpolation scale factor for y axis - :return: value at node P + :return: Value at node P """ # Define the inverse of the coefficient matrix - cinv = np.array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], - [-3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1, 0, 0, 0, 0], - [2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, -3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1], - [0, 0, 0, 0, 2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1], - [-3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0], - [9, -9, 9, -9, 6, 3, -3, -6, 6, -6, -3, 3, 4, 2, 1, 2], - [-6, 6, -6, 6, -4, -2, 2, 4, -3, 3, 3, -3, -2, -1, -1, -2], - [2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0], - [-6, 6, -6, 6, -3, -3, 3, 3, -4, 4, 2, -2, -2, -2, -1, -1], - [4, -4, 4, -4, 2, 2, -2, -2, 2, -2, -2, 2, 1, 1, 1, 1]]) + cinv = np.array( + [ + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [-3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1, 0, 0, 0, 0], + [2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, -3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1], + [0, 0, 0, 0, 2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1], + [-3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0], + [9, -9, 9, -9, 6, 3, -3, -6, 6, -6, -3, 3, 4, 2, 1, 2], + [-6, 6, -6, 6, -4, -2, 2, 4, -3, 3, 3, -3, -2, -1, -1, -2], + [2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0], + [-6, 6, -6, 6, -3, -3, 3, 3, -4, 4, 2, -2, -2, -2, -1, -1], + [4, -4, 4, -4, 2, 2, -2, -2, 2, -2, -2, 2, 1, 1, 1, 1], + ] + ) # Define x parameters # Function values @@ -337,7 +482,9 @@ def bicubic_interpolation(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13 x16 = (n12 - n2 - n14 + n16) / 4 # Create array from x parameters - xarr = np.array([x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16]) + xarr = np.array( + [x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16] + ) # Multiply the inverse of the coefficient matrix by the array of x values to give array of alpha values alpha = np.matmul(cinv, xarr) @@ -346,7 +493,7 @@ def bicubic_interpolation(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13 n_p = 0 for i in range(0, 4): for j in range(0, 4): - n_p = n_p + alpha[i * 4 + j] * x ** i * y ** j + n_p = n_p + alpha[i * 4 + j] * x**i * y**j return n_p @@ -356,68 +503,68 @@ def read_ntv2_file(ntv2_gsb_file): Function to read an ntv2 gsb file and create grid & subgrid objects :param ntv2_gsb_file: full path to ntv2 file - :return: ntv2 grid object + :return: Ntv2 grid object """ - with open(ntv2_gsb_file, 'rb') as f: + with open(ntv2_gsb_file, "rb") as f: # NUM_OREC f.seek(8, 1) byte = f.read(4) - num_orec = int.from_bytes(byte, byteorder='little') + num_orec = int.from_bytes(byte, byteorder="little") f.seek(4, 1) # NUM_SREC f.seek(8, 1) byte = f.read(4) - num_srec = int.from_bytes(byte, byteorder='little') + num_srec = int.from_bytes(byte, byteorder="little") f.seek(4, 1) # NUM_FILE f.seek(8, 1) byte = f.read(4) - num_file = int.from_bytes(byte, byteorder='little') + num_file = int.from_bytes(byte, byteorder="little") f.seek(4, 1) # GS_TYPE f.seek(8, 1) byte = f.read(8) - gs_type = byte.decode('utf8').strip('\x00').strip() + gs_type = byte.decode("utf8").strip("\x00").strip() # VERSION f.seek(8, 1) byte = f.read(8) - version = byte.decode('utf8').strip('\x00').strip() + version = byte.decode("utf8").strip("\x00").strip() # SYSTEM_F f.seek(8, 1) byte = f.read(8) - system_f = byte.decode('utf8').strip('\x00').strip() + system_f = byte.decode("utf8").strip("\x00").strip() # SYSTEM_T f.seek(8, 1) byte = f.read(8) - system_t = byte.decode('utf8').strip('\x00').strip() + system_t = byte.decode("utf8").strip("\x00").strip() # MAJOR_F f.seek(8, 1) byte = f.read(8) - major_f = struct.unpack('d', byte)[0] + major_f = struct.unpack("d", byte)[0] # MINOR_F f.seek(8, 1) byte = f.read(8) - minor_f = struct.unpack('d', byte)[0] + minor_f = struct.unpack("d", byte)[0] # MAJOR_T f.seek(8, 1) byte = f.read(8) - major_t = struct.unpack('d', byte)[0] + major_t = struct.unpack("d", byte)[0] # MINOR_T f.seek(8, 1) byte = f.read(8) - minor_t = struct.unpack('d', byte)[0] + minor_t = struct.unpack("d", byte)[0] grid = NTv2Grid( num_orec=num_orec, @@ -431,7 +578,7 @@ def read_ntv2_file(ntv2_gsb_file): minor_f=minor_f, major_t=major_t, minor_t=minor_t, - file_path=ntv2_gsb_file + file_path=ntv2_gsb_file, ) # read subgrids @@ -439,57 +586,61 @@ def read_ntv2_file(ntv2_gsb_file): # SUB_NAME f.seek(8, 1) byte = f.read(8) - sub_name = byte.decode('utf').strip('\x00').strip() + sub_name = byte.decode("utf").strip("\x00").strip() # PARENT f.seek(8, 1) byte = f.read(8) - parent = byte.decode('utf').strip('\x00').strip() + parent = byte.decode("utf").strip("\x00").strip() # CREATED f.seek(8, 1) byte = f.read(8) - created = dt.strptime(byte.decode('utf').strip('\x00').strip(), '%d%m%Y').strftime('%d/%m/%Y') + created = dt.strptime( + byte.decode("utf").strip("\x00").strip(), "%d%m%Y" + ).strftime("%d/%m/%Y") # UPDATED f.seek(8, 1) byte = f.read(8) - updated = dt.strptime(byte.decode('utf').strip('\x00').strip(), '%d%m%Y').strftime('%d/%m/%Y') + updated = dt.strptime( + byte.decode("utf").strip("\x00").strip(), "%d%m%Y" + ).strftime("%d/%m/%Y") # S_LAT f.seek(8, 1) byte = f.read(8) - s_lat = round(struct.unpack('d', byte)[0], 3) + s_lat = round(struct.unpack("d", byte)[0], 3) # N_LAT f.seek(8, 1) byte = f.read(8) - n_lat = round(struct.unpack('d', byte)[0], 3) + n_lat = round(struct.unpack("d", byte)[0], 3) # E_LONG f.seek(8, 1) byte = f.read(8) - e_long = round(struct.unpack('d', byte)[0], 3) + e_long = round(struct.unpack("d", byte)[0], 3) # W_LONG f.seek(8, 1) byte = f.read(8) - w_long = round(struct.unpack('d', byte)[0], 3) + w_long = round(struct.unpack("d", byte)[0], 3) # LAT_INC f.seek(8, 1) byte = f.read(8) - lat_inc = round(struct.unpack('d', byte)[0], 6) + lat_inc = round(struct.unpack("d", byte)[0], 6) # LONG_INC f.seek(8, 1) byte = f.read(8) - long_inc = round(struct.unpack('d', byte)[0], 6) + long_inc = round(struct.unpack("d", byte)[0], 6) # GS_COUNT f.seek(8, 1) byte = f.read(4) - gs_count = int.from_bytes(byte, byteorder='little') + gs_count = int.from_bytes(byte, byteorder="little") f.seek(4, 1) # skip ahead to next subgrid @@ -506,27 +657,25 @@ def read_ntv2_file(ntv2_gsb_file): w_long=w_long, lat_inc=lat_inc, long_inc=long_inc, - gs_count=gs_count + gs_count=gs_count, ) return grid -def interpolate_ntv2(grid_object, lat, lon, method='bicubic'): +def interpolate_ntv2(grid_object, lat, lon, method="bicubic"): """ Function to interpolate Ntv2Grid objects + :param grid_object: Ntv2Grid object :param lat: latitude (decimal degrees) :param lon: longitude (decimal degrees) :param method: interpolation strategy, bicubic or bilinear - :return: tuple of four ntv2 fields + :return: Tuple of four ntv2 fields """ - interpolation_methods = { - 'bicubic', - 'bilinear' - } + interpolation_methods = {"bicubic", "bilinear"} if method not in interpolation_methods: raise ValueError(f'interpolation method "{method}" not supported') @@ -566,17 +715,21 @@ def interpolate_ntv2(grid_object, lat, lon, method='bicubic'): col = int((lon - in_grid.e_long) / in_grid.long_inc) # locate data in gsb_file - skip_bytes = 176 # grid header length + skip_bytes = 176 # grid header length - with open(grid_object.file_path, 'rb') as f: + with open(grid_object.file_path, "rb") as f: for sg in grid_object.subgrids.values(): - skip_bytes += 176 # subgrid header length + skip_bytes += 176 # subgrid header length if sg.sub_name == in_grid.sub_name: - if method == 'bilinear': - results = in_grid.ntv2_bilinear(lat, lon, num_cols, row, col, f, skip_bytes) - elif method == 'bicubic': - results = in_grid.ntv2_bicubic(lat, lon, num_cols, row, col, f, skip_bytes) + if method == "bilinear": + results = in_grid.ntv2_bilinear( + lat, lon, num_cols, row, col, f, skip_bytes + ) + elif method == "bicubic": + results = in_grid.ntv2_bicubic( + lat, lon, num_cols, row, col, f, skip_bytes + ) else: skip_bytes += sg.gs_count * 16 diff --git a/geodepy/statistics.py b/geodepy/statistics.py index d1c3ae0..054ac5c 100644 --- a/geodepy/statistics.py +++ b/geodepy/statistics.py @@ -12,13 +12,15 @@ def rotation_matrix(lat, lon): :param lat: latitude in decimal degrees :param lon: longitude in decimal degrees - :return: rot_matrix (3x3) + :return: rotation matrix (3x3) """ (rlat, rlon) = (radians(lat), radians(lon)) rot_matrix = np.array( - [[-sin(rlon), -sin(rlat) * cos(rlon), cos(rlat) * cos(rlon)], - [cos(rlon), -sin(rlat) * sin(rlon), cos(rlat) * sin(rlon)], - [0.0, cos(rlat), sin(rlat)]] + [ + [-sin(rlon), -sin(rlat) * cos(rlon), cos(rlat) * cos(rlon)], + [cos(rlon), -sin(rlat) * sin(rlon), cos(rlat) * sin(rlon)], + [0.0, cos(rlat), sin(rlat)], + ] ) return rot_matrix @@ -33,26 +35,29 @@ def vcv_cart2local(vcv_cart, lat, lon): :param vcv_cart: a VCV in the Cartesian reference frame (3x1 or 3x3) :param lat: latitude in decimal degrees :param lon: longitude in decimal degrees - :return: vcv_local (3x1 or 3x3) + :return: vcv array (3x1 or 3x3) """ column_vector = False if vcv_cart.shape[0] == 3: if vcv_cart.shape[1] == 1: column_vector = True - vcv_cart = np.array([[vcv_cart[0, 0], 0.0, 0.0], - [0.0, vcv_cart[1, 0], 0.0], - [0.0, 0.0, vcv_cart[2, 0]]]) + vcv_cart = np.array( + [ + [vcv_cart[0, 0], 0.0, 0.0], + [0.0, vcv_cart[1, 0], 0.0], + [0.0, 0.0, vcv_cart[2, 0]], + ] + ) elif vcv_cart.shape[1] == 3: pass else: - raise ValueError('Matrix must be either 3x1 or 3x3') + raise ValueError("Matrix must be either 3x1 or 3x3") else: - raise ValueError('Matrix must be either 3x1 or 3x3') + raise ValueError("Matrix must be either 3x1 or 3x3") rot_matrix = rotation_matrix(lat, lon) vcv_local = rot_matrix.transpose() @ vcv_cart @ rot_matrix if column_vector: - vcv_local = np.array([[vcv_local[0, 0]], [vcv_local[1, 1]], - [vcv_local[2, 2]]]) + vcv_local = np.array([[vcv_local[0, 0]], [vcv_local[1, 1]], [vcv_local[2, 2]]]) return vcv_local @@ -66,26 +71,29 @@ def vcv_local2cart(vcv_local, lat, lon): :param vcv_local: a VCV in the local reference frame (3x1 or 3x3) :param lat: latitude in decimal degrees :param lon: longitude in decimal degrees - :return: vcv_cart (3x1 or 3x3) + :return: vcv array (3x1 or 3x3) """ column_vector = False if vcv_local.shape[0] == 3: if vcv_local.shape[1] == 1: column_vector = True - vcv_local = np.array([[vcv_local[0, 0], 0.0, 0.0], - [0.0, vcv_local[1, 0], 0.0], - [0.0, 0.0, vcv_local[2, 0]]]) + vcv_local = np.array( + [ + [vcv_local[0, 0], 0.0, 0.0], + [0.0, vcv_local[1, 0], 0.0], + [0.0, 0.0, vcv_local[2, 0]], + ] + ) elif vcv_local.shape[1] == 3: pass else: - raise ValueError('Matrix must be either 3x1 or 3x3') + raise ValueError("Matrix must be either 3x1 or 3x3") else: - raise ValueError('Matrix must be either 3x1 or 3x3') + raise ValueError("Matrix must be either 3x1 or 3x3") rot_matrix = rotation_matrix(lat, lon) vcv_cart = rot_matrix @ vcv_local @ rot_matrix.transpose() if column_vector: - vcv_cart = np.array([[vcv_cart[0, 0]], [vcv_cart[1, 1]], - [vcv_cart[2, 2]]]) + vcv_cart = np.array([[vcv_cart[0, 0]], [vcv_cart[1, 1]], [vcv_cart[2, 2]]]) return vcv_cart @@ -98,13 +106,12 @@ def error_ellipse(vcv): :param vcv: a VCV (3x3) :return: a, semi-major axis :return: b, semi-minor axis - :return: orientation, the orientation of the error ellipse + :return: The orientation of the error ellipse """ - z = sqrt((vcv[0, 0] - vcv[1, 1])**2 + 4 * vcv[0, 1]**2) + z = sqrt((vcv[0, 0] - vcv[1, 1]) ** 2 + 4 * vcv[0, 1] ** 2) a = sqrt(0.5 * (vcv[0, 0] + vcv[1, 1] + z)) b = sqrt(0.5 * (vcv[0, 0] + vcv[1, 1] - z)) - orientation = 90 - degrees(0.5 * atan2((2 * vcv[0, 1]), - (vcv[0, 0] - vcv[1, 1]))) + orientation = 90 - degrees(0.5 * atan2((2 * vcv[0, 1]), (vcv[0, 0] - vcv[1, 1]))) return a, b, orientation @@ -112,9 +119,10 @@ def error_ellipse(vcv): def relative_error(lat, lon, var1, var2, cov12): """ Function to compute relative error between two 3D stations: - - 2D relative error ellipse [semi-major axis, semi-minor axis, bearing] - - 1D relative 'up' error - + + * 2D relative error ellipse [semi-major axis, semi-minor axis, bearing] + * 1D relative 'up' error + Adapted from Harvey B.R. (1998) Practical least squares and statistics for surveyors, Monograph 13, Section 4.4, p.135 @@ -136,7 +144,7 @@ def relative_error(lat, lon, var1, var2, cov12): # diagonal terms rel_var[0, 0] = var1_enu[0, 0] + var2_enu[0, 0] - 2 * cov12_enu[0, 0] - rel_var[1, 1] = var1_enu[1, 1] + var2_enu[1, 1] - 2 * cov12_enu[1, 1] + rel_var[1, 1] = var1_enu[1, 1] + var2_enu[1, 1] - 2 * cov12_enu[1, 1] rel_var[2, 2] = var1_enu[2, 2] + var2_enu[2, 2] - 2 * cov12_enu[2, 2] # east-north covariance @@ -166,7 +174,7 @@ def circ_hz_pu(a, b): :param a: semi-major axis :param b: semi-minor axis - :return: r the radius of the circularised error + :return: Radius of the circularised error """ q0 = 1.960790 q1 = 0.004071 @@ -181,19 +189,22 @@ def circ_hz_pu(a, b): def k_val95(dof): """ - Returns the Coverage Factor k for a given 1 sigma (68.27%) Standard - Deviation to allow conversion to a 95% Standard Deviation. This + Returns the Coverage Factor k for a given 1 sigma (68.27%) standard + deviation to allow conversion to a 95% standard deviation. This uses a simplified table of k values rounded to 5 decimal places for Degrees of Freedom (DOF) in the range 1 to 120. For DOF above 120, returns k value of 1.96 and for DOF below 1, returns k value - for DOF = 1. Coverage Factor produced using following scipy stats - code: stats.t.ppf(1-0.025,dof) + for DOF = 1. Coverage Factor produced using following scipy stats: + + .. code:: python + + stats.t.ppf(1-0.025,dof) :param dof: Degrees of Freedom (Number of Measurements Minus 1) :return: Coverage Factor k """ if not isinstance(dof, int): - raise TypeError('Degrees of Freedom must be Int') + raise TypeError("Degrees of Freedom must be Int") if dof < 1: return ttable_p95[0] elif dof > 120: @@ -202,23 +213,125 @@ def k_val95(dof): return ttable_p95[dof - 1] -ttable_p95 = ([12.7062, 4.30265, 3.18245, 2.77645, 2.57058, 2.44691, - 2.36462, 2.30600, 2.26216, 2.22814, 2.20099, 2.17881, - 2.16037, 2.14479, 2.13145, 2.11991, 2.10982, 2.10092, - 2.09302, 2.08596, 2.07961, 2.07387, 2.06866, 2.06390, - 2.05954, 2.05553, 2.05183, 2.04841, 2.04523, 2.04227, - 2.03951, 2.03693, 2.03452, 2.03224, 2.03011, 2.02809, - 2.02619, 2.02439, 2.02269, 2.02108, 2.01954, 2.01808, - 2.01669, 2.01537, 2.01410, 2.01290, 2.01174, 2.01063, - 2.00958, 2.00856, 2.00758, 2.00665, 2.00575, 2.00488, - 2.00404, 2.00324, 2.00247, 2.00172, 2.00100, 2.00030, - 1.99962, 1.99897, 1.99834, 1.99773, 1.99714, 1.99656, - 1.99601, 1.99547, 1.99495, 1.99444, 1.99394, 1.99346, - 1.99300, 1.99254, 1.99210, 1.99167, 1.99125, 1.99085, - 1.99045, 1.99006, 1.98969, 1.98932, 1.98896, 1.98861, - 1.98827, 1.98793, 1.98761, 1.98729, 1.98698, 1.98667, - 1.98638, 1.98609, 1.98580, 1.98552, 1.98525, 1.98498, - 1.98472, 1.98447, 1.98422, 1.98397, 1.98373, 1.98350, - 1.98326, 1.98304, 1.98282, 1.98260, 1.98238, 1.98217, - 1.98197, 1.98177, 1.98157, 1.98137, 1.98118, 1.98099, - 1.98081, 1.98063, 1.98045, 1.98027, 1.98010, 1.97993]) +ttable_p95 = [ + 12.7062, + 4.30265, + 3.18245, + 2.77645, + 2.57058, + 2.44691, + 2.36462, + 2.30600, + 2.26216, + 2.22814, + 2.20099, + 2.17881, + 2.16037, + 2.14479, + 2.13145, + 2.11991, + 2.10982, + 2.10092, + 2.09302, + 2.08596, + 2.07961, + 2.07387, + 2.06866, + 2.06390, + 2.05954, + 2.05553, + 2.05183, + 2.04841, + 2.04523, + 2.04227, + 2.03951, + 2.03693, + 2.03452, + 2.03224, + 2.03011, + 2.02809, + 2.02619, + 2.02439, + 2.02269, + 2.02108, + 2.01954, + 2.01808, + 2.01669, + 2.01537, + 2.01410, + 2.01290, + 2.01174, + 2.01063, + 2.00958, + 2.00856, + 2.00758, + 2.00665, + 2.00575, + 2.00488, + 2.00404, + 2.00324, + 2.00247, + 2.00172, + 2.00100, + 2.00030, + 1.99962, + 1.99897, + 1.99834, + 1.99773, + 1.99714, + 1.99656, + 1.99601, + 1.99547, + 1.99495, + 1.99444, + 1.99394, + 1.99346, + 1.99300, + 1.99254, + 1.99210, + 1.99167, + 1.99125, + 1.99085, + 1.99045, + 1.99006, + 1.98969, + 1.98932, + 1.98896, + 1.98861, + 1.98827, + 1.98793, + 1.98761, + 1.98729, + 1.98698, + 1.98667, + 1.98638, + 1.98609, + 1.98580, + 1.98552, + 1.98525, + 1.98498, + 1.98472, + 1.98447, + 1.98422, + 1.98397, + 1.98373, + 1.98350, + 1.98326, + 1.98304, + 1.98282, + 1.98260, + 1.98238, + 1.98217, + 1.98197, + 1.98177, + 1.98157, + 1.98137, + 1.98118, + 1.98099, + 1.98081, + 1.98063, + 1.98045, + 1.98027, + 1.98010, + 1.97993, +] diff --git a/geodepy/survey.py b/geodepy/survey.py index 0dad709..120bd01 100644 --- a/geodepy/survey.py +++ b/geodepy/survey.py @@ -14,127 +14,142 @@ def first_vel_params(wavelength, frequency, n_REF=None, unit_length=None): """ Calculates the constant First Velocity Correction Parameters C and D for a given set of standard instrument settings + + Reference Rueger, J.M., 2012, Electronic Distance Measurement: An Introduction, 4rd edition, Springer, Berlin + :param wavelength: Instrument Carrier Wavelength (Micrometers) - Mandatory :param frequency: Instrument modulation frequency (Hz) - Optional :param n_REF: manufacturers reference refractive index - Recommended :param unit_length: unit length of instrument - Optional :return: First Velocity Correction Parameters C and D - Reference Rueger, J.M., 2012, Electronic Distance Measurement – An Introduction, 4rd edition, Springer, Berlin + """ - - if not n_REF : + + if not n_REF: if not unit_length: - raise ValueError('Error - n_REF and unit_length cannot both be None') + raise ValueError("Error - n_REF and unit_length cannot both be None") if not frequency: - raise ValueError('Error - n_REF and frequency cannot both be None') + raise ValueError("Error - n_REF and frequency cannot both be None") # Rueger eq 6.3 n_REF = 299792458 / (2 * unit_length * frequency) - + # Rueger eq 6.12 - param_c = (n_REF-1)*10**6 - + param_c = (n_REF - 1) * 10**6 + # Rueger eq 5.12 # https://office.iag-aig.org/doc/5d7b8fda0c032.pdf Resolution 3 - nG_1_10_6 = 287.6155 + (4.8866 / wavelength ** 2) + (0.068 / wavelength ** 4) + nG_1_10_6 = 287.6155 + (4.8866 / wavelength**2) + (0.068 / wavelength**4) # Rueger eq 6.13 param_d = (273.15 / 1013.25) * nG_1_10_6 - + return param_c, param_d def part_h2o_vap_press(dry_temp, pressure, rel_humidity=None, wet_temp=None): """ + Reference Rueger, J.M., 2012, Electronic Distance Measurement: An Introduction, 4rd edition, Springer, Berlin + :param dry_temp: Observed Dry Temperature (degrees Celsius) :param pressure: Observed Pressure (hectopascals or millibars) :param rel_humidity: Observed Relative Humidity (percentage) - Optional if wet_temp supplied :param wet_temp: Observed Wet Temperature (degrees Celsius) - Optional if rel_humidity supplied - Reference Rueger, J.M., 2012, Electronic Distance Measurement – An Introduction, 4rd edition, Springer, Berlin + :return: Partial water vapour pressure + """ - + if not rel_humidity and not wet_temp: - raise ValueError('Error - rel_humidity and wet_temp cannot both be None') - + raise ValueError("Error - rel_humidity and wet_temp cannot both be None") + if rel_humidity: wet_temp = dry_temp # Rueger eq 5.27 - E_w = ((1.0007 + ((3.46 * pressure) * (10**-6))) - * 6.1121 * exp((17.502 * wet_temp) / (240.94 + wet_temp))) - + E_w = ( + (1.0007 + ((3.46 * pressure) * (10**-6))) + * 6.1121 + * exp((17.502 * wet_temp) / (240.94 + wet_temp)) + ) + if rel_humidity: # Rueger eq 5.29 - e = (E_w*rel_humidity)/100 + e = (E_w * rel_humidity) / 100 else: e = E_w - 0.000662 * pressure * (dry_temp - wet_temp) - + return e - -def first_vel_corrn(dist, first_vel_param, temp, pressure, - rel_humidity=None, wet_temp=None, CO2_ppm=None, - wavelength=None): + +def first_vel_corrn( + dist, + first_vel_param, + temp, + pressure, + rel_humidity=None, + wet_temp=None, + CO2_ppm=None, + wavelength=None, +): """ Carries out First Velocity Correction of Electronic Distance Measurement, given correction parameters and atmospheric observations + :param dist: Uncorrected Observed Slope Distance :param first_vel_param: Tuple of First Velocity Parameters C and D (see function first_vel_params) :param temp: Observed Temperature (degrees Celsius) :param pressure: Observed Pressure (hectopascals or millibars) :param rel_humidity: Observed Relative Humidity (percentage) - :return: Slope Distance with First Velocity Correction applied + :return: First Velocity Correction """ param_c = first_vel_param[0] - if not CO2_ppm: + if not CO2_ppm: param_d = first_vel_param[1] e = part_h2o_vap_press(temp, pressure, rel_humidity, wet_temp) - + # Rueger eq 6.11 - t_273_15 = temp+273.15 - first_vel_corrn_ppm = (param_c- - ((param_d*pressure)/t_273_15)+ - ((11.27*e)/t_273_15)) - first_vel_corrn_metres = dist * first_vel_corrn_ppm * (10 ** -6) - + t_273_15 = temp + 273.15 + first_vel_corrn_ppm = ( + param_c - ((param_d * pressure) / t_273_15) + ((11.27 * e) / t_273_15) + ) + first_vel_corrn_metres = dist * first_vel_corrn_ppm * (10**-6) + else: if all([temp, pressure, rel_humidity, wavelength]): e = humidity2part_water_vapour_press(rel_humidity, temp) - NPROPG_1 = group_refractivity( - wavelength, temp, pressure, e, CO2_ppm) + NPROPG_1 = group_refractivity(wavelength, temp, pressure, e, CO2_ppm) else: - raise ValueError('Error - temp, pressure, rel_humidity and wavelength '+ - 'are all mandatory when CO2_ppm is not None') - + raise ValueError( + "Error - temp, pressure, rel_humidity and wavelength " + + "are all mandatory when CO2_ppm is not None" + ) + n_ref = 1 + (param_c / 1.0e6) n_g = 1 + (NPROPG_1 / 1.0e8) - first_vel_corrn_metres = ((n_ref / n_g)-1) * dist - + first_vel_corrn_metres = ((n_ref / n_g) - 1) * dist + return first_vel_corrn_metres -def mets_partial_differentials(group_ref_Index=1.00028, - temp=15, - pressure=1013.25, - rel_humidity=60): - """ Returns the sensitivity coefficients for temp, pressure and humidity in ppm +def mets_partial_differentials( + group_ref_Index=1.00028, temp=15, pressure=1013.25, rel_humidity=60 +): + """ + Calculates the sensitivity coefficients for temp, pressure and humidity in ppm + :param group_ref_Index: manufacturers group refractive index of light :param temp: Observed Dry Temperature (degrees Celsius) :param pressure: Observed Pressure (hectopascals or millibars) :param rel_humidity: Observed Relative Humidity (percentage) - Optional if wet_temp supplied + :return: Sensitivity Coefficients K, L and M in ppm """ ng_1 = group_ref_Index - 1 t_273_15 = temp + 273.15 e = part_h2o_vap_press(temp, pressure, rel_humidity) - - param_k = (((((ng_1 * 273.15 * pressure) - /1013.25) - -(11.27 * e * 10**-6)) - /t_273_15**2) - *(10**6)) - param_l = (0.26957809 * (ng_1 / t_273_15) - *(10**6)) - param_m = (((11.27 * 10**-6) - /t_273_15) - *(10**6)) - + + param_k = ( + (((ng_1 * 273.15 * pressure) / 1013.25) - (11.27 * e * 10**-6)) / t_273_15**2 + ) * (10**6) + param_l = 0.26957809 * (ng_1 / t_273_15) * (10**6) + param_m = ((11.27 * 10**-6) / t_273_15) * (10**6) + return param_k, param_l, param_m @@ -143,13 +158,14 @@ def precise_inst_ht(vert_list, spacing, offset): Uses a set of Vertical Angle Observations taken to a levelling staff at regular intervals to determine the height of the instrument above a reference mark + :param vert_list: List of Vertical (Zenith) Angle Observations (minimum of 3) in Decimal Degrees format :param spacing: Distance in metres between each vertical angle observation :param offset: Lowest observed height above reference mark :return: Instrument Height above reference mark and its standard deviation """ if len(vert_list) < 3: - raise ValueError('ValueError: 3 or more vertical angles required') + raise ValueError("ValueError: 3 or more vertical angles required") vert_list.sort(reverse=True) vert_pairs = [(va1, va2) for va1, va2 in zip(vert_list, vert_list[1:])] base_ht = [] @@ -164,10 +180,30 @@ def precise_inst_ht(vert_list, spacing, offset): def joins(east1, north1, east2, north2): + """ + Calculates the bearing and distance from point 1 to point 2 + + :param east1: Easting of Point 1 + :param north1: Northing of Point 1 + :param east2: Easting of Point 2 + :param north2: Northing of Point 2 + :return: Distance and Bearing from Point 1 to Point 2 + """ return rect2polar(east2 - east1, north2 - north1) def radiations(east1, north1, brg1to2, dist, rotation=0, psf=1): + """ + Calculates the coordinates of Point 2 given Point 1, bearing and distance + + :param east1: Easting of Point 1 + :param north1: Northing of Point 1 + :param brg1to2: Bearing from Point 1 to Point 2 (decimal degrees) + :param dist: Distance from Point 1 to Point 2 (metres) + :param rotation: Rotation to be applied to bearing (decimal degrees) - Optional + :param psf: Prism Scale Factor to be applied to distance - Optional + :return: Easting and Northing of Point 2 + """ delta_east, delta_north = polar2rect(dist * psf, brg1to2 + rotation) return east1 + delta_east, north1 + delta_north @@ -185,11 +221,11 @@ def va_conv(zenith_angle, slope_dist, height_inst=0, height_tgt=0): :param height_inst: Height of Instrument. Optional - Default Value of 0m :param height_tgt: Height of Target. Optional - Default Value of 0m - :return: vert_angle_pt: Vertical Angle between Ground Points, expressed - in decimal degrees - :return: slope_dist_pt: Slope Distance between Ground Points in metres - :return: hz_dist: Horizontal Distance - :return: delta_ht: Change in height between Ground Points in metres + :return: + - vert_angle_pt - Vertical Angle between Ground Points, expressed in decimal degrees + - slope_dist_pt - Slope Distance between Ground Points in metres + - hz_dist - Horizontal Distance + - delta_ht - Change in height between Ground Points in metres """ # Convert Zenith Angle to Vertical Angle try: @@ -202,17 +238,18 @@ def va_conv(zenith_angle, slope_dist, height_inst=0, height_tgt=0): else: raise ValueError except ValueError: - raise ValueError('ValueError: Vertical Angle Invalid') + raise ValueError("ValueError: Vertical Angle Invalid") # Calculate Horizontal Dist and Delta Height hz_dist = slope_dist * cos(zenith_angle) delta_ht = slope_dist * sin(zenith_angle) # Account for Target and Instrument Heights delta_ht = height_inst + delta_ht - height_tgt - slope_dist_pt = sqrt(delta_ht ** 2 + hz_dist ** 2) + slope_dist_pt = sqrt(delta_ht**2 + hz_dist**2) vert_angle_pt = atan(delta_ht / hz_dist) vert_angle_pt = degrees(vert_angle_pt) return vert_angle_pt, slope_dist_pt, hz_dist, delta_ht + """ The following has been translated from UNSW FORTRAN code """ @@ -295,6 +332,9 @@ def va_conv(zenith_angle, slope_dist, height_inst=0, height_tgt=0): def refractivity_constants(): + """ + :return: Refractivity constants used in the refractivity calculations. + """ # PECK & REEDER (1972) AS AMMENDED BY CIDDOR # (DRY AIR REFRACTIVITY) (K0, K1, K2, K3) = (238.0185, 5792105.0, 57.362, 167917.0) @@ -307,8 +347,16 @@ def refractivity_constants(): # COMPRESSIBILITY (DAVIES 1992) (A0, A1, A2, B0, B1, C0, C1, DLC, E) = ( - 1.58123e-6, -2.9331e-8, 1.1043e-10, 5.707e-6, -2.051e-8, - 1.9898e-4, -2.376e-6, 1.83e-11, -0.765e-8) + 1.58123e-6, + -2.9331e-8, + 1.1043e-10, + 5.707e-6, + -2.051e-8, + 1.9898e-4, + -2.376e-6, + 1.83e-11, + -0.765e-8, + ) # CIDDOR 1995 AND DAVIES 1992. NEW CF ON 28/06/95 (CF, R, MV) = (1.022, 8.314510, 0.018015) @@ -316,41 +364,46 @@ def refractivity_constants(): # STANDARD CONDITION DRY AIR (PECK & REEDER 1972) (TCSTDAIR, TKSTDAIR, PSTDAIR) = (15.0, 288.15, 101325.0) (TCSTDWV, TKSTDWV, PSTDWV) = (20.0, 293.15, 1333.0) - - return ((K0, K1, K2, K3), - (W0, W1, W2, W3), - (ALPHA, BETA, GAMMA), - (A0, A1, A2, B0, B1, C0, C1, DLC, E), - (CF, R, MV), - (TCSTDAIR, TKSTDAIR, PSTDAIR), - (TCSTDWV, TKSTDWV, PSTDWV)) - + + return ( + (K0, K1, K2, K3), + (W0, W1, W2, W3), + (ALPHA, BETA, GAMMA), + (A0, A1, A2, B0, B1, C0, C1, DLC, E), + (CF, R, MV), + (TCSTDAIR, TKSTDAIR, PSTDAIR), + (TCSTDWV, TKSTDWV, PSTDWV), + ) + def phase_refractivity(LAMDA, TC, P, PV, XC=420): """ - Input - LAMDA = wavelength (micrometre) - TC = temperature (degrees Celcius) - P = pressure (hPa) - PV = partial water vapour pressure (Pa) - XC = carbon dioxide content (ppm) - Return - NPROPP_1 = phase refractivity = (NPROPphase-1)10E8 - """ - - ((K0, K1, K2, K3), - (W0, W1, W2, W3), - (ALPHA, BETA, GAMMA), - (A0, A1, A2, B0, B1, C0, C1, DLC, E), - (CF, R, MV), - (TCSTDAIR, TKSTDAIR, PSTDAIR), - (TCSTDWV, TKSTDWV, PSTDWV)) = refractivity_constants() + Calculates the phase refractivity of moist air using Ciddor's equations + + :param LAMDA: wavelength (micrometre) + :param TC: temperature (degrees Celcius) + :param P: pressure (hPa) + :param PV: partial water vapour pressure (Pa) + :param XC: carbon dioxide content (ppm) + :return: NPROPP_1 - phase refractivity = (NPROPphase-1)10E8 + """ + + ( + (K0, K1, K2, K3), + (W0, W1, W2, W3), + (ALPHA, BETA, GAMMA), + (A0, A1, A2, B0, B1, C0, C1, DLC, E), + (CF, R, MV), + (TCSTDAIR, TKSTDAIR, PSTDAIR), + (TCSTDWV, TKSTDWV, PSTDWV), + ) = refractivity_constants() + # CONVERT LAMDA TO SIGMA, THE PRESSURE UNITS TO PASCAL AND CREATE TC AND PP SIGMA = 1.0 / LAMDA # CONVERSION FROM hPa TO Pa AND C TO K - P = P * 100.0 + P = P * 100.0 PV = PV * 100.0 TK = TC + 273.15 @@ -358,14 +411,12 @@ def phase_refractivity(LAMDA, TC, P, PV, XC=420): TEMP1 = SIGMA * SIGMA # CIDDOR EQ.(1) - NAS_1 = (K1 / (K0 - TEMP1)) + (K3 / (K2 - TEMP1)) + NAS_1 = (K1 / (K0 - TEMP1)) + (K3 / (K2 - TEMP1)) # CIDDOR EQ.(2) FOR AMBIENT C02 CONTENT (XC) NASX_1 = NAS_1 * (1 + 0.534e-6 * (XC - 450.0)) - STD_AIR = { - '(Nas-1)10E8': NAS_1, - '(Nasx-1)10E8':NASX_1} - + STD_AIR = {"(Nas-1)10E8": NAS_1, "(Nasx-1)10E8": NASX_1} + # USING EQ. 2, CALCULATE REFRACTIVITY OF WATER VAPOUR AT STANDARD CONDITIONS # (TC=20, P=1333) TEMP2 = TEMP1 * TEMP1 @@ -373,112 +424,120 @@ def phase_refractivity(LAMDA, TC, P, PV, XC=420): # FOLLOWING OWENS (1967) AS AMENDED TO FIT BIRCH & DOWNS (1988) NWS_1 = CF * (W0 + W1 * TEMP1 + W2 * TEMP2 + W3 * TEMP3) - STD_WATER_VAPOUR = { - '(Nws-1)10E8': NWS_1} - + STD_WATER_VAPOUR = {"(Nws-1)10E8": NWS_1} + # CALCULATE THE DENSITY OF STANDARD AIR (XV=0, TC=15, P=101325) # EQ.(2) DAVIS (1992) XC = MOLECULAR FRACTION OF C02 # XC IN CIDDOR'S FORMULA IS IN PPM, CONVERTED TO A FRACTION - MA = (28.9635 + 12.011e-6 * (XC - 400.0)) * 1.0e-3 + MA = (28.9635 + 12.011e-6 * (XC - 400.0)) * 1.0e-3 # USING CIDDOR EQ.(A1) WITH XV = 0 : DRY AIR COMPRESSIBILITY - ZA = (1.0 - (PSTDAIR / TKSTDAIR) * (A0 + (A1 * TCSTDAIR) + - (A2 * TCSTDAIR * TCSTDAIR)) + ((PSTDAIR / TKSTDAIR) * - (PSTDAIR / TKSTDAIR) * DLC)) + ZA = ( + 1.0 + - (PSTDAIR / TKSTDAIR) * (A0 + (A1 * TCSTDAIR) + (A2 * TCSTDAIR * TCSTDAIR)) + + ((PSTDAIR / TKSTDAIR) * (PSTDAIR / TKSTDAIR) * DLC) + ) # USING EQ. 3 FOR DRY AIR RHOASX = (PSTDAIR * MA) / (ZA * R * TKSTDAIR) - DENSITY_OF_STD_AIR = { - 'Ma': MA, - 'Z_(DRY AIR)': ZA, - 'RHOasx': RHOASX} - + DENSITY_OF_STD_AIR = {"Ma": MA, "Z_(DRY AIR)": ZA, "RHOasx": RHOASX} + # CALCULATE THE DENSITY OF WATER VAPOUR AT STANDARD CONDITIONS # (XV=1, TC=20, P=1333) # USING CIDDOR EQUATION A1 WITH XV = 1 AND PARTIAL # WATER VAPOUR PRESSURE - ZWV = (1.0 - (PSTDWV / TKSTDWV) * (A0 + (A1 * TCSTDWV) + - (A2 * TCSTDWV * TCSTDWV) + (B0 + B1 * TCSTDWV) + - (C0 + C1 * TCSTDWV)) + ((PSTDWV / TKSTDWV) * - (PSTDWV / TKSTDWV) * (DLC + E))) + ZWV = ( + 1.0 + - (PSTDWV / TKSTDWV) + * ( + A0 + + (A1 * TCSTDWV) + + (A2 * TCSTDWV * TCSTDWV) + + (B0 + B1 * TCSTDWV) + + (C0 + C1 * TCSTDWV) + ) + + ((PSTDWV / TKSTDWV) * (PSTDWV / TKSTDWV) * (DLC + E)) + ) # USING EQ. 3 RHOWS = (PSTDWV * MV) / (ZWV * R * TKSTDWV) - DENSITY_OF_STD_WATER_VAPOUR = { - 'Z_(WATER VAPOUR)': ZWV, - 'RHOws':RHOWS} - + DENSITY_OF_STD_WATER_VAPOUR = {"Z_(WATER VAPOUR)": ZWV, "RHOws": RHOWS} + # CALCULATE THE DENSITY OF DRY AMBIENT AIR (TC= USER, P=USER) F = ALPHA + (BETA * P) + (GAMMA * TC * TC) XV = F * PV / P # USING CIDDOR EQUATION A1 - Z = (1.0 - (P / TK) * (A0 + (A1 * TC) + (A2 * TC * TC) + - ((B0 + B1 * TC) * XV) + ((C0 + C1 * TC) * XV * XV)) + - ((P / TK) * (P / TK) * (DLC + (E * XV * XV)))) + Z = ( + 1.0 + - (P / TK) + * ( + A0 + + (A1 * TC) + + (A2 * TC * TC) + + ((B0 + B1 * TC) * XV) + + ((C0 + C1 * TC) * XV * XV) + ) + + ((P / TK) * (P / TK) * (DLC + (E * XV * XV))) + ) RHOA = (P * MA * (1.0 - XV)) / (Z * R * TK) - DENSITY_OF_DRY_AMBIENT_AIR = { - 'f': F, - 'Xv': XV, - 'Z_(AMB. AIR)': Z, - 'RHOa':RHOA} - + DENSITY_OF_DRY_AMBIENT_AIR = {"f": F, "Xv": XV, "Z_(AMB. AIR)": Z, "RHOa": RHOA} + # CALCULATE THE DENSITY OF AMBIENT WATER VAPOUR # USING EQ. 3 RHOW = (P * MV * XV) / (Z * R * TK) - + # CALCULATE THE PHASE REFRACTIVITY OF MOIST AIR FROM CIDDOR EQ.(4) TEMP1 = RHOA / RHOASX TEMP2 = RHOW / RHOWS - DENSITY_OF_AMBIENT_WATER_VAPOUR = { - 'RHOw' : RHOW, - 'Da' : TEMP1, - 'Dv' : TEMP2} - + DENSITY_OF_AMBIENT_WATER_VAPOUR = {"RHOw": RHOW, "Da": TEMP1, "Dv": TEMP2} + TEMP1 = TEMP1 * NASX_1 TEMP2 = TEMP2 * NWS_1 - - DENSITY_OF_AMBIENT_WATER_VAPOUR['DaNasx'] = TEMP1 - DENSITY_OF_AMBIENT_WATER_VAPOUR['DvNvs'] = TEMP2 - + + DENSITY_OF_AMBIENT_WATER_VAPOUR["DaNasx"] = TEMP1 + DENSITY_OF_AMBIENT_WATER_VAPOUR["DvNvs"] = TEMP2 + NPROPP_1 = TEMP1 + TEMP2 - - REFRACTIVITY_OF_MOIST_AIR = { - '(NpropP-1)10E8': NPROPP_1} - + + REFRACTIVITY_OF_MOIST_AIR = {"(NpropP-1)10E8": NPROPP_1} + return NPROPP_1 def group_refractivity(LAMDA, TC, P, PV, XC=420): """ - Input - LAMDA = wavelength (micrometre) - TC = temperature (degrees Celcius) - P = pressure (hPa) - PV = partial water vapour pressure (Pa) - XC = carbon dioxide content (ppm) - Return - NPROPG_1 = group refractivity = (NPROPgroup-1)10E8 - """ + Calculates the group refractivity of moist air using Ciddor's equations + + :param LAMDA: wavelength (micrometre) + :param TC: temperature (degrees Celcius) + :param P: pressure (hPa) + :param PV: partial water vapour pressure (Pa) + :param XC: carbon dioxide content (ppm) - ((K0, K1, K2, K3), - (W0, W1, W2, W3), - (ALPHA, BETA, GAMMA), - (A0, A1, A2, B0, B1, C0, C1, DLC, E), - (CF, R, MV), - (TCSTDAIR, TKSTDAIR, PSTDAIR), - (TCSTDWV, TKSTDWV, PSTDWV)) = refractivity_constants() + :return: NPROPG_1 - group refractivity = (NPROPgroup-1)10E8 + """ + + ( + (K0, K1, K2, K3), + (W0, W1, W2, W3), + (ALPHA, BETA, GAMMA), + (A0, A1, A2, B0, B1, C0, C1, DLC, E), + (CF, R, MV), + (TCSTDAIR, TKSTDAIR, PSTDAIR), + (TCSTDWV, TKSTDWV, PSTDWV), + ) = refractivity_constants() # CONVERT LAMDA TO SIGMA, THE PRESSURE UNITS TO PASCAL AND CREATE TC AND PP SIGMA = 1.0 / LAMDA # CONVERSION FROM hPa TO Pa AND C TO K - P = P * 100.0 + P = P * 100.0 PV = PV * 100.0 TK = TC + 273.15 @@ -486,14 +545,13 @@ def group_refractivity(LAMDA, TC, P, PV, XC=420): TEMP1 = SIGMA * SIGMA # CIDDOR EQ.(9) - NGAS_1 = (K1 * ((K0 + TEMP1) / ((K0 - TEMP1) * (K0 - TEMP1))) + - K3 * ((K2 + TEMP1) / ((K2 - TEMP1) * (K2 - TEMP1)))) + NGAS_1 = K1 * ((K0 + TEMP1) / ((K0 - TEMP1) * (K0 - TEMP1))) + K3 * ( + (K2 + TEMP1) / ((K2 - TEMP1) * (K2 - TEMP1)) + ) # CIDDOR EQ.(2) FOR AMBIENT C02 CONTENT (XC) - NGASX_1 = NGAS_1 * (1 + 0.534e-6 * (XC - 450.0)) - STD_AIR = { - '(Ngas-1)10E8': NGAS_1, - '(Ngasx-1)10E8':NGASX_1} + NGASX_1 = NGAS_1 * (1 + 0.534e-6 * (XC - 450.0)) + STD_AIR = {"(Ngas-1)10E8": NGAS_1, "(Ngasx-1)10E8": NGASX_1} # USING EQ. 2, CALCULATE REFRACTIVITY OF WATER VAPOUR AT STANDARD CONDITIONS # (TC=20, P=1333) @@ -501,62 +559,70 @@ def group_refractivity(LAMDA, TC, P, PV, XC=420): TEMP3 = TEMP1 * TEMP2 # FOLLOWING OWENS (1967) AS AMENDED TO FIT BIRCH & DOWNS (1988) - NGWS_1 = (CF * (W0 + 3.0 * W1 * TEMP1 + 5.0 * W2 * TEMP2 + - 7.0 * W3 * TEMP3)) - STD_WATER_VAPOUR = { - '(Ngws-1)10E8': NGWS_1} - + NGWS_1 = CF * (W0 + 3.0 * W1 * TEMP1 + 5.0 * W2 * TEMP2 + 7.0 * W3 * TEMP3) + STD_WATER_VAPOUR = {"(Ngws-1)10E8": NGWS_1} + # CALCULATE THE DENSITY OF STANDARD AIR (XV=0, TC=15, P=101325) # EQ.(2) DAVIS (1992) XC = MOLECULAR FRACTION OF C02 # XC IN CIDDOR'S FORMULA IS IN PPM, CONVERTED TO A FRACTION - MA = (28.9635 + 12.011e-6 * (XC - 400.0)) * 1.0e-3 + MA = (28.9635 + 12.011e-6 * (XC - 400.0)) * 1.0e-3 # USING CIDDOR EQ.(A1) WITH XV = 0 : DRY AIR COMPRESSIBILITY - ZA = (1.0 - (PSTDAIR / TKSTDAIR) * (A0 + (A1 * TCSTDAIR) + - (A2 * TCSTDAIR * TCSTDAIR)) + ((PSTDAIR / TKSTDAIR) * - (PSTDAIR / TKSTDAIR) * DLC)) + ZA = ( + 1.0 + - (PSTDAIR / TKSTDAIR) * (A0 + (A1 * TCSTDAIR) + (A2 * TCSTDAIR * TCSTDAIR)) + + ((PSTDAIR / TKSTDAIR) * (PSTDAIR / TKSTDAIR) * DLC) + ) # USING EQ. 3 FOR DRY AIR RHOASX = (PSTDAIR * MA) / (ZA * R * TKSTDAIR) - DENSITY_OF_STD_AIR = { - 'Ma': MA, - 'Z_(DRY AIR)': ZA, - 'RHOasx': RHOASX} + DENSITY_OF_STD_AIR = {"Ma": MA, "Z_(DRY AIR)": ZA, "RHOasx": RHOASX} # CALCULATE THE DENSITY OF WATER VAPOUR AT STANDARD CONDITIONS # (XV=1, TC=20, P=1333) - + # USING CIDDOR EQUATION A1 WITH XV = 1 AND PARTIAL # WATER VAPOUR PRESSURE - ZWV = (1.0 - (PSTDWV / TKSTDWV) * (A0 + (A1 * TCSTDWV) + - (A2 * TCSTDWV * TCSTDWV) + (B0 + B1 * TCSTDWV) + - (C0 + C1 * TCSTDWV)) + ((PSTDWV / TKSTDWV) * - (PSTDWV / TKSTDWV) * (DLC + E))) - + ZWV = ( + 1.0 + - (PSTDWV / TKSTDWV) + * ( + A0 + + (A1 * TCSTDWV) + + (A2 * TCSTDWV * TCSTDWV) + + (B0 + B1 * TCSTDWV) + + (C0 + C1 * TCSTDWV) + ) + + ((PSTDWV / TKSTDWV) * (PSTDWV / TKSTDWV) * (DLC + E)) + ) + # USING EQ. 3 RHOWS = (PSTDWV * MV) / (ZWV * R * TKSTDWV) - DENSITY_OF_STD_WATER_VAPOUR = { - 'Z_(WATER VAPOUR)': ZWV, - 'RHOws':RHOWS} + DENSITY_OF_STD_WATER_VAPOUR = {"Z_(WATER VAPOUR)": ZWV, "RHOws": RHOWS} # CALCULATE THE DENSITY OF DRY AMBIENT AIR (TC= USER, P=USER) F = ALPHA + (BETA * P) + (GAMMA * TC * TC) XV = F * PV / P # USING CIDDOR EQUATION A1 - Z = (1.0 - (P / TK) * (A0 + (A1 * TC) + (A2 * TC * TC) + - ((B0 + B1 * TC) * XV) + ((C0 + C1 * TC) * XV * XV)) + - ((P / TK) * (P / TK) * (DLC + (E * XV * XV)))) + Z = ( + 1.0 + - (P / TK) + * ( + A0 + + (A1 * TC) + + (A2 * TC * TC) + + ((B0 + B1 * TC) * XV) + + ((C0 + C1 * TC) * XV * XV) + ) + + ((P / TK) * (P / TK) * (DLC + (E * XV * XV))) + ) # USING EQ. 3 RHOA = (P * MA * (1.0 - XV)) / (Z * R * TK) - DENSITY_OF_DRY_AMBIENT_AIR = { - 'f': F, - 'Xv': XV, - 'Z_(AMB. AIR)': Z, - 'RHOa':RHOA} + DENSITY_OF_DRY_AMBIENT_AIR = {"f": F, "Xv": XV, "Z_(AMB. AIR)": Z, "RHOa": RHOA} # CALCULATE THE DENSITY OF AMBIENT WATER VAPOUR # USING EQ. 3 @@ -566,42 +632,38 @@ def group_refractivity(LAMDA, TC, P, PV, XC=420): TEMP1 = RHOA / RHOASX TEMP2 = RHOW / RHOWS - DENSITY_OF_AMBIENT_WATER_VAPOUR = { - 'RHOw' : RHOW, - 'Da' : TEMP1, - 'Dv' : TEMP2} - + DENSITY_OF_AMBIENT_WATER_VAPOUR = {"RHOw": RHOW, "Da": TEMP1, "Dv": TEMP2} + TEMP1 = TEMP1 * NGASX_1 TEMP2 = TEMP2 * NGWS_1 - DENSITY_OF_AMBIENT_WATER_VAPOUR['DaNgasx'] = TEMP1 - DENSITY_OF_AMBIENT_WATER_VAPOUR['DvNgvs'] = TEMP2 + DENSITY_OF_AMBIENT_WATER_VAPOUR["DaNgasx"] = TEMP1 + DENSITY_OF_AMBIENT_WATER_VAPOUR["DvNgvs"] = TEMP2 NPROPG_1 = TEMP1 + TEMP2 - - REFRACTIVITY_OF_MOIST_AIR = { - '(NpropG-1)10E8': NPROPG_1} - + + REFRACTIVITY_OF_MOIST_AIR = {"(NpropG-1)10E8": NPROPG_1} + return NPROPG_1 - - + + def humidity2part_water_vapour_press(H, TC): """ - Input - H = humidity (%) - TC = temperature (degrees Celcius) - Return - PV = partial water vapour pressure (Pa) - """ + Calculates the partial water vapour pressure from relative humidity and temperature + + :param H: humidity (%) + :param TC: temperature (degrees Celcius) - (A, B, C, D) = (1.2378847e-5, -1.9121316e-2, - 33.93711047, -6.3431645e3) + :return: PV - partial water vapour pressure (Pa) + """ + + (A, B, C, D) = (1.2378847e-5, -1.9121316e-2, 33.93711047, -6.3431645e3) TK = TC + 273.15 - + # NOW CONVERT THE RELATIVE HUMIDITY TO PATIAL WATER VAPOUR PRESSURE (hPa) # SEE EQ.(22) GIACOMO 1982 SVP = exp(A * TK * TK + B * TK + C + D / TK) - PV = (H / 100.0) * SVP + PV = (H / 100.0) * SVP PV = PV / 100.0 - return PV \ No newline at end of file + return PV diff --git a/geodepy/tests/test_angles.py b/geodepy/tests/test_angles.py index 8fd9574..afce1e6 100644 --- a/geodepy/tests/test_angles.py +++ b/geodepy/tests/test_angles.py @@ -2,17 +2,44 @@ import os from math import radians, pi -from geodepy.angles import (DECAngle, HPAngle, GONAngle, DMSAngle, DDMAngle, - dec2hp, dec2hpa, dec2gon, dec2gona, - dec2dms, dec2ddm, - hp2dec, hp2deca, hp2gon, hp2gona, - hp2dms, hp2ddm, hp2rad, - gon2dec, gon2deca, gon2hp, gon2hpa, - gon2dms, gon2ddm, gon2rad, - dd2sec, angular_typecheck) - -rad_exs = [radians(123.74875), radians(12.575), radians(-12.575), - radians(0.0525), radians(0.005), radians(-0.005)] +from geodepy.angles import ( + DECAngle, + HPAngle, + GONAngle, + DMSAngle, + DDMAngle, + dec2hp, + dec2hpa, + dec2gon, + dec2gona, + dec2dms, + dec2ddm, + hp2dec, + hp2deca, + hp2gon, + hp2gona, + hp2dms, + hp2ddm, + hp2rad, + gon2dec, + gon2deca, + gon2hp, + gon2hpa, + gon2dms, + gon2ddm, + gon2rad, + dd2sec, + angular_typecheck, +) + +rad_exs = [ + radians(123.74875), + radians(12.575), + radians(-12.575), + radians(0.0525), + radians(0.005), + radians(-0.005), +] dec_ex = 123.74875 dec_ex2 = 12.575 @@ -55,12 +82,12 @@ dms_ex6 = DMSAngle(0, 0, 18, positive=False) dms_exs = [dms_ex, dms_ex2, dms_ex3, dms_ex4, dms_ex5, dms_ex6] -dms_str = '123 44 55.5' -dms_str2 = '12 34 30' -dms_str3 = '-12 34 30' -dms_str4 = '0 3 9' -dms_str5 = '0 0 18' -dms_str6 = '-0 0 18' +dms_str = "123 44 55.5" +dms_str2 = "12 34 30" +dms_str3 = "-12 34 30" +dms_str4 = "0 3 9" +dms_str5 = "0 0 18" +dms_str6 = "-0 0 18" dms_strs = [dms_str, dms_str2, dms_str3, dms_str4, dms_str5, dms_str6] ddm_ex = DDMAngle(123, 44.925) @@ -71,12 +98,12 @@ ddm_ex6 = DDMAngle(0, 0.3, positive=False) ddm_exs = [ddm_ex, ddm_ex2, ddm_ex3, ddm_ex4, ddm_ex5, ddm_ex6] -ddm_str = '123 44.925' -ddm_str2 = '12 34.5' -ddm_str3 = '-12 34.5' -ddm_str4 = '0 3.15' -ddm_str5 = '0 0.3' -ddm_str6 = '-0 0.3' +ddm_str = "123 44.925" +ddm_str2 = "12 34.5" +ddm_str3 = "-12 34.5" +ddm_str4 = "0 3.15" +ddm_str5 = "0 0.3" +ddm_str6 = "-0 0.3" ddm_strs = [ddm_str, ddm_str2, ddm_str3, ddm_str4, ddm_str5, ddm_str6] gon_ex = 137.4986111111111 @@ -101,18 +128,24 @@ def setUp(self): self.testData = [] degreeValues = [0, 1, 2, 4, 8, 16, 32, 64, 128, 256] dec_places = 13 - error = 10**-(dec_places - 4) + error = 10 ** -(dec_places - 4) for deg in degreeValues: for min in range(60): for sec in range(60): if sec: - hp_minus = float(f'{deg:4d}.{min:02d}{sec-1:02d}' + '9' * (dec_places - 4)) + hp_minus = float( + f"{deg:4d}.{min:02d}{sec-1:02d}" + "9" * (dec_places - 4) + ) dec_minus = deg + (min / 60.0 + (sec - error) / 3600.0) - gon_minus = 400.0 / 360.0 * dec_minus + gon_minus = 400.0 / 360.0 * dec_minus rad_minus = pi / 180.0 * dec_minus - self.testData.append([hp_minus, dec_minus, gon_minus, rad_minus]) - hp = float(f'{deg:4d}.{min:02d}{sec:02d}') - hp_plus = float(f'{deg:4d}.{min:02d}{sec:02d}' + '0' * (dec_places - 5) + '1') + self.testData.append( + [hp_minus, dec_minus, gon_minus, rad_minus] + ) + hp = float(f"{deg:4d}.{min:02d}{sec:02d}") + hp_plus = float( + f"{deg:4d}.{min:02d}{sec:02d}" + "0" * (dec_places - 5) + "1" + ) dec = deg + (min / 60.0 + sec / 3600.0) gon = 400.0 / 360.0 * dec rad = pi / 180.0 * dec @@ -148,8 +181,8 @@ def test_DECAngle(self): self.assertEqual(round(ex), DECAngle(round(dec_exs[num]))) # Test HPAngle Representation - self.assertEqual(repr(deca_ex), '{DECAngle: +123.74875}') - self.assertEqual(repr(deca_ex3), '{DECAngle: -12.575}') + self.assertEqual(repr(deca_ex), "{DECAngle: +123.74875}") + self.assertEqual(repr(deca_ex3), "{DECAngle: -12.575}") # Test DECAngle Overloads self.assertEqual(dec_ex + dec_ex2, (deca_ex + deca_ex2).dec()) @@ -171,19 +204,19 @@ def test_DECAngle(self): self.assertTrue(deca_ex2 < deca_ex) self.assertFalse(deca_ex < deca_ex2) with self.assertRaises(TypeError): - deca_ex * 'a' + deca_ex * "a" with self.assertRaises(TypeError): - 'a' * deca_ex + "a" * deca_ex with self.assertRaises(TypeError): - deca_ex / 'a' + deca_ex / "a" with self.assertRaises(TypeError): - deca_ex + 'a' + deca_ex + "a" with self.assertRaises(TypeError): - 'a' + deca_ex + "a" + deca_ex with self.assertRaises(TypeError): - deca_ex - 'a' + deca_ex - "a" with self.assertRaises(TypeError): - 'a' - deca_ex + "a" - deca_ex def test_HPAngle(self): # Test HPAngle Methods @@ -211,8 +244,8 @@ def test_HPAngle(self): self.assertEqual(round(ex), HPAngle(round(hp_exs[num]))) # Test HPAngle Representation - self.assertEqual(repr(hpa_ex), '{HPAngle: +123.44555}') - self.assertEqual(repr(hpa_ex3), '{HPAngle: -12.343}') + self.assertEqual(repr(hpa_ex), "{HPAngle: +123.44555}") + self.assertEqual(repr(hpa_ex3), "{HPAngle: -12.343}") # Test DMSAngle Overloads self.assertAlmostEqual(dec_ex + dec_ex2, (hpa_ex + hpa_ex2).dec(), 12) @@ -234,19 +267,19 @@ def test_HPAngle(self): self.assertTrue(hpa_ex2 < hpa_ex) self.assertFalse(hpa_ex < hpa_ex2) with self.assertRaises(TypeError): - hpa_ex * 'a' + hpa_ex * "a" with self.assertRaises(TypeError): - 'a' * hpa_ex + "a" * hpa_ex with self.assertRaises(TypeError): - hpa_ex / 'a' + hpa_ex / "a" with self.assertRaises(TypeError): - hpa_ex + 'a' + hpa_ex + "a" with self.assertRaises(TypeError): - 'a' + hpa_ex + "a" + hpa_ex with self.assertRaises(TypeError): - hpa_ex - 'a' + hpa_ex - "a" with self.assertRaises(TypeError): - 'a' - hpa_ex + "a" - hpa_ex # Test Validity Checking of HP format input with self.assertRaises(ValueError): @@ -279,8 +312,8 @@ def test_GONAngle(self): self.assertEqual(round(ex), GONAngle(round(gon_exs[num]))) # Test GONAngle Representation - self.assertEqual(repr(gona_ex), '{GONAngle: +137.4986111111111}') - self.assertEqual(repr(gona_ex3), '{GONAngle: -13.97222222222222}') + self.assertEqual(repr(gona_ex), "{GONAngle: +137.4986111111111}") + self.assertEqual(repr(gona_ex3), "{GONAngle: -13.97222222222222}") # Test DMSAngle Overloads self.assertAlmostEqual(dec_ex + dec_ex2, (gona_ex + gona_ex2).dec(), 12) @@ -302,19 +335,19 @@ def test_GONAngle(self): self.assertTrue(gona_ex2 < gona_ex) self.assertFalse(gona_ex < gona_ex2) with self.assertRaises(TypeError): - gona_ex * 'a' + gona_ex * "a" with self.assertRaises(TypeError): - 'a' * gona_ex + "a" * gona_ex with self.assertRaises(TypeError): - gona_ex / 'a' + gona_ex / "a" with self.assertRaises(TypeError): - gona_ex + 'a' + gona_ex + "a" with self.assertRaises(TypeError): - 'a' + gona_ex + "a" + gona_ex with self.assertRaises(TypeError): - gona_ex - 'a' + gona_ex - "a" with self.assertRaises(TypeError): - 'a' - gona_ex + "a" - gona_ex def test_DMSAngle(self): # Test DMSAngle Methods @@ -339,14 +372,18 @@ def test_DMSAngle(self): self.assertEqual((round(-ex, 2)).hp(), round(-hp_exs[num], 5)) # Test DMSAngle Sign Conventions - self.assertEqual(-dec_ex, DMSAngle(-dms_ex.degree, dms_ex.minute, - dms_ex.second).dec()) - self.assertEqual(dec_ex, DMSAngle(dms_ex.degree, -dms_ex.minute, - -dms_ex.second).dec()) - self.assertAlmostEqual(-dec_ex4, DMSAngle(0, -dms_ex4.minute, - dms_ex4.second).dec(), 9) - self.assertAlmostEqual(dec_ex4, DMSAngle(0, dms_ex4.minute, - dms_ex4.second).dec(), 9) + self.assertEqual( + -dec_ex, DMSAngle(-dms_ex.degree, dms_ex.minute, dms_ex.second).dec() + ) + self.assertEqual( + dec_ex, DMSAngle(dms_ex.degree, -dms_ex.minute, -dms_ex.second).dec() + ) + self.assertAlmostEqual( + -dec_ex4, DMSAngle(0, -dms_ex4.minute, dms_ex4.second).dec(), 9 + ) + self.assertAlmostEqual( + dec_ex4, DMSAngle(0, dms_ex4.minute, dms_ex4.second).dec(), 9 + ) self.assertEqual(-dec_ex5, DMSAngle(0, 0, -dms_ex5.second).dec()) self.assertEqual(dec_ex5, DMSAngle(0, 0, dms_ex5.second).dec()) self.assertEqual(-dms_ex3, DMSAngle(12, 34, -30)) @@ -363,8 +400,8 @@ def test_DMSAngle(self): self.assertFalse(DMSAngle(0, 0, -3).positive) self.assertFalse(DMSAngle(0, 1, 2, positive=False).positive) self.assertFalse(DMSAngle(-0.0, 1, 2).positive) - self.assertEqual(repr(dms_ex), '{DMSAngle: +123d 44m 55.5s}') - self.assertEqual(repr(dms_ex3), '{DMSAngle: -12d 34m 30s}') + self.assertEqual(repr(dms_ex), "{DMSAngle: +123d 44m 55.5s}") + self.assertEqual(repr(dms_ex3), "{DMSAngle: -12d 34m 30s}") # Test DMSAngle Overloads self.assertEqual(dec_ex + dec_ex2, (dms_ex + dms_ex2).dec()) @@ -387,19 +424,19 @@ def test_DMSAngle(self): self.assertTrue(dms_ex2 < dms_ex) self.assertFalse(dms_ex < dms_ex2) with self.assertRaises(TypeError): - dms_ex * 'a' + dms_ex * "a" with self.assertRaises(TypeError): - 'a' * dms_ex + "a" * dms_ex with self.assertRaises(TypeError): - dms_ex / 'a' + dms_ex / "a" with self.assertRaises(TypeError): - dms_ex + 'a' + dms_ex + "a" with self.assertRaises(TypeError): - 'a' + dms_ex + "a" + dms_ex with self.assertRaises(TypeError): - dms_ex - 'a' + dms_ex - "a" with self.assertRaises(TypeError): - 'a' - dms_ex + "a" - dms_ex # Test reading in formatted strings for num, ex in enumerate(dms_strs): @@ -429,8 +466,7 @@ def test_DDMAngle(self): self.assertEqual((round(-ex)).hp(), round(-hp_exs[num], 2)) # Test DMSAngle Sign Conventions - self.assertEqual(-dec_ex, DDMAngle(-dms_ex.degree, - ddm_ex.minute).dec()) + self.assertEqual(-dec_ex, DDMAngle(-dms_ex.degree, ddm_ex.minute).dec()) self.assertEqual(dec_ex, DDMAngle(dms_ex.degree, -ddm_ex.minute).dec()) self.assertAlmostEqual(-dec_ex4, DDMAngle(0, -ddm_ex4.minute).dec(), 9) self.assertAlmostEqual(dec_ex4, DDMAngle(0, ddm_ex4.minute).dec(), 9) @@ -447,8 +483,8 @@ def test_DDMAngle(self): self.assertFalse(DDMAngle(0, -1).positive) self.assertFalse(DDMAngle(0, 1, positive=False).positive) self.assertFalse(DDMAngle(-0.0, 1).positive) - self.assertEqual(repr(ddm_ex), '{DDMAngle: +123d 44.925m}') - self.assertEqual(repr(ddm_ex3), '{DDMAngle: -12d 34.5m}') + self.assertEqual(repr(ddm_ex), "{DDMAngle: +123d 44.925m}") + self.assertEqual(repr(ddm_ex3), "{DDMAngle: -12d 34.5m}") # Test DDMAngle Overloads self.assertEqual(dec_ex + dec_ex2, (ddm_ex + ddm_ex2).dec()) @@ -471,19 +507,19 @@ def test_DDMAngle(self): self.assertTrue(ddm_ex2 < ddm_ex) self.assertFalse(ddm_ex < ddm_ex2) with self.assertRaises(TypeError): - ddm_ex * 'a' + ddm_ex * "a" with self.assertRaises(TypeError): - 'a' * ddm_ex + "a" * ddm_ex with self.assertRaises(TypeError): - ddm_ex / 'a' + ddm_ex / "a" with self.assertRaises(TypeError): - ddm_ex + 'a' + ddm_ex + "a" with self.assertRaises(TypeError): - 'a' + ddm_ex + "a" + ddm_ex with self.assertRaises(TypeError): - ddm_ex - 'a' + ddm_ex - "a" with self.assertRaises(TypeError): - 'a' - ddm_ex + "a" - ddm_ex # Test reading in formatted strings for num, ex in enumerate(ddm_strs): @@ -492,11 +528,9 @@ def test_DDMAngle(self): def test_angles_interoperability(self): self.assertEqual(DMSAngle(1, 2, 3) + DDMAngle(2, 3), DMSAngle(3, 5, 3)) - self.assertEqual(DMSAngle(3, 2, 0) - DDMAngle(2, 2.5), - DMSAngle(0, 59, 30)) + self.assertEqual(DMSAngle(3, 2, 0) - DDMAngle(2, 2.5), DMSAngle(0, 59, 30)) self.assertEqual(DDMAngle(2, 3) + DMSAngle(1, 2, 3), DDMAngle(3, 5.05)) - self.assertEqual(DDMAngle(3, 2) - DMSAngle(2, 2, 30), - DDMAngle(0, 59.5)) + self.assertEqual(DDMAngle(3, 2) - DMSAngle(2, 2, 30), DDMAngle(0, 59.5)) def test_dec2hp(self): for num, ex in enumerate(hp_exs): @@ -523,10 +557,8 @@ def test_dec2gon(self): def test_dec2gona(self): for num, ex in enumerate(dec_exs): - self.assertEqual(round(dec2gona(ex), 13), - round(gona_exs[num], 13)) - self.assertEqual(round(dec2gona(-ex), 13), - round(-gona_exs[num], 13)) + self.assertEqual(round(dec2gona(ex), 13), round(gona_exs[num], 13)) + self.assertEqual(round(dec2gona(-ex), 13), round(-gona_exs[num], 13)) def test_dec2dms(self): self.assertEqual(dms_ex, dec2dms(dec_ex)) @@ -547,8 +579,9 @@ def test_hp2dec(self): self.assertAlmostEqual(0, hp2dec(0), 13) self.assertAlmostEqual(258, hp2dec(258), 13) - self.assertAlmostEqual(hp2dec(hp_exs[0]) + hp2dec(hp_exs[1]), - dec_exs[0] + dec_exs[1], 13) + self.assertAlmostEqual( + hp2dec(hp_exs[0]) + hp2dec(hp_exs[1]), dec_exs[0] + dec_exs[1], 13 + ) # Test that invalid minutes and seconds components raise errors # (Minutes and/or Seconds can't be greater than 60, therefore # 123.718 - representing 123 degrees, 71 minutes, 80 seconds @@ -614,8 +647,8 @@ def test_gon2dec(self): self.assertAlmostEqual(gon2dec(-ex), -dec_exs[num], 14) for check in self.testData: hp, dec, gon, rad = check - self.assertAlmostEqual(dec, gon2dec(gon), delta = 5.8e-14) - self.assertAlmostEqual(-dec, gon2dec(-gon), delta = 5.8e-14) + self.assertAlmostEqual(dec, gon2dec(gon), delta=5.8e-14) + self.assertAlmostEqual(-dec, gon2dec(-gon), delta=5.8e-14) def test_gon2deca(self): for num, ex in enumerate(gon_exs): @@ -670,14 +703,12 @@ def test_angular_typecheck(self): class_exs = [deca_exs, hpa_exs, gona_exs, dms_exs, ddm_exs] for class_ex in class_exs: for num, ex in enumerate(class_ex): - self.assertAlmostEqual(angular_typecheck(ex), - dec_exs[num], 13) - self.assertAlmostEqual(angular_typecheck(-ex), - -dec_exs[num], 13) + self.assertAlmostEqual(angular_typecheck(ex), dec_exs[num], 13) + self.assertAlmostEqual(angular_typecheck(-ex), -dec_exs[num], 13) for ex in dec_exs: self.assertEqual(angular_typecheck(ex), ex) self.assertEqual(angular_typecheck(-ex), -ex) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/geodepy/tests/test_convert.py b/geodepy/tests/test_convert.py index 21c3bd5..3455728 100644 --- a/geodepy/tests/test_convert.py +++ b/geodepy/tests/test_convert.py @@ -4,107 +4,137 @@ import numpy as np from geodepy.fileio import read_dnacoord from geodepy.constants import grs80, ans, isg -from geodepy.convert import (dec2hp, hp2dec, DMSAngle, DDMAngle, dec2dms, - dec2ddm, hp2dms, hp2ddm, dd2sec, - yyyydoy_to_date, date_to_yyyydoy, grid2geo, - hp2dec_v, geo2grid, llh2xyz, xyz2llh) +from geodepy.convert import ( + dec2hp, + hp2dec, + DMSAngle, + DDMAngle, + dec2dms, + dec2ddm, + hp2dms, + hp2ddm, + dd2sec, + yyyydoy_to_date, + date_to_yyyydoy, + grid2geo, + hp2dec_v, + geo2grid, + llh2xyz, + xyz2llh, +) class TestConvert(unittest.TestCase): def test_date_to_yyyydoy(self): - self.assertEqual(date_to_yyyydoy(datetime.date(2020, 1, 4)), - '2020.004') - self.assertEqual(date_to_yyyydoy(datetime.date(2020, 10, 12)), - '2020.286') - self.assertEqual(date_to_yyyydoy(datetime.date(1998, 4, 7)), - '1998.097') - self.assertEqual(date_to_yyyydoy(datetime.date(2000, 11, 22)), - '2000.327') - self.assertEqual(date_to_yyyydoy(datetime.date(2008, 2, 29)), - '2008.060') + self.assertEqual(date_to_yyyydoy(datetime.date(2020, 1, 4)), "2020.004") + self.assertEqual(date_to_yyyydoy(datetime.date(2020, 10, 12)), "2020.286") + self.assertEqual(date_to_yyyydoy(datetime.date(1998, 4, 7)), "1998.097") + self.assertEqual(date_to_yyyydoy(datetime.date(2000, 11, 22)), "2000.327") + self.assertEqual(date_to_yyyydoy(datetime.date(2008, 2, 29)), "2008.060") with self.assertRaises(AttributeError): - date_to_yyyydoy('a') + date_to_yyyydoy("a") with self.assertRaises(AttributeError): - date_to_yyyydoy('2020123') + date_to_yyyydoy("2020123") def test_yyyydoy_to_date(self): - self.assertEqual(yyyydoy_to_date('2020.004'), - datetime.date(2020, 1, 4)) - self.assertEqual(yyyydoy_to_date('2020.286'), - datetime.date(2020, 10, 12)) - self.assertEqual(yyyydoy_to_date('1998.097'), - datetime.date(1998, 4, 7)) - self.assertEqual(yyyydoy_to_date('2000.327'), - datetime.date(2000, 11, 22)) - self.assertEqual(yyyydoy_to_date('2008.060'), - datetime.date(2008, 2, 29)) - self.assertEqual(yyyydoy_to_date('2020004'), - datetime.date(2020, 1, 4)) - self.assertEqual(yyyydoy_to_date('2020286'), - datetime.date(2020, 10, 12)) - self.assertEqual(yyyydoy_to_date('1998097'), - datetime.date(1998, 4, 7)) - self.assertEqual(yyyydoy_to_date('2000327'), - datetime.date(2000, 11, 22)) - self.assertEqual(yyyydoy_to_date('2008060'), - datetime.date(2008, 2, 29)) + self.assertEqual(yyyydoy_to_date("2020.004"), datetime.date(2020, 1, 4)) + self.assertEqual(yyyydoy_to_date("2020.286"), datetime.date(2020, 10, 12)) + self.assertEqual(yyyydoy_to_date("1998.097"), datetime.date(1998, 4, 7)) + self.assertEqual(yyyydoy_to_date("2000.327"), datetime.date(2000, 11, 22)) + self.assertEqual(yyyydoy_to_date("2008.060"), datetime.date(2008, 2, 29)) + self.assertEqual(yyyydoy_to_date("2020004"), datetime.date(2020, 1, 4)) + self.assertEqual(yyyydoy_to_date("2020286"), datetime.date(2020, 10, 12)) + self.assertEqual(yyyydoy_to_date("1998097"), datetime.date(1998, 4, 7)) + self.assertEqual(yyyydoy_to_date("2000327"), datetime.date(2000, 11, 22)) + self.assertEqual(yyyydoy_to_date("2008060"), datetime.date(2008, 2, 29)) with self.assertRaises(ValueError): - yyyydoy_to_date('a') + yyyydoy_to_date("a") with self.assertRaises(ValueError): - yyyydoy_to_date('20201234') + yyyydoy_to_date("20201234") with self.assertRaises(ValueError): - yyyydoy_to_date('2020.1234') + yyyydoy_to_date("2020.1234") with self.assertRaises(ValueError): - yyyydoy_to_date('202012') + yyyydoy_to_date("202012") with self.assertRaises(ValueError): - yyyydoy_to_date('2020.12') + yyyydoy_to_date("2020.12") def test_geo_grid_transform_interoperability(self): abs_path = os.path.abspath(os.path.dirname(__file__)) - test_geo_coords = np.genfromtxt(os.path.join(abs_path, 'resources/Test_Conversion_Geo.csv'), - delimiter=',', - dtype='S4,f8,f8', - names=['site', 'lat', 'lon']) - - test_grid_coords = np.genfromtxt(os.path.join(abs_path, 'resources/Test_Conversion_Grid.csv'), - delimiter=',', - dtype='S4,i4,f8,f8', - names=['site', 'zone', 'east', 'north']) - - geoed_grid = np.array([grid2geo(*x) for x in test_grid_coords[['zone', 'east', 'north']]]) - np.testing.assert_almost_equal(geoed_grid[:, :2], hp2dec_v(np.array(test_geo_coords[['lat', 'lon']].tolist())), - decimal=8) - - gridded_geo = np.stack([geo2grid(*x) for x in hp2dec_v(np.array(test_geo_coords[['lat', 'lon']].tolist()))]) - np.testing.assert_almost_equal(gridded_geo[:, 2:4].astype(float), - np.array(test_grid_coords[['east', 'north']].tolist()), - decimal=3) + test_geo_coords = np.genfromtxt( + os.path.join(abs_path, "resources/Test_Conversion_Geo.csv"), + delimiter=",", + dtype="S4,f8,f8", + names=["site", "lat", "lon"], + ) + + test_grid_coords = np.genfromtxt( + os.path.join(abs_path, "resources/Test_Conversion_Grid.csv"), + delimiter=",", + dtype="S4,i4,f8,f8", + names=["site", "zone", "east", "north"], + ) + + geoed_grid = np.array( + [grid2geo(*x) for x in test_grid_coords[["zone", "east", "north"]]] + ) + np.testing.assert_almost_equal( + geoed_grid[:, :2], + hp2dec_v(np.array(test_geo_coords[["lat", "lon"]].tolist())), + decimal=8, + ) + + gridded_geo = np.stack( + [ + geo2grid(*x) + for x in hp2dec_v(np.array(test_geo_coords[["lat", "lon"]].tolist())) + ] + ) + np.testing.assert_almost_equal( + gridded_geo[:, 2:4].astype(float), + np.array(test_grid_coords[["east", "north"]].tolist()), + decimal=3, + ) def test_geo_grid_transform_interoperability_isg(self): abs_path = os.path.abspath(os.path.dirname(__file__)) - test_geo_coords = np.genfromtxt(os.path.join(abs_path, 'resources/Test_Conversion_ISG_Geo.csv'), - delimiter=',', - dtype='S4,f8,f8', - names=['site', 'lat', 'lon']) - - test_grid_coords = np.genfromtxt(os.path.join(abs_path, 'resources/Test_Conversion_ISG_Grid.csv'), - delimiter=',', - dtype='S4,i4,f8,f8', - names=['site', 'zone', 'east', 'north']) - - geoed_grid = np.array([grid2geo(*x, ellipsoid=ans, prj=isg) - for x in test_grid_coords[['zone', 'east', 'north']]]) - np.testing.assert_almost_equal(geoed_grid[:, :2], np.array(test_geo_coords[['lat', 'lon']].tolist()), - decimal=8) - - gridded_geo = np.stack([geo2grid(*x, ellipsoid=ans, prj=isg) - for x in np.array(test_geo_coords[['lat', 'lon']].tolist()) - ]) - np.testing.assert_almost_equal(gridded_geo[:, 2:4].astype(float), - np.array(test_grid_coords[['east', 'north']].tolist()), - decimal=3) - + test_geo_coords = np.genfromtxt( + os.path.join(abs_path, "resources/Test_Conversion_ISG_Geo.csv"), + delimiter=",", + dtype="S4,f8,f8", + names=["site", "lat", "lon"], + ) + + test_grid_coords = np.genfromtxt( + os.path.join(abs_path, "resources/Test_Conversion_ISG_Grid.csv"), + delimiter=",", + dtype="S4,i4,f8,f8", + names=["site", "zone", "east", "north"], + ) + + geoed_grid = np.array( + [ + grid2geo(*x, ellipsoid=ans, prj=isg) + for x in test_grid_coords[["zone", "east", "north"]] + ] + ) + np.testing.assert_almost_equal( + geoed_grid[:, :2], + np.array(test_geo_coords[["lat", "lon"]].tolist()), + decimal=8, + ) + + gridded_geo = np.stack( + [ + geo2grid(*x, ellipsoid=ans, prj=isg) + for x in np.array(test_geo_coords[["lat", "lon"]].tolist()) + ] + ) + np.testing.assert_almost_equal( + gridded_geo[:, 2:4].astype(float), + np.array(test_grid_coords[["east", "north"]].tolist()), + decimal=3, + ) def test_llh2xyz(self): @@ -115,17 +145,15 @@ def test_llh2xyz(self): self.assertAlmostEqual(z, -3888601.3067, 3) # Test DMSAngle input - x, y, z = llh2xyz(DMSAngle(-37, 48, 26.67598), - DMSAngle(144, 58, 16.44114), - 39.6514) + x, y, z = llh2xyz( + DMSAngle(-37, 48, 26.67598), DMSAngle(144, 58, 16.44114), 39.6514 + ) self.assertAlmostEqual(x, -4131654.2815, 3) self.assertAlmostEqual(y, 2896107.9738, 3) self.assertAlmostEqual(z, -3888601.3067, 3) # Test DDMAngle input - x, y, z = llh2xyz(DDMAngle(-37, 48.4445996), - DDMAngle(144, 58.274019), - 39.6514) + x, y, z = llh2xyz(DDMAngle(-37, 48.4445996), DDMAngle(144, 58.274019), 39.6514) self.assertAlmostEqual(x, -4131654.2815, 3) self.assertAlmostEqual(y, 2896107.9738, 3) self.assertAlmostEqual(z, -3888601.3067, 3) @@ -134,30 +162,35 @@ def test_llh2xyz(self): # including 109 Points Across Australia abs_path = os.path.abspath(os.path.dirname(__file__)) - testdata = read_dnacoord(os.path.join(abs_path, 'resources/natadjust_rvs_example.dat')) + testdata = read_dnacoord( + os.path.join(abs_path, "resources/natadjust_rvs_example.dat") + ) for coord in testdata: coord.converthptodd() xcomp, ycomp, zcomp = llh2xyz(coord.lat, coord.long, coord.ell_ht) - assert (abs(coord.x - xcomp) < 2e-4) - assert (abs(coord.y - ycomp) < 2e-4) - assert (abs(coord.z - zcomp) < 2e-4) + assert abs(coord.x - xcomp) < 2e-4 + assert abs(coord.y - ycomp) < 2e-4 + assert abs(coord.z - zcomp) < 2e-4 def test_xyz2llh(self): abs_path = os.path.abspath(os.path.dirname(__file__)) - testdata = read_dnacoord(os.path.join(abs_path, 'resources/natadjust_rvs_example.dat')) + testdata = read_dnacoord( + os.path.join(abs_path, "resources/natadjust_rvs_example.dat") + ) for coord in testdata: coord.converthptodd() latcomp, longcomp, ell_htcomp = xyz2llh(coord.x, coord.y, coord.z) - assert (abs(latcomp - coord.lat) < 2e-9) - assert (abs(longcomp - coord.long) < 2e-9) - assert (abs(ell_htcomp - coord.ell_ht) < 1e-4) + assert abs(latcomp - coord.lat) < 2e-9 + assert abs(longcomp - coord.long) < 2e-9 + assert abs(ell_htcomp - coord.ell_ht) < 1e-4 def test_geo2grid(self): # Single Point Test - hem, zone, east, north, psf, grid_conv = geo2grid(hp2dec(-37.482667598), - hp2dec(144.581644114)) - self.assertEqual(hem, 'South') + hem, zone, east, north, psf, grid_conv = geo2grid( + hp2dec(-37.482667598), hp2dec(144.581644114) + ) + self.assertEqual(hem, "South") self.assertEqual(zone, 55) self.assertAlmostEqual(east, 321405.5592, 3) self.assertAlmostEqual(north, 5813614.1613, 3) @@ -165,10 +198,10 @@ def test_geo2grid(self): self.assertAlmostEqual(grid_conv, -1.2439811331, 9) # Test DMSAngle Input - (hem, zone, east, - north, psf, grid_conv) = geo2grid(DMSAngle(-37, 48, 26.67598), - DMSAngle(144, 58, 16.44114)) - self.assertEqual(hem, 'South') + (hem, zone, east, north, psf, grid_conv) = geo2grid( + DMSAngle(-37, 48, 26.67598), DMSAngle(144, 58, 16.44114) + ) + self.assertEqual(hem, "South") self.assertEqual(zone, 55) self.assertAlmostEqual(east, 321405.5592, 3) self.assertAlmostEqual(north, 5813614.1613, 3) @@ -176,10 +209,10 @@ def test_geo2grid(self): self.assertAlmostEqual(grid_conv, -1.2439811331, 9) # Test DDMAngle Input - (hem, zone, east, - north, psf, grid_conv) = geo2grid(DDMAngle(-37, 48.4445997), - DDMAngle(144, 58.274019)) - self.assertEqual(hem, 'South') + (hem, zone, east, north, psf, grid_conv) = geo2grid( + DDMAngle(-37, 48.4445997), DDMAngle(144, 58.274019) + ) + self.assertEqual(hem, "South") self.assertEqual(zone, 55) self.assertAlmostEqual(east, 321405.5592, 3) self.assertAlmostEqual(north, 5813614.1613, 3) @@ -189,21 +222,28 @@ def test_geo2grid(self): abs_path = os.path.abspath(os.path.dirname(__file__)) # Test various coordinates in Australia - testdata = read_dnacoord(os.path.join(abs_path, 'resources/natadjust_rvs_example.dat')) + testdata = read_dnacoord( + os.path.join(abs_path, "resources/natadjust_rvs_example.dat") + ) for coord in testdata: coord.converthptodd() - hem, zonecomp, eastcomp, northcomp, psf, grid_conv = geo2grid(coord.lat, coord.long) + hem, zonecomp, eastcomp, northcomp, psf, grid_conv = geo2grid( + coord.lat, coord.long + ) self.assertEqual(zonecomp, coord.zone) self.assertLess(abs(eastcomp - coord.easting), 4e-4) self.assertLess((northcomp - coord.northing), 4e-4) # Test North and South Hemisphere Output north_ex = (DMSAngle(34, 57, 00.79653).dec(), DMSAngle(117, 48, 36.68783).dec()) - south_ex = (DMSAngle(-34, 57, 00.79653).dec(), DMSAngle(117, 48, 36.68783).dec()) + south_ex = ( + DMSAngle(-34, 57, 00.79653).dec(), + DMSAngle(117, 48, 36.68783).dec(), + ) north_grid = geo2grid(north_ex[0], north_ex[1]) south_grid = geo2grid(south_ex[0], south_ex[1]) - self.assertEqual(north_grid[0], 'North') - self.assertEqual(south_grid[0], 'South') + self.assertEqual(north_grid[0], "North") + self.assertEqual(south_grid[0], "South") self.assertEqual(north_grid[1], south_grid[1]) # Zone self.assertEqual(north_grid[2], south_grid[2]) # Easting self.assertEqual(north_grid[3], 10000000 - south_grid[3]) # Northing @@ -233,16 +273,20 @@ def test_geo2grid(self): def test_grid2geo(self): abs_path = os.path.abspath(os.path.dirname(__file__)) - testdata = read_dnacoord(os.path.join(abs_path, 'resources/natadjust_rvs_example.dat')) + testdata = read_dnacoord( + os.path.join(abs_path, "resources/natadjust_rvs_example.dat") + ) for coord in testdata: coord.converthptodd() - latcomp, longcomp, psf, grid_conv = grid2geo(coord.zone, coord.easting, coord.northing) + latcomp, longcomp, psf, grid_conv = grid2geo( + coord.zone, coord.easting, coord.northing + ) self.assertLess(abs(latcomp - coord.lat), 5e-9) self.assertLess(abs(longcomp - coord.long), 5e-9) # Test North and South Hemisphere Output - north_ex = (50, 573976.8747, 3867822.4539, 'North') - south_ex = (50, 573976.8747, 6132177.5461, 'South') + north_ex = (50, 573976.8747, 3867822.4539, "North") + south_ex = (50, 573976.8747, 6132177.5461, "South") north_geo = grid2geo(*north_ex) south_geo = grid2geo(*south_ex) self.assertEqual(north_geo[0], -south_geo[0]) @@ -264,7 +308,7 @@ def test_grid2geo(self): with self.assertRaises(ValueError): grid2geo(0, 0, 10000001) with self.assertRaises(ValueError): - grid2geo(0, 0, 500000, 'fail') + grid2geo(0, 0, 500000, "fail") # test ValueError raised if not a valid ISG zone with self.assertRaises(ValueError): grid2geo(zone=530, east=300000, north=1500000, ellipsoid=ans, prj=isg) @@ -272,5 +316,6 @@ def test_grid2geo(self): with self.assertWarns(UserWarning): grid2geo(zone=551, east=300000, north=1500000, ellipsoid=grs80, prj=isg) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/geodepy/tests/test_coord.py b/geodepy/tests/test_coord.py index 0ca276a..64e14c1 100644 --- a/geodepy/tests/test_coord.py +++ b/geodepy/tests/test_coord.py @@ -6,29 +6,51 @@ cart_ex1 = CoordCart(-4052052.7379, 4212835.9897, -2545104.5898, 14.269) cart_ex2 = CoordCart(-4052052.7379, 4212835.9897, -2545104.5898) -geo_ex1 = CoordGeo(DMSAngle(-23, 40, 12.39650).deca(), - DMSAngle(133, 53, 7.87779).deca(), - 603.2489, 588.9799) +geo_ex1 = CoordGeo( + DMSAngle(-23, 40, 12.39650).deca(), + DMSAngle(133, 53, 7.87779).deca(), + 603.2489, + 588.9799, +) geo_ex2 = CoordGeo(geo_ex1.lat, geo_ex1.lon, None, 588.9799) geo_ex3 = CoordGeo(geo_ex1.lat, geo_ex1.lon, 603.2489, None) geo_ex4 = CoordGeo(geo_ex1.lat, geo_ex1.lon) -tm_ex1 = CoordTM(53, 386353.2343, 7381852.2986, 603.2489, 588.9799, - hemi_north=False, projection=utm) -tm_ex2 = CoordTM(tm_ex1.zone, tm_ex1.east, tm_ex1.north, None, tm_ex1.orth_ht, - hemi_north=False, projection=utm) -tm_ex3 = CoordTM(tm_ex1.zone, tm_ex1.east, tm_ex1.north, tm_ex1.ell_ht, None, - hemi_north=False, projection=utm) -tm_ex4 = CoordTM(tm_ex1.zone, tm_ex1.east, tm_ex1.north, None, None, - hemi_north=False, projection=utm) +tm_ex1 = CoordTM( + 53, 386353.2343, 7381852.2986, 603.2489, 588.9799, hemi_north=False, projection=utm +) +tm_ex2 = CoordTM( + tm_ex1.zone, + tm_ex1.east, + tm_ex1.north, + None, + tm_ex1.orth_ht, + hemi_north=False, + projection=utm, +) +tm_ex3 = CoordTM( + tm_ex1.zone, + tm_ex1.east, + tm_ex1.north, + tm_ex1.ell_ht, + None, + hemi_north=False, + projection=utm, +) +tm_ex4 = CoordTM( + tm_ex1.zone, tm_ex1.east, tm_ex1.north, None, None, hemi_north=False, projection=utm +) class TestCoord(unittest.TestCase): def test_CoordCart(self): # Test Overloads - self.assertEqual(repr(cart_ex1), 'CoordCart: X: -4052052.7379 ' - 'Y: 4212835.9897 Z: -2545104.5898 ' - 'NVal: 14.269') + self.assertEqual( + repr(cart_ex1), + "CoordCart: X: -4052052.7379 " + "Y: 4212835.9897 Z: -2545104.5898 " + "NVal: 14.269", + ) self.assertEqual(cart_ex1, cart_ex1) with self.assertRaises(ValueError): @@ -64,9 +86,12 @@ def test_CoordCart(self): def test_CoordGeo(self): # Test Overloads - self.assertEqual(repr(geo_ex1), 'CoordGeo: Lat: -23.67011013888889 ' - 'Lon: 133.88552160833333 ' - 'Ell_Ht: 603.2489 Orth_Ht: 588.9799') + self.assertEqual( + repr(geo_ex1), + "CoordGeo: Lat: -23.67011013888889 " + "Lon: 133.88552160833333 " + "Ell_Ht: 603.2489 Orth_Ht: 588.9799", + ) self.assertEqual(geo_ex1, geo_ex1) with self.assertRaises(ValueError): @@ -115,9 +140,12 @@ def test_CoordGeo(self): def test_CoordTM(self): # Test Overloads - self.assertEqual(repr(tm_ex1), 'CoordTM: Zone: 53 East: 386353.2343 ' - 'North: 7381852.2986 Ell_Ht: 603.2489 ' - 'Orth_Ht: 588.9799 Hemisphere: South') + self.assertEqual( + repr(tm_ex1), + "CoordTM: Zone: 53 East: 386353.2343 " + "North: 7381852.2986 Ell_Ht: 603.2489 " + "Orth_Ht: 588.9799 Hemisphere: South", + ) self.assertEqual(tm_ex1, tm_ex1) with self.assertRaises(ValueError): diff --git a/geodepy/tests/test_geodesy.py b/geodepy/tests/test_geodesy.py index 20e0ef6..bf96c81 100644 --- a/geodepy/tests/test_geodesy.py +++ b/geodepy/tests/test_geodesy.py @@ -2,8 +2,15 @@ import os.path import numpy as np import numpy.lib.recfunctions as rfn -from geodepy.convert import (hp2dec, dec2hp, rect2polar, polar2rect, - grid2geo, llh2xyz, DMSAngle) +from geodepy.convert import ( + hp2dec, + dec2hp, + rect2polar, + polar2rect, + grid2geo, + llh2xyz, + DMSAngle, +) from geodepy.geodesy import vincinv, vincdir, vincinv_utm, vincdir_utm, enu2xyz, xyz2enu @@ -21,14 +28,18 @@ def test_enu2xyz(self): MOBS_GDA1994_XYZ = llh2xyz(MOBS_GDA1994[0], MOBS_GDA1994[1], MOBS_MGA1994[3]) # Generate Vector Between UTM Projection Coordinates - mga_vector = [MOBS_MGA2020[1] - MOBS_MGA1994[1], - MOBS_MGA2020[2] - MOBS_MGA1994[2], - MOBS_MGA2020[3] - MOBS_MGA1994[3]] + mga_vector = [ + MOBS_MGA2020[1] - MOBS_MGA1994[1], + MOBS_MGA2020[2] - MOBS_MGA1994[2], + MOBS_MGA2020[3] - MOBS_MGA1994[3], + ] # Generate Vector Between Cartesian XYZ Coordinates - xyz_vector = (MOBS_GDA2020_XYZ[0] - MOBS_GDA1994_XYZ[0], - MOBS_GDA2020_XYZ[1] - MOBS_GDA1994_XYZ[1], - MOBS_GDA2020_XYZ[2] - MOBS_GDA1994_XYZ[2]) + xyz_vector = ( + MOBS_GDA2020_XYZ[0] - MOBS_GDA1994_XYZ[0], + MOBS_GDA2020_XYZ[1] - MOBS_GDA1994_XYZ[1], + MOBS_GDA2020_XYZ[2] - MOBS_GDA1994_XYZ[2], + ) # Rotate UTM Projection Vector by Grid Convergence grid_dist, grid_brg = rect2polar(mga_vector[0], mga_vector[1]) @@ -84,8 +95,9 @@ def test_vincinv(self): self.assertEqual(test3, (0, 0, 0)) # Test DMSAngle Input - ell_dist, azimuth1to2, azimuth2to1 = vincinv(lat1_DMS, lon1_DMS, - lat2_DMS, lon2_DMS) + ell_dist, azimuth1to2, azimuth2to1 = vincinv( + lat1_DMS, lon1_DMS, lat2_DMS, lon2_DMS + ) self.assertEqual(round(ell_dist, 3), 54972.271) self.assertEqual(round(dec2hp(azimuth1to2), 6), 306.520537) self.assertEqual(round(dec2hp(azimuth2to1), 6), 127.102507) @@ -94,16 +106,14 @@ def test_vincinv(self): self.assertEqual(test2, (0, 0, 0)) # Test DDMAngle Input - (ell_dist, - azimuth1to2, - azimuth2to1) = vincinv(lat1_DMS.ddm(), lon1_DMS.ddm(), - lat2_DMS.ddm(), lon2_DMS.ddm()) + (ell_dist, azimuth1to2, azimuth2to1) = vincinv( + lat1_DMS.ddm(), lon1_DMS.ddm(), lat2_DMS.ddm(), lon2_DMS.ddm() + ) self.assertEqual(round(ell_dist, 3), 54972.271) self.assertEqual(round(dec2hp(azimuth1to2), 6), 306.520537) self.assertEqual(round(dec2hp(azimuth2to1), 6), 127.102507) - test2 = vincinv(lat1_DMS.ddm(), lon1_DMS.ddm(), - lat1_DMS.ddm(), lon1_DMS.ddm()) + test2 = vincinv(lat1_DMS.ddm(), lon1_DMS.ddm(), lat1_DMS.ddm(), lon1_DMS.ddm()) self.assertEqual(test2, (0, 0, 0)) def test_vincdir(self): @@ -125,15 +135,17 @@ def test_vincdir(self): self.assertEqual(round(dec2hp(azimuth2to1), 6), 127.102507) # Test DMSAngle Input - lat2, long2, azimuth2to1 = vincdir(lat1_DMS, lon1_DMS, - azimuth1to2_DMS, ell_dist) + lat2, long2, azimuth2to1 = vincdir( + lat1_DMS, lon1_DMS, azimuth1to2_DMS, ell_dist + ) self.assertEqual(round(dec2hp(lat2), 8), -37.39101561) self.assertEqual(round(dec2hp(long2), 8), 143.55353839) self.assertEqual(round(dec2hp(azimuth2to1), 6), 127.102507) # Test DDMAngle Input - lat2, long2, azimuth2to1 = vincdir(lat1_DMS.ddm(), lon1_DMS.ddm(), - azimuth1to2_DMS.ddm(), ell_dist) + lat2, long2, azimuth2to1 = vincdir( + lat1_DMS.ddm(), lon1_DMS.ddm(), azimuth1to2_DMS.ddm(), ell_dist + ) self.assertEqual(round(dec2hp(lat2), 8), -37.39101561) self.assertEqual(round(dec2hp(long2), 8), 143.55353839) self.assertEqual(round(dec2hp(azimuth2to1), 6), 127.102507) @@ -153,8 +165,9 @@ def test_vincinv_utm(self): north3 = 5828674.3402 # Test Coordinates in Zone 55 only - grid_dist, grid1to2, grid2to1, lsf = vincinv_utm(zone1, east1, north1, - zone2, east2, north2) + grid_dist, grid1to2, grid2to1, lsf = vincinv_utm( + zone1, east1, north1, zone2, east2, north2 + ) self.assertAlmostEqual(lsf, 1.00036397, 8) self.assertAlmostEqual(grid_dist, 54992.279, 3) self.assertAlmostEqual(dec2hp(grid1to2), 305.17017259, 7) @@ -162,8 +175,9 @@ def test_vincinv_utm(self): # Test Coordinates in Different Zones (55 and 54) # (Point 2 Grid Bearing Different (Zone 54 Grid Bearing)) - grid_dist, grid1to2, grid2to1, lsf = vincinv_utm(zone1, east1, north1, - zone3, east3, north3) + grid_dist, grid1to2, grid2to1, lsf = vincinv_utm( + zone1, east1, north1, zone3, east3, north3 + ) self.assertAlmostEqual(lsf, 1.00036397, 8) self.assertAlmostEqual(grid_dist, 54992.279, 3) self.assertAlmostEqual(dec2hp(grid1to2), 305.17017259, 7) @@ -180,9 +194,9 @@ def test_vincdir_utm(self): grid1to2_DMS = DMSAngle(305, 17, 1.7259) # Test Decimal Degrees Input - (zone2, east2, north2, - grid2to1, lsf) = vincdir_utm(zone1, east1, north1, - grid1to2, grid_dist) + (zone2, east2, north2, grid2to1, lsf) = vincdir_utm( + zone1, east1, north1, grid1to2, grid_dist + ) self.assertEqual(zone2, zone1) self.assertAlmostEqual(east2, 228854.0513, 3) self.assertAlmostEqual(north2, 5828259.0384, 3) @@ -190,9 +204,9 @@ def test_vincdir_utm(self): self.assertAlmostEqual(lsf, 1.00036397, 8) # Test DMSAngle Input - (zone2, east2, north2, - grid2to1, lsf) = vincdir_utm(zone1, east1, north1, - grid1to2_DMS, grid_dist) + (zone2, east2, north2, grid2to1, lsf) = vincdir_utm( + zone1, east1, north1, grid1to2_DMS, grid_dist + ) self.assertEqual(zone2, zone1) self.assertAlmostEqual(east2, 228854.0513, 3) self.assertAlmostEqual(north2, 5828259.0384, 3) @@ -203,39 +217,51 @@ def test_equality_vincentys(self): # Test multiple point-to-point vincinv calculations abs_path = os.path.abspath(os.path.dirname(__file__)) - test_geo_coords =\ - np.genfromtxt(os.path.join(abs_path, - 'resources/Test_Conversion_Geo.csv'), - delimiter=',', - dtype='S4,f8,f8', - names=['site', 'lat1', 'long1'], - usecols=('lat1', 'long1')) - - test_geo_coord2 = \ - np.genfromtxt(os.path.join(abs_path, - 'resources/Test_Conversion_Geo.csv'), - delimiter=',', - dtype='S4,f8,f8', - names=['site', 'lat2', 'long2'], - usecols=('lat2', 'long2')) + test_geo_coords = np.genfromtxt( + os.path.join(abs_path, "resources/Test_Conversion_Geo.csv"), + delimiter=",", + dtype="S4,f8,f8", + names=["site", "lat1", "long1"], + usecols=("lat1", "long1"), + ) + + test_geo_coord2 = np.genfromtxt( + os.path.join(abs_path, "resources/Test_Conversion_Geo.csv"), + delimiter=",", + dtype="S4,f8,f8", + names=["site", "lat2", "long2"], + usecols=("lat2", "long2"), + ) # Form array with point pairs from test file - test_pairs = rfn.merge_arrays([test_geo_coords, np.roll(test_geo_coord2, 1)], flatten=True) + test_pairs = rfn.merge_arrays( + [test_geo_coords, np.roll(test_geo_coord2, 1)], flatten=True + ) # Calculate Vincenty's Inverse Result using Lat, Long Pairs - vincinv_result = np.array(list(vincinv(*x) for x in test_pairs[['lat1', 'long1', 'lat2', 'long2']])) + vincinv_result = np.array( + list(vincinv(*x) for x in test_pairs[["lat1", "long1", "lat2", "long2"]]) + ) # Calculate Vincenty's Direct Result using Results from Inverse Function - vincdir_input = rfn.merge_arrays([test_geo_coords, vincinv_result[:, 1], vincinv_result[:, 0]], flatten=True) - vincdir_input.dtype.names = ['lat1', 'long1', 'az1to2', 'ell_dist'] - vincdir_result = np.array(list(vincdir(*x) for x in vincdir_input[['lat1', 'long1', 'az1to2', 'ell_dist']])) - - np.testing.assert_almost_equal(test_pairs['lat2'], - vincdir_result[:, 0], decimal=8) - np.testing.assert_almost_equal(test_pairs['long2'], - vincdir_result[:, 1], decimal=8) - np.testing.assert_almost_equal(vincinv_result[:, 2], - vincdir_result[:, 2]) + vincdir_input = rfn.merge_arrays( + [test_geo_coords, vincinv_result[:, 1], vincinv_result[:, 0]], flatten=True + ) + vincdir_input.dtype.names = ["lat1", "long1", "az1to2", "ell_dist"] + vincdir_result = np.array( + list( + vincdir(*x) + for x in vincdir_input[["lat1", "long1", "az1to2", "ell_dist"]] + ) + ) + + np.testing.assert_almost_equal( + test_pairs["lat2"], vincdir_result[:, 0], decimal=8 + ) + np.testing.assert_almost_equal( + test_pairs["long2"], vincdir_result[:, 1], decimal=8 + ) + np.testing.assert_almost_equal(vincinv_result[:, 2], vincdir_result[:, 2]) def test_vincinv_edgecases(self): lat1 = -32.153892 @@ -251,5 +277,5 @@ def test_vincinv_edgecases(self): self.assertEqual(az21, az21_2) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/geodepy/tests/test_statistics.py b/geodepy/tests/test_statistics.py index 3172c5f..561b347 100644 --- a/geodepy/tests/test_statistics.py +++ b/geodepy/tests/test_statistics.py @@ -4,23 +4,19 @@ lat = 19.4792453 lon = 70.69315634 -vcv = np.array([ - [1.44, -1.32, 1.32], - [-1.32, 1.22, -1.20], - [1.32, -1.20, 1.20] -]) -var = np.array([ - [1.44], [1.20], [1.20] -]) +vcv = np.array([[1.44, -1.32, 1.32], [-1.32, 1.22, -1.20], [1.32, -1.20, 1.20]]) +var = np.array([[1.44], [1.20], [1.20]]) class TestStatistics(unittest.TestCase): def test_rotation_matrix(self): - expected_result = np.array([ - [-0.94376147, -0.1102527, 0.3117028], - [0.33062712, -0.31471177, 0.88974278], - [0.0, 0.94276235, 0.33346538], - ]) + expected_result = np.array( + [ + [-0.94376147, -0.1102527, 0.3117028], + [0.33062712, -0.31471177, 0.88974278], + [0.0, 0.94276235, 0.33346538], + ] + ) result = statistics.rotation_matrix(lat, lon) @@ -28,11 +24,13 @@ def test_rotation_matrix(self): self.assertEqual(type(expected_result), type(result)) def test_vcv_cart2local_3x3(self): - expected_result = np.array([ - [2.23971834, -1.86955194, 0.3599339], - [-1.86955194, 1.55096504, -0.29615085], - [0.3599339, -0.29615085, 0.06931662] - ]) + expected_result = np.array( + [ + [2.23971834, -1.86955194, 0.3599339], + [-1.86955194, 1.55096504, -0.29615085], + [0.3599339, -0.29615085, 0.06931662], + ] + ) result = statistics.vcv_cart2local(vcv, lat, lon) @@ -40,9 +38,7 @@ def test_vcv_cart2local_3x3(self): self.assertEqual(type(expected_result), type(result)) def test_vcv_cart2local_3x1(self): - expected_result = np.array([ - [1.41376457], [1.20291736], [1.22331807] - ]) + expected_result = np.array([[1.41376457], [1.20291736], [1.22331807]]) result = statistics.vcv_cart2local(var, lat, lon) @@ -56,11 +52,13 @@ def test_vcv_cart2local_3X2(self): statistics.vcv_cart2local(v_cart, 0.0, 0.0) def test_vcv_local2cart_3X3(self): - expected_result = np.array([ - [0.44517136, -1.15507667, 0.44844663], - [-1.15507667, 2.95156126, -1.15249271], - [0.44844663, -1.15249271, 0.46326737] - ]) + expected_result = np.array( + [ + [0.44517136, -1.15507667, 0.44844663], + [-1.15507667, 2.95156126, -1.15249271], + [0.44844663, -1.15249271, 0.46326737], + ] + ) result = statistics.vcv_local2cart(vcv, lat, lon) @@ -68,9 +66,7 @@ def test_vcv_local2cart_3X3(self): self.assertEqual(type(expected_result), type(result)) def test_vcv_local2cart_3X1(self): - expected_result = np.array([ - [1.41376457], [1.20291736], [1.22331807] - ]) + expected_result = np.array([[1.41376457], [1.20291736], [1.22331807]]) result = statistics.vcv_cart2local(var, lat, lon) @@ -84,8 +80,7 @@ def test_vcv_local2cart_3X2(self): statistics.vcv_local2cart(v_cart, lat, lon) def test_error_ellipse(self): - expected_result = (1.6292867776015223, 0.07365185899111726, - 132.61817915463692) + expected_result = (1.6292867776015223, 0.07365185899111726, 132.61817915463692) result = statistics.error_ellipse(vcv) @@ -95,33 +90,39 @@ def test_error_ellipse(self): def test_relative_error(self): lat1 = -33.371389383333 lon1 = 145.673034975000 - var1 = np.array([ - [1.7671344090e-04, -8.9986817000e-05, 1.1789951440e-04], - [-8.9986817000e-05, 1.0963890720e-04, -7.1820721020e-05], - [1.1789951440e-04, -7.1820721020e-05, 1.4015891560e-04] - ]) - var2 = np.array([ - [1.7105233790e-04, -8.8389059620e-05, 1.1156043490e-04], - [-8.8389059620e-05, 1.0754378720e-04, -6.8637225330e-05], - [1.1156043490e-04, -6.8637225330e-05, 1.3097055280e-04] - ]) - cov12 = np.array([ - [1.5518691860e-04, -7.8107376300e-05, 1.0312885480e-04], - [-7.8392614650e-05, 9.5884929150e-05, -6.2159214580e-05], - [1.0310493330e-04, -6.1905790840e-05, 1.2226743540e-04] - ]) + var1 = np.array( + [ + [1.7671344090e-04, -8.9986817000e-05, 1.1789951440e-04], + [-8.9986817000e-05, 1.0963890720e-04, -7.1820721020e-05], + [1.1789951440e-04, -7.1820721020e-05, 1.4015891560e-04], + ] + ) + var2 = np.array( + [ + [1.7105233790e-04, -8.8389059620e-05, 1.1156043490e-04], + [-8.8389059620e-05, 1.0754378720e-04, -6.8637225330e-05], + [1.1156043490e-04, -6.8637225330e-05, 1.3097055280e-04], + ] + ) + cov12 = np.array( + [ + [1.5518691860e-04, -7.8107376300e-05, 1.0312885480e-04], + [-7.8392614650e-05, 9.5884929150e-05, -6.2159214580e-05], + [1.0310493330e-04, -6.1905790840e-05, 1.2226743540e-04], + ] + ) expected_results = ( 0.003122110653270988, 0.0028032035987053208, 133.65771030508103, - 0.008473125158578584 + 0.008473125158578584, ) results = statistics.relative_error(lat1, lon1, var1, var2, cov12) np.testing.assert_almost_equal(expected_results, results) self.assertEqual(type(expected_results), type(results)) - + def test_circ_hz_pu(self): a = 1 b = 0 @@ -163,5 +164,5 @@ def test_k_val95_between_1_and_120(self): self.assertEqual(expected_result, result) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/geodepy/tests/test_survey.py b/geodepy/tests/test_survey.py index ad06595..9a5349c 100644 --- a/geodepy/tests/test_survey.py +++ b/geodepy/tests/test_survey.py @@ -1,8 +1,15 @@ import unittest from geodepy.convert import DMSAngle -from geodepy.survey import (first_vel_params, first_vel_corrn, - mets_partial_differentials, precise_inst_ht, - va_conv, radiations, joins) +from geodepy.survey import ( + first_vel_params, + first_vel_corrn, + mets_partial_differentials, + precise_inst_ht, + va_conv, + radiations, + joins, +) + class TestSurveyConvert(unittest.TestCase): def test_first_vel_params(self): @@ -16,43 +23,52 @@ def test_first_vel_params(self): self.assertEqual(round(params_new[1], 3), param_d) def test_first_vel_corrn(self): - params = first_vel_params(0.85,49951.33424,1.000281783) + params = first_vel_params(0.85, 49951.33424, 1.000281783) raw_obs_distance = 1117.8517 obs_temperature = 6.8 obs_pressure = 960.8 obs_relative_humidity = 58.6 - correction = first_vel_corrn(raw_obs_distance, params, obs_temperature, - obs_pressure, obs_relative_humidity) + correction = first_vel_corrn( + raw_obs_distance, + params, + obs_temperature, + obs_pressure, + obs_relative_humidity, + ) corrected_obs_distance = raw_obs_distance + correction self.assertEqual(round(corrected_obs_distance, 4), 1117.8624) def test_first_vel_corrn_with_CO2(self): edm_wavelength = 0.850 - params = first_vel_params(edm_wavelength,49951.33424,1.000281783) + params = first_vel_params(edm_wavelength, 49951.33424, 1.000281783) raw_obs_distance = 1117.8517 obs_temperature = 6.8 obs_pressure = 960.8 obs_relative_humidity = 58.6 CO2_ppm_2022 = 420 - correction = first_vel_corrn(raw_obs_distance, params, obs_temperature, - obs_pressure, obs_relative_humidity, - CO2_ppm = CO2_ppm_2022, - wavelength = edm_wavelength) + correction = first_vel_corrn( + raw_obs_distance, + params, + obs_temperature, + obs_pressure, + obs_relative_humidity, + CO2_ppm=CO2_ppm_2022, + wavelength=edm_wavelength, + ) corrected_obs_distance = raw_obs_distance + correction self.assertEqual(round(corrected_obs_distance, 4), 1117.8624) - + def test_mets_partial_differentials(self): group_ref_Index = 1.00028 temp = 15 pressure = 1013.25 rel_humidity = 60 - K = round(0.91973804217265260,2) - L = round(0.2619533756724471,2) - M = round(0.03911157383307305,2) - differ_new = mets_partial_differentials(group_ref_Index, - temp, - pressure, - rel_humidity) + K = round(0.91973804217265260, 2) + L = round(0.2619533756724471, 2) + M = round(0.03911157383307305, 2) + differ_new = mets_partial_differentials( + group_ref_Index, temp, pressure, rel_humidity + ) self.assertEqual(round(differ_new[0], 2), K) self.assertEqual(round(differ_new[1], 2), L) self.assertEqual(round(differ_new[2], 2), M) @@ -80,7 +96,7 @@ def test_va_conv(self): self.assertAlmostEqual(test4[3], 1.69995, 5) with self.assertRaises(ValueError): va_conv(-3.62, 1.5) - va_conv('brian', 16) + va_conv("brian", 16) def test_precise_inst_ht(self): va1 = 99.50920833333333 @@ -111,20 +127,30 @@ def test_joins(self): self.assertAlmostEqual(test4[1], 172.0831, 4) def test_radiations(self): - test1 = radiations(500, 500, - DMSAngle(290, 13).dec(), 44.620, - DMSAngle(2, 18, 35).dec(), 1.002515) + test1 = radiations( + 500, + 500, + DMSAngle(290, 13).dec(), + 44.620, + DMSAngle(2, 18, 35).dec(), + 1.002515, + ) self.assertAlmostEqual(test1[0], 458.681, 3) self.assertAlmostEqual(test1[1], 517.137, 3) test2 = radiations(500, 500, DMSAngle(290, 13).dec(), 44.620) self.assertAlmostEqual(test2[0], 458.129, 3) self.assertAlmostEqual(test2[1], 515.419, 3) - test3 = radiations(564.747, 546.148, - DMSAngle(41, 7).dec(), 27.720, - DMSAngle(2, 18, 35).dec(), 1.002515) + test3 = radiations( + 564.747, + 546.148, + DMSAngle(41, 7).dec(), + 27.720, + DMSAngle(2, 18, 35).dec(), + 1.002515, + ) self.assertAlmostEqual(test3[0], 583.850, 3) self.assertAlmostEqual(test3[1], 566.331, 3) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/geodepy/tests/test_transform.py b/geodepy/tests/test_transform.py index 246be5e..6b67933 100644 --- a/geodepy/tests/test_transform.py +++ b/geodepy/tests/test_transform.py @@ -1,12 +1,14 @@ import unittest -from geodepy.transform import (conform7, - conform14, - atrf2014_to_gda2020, - transform_gda2020_to_atrf2014, - transform_atrf2014_to_gda2020, - transform_mga94_to_mga2020, - transform_mga2020_to_mga94) +from geodepy.transform import ( + conform7, + conform14, + atrf2014_to_gda2020, + transform_gda2020_to_atrf2014, + transform_atrf2014_to_gda2020, + transform_mga94_to_mga2020, + transform_mga2020_to_mga94, +) from geodepy.constants import itrf2014_to_gda2020, gda94_to_gda2020 from datetime import date @@ -20,70 +22,86 @@ def test_conform7(self): alic_gda2020 = (-4052052.7379, 4212835.9897, -2545104.5898) alic_gda2020_comp = conform7(*alic_gda1994, gda94_to_gda2020) alic_gda1994_comp = conform7(*alic_gda2020, -gda94_to_gda2020) - assert (abs(alic_gda2020_comp[0] - alic_gda2020[0]) < 5e-5) - assert (abs(alic_gda2020_comp[1] - alic_gda2020[1]) < 5e-5) - assert (abs(alic_gda2020_comp[2] - alic_gda2020[2]) < 5e-5) - assert (abs(alic_gda1994_comp[0] - alic_gda1994[0]) < 5e-5) - assert (abs(alic_gda1994_comp[1] - alic_gda1994[1]) < 5e-5) - assert (abs(alic_gda1994_comp[2] - alic_gda1994[2]) < 5e-5) + assert abs(alic_gda2020_comp[0] - alic_gda2020[0]) < 5e-5 + assert abs(alic_gda2020_comp[1] - alic_gda2020[1]) < 5e-5 + assert abs(alic_gda2020_comp[2] - alic_gda2020[2]) < 5e-5 + assert abs(alic_gda1994_comp[0] - alic_gda1994[0]) < 5e-5 + assert abs(alic_gda1994_comp[1] - alic_gda1994[1]) < 5e-5 + assert abs(alic_gda1994_comp[2] - alic_gda1994[2]) < 5e-5 def test_conform14(self): # Replication of tests in GDA2020 Tech Manual v1.2 - Sect 3.3.1 alic_gda2020 = (-4052052.7373, 4212835.9835, -2545104.5867) alic_itrf14at2018 = (-4052052.6588, 4212835.9938, -2545104.6946) - alic_itrf14at2018_comp = conform14(*alic_gda2020, date(2018, 1, 1), -itrf2014_to_gda2020) - alic_gda2020_comp = conform14(*alic_itrf14at2018, date(2018, 1, 1), itrf2014_to_gda2020) - assert (abs(alic_itrf14at2018_comp[0] - alic_itrf14at2018[0]) < 5e-5) - assert (abs(alic_itrf14at2018_comp[1] - alic_itrf14at2018[1]) < 5e-5) - assert (abs(alic_itrf14at2018_comp[2] - alic_itrf14at2018[2]) < 5e-5) - assert (abs(alic_gda2020_comp[0] - alic_gda2020[0]) < 5e-5) - assert (abs(alic_gda2020_comp[1] - alic_gda2020[1]) < 5e-5) - assert (abs(alic_gda2020_comp[2] - alic_gda2020[2]) < 5e-5) + alic_itrf14at2018_comp = conform14( + *alic_gda2020, date(2018, 1, 1), -itrf2014_to_gda2020 + ) + alic_gda2020_comp = conform14( + *alic_itrf14at2018, date(2018, 1, 1), itrf2014_to_gda2020 + ) + assert abs(alic_itrf14at2018_comp[0] - alic_itrf14at2018[0]) < 5e-5 + assert abs(alic_itrf14at2018_comp[1] - alic_itrf14at2018[1]) < 5e-5 + assert abs(alic_itrf14at2018_comp[2] - alic_itrf14at2018[2]) < 5e-5 + assert abs(alic_gda2020_comp[0] - alic_gda2020[0]) < 5e-5 + assert abs(alic_gda2020_comp[1] - alic_gda2020[1]) < 5e-5 + assert abs(alic_gda2020_comp[2] - alic_gda2020[2]) < 5e-5 def test_transform_atrf2014_to_gda2020(self): alic_gda2020 = (-4052052.7373, 4212835.9835, -2545104.5867) alic_itrf14at2018 = (-4052052.6588, 4212835.9938, -2545104.6946) - alic_itrf14at2018_comp = transform_gda2020_to_atrf2014(*alic_gda2020, date(2018, 1, 1)) - alic_gda2020_comp = transform_atrf2014_to_gda2020(*alic_itrf14at2018, date(2018, 1, 1)) - assert (abs(alic_itrf14at2018_comp[0] - alic_itrf14at2018[0]) < 5e-5) - assert (abs(alic_itrf14at2018_comp[1] - alic_itrf14at2018[1]) < 5e-5) - assert (abs(alic_itrf14at2018_comp[2] - alic_itrf14at2018[2]) < 5e-5) - assert (abs(alic_gda2020_comp[0] - alic_gda2020[0]) < 5e-5) - assert (abs(alic_gda2020_comp[1] - alic_gda2020[1]) < 5e-5) - assert (abs(alic_gda2020_comp[2] - alic_gda2020[2]) < 5e-5) + alic_itrf14at2018_comp = transform_gda2020_to_atrf2014( + *alic_gda2020, date(2018, 1, 1) + ) + alic_gda2020_comp = transform_atrf2014_to_gda2020( + *alic_itrf14at2018, date(2018, 1, 1) + ) + assert abs(alic_itrf14at2018_comp[0] - alic_itrf14at2018[0]) < 5e-5 + assert abs(alic_itrf14at2018_comp[1] - alic_itrf14at2018[1]) < 5e-5 + assert abs(alic_itrf14at2018_comp[2] - alic_itrf14at2018[2]) < 5e-5 + assert abs(alic_gda2020_comp[0] - alic_gda2020[0]) < 5e-5 + assert abs(alic_gda2020_comp[1] - alic_gda2020[1]) < 5e-5 + assert abs(alic_gda2020_comp[2] - alic_gda2020[2]) < 5e-5 def test_transform_mga94_to_mga2020(self): alic_mga94 = (53, 386352.3979, 7381850.7689, 603.3466) alic_mga20 = (53, 386353.2343, 7381852.2986, 603.2489) # Test with no ellipsoid height supplied - alic_mga20_noellht_comp = transform_mga94_to_mga2020(alic_mga94[0], alic_mga94[1], alic_mga94[2]) - assert ((alic_mga20_noellht_comp[0] - alic_mga20[0]) == 0) - assert (abs(alic_mga20_noellht_comp[1] - alic_mga20[1]) < 5e-5) - assert (abs(alic_mga20_noellht_comp[2] - alic_mga20[2]) < 5e-5) - assert (alic_mga20_noellht_comp[3] == 0) + alic_mga20_noellht_comp = transform_mga94_to_mga2020( + alic_mga94[0], alic_mga94[1], alic_mga94[2] + ) + assert (alic_mga20_noellht_comp[0] - alic_mga20[0]) == 0 + assert abs(alic_mga20_noellht_comp[1] - alic_mga20[1]) < 5e-5 + assert abs(alic_mga20_noellht_comp[2] - alic_mga20[2]) < 5e-5 + assert alic_mga20_noellht_comp[3] == 0 # Test with ellipsoid height supplied - alic_mga20_ellht_comp = transform_mga94_to_mga2020(alic_mga94[0], alic_mga94[1], alic_mga94[2], alic_mga94[3]) - assert ((alic_mga20_ellht_comp[0] - alic_mga20[0]) == 0) - assert (abs(alic_mga20_ellht_comp[1] - alic_mga20[1]) < 5e-5) - assert (abs(alic_mga20_ellht_comp[2] - alic_mga20[2]) < 5e-5) - assert (abs(alic_mga20_ellht_comp[3] - alic_mga20[3]) < 5e-5) + alic_mga20_ellht_comp = transform_mga94_to_mga2020( + alic_mga94[0], alic_mga94[1], alic_mga94[2], alic_mga94[3] + ) + assert (alic_mga20_ellht_comp[0] - alic_mga20[0]) == 0 + assert abs(alic_mga20_ellht_comp[1] - alic_mga20[1]) < 5e-5 + assert abs(alic_mga20_ellht_comp[2] - alic_mga20[2]) < 5e-5 + assert abs(alic_mga20_ellht_comp[3] - alic_mga20[3]) < 5e-5 def test_transform_mga2020_to_mga94(self): alic_mga94 = (53, 386352.3979, 7381850.7689, 603.3466) alic_mga20 = (53, 386353.2343, 7381852.2986, 603.2489) # Test with no ellipsoid height supplied - alic_mga94_noellht_comp = transform_mga2020_to_mga94(alic_mga20[0], alic_mga20[1], alic_mga20[2]) - assert ((alic_mga94_noellht_comp[0] - alic_mga94[0]) == 0) - assert (abs(alic_mga94_noellht_comp[1] - alic_mga94[1]) < 5e-5) - assert (abs(alic_mga94_noellht_comp[2] - alic_mga94[2]) < 5e-5) - assert (alic_mga94_noellht_comp[3] == 0) + alic_mga94_noellht_comp = transform_mga2020_to_mga94( + alic_mga20[0], alic_mga20[1], alic_mga20[2] + ) + assert (alic_mga94_noellht_comp[0] - alic_mga94[0]) == 0 + assert abs(alic_mga94_noellht_comp[1] - alic_mga94[1]) < 5e-5 + assert abs(alic_mga94_noellht_comp[2] - alic_mga94[2]) < 5e-5 + assert alic_mga94_noellht_comp[3] == 0 # Test with ellipsoid height supplied - alic_mga94_ellht_comp = transform_mga2020_to_mga94(alic_mga20[0], alic_mga20[1], alic_mga20[2], alic_mga20[3]) - assert ((alic_mga94_ellht_comp[0] - alic_mga94[0]) == 0) - assert (abs(alic_mga94_ellht_comp[1] - alic_mga94[1]) < 5e-5) - assert (abs(alic_mga94_ellht_comp[2] - alic_mga94[2]) < 5e-5) - assert (abs(alic_mga94_ellht_comp[3] - alic_mga94[3]) < 5e-5) + alic_mga94_ellht_comp = transform_mga2020_to_mga94( + alic_mga20[0], alic_mga20[1], alic_mga20[2], alic_mga20[3] + ) + assert (alic_mga94_ellht_comp[0] - alic_mga94[0]) == 0 + assert abs(alic_mga94_ellht_comp[1] - alic_mga94[1]) < 5e-5 + assert abs(alic_mga94_ellht_comp[2] - alic_mga94[2]) < 5e-5 + assert abs(alic_mga94_ellht_comp[3] - alic_mga94[3]) < 5e-5 -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/geodepy/transform.py b/geodepy/transform.py index c41c772..9e5aa1f 100644 --- a/geodepy/transform.py +++ b/geodepy/transform.py @@ -14,11 +14,14 @@ import datetime from math import radians import numpy as np -from geodepy.constants import (Transformation, TransformationSD, - atrf2014_to_gda2020, gda94_to_gda2020) +from geodepy.constants import ( + Transformation, + TransformationSD, + atrf2014_to_gda2020, + gda94_to_gda2020, +) from geodepy.statistics import vcv_local2cart, vcv_cart2local -from geodepy.convert import (hp2dec, geo2grid, - grid2geo, xyz2llh, llh2xyz) +from geodepy.convert import hp2dec, geo2grid, grid2geo, xyz2llh, llh2xyz from geodepy.ntv2reader import NTv2Grid, interpolate_ntv2 @@ -26,6 +29,7 @@ def conform7(x, y, z, trans, vcv=None): """ Performs a Helmert 7 Parameter Conformal Transformation using Cartesian point co-ordinates and a predefined transformation object. + :param x: Cartesian X (m) :param y: Cartesian Y (m) :param z: Cartesian Z (m) @@ -34,24 +38,18 @@ def conform7(x, y, z, trans, vcv=None): :return: Transformed X, Y, Z Cartesian Co-ordinates, vcv matrix """ if type(trans) != Transformation: - raise ValueError('trans must be a Transformation Object') + raise ValueError("trans must be a Transformation Object") # Create XYZ Vector - xyz_before = np.array([[x], - [y], - [z]]) + xyz_before = np.array([[x], [y], [z]]) # Convert Units for Transformation Parameters scale = 1 + trans.sc / 1000000 rx = radians(hp2dec(trans.rx / 10000)) ry = radians(hp2dec(trans.ry / 10000)) rz = radians(hp2dec(trans.rz / 10000)) # Create Translation Vector - translation = np.array([[trans.tx], - [trans.ty], - [trans.tz]]) + translation = np.array([[trans.tx], [trans.ty], [trans.tz]]) # Create Rotation Matrix - rotation = np.array([[1., rz, -ry], - [-rz, 1., rx], - [ry, -rx, 1.]]) + rotation = np.array([[1.0, rz, -ry], [-rz, 1.0, rx], [ry, -rx, 1.0]]) rot_xyz = rotation @ xyz_before @@ -74,10 +72,10 @@ def conform7(x, y, z, trans, vcv=None): q_mat[i, j] = vcv[i, j] # transformation variances - q_mat[3, 3] = (trans.tf_sd.sd_sc / 1000000)**2 - q_mat[4, 4] = radians(trans.tf_sd.sd_rx/3600)**2 - q_mat[5, 5] = radians(trans.tf_sd.sd_ry/3600)**2 - q_mat[6, 6] = radians(trans.tf_sd.sd_rz/3600)**2 + q_mat[3, 3] = (trans.tf_sd.sd_sc / 1000000) ** 2 + q_mat[4, 4] = radians(trans.tf_sd.sd_rx / 3600) ** 2 + q_mat[5, 5] = radians(trans.tf_sd.sd_ry / 3600) ** 2 + q_mat[6, 6] = radians(trans.tf_sd.sd_rz / 3600) ** 2 q_mat[7, 7] = trans.tf_sd.sd_tx**2 q_mat[8, 8] = trans.tf_sd.sd_ty**2 q_mat[9, 9] = trans.tf_sd.sd_tz**2 @@ -128,6 +126,7 @@ def conform14(x, y, z, to_epoch, trans, vcv=None): Performs a Helmert 14 Parameter Conformal Transformation using Cartesian point co-ordinates and a predefined transformation object. The transformation parameters are projected from the transformation objects reference epoch to a specified epoch. + :param x: Cartesian X (m) :param y: Cartesian Y (m) :param z: Cartesian Z (m) @@ -137,9 +136,9 @@ def conform14(x, y, z, to_epoch, trans, vcv=None): :return: Cartesian X, Y, Z co-ordinates and vcv matrix transformed using Transformation parameters at desired epoch """ if type(trans) != Transformation: - raise ValueError('trans must be a Transformation Object') + raise ValueError("trans must be a Transformation Object") if type(to_epoch) != datetime.date: - raise ValueError('to_epoch must be a datetime.date Object') + raise ValueError("to_epoch must be a datetime.date Object") # Calculate 7 Parameters from 14 Parameter Transformation Object timetrans = trans + to_epoch @@ -152,6 +151,7 @@ def transform_mga94_to_mga2020(zone, east, north, ell_ht=False, vcv=None): """ Performs conformal transformation of Map Grid of Australia 1994 to Map Grid of Australia 2020 Coordinates using the GDA2020 Tech Manual v1.2 7 parameter similarity transformation parameters + :param zone: Zone Number - 1 to 60 :param east: Easting (m, within 3330km of Central Meridian) :param north: Northing (m, 0 to 10,000,000m) @@ -181,6 +181,7 @@ def transform_mga2020_to_mga94(zone, east, north, ell_ht=False, vcv=None): """ Performs conformal transformation of Map Grid of Australia 2020 to Map Grid of Australia 1994 Coordinates using the reverse form of the GDA2020 Tech Manual v1.2 7 parameter similarity transformation parameters + :param zone: Zone Number - 1 to 60 :param east: Easting (m, within 3330km of Central Meridian) :param north: Northing (m, 0 to 10,000,000m) @@ -210,6 +211,7 @@ def transform_atrf2014_to_gda2020(x, y, z, epoch_from, vcv=None): """ Transforms Cartesian (x, y, z) Coordinates in terms of the Australian Terrestrial Reference Frame (ATRF) at a specified epoch to coordinates in terms of Geocentric Datum of Australia 2020 (GDA2020 - reference epoch 2020.0) + :param x: ATRF Cartesian X Coordinate (m) :param y: ATRF Cartesian Y Coordinate (m) :param z: ATRF Cartesian Z Coordinate (m) @@ -225,6 +227,7 @@ def transform_gda2020_to_atrf2014(x, y, z, epoch_to, vcv=None): Transforms Cartesian (x, y, z) Coordinates in terms of Geocentric Datum of Australia 2020 (GDA2020 - reference epoch 2020.0) to coordinates in terms of the Australian Terrestrial Reference Frame (ATRF) at a specified epoch + :param x: GDA2020 Cartesian X Coordinate (m) :param y: GDA2020 Cartesian Y Coordinate (m) :param z: GDA2020 Cartesian Z Coordinate (m) @@ -235,9 +238,10 @@ def transform_gda2020_to_atrf2014(x, y, z, epoch_to, vcv=None): return conform14(x, y, z, epoch_to, -atrf2014_to_gda2020, vcv=vcv) -def ntv2_2d(ntv2_grid, lat, lon, forward_tf=True, method='bicubic'): +def ntv2_2d(ntv2_grid, lat, lon, forward_tf=True, method="bicubic"): """ Performs a 2D transformation based on ntv2 grid shifts. + :param ntv2_grid: Ntv2Grid object (create with read_ntv2_file() function in geodepy.ntv2reader module) :param lat: latitude in decimal degrees :param lon: longitude in decimal degrees @@ -250,8 +254,8 @@ def ntv2_2d(ntv2_grid, lat, lon, forward_tf=True, method='bicubic'): # validate input data if not isinstance(ntv2_grid, NTv2Grid): - raise TypeError('ntv2_grid must be Ntv2Grid object') - if method != 'bicubic' and method != 'bilinear': + raise TypeError("ntv2_grid must be Ntv2Grid object") + if method != "bicubic" and method != "bilinear": raise ValueError(f'interpolation strategy "{method}" not supported') # interrogate grid @@ -259,7 +263,7 @@ def ntv2_2d(ntv2_grid, lat, lon, forward_tf=True, method='bicubic'): # null results are outside of grid extents. if shifts[0] is None: - raise ValueError('Coordinate outside of grid extents') + raise ValueError("Coordinate outside of grid extents") if forward_tf: tf_lat = lat + shifts[0] / 3600