├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── docs ├── Makefile ├── conf.py ├── curvsp.rst ├── data │ ├── cc.tfw │ ├── cc.tif │ ├── cc.tif.aux.xml │ ├── cc.tif.ovr │ ├── cc.tif.xml │ ├── center.CPG │ ├── center.dbf │ ├── center.sbn │ ├── center.sbx │ ├── center.shp │ ├── center.shp.xml │ ├── center.shx │ ├── homo_baseline.CPG │ ├── homo_baseline.dbf │ ├── homo_baseline.sbn │ ├── homo_baseline.sbx │ ├── homo_baseline.shp │ ├── homo_baseline.shp.xml │ ├── homo_baseline.shx │ ├── homo_start_end.CPG │ ├── homo_start_end.dbf │ ├── homo_start_end.sbn │ ├── homo_start_end.sbx │ ├── homo_start_end.shp │ ├── homo_start_end.shp.xml │ ├── homo_start_end.shx │ └── homo_stripe.tif ├── index.rst ├── make.bat ├── modules.rst ├── notebooks │ ├── Slice_hist.ipynb │ ├── cross_swath.ipynb │ ├── customized_swath.ipynb │ ├── density_scatter.ipynb │ ├── fix_radius_cir.ipynb │ ├── fix_width_curv.ipynb │ ├── object_homo.ipynb │ ├── pyosp_licking.ipynb │ ├── pyosp_olympus.ipynb │ ├── pyosp_teton.ipynb │ ├── reclass.ipynb │ └── swath_scatter.ipynb ├── pyosp.cirsp.rst ├── pyosp.curvsp.rst ├── pyosp.rst ├── setup.rst ├── tests.rst └── user_guide │ └── installation.rst ├── environment.yml ├── pyosp ├── __init__.py ├── _elevation.py ├── _slope.py ├── _tpi.py ├── cirsp │ ├── __init__.py │ ├── base_cir.py │ ├── elev_cir.py │ ├── orig_cir.py │ ├── slope_cir.py │ └── tpi_cir.py ├── curvsp │ ├── __init__.py │ ├── base_curv.py │ ├── elev_curv.py │ ├── orig_curv.py │ ├── slope_curv.py │ └── tpi_curv.py ├── datasets │ ├── __init__.py │ ├── center.CPG │ ├── center.dbf │ ├── center.sbn │ ├── center.sbx │ ├── center.shp │ ├── center.shp.xml │ ├── center.shx │ ├── checking_points.CPG │ ├── checking_points.dbf │ ├── checking_points.sbn │ ├── checking_points.sbx │ ├── checking_points.shp │ ├── checking_points.shp.xml │ ├── checking_points.shx │ ├── crater.tfw │ ├── crater.tif │ ├── crater.tif.aux.xml │ ├── crater.tif.ovr │ ├── crater.tif.xml │ ├── cross_data.txt │ ├── elev_valid.txt │ ├── homo_baseline.CPG │ ├── homo_baseline.dbf │ ├── homo_baseline.sbn │ ├── homo_baseline.sbx │ ├── homo_baseline.shp │ ├── homo_baseline.shp.xml │ ├── homo_baseline.shx │ ├── homo_baselineOffset.CPG │ ├── homo_baselineOffset.dbf │ ├── homo_baselineOffset.sbn │ ├── homo_baselineOffset.sbx │ ├── homo_baselineOffset.shp │ ├── homo_baselineOffset.shx │ ├── homo_elev10.tfw │ ├── homo_elev10.tif │ ├── homo_elev10.tif.aux.xml │ ├── homo_elev10.tif.ovr │ ├── homo_mount.tif │ ├── homo_start_end.CPG │ ├── homo_start_end.dbf │ ├── homo_start_end.sbn │ ├── homo_start_end.sbx │ ├── homo_start_end.shp │ ├── homo_start_end.shp.xml │ ├── homo_start_end.shx │ ├── slope_valid.txt │ └── tpi_valid.txt ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── test_cirBase.py │ ├── test_cirPost.py │ ├── test_cirSP.py │ ├── test_curvBase.py │ ├── test_curvPost.py │ └── test_curvSP.py └── util.py ├── readthedocs.yml ├── requirements.txt ├── requirements_dev.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .DS_Store 6 | 7 | # Distribution / packaging 8 | .Python 9 | env/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *.cover 45 | .hypothesis/ 46 | .pytest_cache/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Scrapy stuff: 53 | .scrapy 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # Jupyter Notebook 62 | .ipynb_checkpoints 63 | 64 | # pyenv 65 | .python-version 66 | 67 | # celery beat schedule file 68 | celerybeat-schedule 69 | 70 | # SageMath parsed files 71 | *.sage.py 72 | 73 | # dotenv 74 | .env 75 | 76 | # virtualenv 77 | .venv 78 | venv/ 79 | ENV/ 80 | 81 | # Spyder project settings 82 | .spyderproject 83 | .spyproject 84 | 85 | # Rope project settings 86 | .ropeproject 87 | 88 | # mkdocs documentation 89 | /site 90 | 91 | # mypy 92 | .mypy_cache/ 93 | 94 | # IDE settings 95 | .vscode/ 96 | *.d 97 | 98 | # paper and test 99 | paper/ 100 | issues/ 101 | 102 | # example and test 103 | intro/ 104 | example/ 105 | backup/ 106 | *.recipe/ 107 | 108 | # bak file 109 | *.bak 110 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "3.7" 5 | - "3.8" 6 | install: 7 | - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 8 | - bash miniconda.sh -b -p $HOME/miniconda 9 | - source "$HOME/miniconda/etc/profile.d/conda.sh" 10 | - hash -r 11 | - conda config --set always_yes yes --set changeps1 no 12 | - conda update -q conda 13 | - conda info -a 14 | 15 | - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION 16 | - conda activate test-environment 17 | - conda install -c conda-forge --file requirements.txt 18 | - conda install -c conda-forge pytest-cov coveralls 19 | - python setup.py install 20 | 21 | script: 22 | - pytest -v --cov=pyosp --cov-report=term-missing 23 | 24 | after_script: 25 | - python setup.py clean 26 | 27 | after_success: 28 | - coveralls || echo "!! intermittent coveralls failure" 29 | 30 | notifications: 31 | email: false 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) 2020, Yichuan Zhu 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include pyosp/datasets * 2 | include LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | pyosp logo

3 |

4 | github release (latest by date) 5 | TravisCI 6 | Coverage Status 7 | Conda Downloads 8 | publication 9 |

10 |

11 | 12 | --- 13 | 14 |

intro

15 | 16 | _Intelligent and comprehensive swath analysis_ 17 | 18 | ## Features 19 | 20 | - :gem: **Intelligent**: objectively identify irregular boundries using elevation, slope, TPI, or other raster analyses. 21 | - :milky_way: **Comprehensive**: cuvilinear and circular swath analyses, reclassification of swath data, cross-swath, slice and histogram, etc. 22 | - :two_women_holding_hands: **Compatible**: work seamlessly with GIS software. 23 | - :anchor: **Dependencies**: numpy, matplotlib, gdal, scipy and shapely. 24 | 25 | ## Documentation 26 | Read the documentation at: https://pyosp.readthedocs.io/en/latest/index.html 27 | 28 | Introduction, methodology, and case studies: https://doi.org/10.1016/j.geomorph.2021.107778 29 | 30 | Applications (starting from scratch): 31 | 1. [Topographic analysis of Teton Range, Wyoming](https://pyosp.readthedocs.io/en/latest/notebooks/pyosp_teton.html) 32 | 2. [Terrace correlation along the Licking River, Kentucky](https://pyosp.readthedocs.io/en/latest/notebooks/pyosp_licking.html) 33 | 3. [Circular swath analysis of Olympus Mons, Mars](https://pyosp.readthedocs.io/en/latest/notebooks/pyosp_olympus.html) 34 | 35 | ## Installation 36 | We recommend to use the [conda](https://conda.io/en/latest/) package manager to install pyosp. It will provide pre-built binaries for all dependencies of pyosp. If you have the [miniconda](https://docs.conda.io/en/latest/miniconda.html) (recommend; only containing python and the conda package manager), or [anaconda distribution](https://www.anaconda.com/) (a python distribution with many installed libraries for data science) installed, then simply execute the following command: 37 | 38 | ```bash 39 | conda install -c conda-forge pyosp 40 | ``` 41 | 42 | ## Installing in a new environment (recommended) 43 | 44 | Although it is not required, installing the library in a clean environment represents 45 | good practice and helps avoid potential dependency conflicts. We also recommends install 46 | all dependencies with pyosp through conda-forge channel 47 | 48 | ```bash 49 | conda create -n env_pyosp 50 | conda activate env_pyosp 51 | conda config --env --add channels conda-forge 52 | conda config --env --set channel_priority strict 53 | conda install python=3 pyosp 54 | ``` 55 | 56 | 57 | ### You can also install from current branch: 58 | 59 | ```bash 60 | git clone https://github.com/pyosp-devs/pyosp.git 61 | cd pyosp 62 | conda install --file requirements.txt 63 | python setup.py install 64 | ``` 65 | 66 | You can verify installation by entering a python shell and typing: 67 | 68 | ```python 69 | import pyosp 70 | print(pyosp.__version__) 71 | ``` 72 | 73 | ## Example 74 | Here is a simple example of using pyosp to perform swath analysis on a synthetic mountain case. The cross-width of mountain is around 90m, and flat ground has elevation of zero. 75 | 76 |

homo_case

77 | 78 | Original, elevation, slope and tpi based swath calculation. 79 | 80 | ```python 81 | import pyosp 82 | 83 | baseline = pyosp.datasets.get_path("homo_baseline.shp") # the path to baseline shapefile 84 | raster = pyosp.datasets.get_path("homo_mount.tif") # the path to raster file 85 | 86 | orig = pyosp.Orig_curv(baseline, raster, width=100, 87 | line_stepsize=3, cross_stepsize=None) 88 | 89 | elev = pyosp.Elev_curv(baseline, raster, width=100, 90 | min_elev=0.01, 91 | line_stepsize=3, cross_stepsize=None) 92 | 93 | slope = pyosp.Slope_curv(baseline, raster, width=100, 94 | min_slope=1, 95 | line_stepsize=3, cross_stepsize=None) 96 | 97 | tpi = pyosp.Tpi_curv(baseline, raster, width=100, 98 | tpi_radius=50, min_tpi=0, 99 | line_stepsize=3, cross_stepsize=None) 100 | ``` 101 | 102 | We can plot with matplotlib, or open in GIS software. 103 | 104 |

homo_polygon

105 | 106 | Plot, for example, elevation based swath profile. 107 | 108 | ```python 109 | elev.profile_plot() 110 | ``` 111 | 112 | elev_sp

113 | 114 | _For more example and usage, please refer to our documentation._ 115 | 116 | ## Citing pyosp 117 | If you use PyOSP for your work, please cite as: 118 | 119 | Y. Zhu, J.M. Dortch, M.A. Massey, et al., An Intelligent Swath Tool to Characterize complex Topographic Features: Theory and Application in the Teton Range, Licking River, and Olympus Mons, Geomorphology (2021), https://doi.org/10.1016/j.geomorph.2021.107778 120 | 121 | ## Contributing 122 | 123 | Any contributions you make are **greatly appreciated**. 124 | 125 | 1. Fork the project 126 | 2. Create your feature branch (`git checkout -b feature/amazingfeature`) 127 | 3. Commit your changes (`git commit -m 'add some amazingfeature'`) 128 | 4. Push to the branch (`git push origin feature/amazingfeature`) 129 | 5. Open a pull request 130 | 131 | ## Feedback 132 | 133 | - If you think pyosp is useful, consider giving it a star. 134 | - If something is not working, [create an issue](https://github.com/pyosp-devs/pyosp/issues/new) 135 | - If you need to get in touch for other reasons, [send us an email](yichuan211@gmail.com) 136 | 137 | ## Credits 138 | This work is supported by [Kentucky Geological Survey](https://www.uky.edu/kgs/). 139 | 140 | ## License 141 | [Apache license, version 2.0](https://github.com/pyosp-devs/pyosp/blob/master/license) 142 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | # add these directories to sys.path here. If the directory is relative to the 9 | # documentation root, use os.path.abspath to make it absolute, like shown here. 10 | # 11 | import os 12 | import sys 13 | sys.path.insert(0, os.path.abspath('..')) 14 | 15 | import pyosp 16 | 17 | # -- Project information ----------------------------------------------------- 18 | 19 | project = 'pyosp' 20 | copyright = '2021, PyOSP developers' 21 | author = 'PyOSP developers' 22 | 23 | # The full version, including alpha/beta/rc tags 24 | version = pyosp.__version__ 25 | release = pyosp.__version__ 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'nbsphinx', 36 | 'sphinx.ext.mathjax' 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # 45 | source_suffix = ['.rst', '.md'] 46 | # source_suffix = '.rst' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # List of patterns, relative to source directory, that match files and 52 | # directories to ignore when looking for source files. 53 | # This pattern also affects html_static_path and html_extra_path. 54 | exclude_patterns = ['_build', '**.ipynb_checkpoints', 'Thumbs.db', '.DS_Store'] 55 | 56 | nbsphinx_execute = "never" 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | # 61 | # This is also used if you do content translation via gettext catalogs. 62 | # Usually you set "language" from the command line for these cases. 63 | language = None 64 | 65 | # The name of Pygments (syntax highlighting) style to use. 66 | pygments_style = 'friendly' 67 | 68 | # If true, `todo` and `todoList` produce output, else they produce nothing. 69 | todo_include_todos = False 70 | 71 | # -- Options for HTML output ------------------------------------------------- 72 | 73 | # The theme to use for HTML and HTML Help pages. See the documentation for 74 | # a list of builtin themes. 75 | # 76 | html_theme = 'sphinx_rtd_theme' 77 | 78 | # Add any paths that contain custom static files (such as style sheets) here, 79 | # relative to this directory. They are copied after the builtin static files, 80 | # so a file named "default.css" will overwrite the builtin "default.css". 81 | html_static_path = ['_static'] 82 | 83 | -------------------------------------------------------------------------------- /docs/curvsp.rst: -------------------------------------------------------------------------------- 1 | curvsp package 2 | ============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | curvsp.base\_curv module 8 | ------------------------ 9 | 10 | .. automodule:: curvsp.base_curv 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | curvsp.elev\_curv module 16 | ------------------------ 17 | 18 | .. automodule:: curvsp.elev_curv 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | curvsp.orig\_curv module 24 | ------------------------ 25 | 26 | .. automodule:: curvsp.orig_curv 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | curvsp.slope\_curv module 32 | ------------------------- 33 | 34 | .. automodule:: curvsp.slope_curv 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | curvsp.tpi\_curv module 40 | ----------------------- 41 | 42 | .. automodule:: curvsp.tpi_curv 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: curvsp 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/data/cc.tfw: -------------------------------------------------------------------------------- 1 | 0.8000932000 2 | 0.0000000000 3 | 0.0000000000 4 | -0.8000932000 5 | -0.0142000000 6 | 200.0131000000 7 | -------------------------------------------------------------------------------- /docs/data/cc.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/cc.tif -------------------------------------------------------------------------------- /docs/data/cc.tif.aux.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generic 4 | 5 | 6 | 7 | 8 | -20.72613906860352 9 | 11.33687114715576 10 | 256 11 | 1 12 | 0 13 | 2|10|8|9|14|21|8|21|31|36|37|37|52|40|55|46|45|44|63|60|52|63|60|67|80|57|70|64|58|62|61|78|82|71|100|100|92|80|82|72|68|58|70|62|35|48|50|44|52|53|41|53|50|60|58|58|61|70|58|60|64|75|60|63|57|45|60|52|53|58|51|47|56|55|50|45|44|40|55|40|45|57|44|40|51|41|63|48|35|45|44|46|48|41|38|38|43|40|34|47|40|28|48|35|28|38|31|40|31|41|27|46|37|23|33|37|34|34|35|29|38|38|28|38|38|36|31|37|35|33|33|38|34|32|34|38|36|41|46|26|40|36|47|47|28|40|50|51|34|41|46|41|34|38|47|38|45|42|39|38|37|41|28|56|6051|5527|2669|2095|1587|1324|1233|1181|1089|985|991|920|854|830|805|730|774|714|661|627|617|576|543|517|480|469|437|419|413|418|447|401|418|422|391|428|403|403|391|399|379|399|392|392|371|365|414|446|477|459|434|402|419|418|377|412|384|373|319|343|350|332|334|329|340|366|409|361|327|359|362|450|446|368|358|278|203|207|180|131|116|56|33|24|35|25|23|18|13|6|1|1 14 | 15 | 16 | 17 | ATHEMATIC 18 | 32.4179752566205 19 | 20 | 11.336871147156 21 | 1.2412949046922 22 | -20.726139068604 23 | 1 24 | 1 25 | 5.6936785347103 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/data/cc.tif.ovr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/cc.tif.ovr -------------------------------------------------------------------------------- /docs/data/cc.tif.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200816150056001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\cc.tifLocal Area Network 3 | -------------------------------------------------------------------------------- /docs/data/center.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /docs/data/center.dbf: -------------------------------------------------------------------------------- 1 | xAIdN 0 -------------------------------------------------------------------------------- /docs/data/center.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/center.sbn -------------------------------------------------------------------------------- /docs/data/center.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/center.sbx -------------------------------------------------------------------------------- /docs/data/center.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/center.shp -------------------------------------------------------------------------------- /docs/data/center.shp.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200816150018001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\centerLocal Area Network 3 | -------------------------------------------------------------------------------- /docs/data/center.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/center.shx -------------------------------------------------------------------------------- /docs/data/homo_baseline.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /docs/data/homo_baseline.dbf: -------------------------------------------------------------------------------- 1 | xAIdN 0 -------------------------------------------------------------------------------- /docs/data/homo_baseline.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_baseline.sbn -------------------------------------------------------------------------------- /docs/data/homo_baseline.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_baseline.sbx -------------------------------------------------------------------------------- /docs/data/homo_baseline.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_baseline.shp -------------------------------------------------------------------------------- /docs/data/homo_baseline.shp.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200812101048001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\homo_baselineLocal Area Network 3 | -------------------------------------------------------------------------------- /docs/data/homo_baseline.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_baseline.shx -------------------------------------------------------------------------------- /docs/data/homo_start_end.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /docs/data/homo_start_end.dbf: -------------------------------------------------------------------------------- 1 | xAIdN 0 0 -------------------------------------------------------------------------------- /docs/data/homo_start_end.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_start_end.sbn -------------------------------------------------------------------------------- /docs/data/homo_start_end.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_start_end.sbx -------------------------------------------------------------------------------- /docs/data/homo_start_end.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_start_end.shp -------------------------------------------------------------------------------- /docs/data/homo_start_end.shp.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200815194556001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\start_endLocal Area Network 3 | -------------------------------------------------------------------------------- /docs/data/homo_start_end.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_start_end.shx -------------------------------------------------------------------------------- /docs/data/homo_stripe.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/docs/data/homo_stripe.tif -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | PyOSP 2 | ===== 3 | 4 | PyOSP (Python Object-oriented Swath Profile) is an intelligent swath tool that can characterize complex 5 | topographic features. Unlike traditional swath methods have been limited to rectangular or simplified 6 | curvilinear sampling blocks, PyOSP can objectively characterize complex geomorphic boundaries using 7 | elevation, slope angle, topographic position index (TPI), or other raster calculations by intelligently 8 | assimilating geo-processing information into swath analysis. 9 | 10 | PyOSP supports Python 3.6 or higher, and depends on `Numpy `_ , `Matplotlib 11 | `_ , `GDAL `_ , `SciPy `_, and `Shapely `_ . 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | :caption: Getting Started 16 | 17 | Installation 18 | Traditional curvilinear swath analysis 19 | Traditional circular swath analysis 20 | Object-oriented swath analysis 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | :caption: Tutorial 25 | 26 | Customized swath analysis and essential data structure 27 | Slice and histogram analysis 28 | Swath profile with scatter plots 29 | Cross-swath analysis 30 | Density(Heat) scatter plot 31 | Data reclassification 32 | 33 | .. toctree:: 34 | :maxdepth: 1 35 | :caption: Applications 36 | 37 | Topographic analysis of the Teton Range, Wyoming 38 | Terrace correlation along Licking River, Kentucky 39 | Swath analysis of Olympus Mons, Mars 40 | 41 | Indices and tables 42 | ================== 43 | 44 | * :ref:`genindex` 45 | * :ref:`modindex` 46 | * :ref:`search` 47 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | pyosp 2 | ===== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pyosp 8 | setup 9 | tests 10 | -------------------------------------------------------------------------------- /docs/pyosp.cirsp.rst: -------------------------------------------------------------------------------- 1 | pyosp.cirsp package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyosp.cirsp.base\_cir module 8 | ---------------------------- 9 | 10 | .. automodule:: pyosp.cirsp.base_cir 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyosp.cirsp.elev\_cir module 16 | ---------------------------- 17 | 18 | .. automodule:: pyosp.cirsp.elev_cir 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyosp.cirsp.orig\_cir module 24 | ---------------------------- 25 | 26 | .. automodule:: pyosp.cirsp.orig_cir 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyosp.cirsp.slope\_cir module 32 | ----------------------------- 33 | 34 | .. automodule:: pyosp.cirsp.slope_cir 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyosp.cirsp.tpi\_cir module 40 | --------------------------- 41 | 42 | .. automodule:: pyosp.cirsp.tpi_cir 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: pyosp.cirsp 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/pyosp.curvsp.rst: -------------------------------------------------------------------------------- 1 | pyosp.curvsp package 2 | ==================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyosp.curvsp.base\_curv module 8 | ------------------------------ 9 | 10 | .. automodule:: pyosp.curvsp.base_curv 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyosp.curvsp.elev\_curv module 16 | ------------------------------ 17 | 18 | .. automodule:: pyosp.curvsp.elev_curv 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyosp.curvsp.orig\_curv module 24 | ------------------------------ 25 | 26 | .. automodule:: pyosp.curvsp.orig_curv 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyosp.curvsp.slope\_curv module 32 | ------------------------------- 33 | 34 | .. automodule:: pyosp.curvsp.slope_curv 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyosp.curvsp.tpi\_curv module 40 | ----------------------------- 41 | 42 | .. automodule:: pyosp.curvsp.tpi_curv 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: pyosp.curvsp 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/pyosp.rst: -------------------------------------------------------------------------------- 1 | pyosp package 2 | ============= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | pyosp.cirsp 11 | pyosp.curvsp 12 | pyosp.tests.bak 13 | 14 | Submodules 15 | ---------- 16 | 17 | pyosp.util module 18 | ----------------- 19 | 20 | .. automodule:: pyosp.util 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | 25 | Module contents 26 | --------------- 27 | 28 | .. automodule:: pyosp 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | -------------------------------------------------------------------------------- /docs/setup.rst: -------------------------------------------------------------------------------- 1 | setup module 2 | ============ 3 | 4 | .. automodule:: setup 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/tests.rst: -------------------------------------------------------------------------------- 1 | tests package 2 | ============= 3 | 4 | Submodules 5 | ---------- 6 | 7 | tests.conftest module 8 | --------------------- 9 | 10 | .. automodule:: tests.conftest 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | tests.test\_cirBase module 16 | -------------------------- 17 | 18 | .. automodule:: tests.test_cirBase 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | tests.test\_cirSP module 24 | ------------------------ 25 | 26 | .. automodule:: tests.test_cirSP 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | tests.test\_curvBase module 32 | --------------------------- 33 | 34 | .. automodule:: tests.test_curvBase 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | tests.test\_curvSP module 40 | ------------------------- 41 | 42 | .. automodule:: tests.test_curvSP 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: tests 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/user_guide/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | =============== 3 | 4 | Installing with Anaconda / conda 5 | -------------------------------- 6 | 7 | The easiest way to install PyOSP is through `conda 8 | `_ 9 | with the following command:: 10 | 11 | conda install -c conda-forge pyosp 12 | 13 | Installing in a new environment (recommended) 14 | --------------------------------------------- 15 | 16 | Although it is not required, installing the library in a clean environment represents 17 | good practice and helps avoid potential dependency conflicts. We also recommends install 18 | all dependencies with pyosp through conda-forge channel:: 19 | 20 | conda create -n env_pyosp 21 | conda activate env_pyosp 22 | conda config --env --add channels conda-forge 23 | conda config --env --set channel_priority strict 24 | conda install python=3 pyosp 25 | 26 | Installing from source 27 | ---------------------- 28 | You may install the development version by cloning the source repository 29 | and installed locally: 30 | 31 | .. code-block:: bash 32 | 33 | git clone https://github.com/PyOSP-devs/PyOSP.git 34 | cd pyosp 35 | conda install --file requirements.txt 36 | python setup.py install 37 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: pyosp 2 | 3 | channels: 4 | - conda-forge 5 | 6 | dependencies: 7 | - numpy 8 | - matplotlib 9 | - gdal >=3 10 | - shapely >=1.6* 11 | - pytest 12 | - nbsphinx 13 | - pandoc 14 | - ipython 15 | - scipy 16 | -------------------------------------------------------------------------------- /pyosp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __version__ = "0.1.7" 4 | 5 | from .curvsp import * 6 | from .cirsp import * 7 | from .util import * 8 | from ._elevation import * 9 | from ._slope import * 10 | from ._tpi import * 11 | 12 | import pyosp.datasets 13 | -------------------------------------------------------------------------------- /pyosp/_elevation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class Point_elevation: 5 | """Get point elevation by given raster. 6 | 7 | :param point: point coordinates 8 | :type point: array-like 9 | :param raster: GeoRaster read by GDAL 10 | :type raster: GDAL dataset 11 | """ 12 | 13 | def __init__(self, point, raster): 14 | self.p = point 15 | self.raster = raster 16 | self.geoTransform = self.raster.GetGeoTransform() 17 | 18 | def point_position(self): 19 | x = int((self.p[0] - self.geoTransform[0]) / self.geoTransform[1]) 20 | y = int((self.geoTransform[3] - self.p[1]) / -self.geoTransform[5]) 21 | return x, y 22 | 23 | @property 24 | def value(self): 25 | px, py = self.point_position() 26 | return self.raster.ReadAsArray(px, py, 1, 1).astype(float) 27 | -------------------------------------------------------------------------------- /pyosp/_slope.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | 6 | class Geo_slope: 7 | def __init__(self, point, raster, cell_size): 8 | """Return a geo-slope value of the point. 9 | 10 | :param point: point coordinates 11 | :type point: array-like 12 | :param raster: GeoRaster read by GDAL 13 | :type raster: GDAL dataset 14 | :param cell_size: cell-size for slope calculation 15 | :type cell_size: float 16 | """ 17 | self.p = point 18 | self.raster = raster 19 | self.geoTransform = self.raster.GetGeoTransform() 20 | self.cell_size = cell_size 21 | 22 | def point_position(self): 23 | x = int((self.p[0] - self.geoTransform[0]) / self.geoTransform[1]) 24 | y = int((self.geoTransform[3] - self.p[1]) / -self.geoTransform[5]) 25 | return y, x 26 | 27 | def raster_window(self): 28 | py, px = self.point_position() 29 | rasterMatrix = self.raster.ReadAsArray().astype(float) 30 | 31 | # pad to the edge 32 | rasterPad = np.pad(rasterMatrix, (1,), "edge") 33 | 34 | return rasterPad[py : py + 3, px : px + 3] 35 | 36 | @property 37 | def value(self): 38 | window = self.raster_window() 39 | 40 | rise = ( 41 | (window[0, 2] + 2 * window[1, 2] + window[2, 2]) 42 | - (window[0, 0] + 2 * window[1, 0] + window[2, 0]) 43 | ) / (8 * self.cell_size) 44 | run = ( 45 | (window[2, 0] + 2 * window[2, 1] + window[2, 2]) 46 | - (window[0, 0] + 2 * window[0, 1] + window[0, 2]) 47 | ) / (8 * self.cell_size) 48 | dist = np.sqrt(np.square(rise) + np.square(run)) 49 | 50 | return np.arctan(dist) * 180 / np.pi 51 | -------------------------------------------------------------------------------- /pyosp/_tpi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | 6 | class Tpi: 7 | def __init__(self, point_coord, raster, radius): 8 | """Calculate the TPI value of the point 9 | 10 | :param point_coord: point coodinates 11 | :type point_coord: array-like 12 | :param raster: GeoRaster read by GDAL 13 | :type raster: GDAL dataset 14 | :param radius: radius of TPI window 15 | :type radius: float 16 | """ 17 | self.p = point_coord 18 | self.raster = raster 19 | self.cols = raster.RasterXSize 20 | self.rows = raster.RasterYSize 21 | self.geoTransform = raster.GetGeoTransform() 22 | self.radiusInPixel = int(radius / self.geoTransform[1]) 23 | 24 | def point_position(self): 25 | " Define the point position on the raster" 26 | x = int((self.p[0] - self.geoTransform[0]) / self.geoTransform[1]) 27 | y = int((self.geoTransform[3] - self.p[1]) / -self.geoTransform[5]) 28 | return y, x 29 | 30 | def avg_window(self): 31 | "Calculate the average raster value within the window, exclude the central point." 32 | py, px = self.point_position() 33 | xmin = max(0, px - self.radiusInPixel) 34 | xmax = min(self.cols, px + self.radiusInPixel + 1) 35 | ymin = max(0, py - self.radiusInPixel) 36 | ymax = min(self.rows, py + self.radiusInPixel + 1) 37 | arr = self.raster.ReadAsArray( 38 | xoff=xmin, yoff=ymin, xsize=xmax - xmin, ysize=ymax - ymin 39 | ).astype(float) 40 | # Treat small values as no data 41 | arr_min = np.min(arr) 42 | arr[arr == arr_min] = np.nan 43 | avg = (np.nansum(arr) - self.point_value()) / (np.sum(~np.isnan(arr)) - 1) 44 | return avg 45 | 46 | def point_value(self): 47 | py, px = self.point_position() 48 | return self.raster.ReadAsArray(xoff=px, yoff=py, xsize=1, ysize=1)[0] 49 | 50 | @property 51 | def value(self): 52 | return self.point_value() - self.avg_window() 53 | -------------------------------------------------------------------------------- /pyosp/cirsp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = ["Base_cir", "Orig_cir", "Elev_cir", "Slope_cir", "Tpi_cir"] 4 | 5 | from .base_cir import Base_cir 6 | from .orig_cir import Orig_cir 7 | from .elev_cir import Elev_cir 8 | from .slope_cir import Slope_cir 9 | from .tpi_cir import Tpi_cir 10 | -------------------------------------------------------------------------------- /pyosp/cirsp/base_cir.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from osgeo import gdal 4 | from shapely.geometry import Polygon, LineString, MultiLineString 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from .._elevation import Point_elevation 8 | from ..util import read_shape 9 | 10 | 11 | class Base_cir: 12 | """Abstract class for circular swath profile. 13 | 14 | :param center: path to center shapefile 15 | :type center: str 16 | :param raster: path to GeoRaster 17 | :type raster: str 18 | :param radius: radius of swath area 19 | :type radius: float 20 | :param ng_start: starting angle 21 | :type ng_start: int, optional 22 | :param ng_end: ending angle 23 | :type ng_end: int, optional 24 | :param ng_stepsize: angular step-size, defaults to 1 25 | :type ng_stepsize: int, optional 26 | :param radial_stepsize: radial step-size, defaults to None 27 | :type radial_stepsize: int, optional 28 | """ 29 | 30 | def __init__( 31 | self, 32 | center, 33 | raster, 34 | radius, 35 | ng_start=None, 36 | ng_end=None, 37 | ng_stepsize=1, 38 | radial_stepsize=None, 39 | ): 40 | # Empty swath profile is line or raster is None 41 | if center is None or raster is None: 42 | return 43 | else: 44 | self.center = read_shape(center) 45 | self.raster = gdal.Open(raster) 46 | 47 | self.radius = radius 48 | 49 | # Identify the boundary of raster 50 | geoTransform = self.raster.GetGeoTransform() 51 | cols = self.raster.RasterXSize 52 | rows = self.raster.RasterYSize 53 | self.rasterXmin = geoTransform[0] 54 | self.rasterXmax = geoTransform[0] + geoTransform[1] * (cols - 1) 55 | self.rasterYmax = geoTransform[3] 56 | self.rasterYmin = geoTransform[3] + geoTransform[5] * (rows - 1) 57 | 58 | # Angular parameters 59 | if not ((0.0 <= ng_start <= 360) and (0.0 <= ng_end <= 360)): 60 | raise AttributeError("start and end values should be " "between 0 and 360.") 61 | else: 62 | if ng_start >= ng_end: 63 | raise AttributeError("angular start point should be " "less than end.") 64 | 65 | self.ng_start = ng_start 66 | self.ng_end = ng_end 67 | self.ng_stepsize = ng_stepsize 68 | 69 | # Using cell size if radial_stepsize is None 70 | if radial_stepsize is None: 71 | self.radial_stepsize = geoTransform[1] 72 | else: 73 | self.radial_stepsize = radial_stepsize 74 | 75 | # swath data 76 | self.distance = np.arange(0.0, self.radius + 1e-10, self.radial_stepsize) 77 | self.lines = self._radial_lines() 78 | if self.lines == None: 79 | return 80 | else: 81 | self.dat_steps = max(len(x) for x in self.lines) 82 | self.dat = self.swath_data() 83 | 84 | def _radial_lines(self): 85 | """ 86 | Depend on different swath methods 87 | """ 88 | pass 89 | 90 | def out_polygon(self): 91 | "Return a shapely polygon object" 92 | try: 93 | coords = list(self.center.coords) + [x[-1] for x in self.lines] 94 | except IndexError: 95 | raise Exception("Empty swath profile, please try reset arguments.") 96 | 97 | poly = Polygon(coords) 98 | return poly 99 | 100 | def out_polylines(self): 101 | "Return a shapely polyline object" 102 | l_points = [x[0] for x in self.lines] 103 | r_points = [x[-1] for x in self.lines[::-1]] 104 | 105 | lines = list(zip(l_points, r_points[::-1])) 106 | return MultiLineString(lines) 107 | 108 | def out_polyring(self, start=None, end=None): 109 | "Return a polygon showing a range of distances" 110 | if start is not None and isinstance(start, (int, float)): 111 | start_ind = np.abs(self.distance - start).argmin() 112 | else: 113 | start_ind = 0 114 | 115 | if end is not None and isinstance(end, (int, float)): 116 | end_ind = np.abs(self.distance - end).argmin() 117 | else: 118 | end_ind = len(self.distance) - 1 119 | 120 | l_points = [] 121 | r_points = [] 122 | for x in self.lines: 123 | if end_ind <= len(x) - 1: 124 | l_points.append(x[start_ind]) 125 | r_points.append(x[end_ind]) 126 | elif start_ind <= len(x) - 1 and end_ind > len(x) - 1: 127 | l_points.append(x[start_ind]) 128 | r_points.append(x[-1]) 129 | else: 130 | l_points.append(x[-1]) 131 | r_points.append(x[-1]) 132 | 133 | poly = Polygon(l_points + r_points[::-1]) 134 | return poly 135 | 136 | def swath_data(self): 137 | "Return a list of elevation data along each profileline" 138 | lines_dat = [] 139 | for line in self.lines: 140 | points_temp = [] 141 | for point in line: 142 | rasterVal = Point_elevation(point, self.raster).value 143 | points_temp.append(rasterVal) 144 | 145 | line_temp = np.append( 146 | points_temp, np.repeat(np.nan, self.dat_steps - len(points_temp)) 147 | ) 148 | lines_dat.append(line_temp) 149 | 150 | return lines_dat 151 | 152 | def profile_stat(self): 153 | "Return a list of summary statistics along each profileline" 154 | min_z = [np.nanmin(x) for x in list(zip(*self.dat))] 155 | max_z = [np.nanmax(x) for x in list(zip(*self.dat))] 156 | mean_z = [np.nanmean(x) for x in list(zip(*self.dat))] 157 | q1 = [np.nanpercentile(x, q=25) for x in list(zip(*self.dat))] 158 | q3 = [np.nanpercentile(x, q=75) for x in list(zip(*self.dat))] 159 | 160 | return [min_z, max_z, mean_z, q1, q3] 161 | 162 | def profile_plot(self, ax=None, color="navy", p_coords=None, **kwargs): 163 | d = np.linspace( 164 | 0, self.radial_stepsize * self.dat_steps, self.dat_steps, endpoint=True 165 | ) 166 | stat = self.profile_stat() 167 | 168 | if ax is None: 169 | fig, ax = plt.subplots() 170 | 171 | ax.plot(d, stat[2], "k-", label="mean elevation") 172 | ax.fill_between( 173 | d, stat[0], stat[1], alpha=0.3, facecolor=color, label="min_max relief" 174 | ) 175 | ax.fill_between( 176 | d, stat[3], stat[4], alpha=0.7, facecolor=color, label="q1_q3 relief" 177 | ) 178 | 179 | if p_coords is not None: 180 | dist_array = np.empty(0) 181 | elev_array = np.empty(0) 182 | for p in p_coords: 183 | dist = np.linalg.norm(np.array(p) - np.array(self.center.coords)) 184 | elev = Point_elevation(p, self.raster).value 185 | dist_array = np.append(dist_array, dist) 186 | elev_array = np.append(elev_array, elev) 187 | 188 | ax.scatter(dist_array, elev_array, **kwargs) 189 | 190 | ax.set_xlabel("Distance") 191 | ax.set_ylabel("Elevation") 192 | ax.legend() 193 | plt.tight_layout() 194 | return ax 195 | 196 | def slice_plot(self, angle, ax=None): 197 | """Plot cross-section of swath data. 198 | 199 | :param angle: the angle of cross-section wrt horizontal line 200 | :type angle: int 201 | """ 202 | if not self.ng_start <= angle <= self.ng_end: 203 | raise ValueError("angle should be between ng_start and ng_end.") 204 | 205 | sector = np.arange(self.ng_start, self.ng_end + 1e-10, self.ng_stepsize) 206 | ng_ind = np.abs(sector - angle).argmin() 207 | points = self.lines[ng_ind] 208 | values = self.dat[ng_ind] 209 | values = values[~np.isnan(values)] 210 | 211 | d = [] 212 | for point in points: 213 | d_temp = np.sqrt( 214 | (point[0] - points[0][0]) ** 2 + (point[1] - points[0][1]) ** 2 215 | ) 216 | d.append(d_temp) 217 | 218 | if ax is None: 219 | fig, ax = plt.subplots() 220 | 221 | ax.plot(d, values) 222 | ax.set_xlabel("Distance to center") 223 | ax.set_ylabel("Elevation") 224 | ax.grid() 225 | plt.tight_layout() 226 | return ax 227 | 228 | def slice_polyline(self, angle): 229 | """Return the polyline of cross-section 230 | 231 | :param angle: angle of cross-section wrt horizontal line 232 | :type angle: int 233 | :return: a shapely polyline object 234 | """ 235 | if not self.ng_start <= angle <= self.ng_end: 236 | raise Exception("angle should be between ng_start and ng_end.") 237 | 238 | sector = np.arange(self.ng_start, self.ng_end + 1e-10, self.ng_stepsize) 239 | ng_ind = np.abs(sector - angle).argmin() 240 | points = self.lines[ng_ind] 241 | line = list((points[0], points[-1])) 242 | 243 | return LineString(line) 244 | 245 | def hist(self, bins=50, ax=None): 246 | "Return a histogram plot" 247 | dat = np.hstack(self.dat) 248 | 249 | if ax is None: 250 | fig, ax = plt.subplots() 251 | 252 | ax.hist(dat, bins=bins, histtype="stepfilled", alpha=1, density=True) 253 | ax.set_xlabel("Elevation") 254 | ax.set_ylabel("PDF") 255 | ax.grid() 256 | plt.tight_layout() 257 | return ax 258 | 259 | def slice_hist(self, angle, bins=10, ax=None): 260 | """Plot the histogram of slice 261 | 262 | :param angle: angle of cross-section wrt horizontal line 263 | :type angle: int 264 | :param bins: number of bins, defaults to 10 265 | :type bins: int, optional 266 | """ 267 | if not self.ng_start <= angle <= self.ng_end: 268 | raise ValueError("angle should be between ng_start and ng_end.") 269 | 270 | sector = np.arange(self.ng_start, self.ng_end + 1e-10, self.ng_stepsize) 271 | ng_ind = np.abs(sector - angle).argmin() 272 | dat = self.dat[ng_ind] 273 | 274 | if ax is None: 275 | fig, ax = plt.subplots() 276 | 277 | ax.hist(dat, bins=bins, histtype="stepfilled", alpha=1, density=True) 278 | ax.set_xlabel("Elevation") 279 | ax.set_ylabel("PDF") 280 | ax.grid() 281 | plt.tight_layout() 282 | return ax 283 | -------------------------------------------------------------------------------- /pyosp/cirsp/elev_cir.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | from .base_cir import Base_cir 5 | from .._elevation import Point_elevation 6 | from ..util import progressBar 7 | import warnings 8 | 9 | 10 | class Elev_cir(Base_cir): 11 | """Elevation-based circular swath profile characterization. 12 | 13 | :param center: path to center shapefile 14 | :type center: str 15 | :param raster: path to GeoRaster 16 | :type raster: str 17 | :param radius: radius of swath area 18 | :type radius: float 19 | :param min_elev: minimal elevation threshold of swath apron 20 | :type min_elev: float 21 | :param ng_start: starting angle 22 | :type ng_start: int, optional 23 | :param ng_end: ending angle 24 | :type ng_end: int, optional 25 | :param ng_stepsize: angular step-size, defaults to 1 26 | :type ng_stepsize: int, optional 27 | :param radial_stepsize: radial step-size, defaults to None 28 | :type radial_stepsize: int, optional 29 | """ 30 | 31 | def __init__( 32 | self, 33 | center, 34 | raster, 35 | radius, 36 | min_elev=float("-inf"), 37 | ng_start=0, 38 | ng_end=360, 39 | ng_stepsize=1, 40 | radial_stepsize=None, 41 | ): 42 | self.min_elev = min_elev 43 | 44 | super(Elev_cir, self).__init__( 45 | center, raster, radius, ng_start, ng_end, ng_stepsize, radial_stepsize 46 | ) 47 | 48 | def __repr__(self): 49 | return "{}".format(self.__class__.__name__) 50 | 51 | def _radial_lines(self): 52 | num = (self.ng_end - self.ng_start) // self.ng_stepsize 53 | sector = list(np.arange(self.ng_start, self.ng_end + 0.00001, self.ng_stepsize)) 54 | radial_line = list(np.arange(0.0, self.radius + 0.00001, self.radial_stepsize)) 55 | 56 | lines = [] 57 | for ng in sector: 58 | line_temp = [] 59 | line_elev = [] 60 | slope = np.radians(ng) 61 | for r in radial_line: 62 | dx = r * np.cos(slope) 63 | dy = r * np.sin(slope) 64 | p = [self.center.x + dx, self.center.y + dy] 65 | 66 | p_elev = Point_elevation(p, self.raster).value 67 | 68 | if not ( 69 | (self.rasterXmin <= p[0] <= self.rasterXmax) 70 | and (self.rasterYmin <= p[1] <= self.rasterYmax) 71 | and (p_elev > -1e20) 72 | ): 73 | break 74 | 75 | line_temp.append(p) 76 | line_elev.append(p_elev) 77 | 78 | # find the maximum elevation point of the line 79 | max_ind = line_elev.index(max(line_elev)) 80 | 81 | if max_ind == len(line_elev) - 1: 82 | lines.append(line_temp) 83 | warnings.warn("Radius is small, not reach the rim top.") 84 | elif all(i > self.min_elev for i in line_elev[max_ind:None]): 85 | lines.append(line_temp) 86 | elif all(i < self.min_elev for i in line_elev[max_ind:None]): 87 | raise Exception( 88 | "allowed minimum elevation is too big or " "radius is too small." 89 | ) 90 | else: 91 | for i in range(max_ind, len(line_elev)): 92 | if line_elev[i] < self.min_elev: 93 | lines.append(line_temp[0:i]) 94 | break 95 | 96 | current = sector.index(ng) 97 | progressBar(current, num) 98 | 99 | return lines 100 | -------------------------------------------------------------------------------- /pyosp/cirsp/orig_cir.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import numpy as np 5 | 6 | from ..util import progressBar 7 | from .base_cir import Base_cir 8 | from .._elevation import Point_elevation 9 | 10 | 11 | class Orig_cir(Base_cir): 12 | """Original circular swath profile characterization. 13 | 14 | :param center: path to center shapefile 15 | :type center: str 16 | :param raster: path to GeoRaster 17 | :type raster: str 18 | :param radius: radius of swath area 19 | :type radius: float 20 | :param ng_start: starting angle 21 | :type ng_start: int, optional 22 | :param ng_end: ending angle 23 | :type ng_end: int, optional 24 | :param ng_stepsize: angular step-size, defaults to 1 25 | :type ng_stepsize: int, optional 26 | :param radial_stepsize: radial step-size, defaults to None 27 | :type radial_stepsize: int, optional 28 | """ 29 | 30 | def __init__( 31 | self, 32 | center, 33 | raster, 34 | radius, 35 | ng_start=0, 36 | ng_end=360, 37 | ng_stepsize=1, 38 | radial_stepsize=None, 39 | ): 40 | 41 | super(Orig_cir, self).__init__( 42 | center, raster, radius, ng_start, ng_end, ng_stepsize, radial_stepsize 43 | ) 44 | 45 | def __repr__(self): 46 | return "{}".format(self.__class__.__name__) 47 | 48 | def _radial_lines(self): 49 | num = (self.ng_end - self.ng_start) // self.ng_stepsize 50 | sector = list(np.arange(self.ng_start, self.ng_end + 0.00001, self.ng_stepsize)) 51 | radial_line = list(np.arange(0.0, self.radius + 0.00001, self.radial_stepsize)) 52 | 53 | lines = [] 54 | for ng in sector: 55 | line_temp = [] 56 | slope = np.radians(ng) 57 | for r in radial_line: 58 | dx = r * np.cos(slope) 59 | dy = r * np.sin(slope) 60 | p = [self.center.x + dx, self.center.y + dy] 61 | 62 | rasterVal = Point_elevation(p, self.raster).value 63 | if not ( 64 | (self.rasterXmin <= p[0] <= self.rasterXmax) 65 | and (self.rasterYmin <= p[1] <= self.rasterYmax) 66 | and (rasterVal > -1e20) 67 | ): 68 | break 69 | 70 | line_temp.append(p) 71 | 72 | lines.append(line_temp) 73 | 74 | current = sector.index(ng) 75 | progressBar(current, num) 76 | 77 | return lines 78 | -------------------------------------------------------------------------------- /pyosp/cirsp/slope_cir.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | from osgeo import gdal 5 | from .base_cir import Base_cir 6 | from .._slope import Geo_slope 7 | from .._elevation import Point_elevation 8 | from ..util import progressBar 9 | import warnings 10 | 11 | 12 | class Slope_cir(Base_cir): 13 | """Slope_based circular swath profile characterization. 14 | 15 | :param center: path to center shapefile 16 | :type center: str 17 | :param raster: path to GeoRaster 18 | :type raster: str 19 | :param radius: radius of swath area 20 | :type radius: float 21 | :param min_slope: minimal slope threshold of swath apron 22 | :type min_slope: float 23 | :param ng_start: starting angle 24 | :type ng_start: int, optional 25 | :param ng_end: ending angle 26 | :type ng_end: int, optional 27 | :param ng_stepsize: angular step-size, defaults to 1 28 | :type ng_stepsize: int, optional 29 | :param radial_stepsize: radial step-size, defaults to None 30 | :type radial_stepsize: int, optional 31 | """ 32 | 33 | def __init__( 34 | self, 35 | center, 36 | raster, 37 | radius, 38 | min_slope=float("-inf"), 39 | ng_start=0, 40 | ng_end=360, 41 | ng_stepsize=1, 42 | radial_stepsize=None, 43 | ): 44 | self.min_slope = min_slope 45 | # self.max_slope = max_slope 46 | self.cell_size = gdal.Open(raster).GetGeoTransform()[1] 47 | 48 | super(Slope_cir, self).__init__( 49 | center, raster, radius, ng_start, ng_end, ng_stepsize, radial_stepsize 50 | ) 51 | 52 | def __repr__(self): 53 | return "{}".format(self.__class__.__name__) 54 | 55 | def _radial_lines(self): 56 | num = (self.ng_end - self.ng_start) // self.ng_stepsize 57 | sector = list(np.arange(self.ng_start, self.ng_end + 0.00001, self.ng_stepsize)) 58 | radial_line = list(np.arange(0.0, self.radius + 0.00001, self.radial_stepsize)) 59 | 60 | lines = [] 61 | for ng in sector: 62 | line_temp = [] 63 | line_elev = [] 64 | line_slope = [] 65 | slope = np.radians(ng) 66 | for r in radial_line: 67 | dx = r * np.cos(slope) 68 | dy = r * np.sin(slope) 69 | p = [self.center.x + dx, self.center.y + dy] 70 | 71 | p_elev = Point_elevation(p, self.raster).value 72 | p_slope = Geo_slope(p, self.raster, self.cell_size).value 73 | 74 | if not ( 75 | (self.rasterXmin <= p[0] <= self.rasterXmax) 76 | and (self.rasterYmin <= p[1] <= self.rasterYmax) 77 | and (p_elev > -1e20) 78 | ): 79 | break 80 | 81 | line_temp.append(p) 82 | line_elev.append(p_elev) 83 | line_slope.append(p_slope) 84 | 85 | # find the maximum elevation point of the line 86 | max_ind = line_elev.index(max(line_elev)) 87 | 88 | if max_ind == len(line_elev) - 1: 89 | lines.append(line_temp) 90 | warnings.warn("Radius is small, not reach the rim top.") 91 | elif all(i > self.min_slope for i in line_slope[max_ind:None]): 92 | lines.append(line_temp) 93 | elif all(i < self.min_slope for i in line_slope[max_ind:None]): 94 | raise Exception( 95 | "allowed minimum slope is too big " "or radius is too small" 96 | ) 97 | else: 98 | for i in range(max_ind, len(line_elev)): 99 | if line_slope[i] < self.min_slope: 100 | lines.append(line_temp[0:i]) 101 | break 102 | 103 | current = sector.index(ng) 104 | progressBar(current, num) 105 | 106 | return lines 107 | -------------------------------------------------------------------------------- /pyosp/cirsp/tpi_cir.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | from .base_cir import Base_cir 5 | from .._tpi import Tpi 6 | from .._elevation import Point_elevation 7 | from ..util import progressBar 8 | import warnings 9 | 10 | 11 | class Tpi_cir(Base_cir): 12 | """Elevation-based circular swath profile characterization. 13 | 14 | :param center: path to center shapefile 15 | :type center: str 16 | :param raster: path to GeoRaster 17 | :type raster: str 18 | :param radius: radius of swath area 19 | :type radius: float 20 | :param tpi_radius: radius of TPI window 21 | :type tpi_radius: float 22 | :param min_tpi: minimal TPI threshold of swath apron 23 | :type min_tpi: float, defaults to -inf 24 | :param ng_start: starting angle 25 | :type ng_start: int, optional 26 | :param ng_end: ending angle 27 | :type ng_end: int, optional 28 | :param ng_stepsize: angular step-size, defaults to 1 29 | :type ng_stepsize: int, optional 30 | :param radial_stepsize: radial step-size, defaults to None 31 | :type radial_stepsize: int, optional 32 | """ 33 | 34 | def __init__( 35 | self, 36 | center, 37 | raster, 38 | radius, 39 | tpi_radius, 40 | min_tpi=float("-inf"), 41 | ng_start=0, 42 | ng_end=360, 43 | ng_stepsize=1, 44 | radial_stepsize=None, 45 | ): 46 | self.tpi_radius = tpi_radius 47 | self.min_tpi = min_tpi 48 | # self.max_tpi= max_tpi 49 | 50 | super(Tpi_cir, self).__init__( 51 | center, raster, radius, ng_start, ng_end, ng_stepsize, radial_stepsize 52 | ) 53 | 54 | def __repr__(self): 55 | return "{}".format(self.__class__.__name__) 56 | 57 | def _radial_lines(self): 58 | num = (self.ng_end - self.ng_start) // self.ng_stepsize 59 | sector = list(np.arange(self.ng_start, self.ng_end + 0.00001, self.ng_stepsize)) 60 | radial_line = list(np.arange(0.0, self.radius + 0.00001, self.radial_stepsize)) 61 | 62 | lines = [] 63 | for ng in sector: 64 | line_temp = [] 65 | line_elev = [] 66 | line_tpi = [] 67 | slope = np.radians(ng) 68 | for r in radial_line: 69 | dx = r * np.cos(slope) 70 | dy = r * np.sin(slope) 71 | p = [self.center.x + dx, self.center.y + dy] 72 | 73 | p_elev = Point_elevation(p, self.raster).value 74 | p_tpi = Tpi(p, self.raster, self.tpi_radius).value 75 | 76 | if not ( 77 | (self.rasterXmin <= p[0] <= self.rasterXmax) 78 | and (self.rasterYmin <= p[1] <= self.rasterYmax) 79 | and (p_elev > -1e20) 80 | ): 81 | break 82 | 83 | line_temp.append(p) 84 | line_elev.append(p_elev) 85 | line_tpi.append(p_tpi) 86 | 87 | # find the maximum elevation point of the line 88 | max_ind = line_elev.index(max(line_elev)) 89 | 90 | if max_ind == len(line_elev) - 1: 91 | lines.append(line_temp) 92 | warnings.warn("Radius is small, not reach the rim top.") 93 | elif all(i > self.min_tpi for i in line_tpi[max_ind:None]): 94 | lines.append(line_temp) 95 | elif all(i < self.min_tpi for i in line_tpi[max_ind:None]): 96 | raise Exception( 97 | "allowed minimum TPI is too big " "or radius is too small" 98 | ) 99 | else: 100 | for i in range(max_ind, len(line_elev)): 101 | if line_tpi[i] < self.min_tpi: 102 | lines.append(line_temp[0:i]) 103 | break 104 | 105 | current = sector.index(ng) 106 | progressBar(current, num) 107 | 108 | return lines 109 | -------------------------------------------------------------------------------- /pyosp/curvsp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = ["Base_curv", "Elev_curv", "Orig_curv", "Slope_curv", "Tpi_curv"] 4 | 5 | from .base_curv import Base_curv 6 | from .elev_curv import Elev_curv 7 | from .orig_curv import Orig_curv 8 | from .slope_curv import Slope_curv 9 | from .tpi_curv import Tpi_curv 10 | -------------------------------------------------------------------------------- /pyosp/curvsp/base_curv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from osgeo import gdal 4 | from shapely.geometry import Polygon, MultiLineString, Point 5 | import numpy as np 6 | from scipy.interpolate import interpn 7 | from matplotlib.colors import Normalize 8 | from matplotlib import cm 9 | import matplotlib.pyplot as plt 10 | from .._elevation import Point_elevation 11 | from .._slope import Geo_slope 12 | from .._tpi import Tpi 13 | from ..util import read_shape, point_coords 14 | import copy 15 | 16 | 17 | class Base_curv: 18 | """Abstract class for cuvilinear swath profile. 19 | 20 | :param line: path to baseline shapefile 21 | :type line: str 22 | :param raster: path to GeoTiff 23 | :type raster: str 24 | :param width: maximum allowed width of swath profile 25 | :type width: Float 26 | :param line_stepsize: step-size along baseline, defaults to resolution of raster 27 | :type line_stepsize: float, optional 28 | :param cross_stepsize: step-size along profilelines, defaults to resolution of raster 29 | :type cross_stepsize: float, optional 30 | """ 31 | 32 | def __init__(self, line, raster, width, line_stepsize=None, cross_stepsize=None): 33 | # Empty swath profile is line, width or raster is None 34 | if None in (line, raster, width): 35 | return 36 | else: 37 | self.line = read_shape(line) 38 | self.raster = gdal.Open(raster) 39 | self.width = width 40 | 41 | # Identify the boundary of raster 42 | geoTransform = self.raster.GetGeoTransform() 43 | cols = self.raster.RasterXSize 44 | rows = self.raster.RasterYSize 45 | self.rasterXmin = geoTransform[0] 46 | self.rasterXmax = geoTransform[0] + geoTransform[1] * (cols - 1) 47 | self.rasterYmax = geoTransform[3] 48 | self.rasterYmin = geoTransform[3] + geoTransform[5] * (rows - 1) 49 | 50 | # Using raster resolution if line_stepsize is None 51 | self.cell_res = geoTransform[1] 52 | 53 | if line_stepsize is None: 54 | self.line_stepsize = self.cell_res 55 | self.line_p = self._line_points(self.cell_res) 56 | else: 57 | self.line_stepsize = line_stepsize 58 | self.line_p = self._line_points(line_stepsize) 59 | 60 | # Using raster resolution if cross_stepsize is None 61 | if cross_stepsize is None: 62 | self.cross_stepsize = self.cell_res 63 | else: 64 | self.cross_stepsize = cross_stepsize 65 | 66 | # swath data 67 | self.distance = np.arange(0.0, self.line.length + 1e-10, self.line_stepsize) 68 | self.lines = self._transect_lines() 69 | self.dat = self.swath_data() 70 | 71 | def _line_points(self, line_stepsize): 72 | nPoints = int(self.line.length // line_stepsize) 73 | coords = [] 74 | for i in range(nPoints + 1): 75 | p = tuple(self.line.interpolate(line_stepsize * i, normalized=False).coords) 76 | coords.append(p[0]) 77 | 78 | return coords 79 | 80 | def _transect_lines(self): 81 | """ 82 | Depend on different terrain type 83 | """ 84 | pass 85 | 86 | def _segment(self, start=None, end=None): 87 | if start is not None and isinstance(start, (int, float)): 88 | start_ind = np.abs(self.distance - start).argmin() 89 | elif start is not None and len(start) == 2: 90 | line_coords = np.asarray(self.line_p) 91 | start_coord = np.asarray(start) 92 | distance = np.sum((line_coords - start_coord) ** 2, axis=1) 93 | start_ind = np.argmin(distance) 94 | else: 95 | start_ind = start 96 | 97 | if end is not None and isinstance(start, (int, float)): 98 | end_ind = np.abs(self.distance - end).argmin() 99 | elif end is not None and len(end) == 2: 100 | line_coords = np.asarray(self.line_p) 101 | end_coord = np.asarray(end) 102 | distance = np.sum((line_coords - end_coord) ** 2, axis=1) 103 | end_ind = np.argmin(distance) 104 | else: 105 | end_ind = end 106 | 107 | return start_ind, end_ind 108 | 109 | def out_polygon(self, start=None, end=None): 110 | """Generate output polygon. 111 | 112 | :param start: starting position of polygon, defaults to starting point of baseline 113 | :type start: float or array-like, optional 114 | :param end: ending position of polygon, defaults to ending point of baseline 115 | :type end: float or array-like, optional 116 | :return: polygon of swath area 117 | """ 118 | start_ind, end_ind = self._segment(start, end) 119 | line_segment = self.lines[start_ind:end_ind] 120 | 121 | try: 122 | l_points = [x[0] for x in line_segment if x] 123 | r_points = [x[-1] for x in line_segment[::-1] if x] 124 | except IndexError: 125 | raise Exception("Empty swath profile, please try reset arguments.") 126 | 127 | coords = np.vstack((l_points, r_points)) 128 | poly = Polygon(coords).buffer(0.01) 129 | return poly 130 | 131 | def out_polylines(self, start=None, end=None): 132 | """Generate output polyline. 133 | 134 | :param start: starting position of Polyline, defaults to start of baseline 135 | :type start: float or array-like, optional 136 | :param end: ending position of Polyline, defaults to end of baseline 137 | :type end: float or array-like, optional 138 | :return: polylines of swath area 139 | """ 140 | start_ind, end_ind = self._segment(start, end) 141 | line_segment = self.lines[start_ind:end_ind] 142 | 143 | try: 144 | l_points = [x[0] for x in line_segment if x] 145 | r_points = [x[-1] for x in line_segment[::-1] if x] 146 | except IndexError: 147 | raise Exception("Empty swath profile, please try reset arguments.") 148 | 149 | lines = list(zip(l_points, r_points[::-1])) 150 | 151 | return MultiLineString(lines) 152 | 153 | def swath_data(self): 154 | """Return a list of elevation data along each profileline""" 155 | lines_dat = [] 156 | try: 157 | for line in self.lines: 158 | points_temp = [] 159 | for point in line: 160 | rasterVal = Point_elevation(point, self.raster).value[0, 0] 161 | points_temp.append(rasterVal) 162 | 163 | lines_dat.append(points_temp) 164 | except TypeError: 165 | pass 166 | 167 | return lines_dat 168 | 169 | def profile_stat(self, z): 170 | """Return a list of summary statistics along each profileline""" 171 | min_z = [np.nanmin(x) if len(x) > 0 else np.nan for x in z] 172 | max_z = [np.nanmax(x) if len(x) > 0 else np.nan for x in z] 173 | mean_z = [np.nanmean(x) if len(x) > 0 else np.nan for x in z] 174 | q1 = [np.nanpercentile(x, q=25) if len(x) > 0 else np.nan for x in z] 175 | q3 = [np.nanpercentile(x, q=75) if len(x) > 0 else np.nan for x in z] 176 | 177 | return [min_z, max_z, mean_z, q1, q3] 178 | 179 | def plot( 180 | self, 181 | distance, 182 | stat, 183 | points=None, 184 | cross=False, 185 | start=None, 186 | end=None, 187 | ax=None, 188 | color="navy", 189 | **kwargs 190 | ): 191 | """Summary statistics plot.""" 192 | start_ind, end_ind = self._segment(start, end) 193 | if ax is None: 194 | fig, ax = plt.subplots() 195 | 196 | ax.plot( 197 | distance[start_ind:end_ind], 198 | stat[2][start_ind:end_ind], 199 | "k-", 200 | linewidth=1.5, 201 | label="mean elevation", 202 | ) 203 | ax.fill_between( 204 | distance[start_ind:end_ind], 205 | stat[0][start_ind:end_ind], 206 | stat[1][start_ind:end_ind], 207 | alpha=0.3, 208 | facecolor=color, 209 | label="min_max relief", 210 | ) 211 | ax.fill_between( 212 | distance[start_ind:end_ind], 213 | stat[3][start_ind:end_ind], 214 | stat[4][start_ind:end_ind], 215 | alpha=0.7, 216 | facecolor=color, 217 | label="q1_q3 relief", 218 | ) 219 | 220 | if points is not None: 221 | p_coords = point_coords(points) 222 | dist_array = np.empty(0) 223 | elev_array = np.empty(0) 224 | for p in p_coords: 225 | point = Point(p) 226 | if cross is True: 227 | # check if the points on the left or right side of baseline 228 | dist_noSign = point.distance(self.line) 229 | line_leftOff = self.line.parallel_offset(distance=1e-5, side="left") 230 | dist_leftLine = point.distance(line_leftOff) 231 | if dist_noSign <= dist_leftLine: 232 | dist = dist_noSign 233 | else: 234 | dist = -dist_noSign 235 | else: 236 | dist = self.line.project(point) 237 | elev = Point_elevation(p, self.raster).value 238 | dist_array = np.append(dist_array, dist) 239 | elev_array = np.append(elev_array, elev) 240 | 241 | ax.scatter(dist_array, elev_array, zorder=3, **kwargs) 242 | 243 | ax.set_xlabel("Distance") 244 | ax.set_ylabel("Elevation") 245 | ax.legend() 246 | plt.tight_layout() 247 | return ax 248 | 249 | def density_scatter( 250 | self, distance=None, dat=None, bins=10, start=None, end=None, ax=None, **kwargs 251 | ): 252 | """Plot the density scatter of all collected raster values. 253 | Use 2D histogram approach to discretize the space of swath profile. 254 | Colors of scatters show the cluster density of collected data points. 255 | 256 | :param distance: longitudial or cross distance, usually set to None 257 | :type distance: optional 258 | :param dat: longitudial or cross data, usually set to None 259 | :type dat: optional 260 | :param bins: number of bin for histogram calculation, defaults to 10 261 | :type bins: int, optional 262 | :param start: starting point, can be coordinates or distance 263 | :type start: float or array-like, optional 264 | :param end: ending point, can be coordinates or distance 265 | :type end: float or array-like, optional 266 | :param ax: matplotlib axes object, defaults to None 267 | :param **kwargs: **kwargs pass to Matplotlib scatter handle 268 | :type **kwargs: arbitrary, optional 269 | """ 270 | start_ind, end_ind = self._segment(start, end) 271 | distance = list( 272 | self.distance[start_ind:end_ind] 273 | if distance is None 274 | else distance[start_ind:end_ind] 275 | ) 276 | dat = self.dat[start_ind:end_ind] if dat is None else dat[start_ind:end_ind] 277 | 278 | # delete empty list 279 | empty_list = [i for i, x in enumerate(dat) if not x] 280 | distance = [i for j, i in enumerate(distance) if j not in empty_list] 281 | dat = [i for j, i in enumerate(dat) if j not in empty_list] 282 | 283 | x, y = np.empty(0), np.empty(0) 284 | for ind, dist in enumerate(distance): 285 | y_temp = np.stack(dat[ind]) 286 | y_temp = y_temp[~np.isnan(y_temp)] 287 | x_temp = np.repeat(dist, len(y_temp)) 288 | x = np.append(x, x_temp) 289 | y = np.append(y, y_temp) 290 | 291 | data, x_e, y_e = np.histogram2d(x, y, bins=bins, density=True) 292 | z = interpn( 293 | (0.5 * (x_e[1:] + x_e[:-1]), 0.5 * (y_e[1:] + y_e[:-1])), 294 | data, 295 | np.vstack([x, y]).T, 296 | method="linear", 297 | bounds_error=False, 298 | fill_value=None, 299 | ) 300 | 301 | # Sort the points by density 302 | idx = z.argsort() 303 | x, y, z = x[idx], y[idx], z[idx] 304 | 305 | if ax is None: 306 | fig, ax = plt.subplots() 307 | 308 | ax.scatter(x, y, c=z, **kwargs) 309 | 310 | norm = Normalize(vmin=np.min(z), vmax=np.max(z)) 311 | if "cmap" in kwargs: 312 | cbar = plt.colorbar( 313 | cm.ScalarMappable(norm=norm, cmap=kwargs.get("cmap")), ax=ax 314 | ) 315 | else: 316 | cbar = plt.colorbar(cm.ScalarMappable(norm=norm), ax=ax) 317 | 318 | cbar.ax.set_ylabel("Density") 319 | ax.set_xlabel("Distance") 320 | ax.set_ylabel("Elevation") 321 | plt.tight_layout() 322 | return ax 323 | 324 | def profile_plot( 325 | self, start=None, end=None, ax=None, color="navy", points=None, **kwargs 326 | ): 327 | """Plot the swath profile. 328 | 329 | :param start: starting position of plot, defaults to starting point of baseline 330 | :type start: float or array-like, optional 331 | :param end: ending position of plot, defaults to ending point of baseline 332 | :type end: float or array-like, optional 333 | :param ax: matplotlib axes object, if not will generate one, default None 334 | :param color: color shading of percentile, defaults to 'navy' 335 | :type color: str, optional 336 | :param points: path to points shapefile that are meant to be plotted with SP, defaults to None 337 | :type points: str, optional 338 | :param **kwargs: **kwargs pass to Matplotlib scatter handle 339 | :type **kwargs: arbitrary, optional 340 | """ 341 | distance = self.distance 342 | stat = self.profile_stat(self.dat) 343 | self.plot( 344 | distance=distance, 345 | stat=stat, 346 | start=start, 347 | end=end, 348 | ax=ax, 349 | color=color, 350 | points=points, 351 | **kwargs 352 | ) 353 | 354 | def slice_plot(self, loc, ax=None): 355 | """plot cross-section of swath data. 356 | 357 | :param loc: location of slice, can be coordinate or distance from starting. 358 | :type loc: float or array-like. 359 | :param ax: matplotlib axes, defaults to None 360 | """ 361 | if loc is not None and isinstance(loc, (int, float)): 362 | loc_ind = np.abs(self.distance - loc).argmin() 363 | elif loc is not None and len(loc) == 2: 364 | line_coords = np.asarray(self.line_p) 365 | loc_coords = np.asarray(loc) 366 | distance = np.sum((line_coords - loc_coords) ** 2, axis=1) 367 | loc_ind = np.argmin(distance) 368 | 369 | line = self.lines[loc_ind] 370 | 371 | d = [] 372 | for point in line: 373 | d_temp = np.sqrt( 374 | (point[0] - line[0][0]) ** 2 + (point[1] - line[0][1]) ** 2 375 | ) 376 | d.append(d_temp) 377 | 378 | if ax is None: 379 | fig, ax = plt.subplots() 380 | 381 | ax.plot(d, self.dat[loc_ind]) 382 | ax.set_xlabel("Distance to left end") 383 | ax.set_ylabel("Elevation") 384 | ax.grid() 385 | plt.tight_layout() 386 | return ax 387 | 388 | def hist(self, dat=None, ax=None, bins=50, **kwargs): 389 | """Return a histogram plot 390 | 391 | :param dat: Input data, defaults to all swath data 392 | :type dat: nested list, optional 393 | :param ax: Matplotlib axes object, defaults to None 394 | :param bins: number of binds, defaults to 50 395 | :type bins: int, optional 396 | :return: Matplotlib axes object 397 | """ 398 | 399 | dat = self.dat if dat is None else dat 400 | 401 | # see if it is a nested list 402 | if any(isinstance(i, list) for i in dat): 403 | dat_array = np.empty(0) 404 | for i in dat: 405 | dat_array = np.append(dat_array, np.hstack(i)) 406 | else: 407 | dat_array = dat 408 | 409 | if ax is None: 410 | fig, ax = plt.subplots() 411 | 412 | ax.hist(dat_array, bins=bins, histtype="stepfilled", density=True, **kwargs) 413 | ax.set_xlabel("Elevation") 414 | ax.set_ylabel("PDF") 415 | # ax.grid() 416 | plt.tight_layout() 417 | return ax 418 | 419 | def slice_hist(self, loc, bins=50): 420 | """Plot the histogram of slice. 421 | 422 | :param loc: location of slice, can be coordinate or distance from starting. 423 | :type loc: float or array-like 424 | :param bins: number of bins, defaults to 50 425 | :type bins: int, optional 426 | """ 427 | if loc is not None and isinstance(loc, (int, float)): 428 | loc_ind = np.abs(self.distance - loc).argmin() 429 | elif loc is not None and len(loc) == 2: 430 | line_coords = np.asarray(self.line_p) 431 | loc_coords = np.asarray(loc) 432 | distance = np.sum((line_coords - loc_coords) ** 2, axis=1) 433 | loc_ind = np.argmin(distance) 434 | 435 | dat = self.dat[loc_ind] 436 | 437 | self.hist(dat=dat, bins=bins) 438 | 439 | def cross_dat(self, dat=None, start=None, end=None): 440 | """Generate cross-swath data. 441 | 442 | :param dat: Swath data passed to calculate cross-swath, defaults to all swath data. 443 | :type dat: list, optional 444 | :param start: Starting position of cross-swath, defaults to starting point of baseline 445 | :type start: float or array-like, optional 446 | :param end: Ending position of cross-swath, defaults to ending point of baseline 447 | :type end: float or array-like, optional 448 | :return: A dict specify distance from the baseline, and corresponding swath data. 449 | :rtype: dict 450 | """ 451 | start_ind, end_ind = self._segment(start, end) 452 | dat = self.dat[start_ind:end_ind] if dat is None else dat 453 | 454 | data = [ele for ele in dat if ele != []] 455 | lines = [line for line in self.lines[start_ind:end_ind] if line != []] 456 | 457 | # only baseline point was restored as tuple, others were lists 458 | left = [] 459 | right = [] 460 | for i in lines: 461 | left_num = i.index([x for x in i if isinstance(x, tuple)][0]) 462 | right_num = len(i) - left_num - 1 463 | left.append(left_num) 464 | right.append(right_num) 465 | 466 | left_max = max(left) 467 | right_max = max(right) 468 | left_dist = -1 * np.arange( 469 | (left_max + 1) * self.cross_stepsize - 1e-10, step=self.cross_stepsize 470 | ) 471 | right_dist = np.arange( 472 | (right_max + 1) * self.cross_stepsize - 1e-10, step=self.cross_stepsize 473 | ) 474 | distance = np.hstack((left_dist[::-1], right_dist[1:None])) 475 | 476 | # cross_matrix = np.zeros((len(distance), len(self.lines[start:end]))) 477 | cross_profile = [] 478 | for count, ele in enumerate(data): 479 | line_dat = np.vstack(ele).flatten() 480 | line_pad = np.pad( 481 | line_dat, 482 | (left_max - left[count], right_max - right[count]), 483 | "constant", 484 | constant_values=np.nan, 485 | ) 486 | cross_profile.append(line_pad) 487 | 488 | return { 489 | "distance": distance, 490 | "cross_matrix": list(map(list, zip(*cross_profile))), 491 | } 492 | 493 | def cross_plot( 494 | self, 495 | start=None, 496 | end=None, 497 | ax=None, 498 | color="navy", 499 | points=None, 500 | density_scatter=False, 501 | **kwargs 502 | ): 503 | """Return the plot of cross-profile. 504 | 505 | :param start: Starting position of cross-swath, defaults to starting point of baseline 506 | :type start: float or array-like, optional 507 | :param end: Ending position of cross-swath, defaults to ending point of baseline 508 | :type end: float or array-like, optional 509 | :param ax: matplotlib axes object, defaults to None 510 | :param color: color of quartiles, defaults to 'navy' 511 | :type color: str, optional 512 | """ 513 | # start_ind, end_ind = self._segment(start, end) 514 | dat = self.cross_dat(start=start, end=end) 515 | 516 | distance = dat["distance"] 517 | cross = dat["cross_matrix"] 518 | cross_stat = self.profile_stat(cross) 519 | 520 | # default to plot all along cross direction 521 | start = None 522 | end = None 523 | 524 | if density_scatter is True: 525 | self.density_scatter( 526 | distance=distance, dat=cross, start=start, end=end, ax=ax, **kwargs 527 | ) 528 | else: 529 | self.plot( 530 | distance=distance, 531 | stat=cross_stat, 532 | start=start, 533 | end=end, 534 | ax=ax, 535 | color=color, 536 | points=points, 537 | cross=True, 538 | **kwargs 539 | ) 540 | 541 | def post_tpi( 542 | self, 543 | radius, 544 | min_val=float("-inf"), 545 | max_val=float("inf"), 546 | start=None, 547 | end=None, 548 | ax=None, 549 | color="navy", 550 | cross=False, 551 | swath_plot=False, 552 | bins=10, 553 | density_scatter=False, 554 | **kwargs 555 | ): 556 | """Post-processing swath data according to TPI criteria. 557 | 558 | :param radius: TPI radius 559 | :type radius: float 560 | :param min_val: minimal TPI threshold, defaults to float("-inf") 561 | :type min_val: float, optional 562 | :param max_val: maximal TPI threshold, defaults to float("inf") 563 | :type max_val: float, optional 564 | :param start: starting point, can be coordinates or distance 565 | :type start: float or array-like, optional 566 | :param end: ending point, can be coordinates or distance 567 | :type end: float or array-like, optional 568 | :param ax: matplotlib axes object, defaults to None 569 | :param color: color of quartiles, defaults to 'navy' 570 | :type color: str, optional 571 | :param cross: processing cross-swath profile, defaults to False 572 | :type cross: bool, optional 573 | :param swath_plot: plot the processed swath profile, defaults to False 574 | :type swath_plot: bool, optional 575 | :param bins: number of bin for density scatter, defaults to 10 576 | :type bins: int, optional 577 | :param density_scatter: plot the processed density scatter, defaults to False 578 | :type density_scatter: bool, optional 579 | :param **kwargs: **kwargs pass to Matplotlib scatter handle 580 | :type **kwargs: arbitrary, optional 581 | :return: a list contain distance and processed elevation data 582 | :rtype: list 583 | """ 584 | if swath_plot and density_scatter: 585 | raise Exception( 586 | "Swath profile and density scatters are not " 587 | "meant to be plotted at the same time." 588 | ) 589 | 590 | start_ind, end_ind = self._segment(start, end) 591 | 592 | lines_val = copy.deepcopy(self.dat[start_ind:end_ind]) 593 | for line_ind, line in enumerate(self.lines[start_ind:end_ind]): 594 | for point_ind, point in enumerate(line): 595 | point_val = Tpi(point, self.raster, radius).value 596 | if not min_val <= point_val <= max_val: 597 | lines_val[line_ind][point_ind] = np.nan 598 | 599 | distance = self.distance[start_ind:end_ind] 600 | values = lines_val 601 | 602 | if cross == True: 603 | cross_dat = self.cross_dat(dat=lines_val, start=start, end=end) 604 | distance = cross_dat["distance"] 605 | values = cross_dat["cross_matrix"] 606 | 607 | if swath_plot == True: 608 | post_stat = self.profile_stat(values) 609 | self.plot(distance=distance, stat=post_stat, ax=ax, color=color) 610 | 611 | if density_scatter == True: 612 | self.density_scatter( 613 | distance=distance, dat=values, bins=bins, ax=ax, **kwargs 614 | ) 615 | 616 | return [distance, values] 617 | 618 | def post_elev( 619 | self, 620 | min_val=float("-inf"), 621 | max_val=float("inf"), 622 | start=None, 623 | end=None, 624 | ax=None, 625 | color="navy", 626 | cross=False, 627 | swath_plot=False, 628 | bins=10, 629 | density_scatter=False, 630 | **kwargs 631 | ): 632 | """Post-processing swath data according to elevation criteria. 633 | 634 | :param min_val: minimal elevation threshold, defaults to float("-inf") 635 | :type min_val: float, optional 636 | :param max_val: maximal elevation threshold, defaults to float("inf") 637 | :type max_val: float, optional 638 | :param start: starting point, can be coordinates or distance 639 | :type start: float or array-like, optional 640 | :param end: ending point, can be coordinates or distance 641 | :type end: float or array-like, optional 642 | :param ax: matplotlib axes object, defaults to None 643 | :param color: color of quartiles, defaults to 'navy' 644 | :type color: str, optional 645 | :param cross: processing cross-swath profile, defaults to False 646 | :type cross: bool, optional 647 | :param swath_plot: plot the processed swath profile, defaults to False 648 | :type swath_plot: bool, optional 649 | :param bins: number of bin for density scatter, defaults to 10 650 | :type bins: int, optional 651 | :param density_scatter: plot the processed density scatter, defaults to False 652 | :type density_scatter: bool, optional 653 | :param **kwargs: **kwargs pass to Matplotlib scatter handle 654 | :type **kwargs: arbitrary, optional 655 | :return: a list contain distance and processed elevation data 656 | :rtype: list 657 | """ 658 | if swath_plot and density_scatter: 659 | raise Exception( 660 | "Swath profile and density scatters are not " 661 | "meant to be plotted at the same time." 662 | ) 663 | 664 | start_ind, end_ind = self._segment(start, end) 665 | 666 | lines_val = copy.deepcopy(self.dat[start_ind:end_ind]) 667 | for line_ind, line in enumerate(self.lines[start_ind:end_ind]): 668 | for point_ind, point in enumerate(line): 669 | point_val = Point_elevation(point, self.raster).value 670 | if not min_val <= point_val <= max_val: 671 | lines_val[line_ind][point_ind] = np.nan 672 | 673 | distance = self.distance[start_ind:end_ind] 674 | values = lines_val 675 | 676 | if cross == True: 677 | cross_dat = self.cross_dat(dat=lines_val, start=start, end=end) 678 | distance = cross_dat["distance"] 679 | values = cross_dat["cross_matrix"] 680 | 681 | if swath_plot == True: 682 | post_stat = self.profile_stat(values) 683 | self.plot(distance=distance, stat=post_stat, ax=ax, color=color) 684 | 685 | if density_scatter == True: 686 | self.density_scatter( 687 | distance=distance, dat=values, bins=bins, ax=ax, **kwargs 688 | ) 689 | 690 | return [distance, values] 691 | 692 | def post_slope( 693 | self, 694 | min_val=0.0, 695 | max_val=90.0, 696 | start=None, 697 | end=None, 698 | ax=None, 699 | color="navy", 700 | cross=False, 701 | swath_plot=False, 702 | bins=10, 703 | density_scatter=False, 704 | **kwargs 705 | ): 706 | """Post-processing swath data according to slope criteria. 707 | 708 | :param min_val: minimal slope threshold, defaults to float("-inf") 709 | :type min_val: float, optional 710 | :param max_val: maximal slope threshold, defaults to float("inf") 711 | :type max_val: float, optional 712 | :param start: starting point, can be coordinates or distance 713 | :type start: float or array-like, optional 714 | :param end: ending point, can be coordinates or distance 715 | :type end: float or array-like, optional 716 | :param ax: matplotlib axes object, defaults to None 717 | :param color: color of quartiles, defaults to 'navy' 718 | :type color: str, optional 719 | :param cross: processing cross-swath profile, defaults to False 720 | :type cross: bool, optional 721 | :param swath_plot: plot the processed swath profile, defaults to False 722 | :type swath_plot: bool, optional 723 | :param bins: number of bin for density scatter, defaults to 10 724 | :type bins: int, optional 725 | :param density_scatter: plot the processed density scatter, defaults to False 726 | :type density_scatter: bool, optional 727 | :param **kwargs: **kwargs pass to Matplotlib scatter handle 728 | :type **kwargs: arbitrary, optional 729 | :return: a list contain distance and processed slope data 730 | :rtype: list 731 | """ 732 | if swath_plot and density_scatter: 733 | raise Exception( 734 | "Swath profile and density scatters are not " 735 | "meant to be plotted at the same time." 736 | ) 737 | 738 | start_ind, end_ind = self._segment(start, end) 739 | 740 | lines_val = copy.deepcopy(self.dat[start_ind:end_ind]) 741 | for line_ind, line in enumerate(self.lines[start_ind:end_ind]): 742 | for point_ind, point in enumerate(line): 743 | point_val = Geo_slope(point, self.raster, self.cell_res).value 744 | if not min_val <= point_val <= max_val: 745 | lines_val[line_ind][point_ind] = np.nan 746 | 747 | distance = self.distance[start_ind:end_ind] 748 | values = lines_val 749 | 750 | if cross == True: 751 | cross_dat = self.cross_dat(dat=lines_val, start=start, end=end) 752 | distance = cross_dat["distance"] 753 | values = cross_dat["cross_matrix"] 754 | 755 | if swath_plot == True: 756 | post_stat = self.profile_stat(values) 757 | self.plot(distance=distance, stat=post_stat, ax=ax, color=color) 758 | 759 | if density_scatter == True: 760 | self.density_scatter( 761 | distance=distance, dat=values, bins=bins, ax=ax, **kwargs 762 | ) 763 | 764 | return [distance, values] 765 | -------------------------------------------------------------------------------- /pyosp/curvsp/elev_curv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import sys 5 | from ..util import pairwise, progressBar 6 | from .._elevation import Point_elevation 7 | from .base_curv import Base_curv 8 | 9 | 10 | class Elev_curv(Base_curv): 11 | """Elevation-based swath profile characterization. 12 | 13 | :param line: path to baseline shapefile 14 | :type line: str 15 | :param raster: path to GeoTiff 16 | :type raster: str 17 | :param width: maximum allowed width of swath profile 18 | :type width: float 19 | :param min_elev: minimal elevation threshold, defaults to float("-inf") 20 | :type min_elev: float, optional 21 | :param max_elev: maximal elevation threshold, defaults to float("inf") 22 | :type max_elev: float, optional 23 | :param line_stepsize: step-size along baseline, defaults to resolution of raster 24 | :type line_stepsize: float, optional 25 | :param cross_stepsize: step-size along profilelines, defaults to resolution of raster 26 | :type cross_stepsize: float, optional 27 | """ 28 | 29 | def __init__( 30 | self, 31 | line, 32 | raster, 33 | width, 34 | min_elev=float("-inf"), 35 | max_elev=float("inf"), 36 | line_stepsize=None, 37 | cross_stepsize=None, 38 | ): 39 | self.min_elev = min_elev 40 | self.max_elev = max_elev 41 | 42 | super(Elev_curv, self).__init__( 43 | line, raster, width, line_stepsize, cross_stepsize 44 | ) 45 | 46 | def __repr__(self): 47 | return "{}".format(self.__class__.__name__) 48 | 49 | def _transect_lines(self): 50 | lines = [] 51 | num = len(self.line_p) 52 | 53 | for p1, p2 in pairwise(self.line_p): 54 | if p2 == self.line_p[-1]: 55 | line_left_1 = self._swath_left(p1, p2) 56 | line_right_1 = self._swath_right(p1, p2) 57 | line_1 = line_left_1 + line_right_1 58 | 59 | line_left_2 = self._swath_left(p1, p2, endPoint=True) 60 | line_right_2 = self._swath_right(p1, p2, endPoint=True) 61 | line_2 = line_left_2 + line_right_2 62 | 63 | lines.append(line_1) 64 | lines.append(line_2) 65 | 66 | current = self.line_p.index(p2) + 1 # processed two points at last step 67 | progressBar(current, num) 68 | else: 69 | line_left = self._swath_left(p1, p2) 70 | line_right = self._swath_right(p1, p2) 71 | line = line_left + line_right 72 | 73 | lines.append(line) 74 | 75 | current = self.line_p.index(p2) 76 | progressBar(current, num) 77 | 78 | return lines 79 | 80 | def _swath_left(self, p1, p2, endPoint=False): 81 | # if touch the end point, using same slope for both last two points 82 | if endPoint: 83 | p_m = p2 84 | else: 85 | p_m = p1 86 | 87 | rasterVal = Point_elevation(p_m, self.raster).value 88 | if not ( 89 | (self.rasterXmin <= p_m[0] <= self.rasterXmax) 90 | and (self.rasterYmin <= p_m[1] <= self.rasterYmax) 91 | and (self.min_elev <= rasterVal <= self.max_elev) 92 | ): 93 | transect_temp = [] 94 | else: 95 | transect_temp = [p_m] 96 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 97 | for i in range(1, sys.maxsize, 1): 98 | dx = np.sqrt((self.cross_stepsize * i) ** 2 / (slope ** 2 + 1)) 99 | dy = dx * abs(slope) 100 | 101 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 102 | p_left = [p_m[0] - dx, p_m[1] - dy] 103 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 104 | p_left = [p_m[0] + dx, p_m[1] + dy] 105 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 106 | p_left = [p_m[0] + dx, p_m[1] - dy] 107 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 108 | p_left = [p_m[0] - dx, p_m[1] + dy] 109 | 110 | # discard point out of bounds 111 | if not ( 112 | (self.rasterXmin <= p_left[0] <= self.rasterXmax) 113 | and (self.rasterYmin <= p_left[1] <= self.rasterYmax) 114 | ): 115 | break 116 | 117 | # break if maximum width reached 118 | if self.width is not None: 119 | hw = np.sqrt(dx ** 2 + dy ** 2) 120 | if hw >= self.width / 2: 121 | break 122 | 123 | p_elev = Point_elevation(p_left, self.raster).value 124 | 125 | if not self.min_elev <= p_elev <= self.max_elev: 126 | break 127 | 128 | transect_temp.insert(0, p_left) 129 | 130 | return transect_temp 131 | 132 | def _swath_right(self, p1, p2, endPoint=False): 133 | # if touch the end point, using same slope for both last two points 134 | if endPoint: 135 | p_m = p2 136 | else: 137 | p_m = p1 138 | 139 | transect_temp = [] 140 | 141 | # nPoints = int(self.width // self.cross_stepsize) 142 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 143 | for i in range(1, sys.maxsize, 1): 144 | dx = np.sqrt((self.cross_stepsize * i) ** 2 / (slope ** 2 + 1)) 145 | dy = dx * abs(slope) 146 | 147 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 148 | p_right = [p_m[0] + dx, p_m[1] + dy] 149 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 150 | p_right = [p_m[0] - dx, p_m[1] - dy] 151 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 152 | p_right = [p_m[0] - dx, p_m[1] + dy] 153 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 154 | p_right = [p_m[0] + dx, p_m[1] - dy] 155 | 156 | # discard point out of bounds 157 | if not ( 158 | (self.rasterXmin <= p_right[0] <= self.rasterXmax) 159 | and (self.rasterYmin <= p_right[1] <= self.rasterYmax) 160 | ): 161 | break 162 | 163 | if self.width is not None: 164 | hw = np.sqrt(dx ** 2 + dy ** 2) 165 | if hw >= self.width / 2: 166 | break 167 | 168 | p_elev = Point_elevation(p_right, self.raster).value 169 | 170 | if not self.min_elev <= p_elev <= self.max_elev: 171 | break 172 | 173 | transect_temp.append(p_right) 174 | 175 | return transect_temp 176 | -------------------------------------------------------------------------------- /pyosp/curvsp/orig_curv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | from ..util import pairwise, progressBar 5 | from .base_curv import Base_curv 6 | from .._elevation import Point_elevation 7 | 8 | 9 | class Orig_curv(Base_curv): 10 | """Original swath method. 11 | 12 | :param line: path to baseline shapefile 13 | :type line: str 14 | :param raster: path to GeoTiff 15 | :type raster: str 16 | :param width: maximum allowed width of swath profile 17 | :type width: float 18 | :param line_stepsize: step-size along baseline, defaults to resolution of raster 19 | :type line_stepsize: float, optional 20 | :param cross_stepsize: step-size along profilelines, defaults to resolution of raster 21 | :type cross_stepsize: float, optional 22 | """ 23 | 24 | def __init__(self, line, raster, width, line_stepsize=None, cross_stepsize=None): 25 | 26 | super(Orig_curv, self).__init__( 27 | line, raster, width, line_stepsize, cross_stepsize 28 | ) 29 | 30 | def __repr__(self): 31 | return "{}".format(self.__class__.__name__) 32 | 33 | def _transect_lines(self): 34 | lines = [] 35 | num = len(self.line_p) 36 | 37 | for p1, p2 in pairwise(self.line_p): 38 | if p2 == self.line_p[-1]: 39 | line_left_1 = self._swath_left(p1, p2) 40 | line_right_1 = self._swath_right(p1, p2) 41 | line_1 = line_left_1 + line_right_1 42 | 43 | line_left_2 = self._swath_left(p1, p2, endPoint=True) 44 | line_right_2 = self._swath_right(p1, p2, endPoint=True) 45 | line_2 = line_left_2 + line_right_2 46 | 47 | lines.append(line_1) 48 | lines.append(line_2) 49 | 50 | current = self.line_p.index(p2) + 1 # processed two points at last step 51 | progressBar(current, num) 52 | else: 53 | line_left = self._swath_left(p1, p2) 54 | line_right = self._swath_right(p1, p2) 55 | line = line_left + line_right 56 | 57 | lines.append(line) 58 | 59 | current = self.line_p.index(p2) 60 | progressBar(current, num) 61 | 62 | return lines 63 | 64 | def _swath_left(self, p1, p2, endPoint=False): 65 | # if touch the end point, using same slope for both last two points 66 | if endPoint: 67 | p_m = p2 68 | else: 69 | p_m = p1 70 | 71 | rasterVal = Point_elevation(p_m, self.raster).value 72 | if not ( 73 | (self.rasterXmin <= p_m[0] <= self.rasterXmax) 74 | and (self.rasterYmin <= p_m[1] <= self.rasterYmax) 75 | and (rasterVal > -1e20) 76 | ): 77 | transect_temp = [] 78 | else: 79 | transect_temp = [p_m] 80 | nPoints = int(self.width / 2 // self.cross_stepsize) 81 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 82 | for i in range(nPoints): 83 | dx = np.sqrt((self.cross_stepsize * (i + 1)) ** 2 / (slope ** 2 + 1)) 84 | dy = dx * abs(slope) 85 | 86 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 87 | p_left = [p_m[0] - dx, p_m[1] - dy] 88 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 89 | p_left = [p_m[0] + dx, p_m[1] + dy] 90 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 91 | p_left = [p_m[0] + dx, p_m[1] - dy] 92 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 93 | p_left = [p_m[0] - dx, p_m[1] + dy] 94 | 95 | # discard point out of bounds 96 | rasterVal = Point_elevation(p_left, self.raster).value 97 | if not ( 98 | (self.rasterXmin <= p_left[0] <= self.rasterXmax) 99 | and (self.rasterYmin <= p_left[1] <= self.rasterYmax) 100 | and (rasterVal > -1e20) 101 | ): 102 | break 103 | 104 | transect_temp.insert(0, p_left) 105 | 106 | return transect_temp 107 | 108 | def _swath_right(self, p1, p2, endPoint=False): 109 | # if touch the end point, using same slope for both last two points 110 | if endPoint: 111 | p_m = p2 112 | else: 113 | p_m = p1 114 | 115 | transect_temp = [] 116 | 117 | nPoints = int(self.width / 2 // self.cross_stepsize) 118 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 119 | for i in range(nPoints): 120 | dx = np.sqrt((self.cross_stepsize * (i + 1)) ** 2 / (slope ** 2 + 1)) 121 | dy = dx * abs(slope) 122 | 123 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 124 | p_right = [p_m[0] + dx, p_m[1] + dy] 125 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 126 | p_right = [p_m[0] - dx, p_m[1] - dy] 127 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 128 | p_right = [p_m[0] - dx, p_m[1] + dy] 129 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 130 | p_right = [p_m[0] + dx, p_m[1] - dy] 131 | 132 | # discard point out of bounds 133 | rasterVal = Point_elevation(p_right, self.raster).value 134 | if not ( 135 | (self.rasterXmin <= p_right[0] <= self.rasterXmax) 136 | and (self.rasterYmin <= p_right[1] <= self.rasterYmax) 137 | and (rasterVal > -1e20) 138 | ): 139 | break 140 | 141 | transect_temp.append(p_right) 142 | 143 | return transect_temp 144 | -------------------------------------------------------------------------------- /pyosp/curvsp/slope_curv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | from osgeo import gdal 5 | import sys 6 | from ..util import pairwise, progressBar 7 | from .._slope import Geo_slope 8 | from .base_curv import Base_curv 9 | 10 | 11 | class Slope_curv(Base_curv): 12 | """Slope-based swath profile characterization. 13 | 14 | :param line: path to baseline shapefile 15 | :type line: str 16 | :param raster: path to GeoTiff 17 | :type raster: str 18 | :param width: maximum allowed width of swath profile 19 | :type width: float 20 | :param min_slope: minimal slope threshold, defaults to 0 21 | :type min_slope: float, optional 22 | :param max_slope: maximal slope threshold, defaults to 90 23 | :type max_slope: float, optional 24 | :param line_stepsize: step-size along baseline, defaults to resolution of raster 25 | :type line_stepsize: float, optional 26 | :param cross_stepsize: step-size along profilelines, defaults to resolution of raster 27 | :type cross_stepsize: float, optional 28 | """ 29 | 30 | def __init__( 31 | self, 32 | line, 33 | raster, 34 | width, 35 | min_slope=0.0, 36 | max_slope=90.0, 37 | line_stepsize=None, 38 | cross_stepsize=None, 39 | ): 40 | self.min_slope = min_slope 41 | self.max_slope = max_slope 42 | self.cell_size = gdal.Open(raster).GetGeoTransform()[1] 43 | 44 | super(Slope_curv, self).__init__( 45 | line, raster, width, line_stepsize, cross_stepsize 46 | ) 47 | 48 | def __repr__(self): 49 | return "{}".format(self.__class__.__name__) 50 | 51 | def _transect_lines(self): 52 | lines = [] 53 | num = len(self.line_p) 54 | 55 | for p1, p2 in pairwise(self.line_p): 56 | if p2 == self.line_p[-1]: 57 | line_left_1 = self._swath_left(p1, p2) 58 | line_right_1 = self._swath_right(p1, p2) 59 | line_1 = line_left_1 + line_right_1 60 | 61 | line_left_2 = self._swath_left(p1, p2, endPoint=True) 62 | line_right_2 = self._swath_right(p1, p2, endPoint=True) 63 | line_2 = line_left_2 + line_right_2 64 | 65 | lines.append(line_1) 66 | lines.append(line_2) 67 | 68 | current = self.line_p.index(p2) + 1 # processed two points at last step 69 | progressBar(current, num) 70 | else: 71 | line_left = self._swath_left(p1, p2) 72 | line_right = self._swath_right(p1, p2) 73 | line = line_left + line_right 74 | 75 | lines.append(line) 76 | 77 | current = self.line_p.index(p2) 78 | progressBar(current, num) 79 | 80 | return lines 81 | 82 | def _swath_left(self, p1, p2, endPoint=False): 83 | # if touch the end point, using same slope for both last two points 84 | if endPoint: 85 | p_m = p2 86 | else: 87 | p_m = p1 88 | 89 | rasterVal = Geo_slope(p_m, self.raster, self.cell_size).value 90 | if not ( 91 | (self.rasterXmin <= p_m[0] <= self.rasterXmax) 92 | and (self.rasterYmin <= p_m[1] <= self.rasterYmax) 93 | and (self.min_slope <= rasterVal <= self.max_slope) 94 | ): 95 | transect_temp = [] 96 | else: 97 | transect_temp = [p_m] 98 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 99 | for i in range(1, sys.maxsize, 1): 100 | dx = np.sqrt((self.cross_stepsize * i) ** 2 / (slope ** 2 + 1)) 101 | dy = dx * abs(slope) 102 | 103 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 104 | p_left = [p_m[0] - dx, p_m[1] - dy] 105 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 106 | p_left = [p_m[0] + dx, p_m[1] + dy] 107 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 108 | p_left = [p_m[0] + dx, p_m[1] - dy] 109 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 110 | p_left = [p_m[0] - dx, p_m[1] + dy] 111 | 112 | # discard point out of bounds 113 | if not ( 114 | (self.rasterXmin < p_left[0] < self.rasterXmax) 115 | and (self.rasterYmin < p_left[1] < self.rasterYmax) 116 | ): 117 | break 118 | 119 | # break if maximum width reached 120 | if self.width is not None: 121 | hw = np.sqrt(dx ** 2 + dy ** 2) 122 | if hw >= self.width / 2: 123 | break 124 | 125 | p_slope = Geo_slope(p_left, self.raster, self.cell_size).value 126 | 127 | if not self.min_slope <= p_slope <= self.max_slope: 128 | break 129 | 130 | transect_temp.insert(0, p_left) 131 | 132 | return transect_temp 133 | 134 | def _swath_right(self, p1, p2, endPoint=False): 135 | # if touch the end point, using same slope for both last two points 136 | if endPoint: 137 | p_m = p2 138 | else: 139 | p_m = p1 140 | 141 | transect_temp = [] 142 | 143 | # nPoints = int(self.width // self.cross_stepsize) 144 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 145 | for i in range(1, sys.maxsize, 1): 146 | dx = np.sqrt((self.cross_stepsize * i) ** 2 / (slope ** 2 + 1)) 147 | dy = dx * abs(slope) 148 | 149 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 150 | p_right = [p_m[0] + dx, p_m[1] + dy] 151 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 152 | p_right = [p_m[0] - dx, p_m[1] - dy] 153 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 154 | p_right = [p_m[0] - dx, p_m[1] + dy] 155 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 156 | p_right = [p_m[0] + dx, p_m[1] - dy] 157 | 158 | # discard point out of bounds 159 | if not ( 160 | (self.rasterXmin < p_right[0] < self.rasterXmax) 161 | and (self.rasterYmin < p_right[1] < self.rasterYmax) 162 | ): 163 | break 164 | 165 | if self.width is not None: 166 | hw = np.sqrt(dx ** 2 + dy ** 2) 167 | if hw >= self.width / 2: 168 | break 169 | 170 | p_slope = Geo_slope(p_right, self.raster, self.cell_size).value 171 | 172 | if not self.min_slope <= p_slope <= self.max_slope: 173 | break 174 | 175 | transect_temp.append(p_right) 176 | 177 | return transect_temp 178 | -------------------------------------------------------------------------------- /pyosp/curvsp/tpi_curv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import numpy as np 4 | import sys 5 | from ..util import pairwise, progressBar 6 | from .._tpi import Tpi 7 | from .base_curv import Base_curv 8 | from osgeo import gdal 9 | 10 | 11 | class Tpi_curv(Base_curv): 12 | """TPI-based swath profile characterization. 13 | 14 | :param line: path to baseline shapefile 15 | :type line: str 16 | :param raster: path to GeoTiff 17 | :type raster: str 18 | :param width: maximum allowed width of swath profile 19 | :type width: float 20 | :param tpi_radius: radius of TPI window 21 | :type tpi_radius: float 22 | :param min_tpi: minimal TPI threshold, defaults to float("-inf") 23 | :type min_tpi: float, optional 24 | :param max_tpi: maximal TPI threshold, defaults to float("inf") 25 | :type max_tpi: float, optional 26 | :param line_stepsize: step-size along baseline, defaults to resolution of raster 27 | :type line_stepsize: float, optional 28 | :param cross_stepsize: step-size along profilelines, defaults to resolution of raster 29 | :type cross_stepsize: float, optional 30 | """ 31 | 32 | def __init__( 33 | self, 34 | line, 35 | raster, 36 | width, 37 | tpi_radius, 38 | min_tpi=float("-inf"), 39 | max_tpi=float("inf"), 40 | line_stepsize=None, 41 | cross_stepsize=None, 42 | ): 43 | self.tpi_radius = tpi_radius 44 | self.min_tpi = min_tpi 45 | self.max_tpi = max_tpi 46 | 47 | super(Tpi_curv, self).__init__( 48 | line, raster, width, line_stepsize, cross_stepsize 49 | ) 50 | 51 | def __repr__(self): 52 | return "{}".format(self.__class__.__name__) 53 | 54 | def _transect_lines(self): 55 | lines = [] 56 | num = len(self.line_p) 57 | for p1, p2 in pairwise(self.line_p): 58 | if p2 == self.line_p[-1]: 59 | line_left_1 = self._swath_left(p1, p2) 60 | line_right_1 = self._swath_right(p1, p2) 61 | line_1 = line_left_1 + line_right_1 62 | 63 | line_left_2 = self._swath_left(p1, p2, endPoint=True) 64 | line_right_2 = self._swath_right(p1, p2, endPoint=True) 65 | line_2 = line_left_2 + line_right_2 66 | 67 | lines.append(line_1) 68 | lines.append(line_2) 69 | 70 | current = self.line_p.index(p2) + 1 # processed two points at last step 71 | progressBar(current, num) 72 | else: 73 | line_left = self._swath_left(p1, p2) 74 | line_right = self._swath_right(p1, p2) 75 | line = line_left + line_right 76 | 77 | lines.append(line) 78 | 79 | current = self.line_p.index(p2) 80 | progressBar(current, num) 81 | 82 | return lines 83 | 84 | def _swath_left(self, p1, p2, endPoint=False): 85 | # if touch the end point, using same slope for both last two points 86 | if endPoint: 87 | p_m = p2 88 | else: 89 | p_m = p1 90 | 91 | rasterVal = Tpi(p_m, self.raster, self.tpi_radius).value 92 | if not ( 93 | (self.rasterXmin <= p_m[0] <= self.rasterXmax) 94 | and (self.rasterYmin <= p_m[1] <= self.rasterYmax) 95 | and (self.min_tpi <= rasterVal <= self.max_tpi) 96 | ): 97 | transect_temp = [] 98 | else: 99 | transect_temp = [p_m] 100 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 101 | for i in range(1, sys.maxsize, 1): 102 | dx = np.sqrt((self.cross_stepsize * i) ** 2 / (slope ** 2 + 1)) 103 | dy = dx * abs(slope) 104 | 105 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 106 | p_left = [p_m[0] - dx, p_m[1] - dy] 107 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 108 | p_left = [p_m[0] + dx, p_m[1] + dy] 109 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 110 | p_left = [p_m[0] + dx, p_m[1] - dy] 111 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 112 | p_left = [p_m[0] - dx, p_m[1] + dy] 113 | 114 | # discard point out of bounds 115 | if not ( 116 | (self.rasterXmin <= p_left[0] <= self.rasterXmax) 117 | and (self.rasterYmin <= p_left[1] <= self.rasterYmax) 118 | ): 119 | break 120 | 121 | # break if maximum width reached 122 | if self.width is not None: 123 | hw = np.sqrt(dx ** 2 + dy ** 2) 124 | if hw >= self.width / 2: 125 | break 126 | 127 | p_index = Tpi(p_left, self.raster, self.tpi_radius).value 128 | if self.min_tpi <= p_index <= self.max_tpi: 129 | transect_temp.insert(0, p_left) 130 | else: 131 | break 132 | 133 | return transect_temp 134 | 135 | def _swath_right(self, p1, p2, endPoint=False): 136 | # if touch the end point, using same slope for both last two points 137 | if endPoint: 138 | p_m = p2 139 | else: 140 | p_m = p1 141 | 142 | transect_temp = [] 143 | slope = -(p2[0] - p1[0]) / (p2[1] - p1[1]) 144 | for i in range(1, sys.maxsize, 1): 145 | dx = np.sqrt((self.cross_stepsize * i) ** 2 / (slope ** 2 + 1)) 146 | dy = dx * abs(slope) 147 | 148 | if slope >= 0 and p2[0] < p1[0] and p2[1] >= p1[1]: 149 | p_right = [p_m[0] + dx, p_m[1] + dy] 150 | elif slope >= 0 and p2[0] >= p1[0] and p2[1] < p1[1]: 151 | p_right = [p_m[0] - dx, p_m[1] - dy] 152 | elif slope < 0 and p2[0] < p1[0] and p2[1] < p1[1]: 153 | p_right = [p_m[0] - dx, p_m[1] + dy] 154 | elif slope < 0 and p2[0] >= p1[0] and p2[1] >= p1[1]: 155 | p_right = [p_m[0] + dx, p_m[1] - dy] 156 | 157 | # discard point out of bounds 158 | if not ( 159 | (self.rasterXmin <= p_right[0] <= self.rasterXmax) 160 | and (self.rasterYmin <= p_right[1] <= self.rasterYmax) 161 | ): 162 | break 163 | 164 | # break if maximum width reached 165 | if self.width is not None: 166 | hw = np.sqrt(dx ** 2 + dy ** 2) 167 | if hw >= self.width / 2: 168 | break 169 | 170 | p_index = Tpi(p_right, self.raster, self.tpi_radius).value 171 | if self.min_tpi <= p_index <= self.max_tpi: 172 | transect_temp.append(p_right) 173 | else: 174 | break 175 | 176 | return transect_temp 177 | -------------------------------------------------------------------------------- /pyosp/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | __all__ = ["_available_file", "get_path"] 4 | 5 | _module_path = os.path.dirname(__file__) 6 | _available_file = [p for p in os.listdir(_module_path) if not p.startswith("__")] 7 | 8 | 9 | def get_path(dataset): 10 | if dataset in _available_file: 11 | return os.path.abspath(os.path.join(_module_path, dataset)) 12 | else: 13 | msg = "The dataset '{data}' is not available. ".format(data=dataset) 14 | msg += "Available datasets are {}".format(", ".join(_available_file)) 15 | raise ValueError(msg) 16 | -------------------------------------------------------------------------------- /pyosp/datasets/center.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /pyosp/datasets/center.dbf: -------------------------------------------------------------------------------- 1 | xAIdN 0 -------------------------------------------------------------------------------- /pyosp/datasets/center.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/center.sbn -------------------------------------------------------------------------------- /pyosp/datasets/center.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/center.sbx -------------------------------------------------------------------------------- /pyosp/datasets/center.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/center.shp -------------------------------------------------------------------------------- /pyosp/datasets/center.shp.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200816150018001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\centerLocal Area Network 3 | -------------------------------------------------------------------------------- /pyosp/datasets/center.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/center.shx -------------------------------------------------------------------------------- /pyosp/datasets/checking_points.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /pyosp/datasets/checking_points.dbf: -------------------------------------------------------------------------------- 1 | x 2 | AIdN 0 0 0 0 -------------------------------------------------------------------------------- /pyosp/datasets/checking_points.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/checking_points.sbn -------------------------------------------------------------------------------- /pyosp/datasets/checking_points.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/checking_points.sbx -------------------------------------------------------------------------------- /pyosp/datasets/checking_points.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/checking_points.shp -------------------------------------------------------------------------------- /pyosp/datasets/checking_points.shp.xml: -------------------------------------------------------------------------------- 1 | 2 | 20201021115134001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\pyosp\datasets\checking_pointsLocal Area Network 3 | -------------------------------------------------------------------------------- /pyosp/datasets/checking_points.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/checking_points.shx -------------------------------------------------------------------------------- /pyosp/datasets/crater.tfw: -------------------------------------------------------------------------------- 1 | 0.8000932000 2 | 0.0000000000 3 | 0.0000000000 4 | -0.8000932000 5 | -0.0142000000 6 | 200.0131000000 7 | -------------------------------------------------------------------------------- /pyosp/datasets/crater.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/crater.tif -------------------------------------------------------------------------------- /pyosp/datasets/crater.tif.aux.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generic 4 | 5 | 6 | 7 | 8 | -20.72613906860352 9 | 11.33687114715576 10 | 256 11 | 1 12 | 0 13 | 2|10|8|9|14|21|8|21|31|36|37|37|52|40|55|46|45|44|63|60|52|63|60|67|80|57|70|64|58|62|61|78|82|71|100|100|92|80|82|72|68|58|70|62|35|48|50|44|52|53|41|53|50|60|58|58|61|70|58|60|64|75|60|63|57|45|60|52|53|58|51|47|56|55|50|45|44|40|55|40|45|57|44|40|51|41|63|48|35|45|44|46|48|41|38|38|43|40|34|47|40|28|48|35|28|38|31|40|31|41|27|46|37|23|33|37|34|34|35|29|38|38|28|38|38|36|31|37|35|33|33|38|34|32|34|38|36|41|46|26|40|36|47|47|28|40|50|51|34|41|46|41|34|38|47|38|45|42|39|38|37|41|28|56|6051|5527|2669|2095|1587|1324|1233|1181|1089|985|991|920|854|830|805|730|774|714|661|627|617|576|543|517|480|469|437|419|413|418|447|401|418|422|391|428|403|403|391|399|379|399|392|392|371|365|414|446|477|459|434|402|419|418|377|412|384|373|319|343|350|332|334|329|340|366|409|361|327|359|362|450|446|368|358|278|203|207|180|131|116|56|33|24|35|25|23|18|13|6|1|1 14 | 15 | 16 | 17 | ATHEMATIC 18 | 32.4179752566205 19 | 20 | 11.336871147156 21 | 1.2412949046922 22 | -20.726139068604 23 | 1 24 | 1 25 | 5.6936785347103 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pyosp/datasets/crater.tif.ovr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/crater.tif.ovr -------------------------------------------------------------------------------- /pyosp/datasets/crater.tif.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200816150056001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\cc.tifLocal Area Network 3 | -------------------------------------------------------------------------------- /pyosp/datasets/cross_data.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/cross_data.txt -------------------------------------------------------------------------------- /pyosp/datasets/elev_valid.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/elev_valid.txt -------------------------------------------------------------------------------- /pyosp/datasets/homo_baseline.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /pyosp/datasets/homo_baseline.dbf: -------------------------------------------------------------------------------- 1 | xAIdN 0 -------------------------------------------------------------------------------- /pyosp/datasets/homo_baseline.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baseline.sbn -------------------------------------------------------------------------------- /pyosp/datasets/homo_baseline.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baseline.sbx -------------------------------------------------------------------------------- /pyosp/datasets/homo_baseline.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baseline.shp -------------------------------------------------------------------------------- /pyosp/datasets/homo_baseline.shp.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200812101048001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\homo_baselineLocal Area Network 3 | -------------------------------------------------------------------------------- /pyosp/datasets/homo_baseline.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baseline.shx -------------------------------------------------------------------------------- /pyosp/datasets/homo_baselineOffset.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /pyosp/datasets/homo_baselineOffset.dbf: -------------------------------------------------------------------------------- 1 | xAIdN 0 -------------------------------------------------------------------------------- /pyosp/datasets/homo_baselineOffset.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baselineOffset.sbn -------------------------------------------------------------------------------- /pyosp/datasets/homo_baselineOffset.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baselineOffset.sbx -------------------------------------------------------------------------------- /pyosp/datasets/homo_baselineOffset.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baselineOffset.shp -------------------------------------------------------------------------------- /pyosp/datasets/homo_baselineOffset.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_baselineOffset.shx -------------------------------------------------------------------------------- /pyosp/datasets/homo_elev10.tfw: -------------------------------------------------------------------------------- 1 | 0.8000000000 2 | 0.0000000000 3 | 0.0000000000 4 | -0.8000000000 5 | 0.0000000000 6 | 200.0000000000 7 | -------------------------------------------------------------------------------- /pyosp/datasets/homo_elev10.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_elev10.tif -------------------------------------------------------------------------------- /pyosp/datasets/homo_elev10.tif.aux.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generic 4 | 5 | 6 | 7 | 8 | 10.00018692016602 9 | 55.00495529174805 10 | 256 11 | 1 12 | 0 13 | 109|99|94|103|83|110|91|101|97|89|115|85|85|106|103|84|94|91|86|93|90|72|94|79|76|66|101|82|82|82|80|78|78|84|79|77|82|80|77|84|73|79|86|67|82|72|75|72|79|83|79|69|70|79|91|85|59|91|79|72|84|80|74|77|73|78|67|83|79|80|69|70|65|87|66|73|70|68|72|74|81|77|70|70|60|72|76|81|82|73|81|73|92|80|81|63|81|74|87|72|67|81|79|74|59|71|63|74|48|66|46|62|50|47|47|62|51|55|46|59|50|52|47|42|65|53|48|64|50|65|66|55|47|44|50|48|56|67|37|49|51|52|53|50|65|61|54|54|57|43|65|64|46|66|44|52|71|60|58|55|56|66|48|46|54|44|63|55|47|38|29|29|41|40|40|40|37|35|30|42|30|25|39|29|34|27|30|28|28|30|24|25|19|24|26|15|23|22|12|17|26|18|25|16|17|21|14|21|15|17|18|20|17|14|18|17|24|15|16|29|17|11|17|20|17|18|19|11|16|16|11|10|12|9|14|9|5|8|5|7|6|9|8|2|8|3|4|5|4|3|1|2|0|1|1|1 14 | 15 | 16 | 17 | ATHEMATIC 18 | 114.9094783848633 19 | 20 | 55.004955291748 21 | 25.869420960576 22 | 10.000186920166 23 | 1 24 | 1 25 | 10.719583871814 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pyosp/datasets/homo_elev10.tif.ovr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_elev10.tif.ovr -------------------------------------------------------------------------------- /pyosp/datasets/homo_mount.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_mount.tif -------------------------------------------------------------------------------- /pyosp/datasets/homo_start_end.CPG: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /pyosp/datasets/homo_start_end.dbf: -------------------------------------------------------------------------------- 1 | xAIdN 0 0 -------------------------------------------------------------------------------- /pyosp/datasets/homo_start_end.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_start_end.sbn -------------------------------------------------------------------------------- /pyosp/datasets/homo_start_end.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_start_end.sbx -------------------------------------------------------------------------------- /pyosp/datasets/homo_start_end.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_start_end.shp -------------------------------------------------------------------------------- /pyosp/datasets/homo_start_end.shp.xml: -------------------------------------------------------------------------------- 1 | 2 | 20200815194556001.0TRUEfile://\\KGSIP141\C$\Users\yzh486\Dropbox\Documents\riverTerrace\pyosp\tests\data\start_endLocal Area Network 3 | -------------------------------------------------------------------------------- /pyosp/datasets/homo_start_end.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/homo_start_end.shx -------------------------------------------------------------------------------- /pyosp/datasets/slope_valid.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/slope_valid.txt -------------------------------------------------------------------------------- /pyosp/datasets/tpi_valid.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/datasets/tpi_valid.txt -------------------------------------------------------------------------------- /pyosp/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyOSP-devs/PyOSP/88fe8dc3073d5f350524ac9f904af912d99b3bfc/pyosp/tests/__init__.py -------------------------------------------------------------------------------- /pyosp/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | import os, sys 5 | from collections import namedtuple 6 | from pyosp import * 7 | 8 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 9 | dat = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../datasets/") 10 | 11 | homo_line = os.path.join(dat, "homo_baseline.shp") 12 | homo_raster = os.path.join(dat, "homo_mount.tif") 13 | 14 | cir_center = os.path.join(dat, "center.shp") 15 | cir_raster = os.path.join(dat, "crater.tif") 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def base_homo(**kwargs): 20 | def _base_homo(**kwargs): 21 | return Base_curv(line=homo_line, raster=homo_raster, width=100, **kwargs) 22 | 23 | return _base_homo 24 | 25 | 26 | @pytest.fixture(scope="module") 27 | def orig_homo(**kwargs): 28 | def _orig_homo(**kwargs): 29 | return Orig_curv(line=homo_line, raster=homo_raster, width=100, **kwargs) 30 | 31 | return _orig_homo 32 | 33 | 34 | @pytest.fixture(scope="module") 35 | def elev_homo(**kwargs): 36 | def _elev_homo(**kwargs): 37 | return Elev_curv( 38 | line=homo_line, raster=homo_raster, width=100, min_elev=0.01, **kwargs 39 | ) 40 | 41 | return _elev_homo 42 | 43 | 44 | @pytest.fixture(scope="module") 45 | def slope_homo(**kwargs): 46 | def _slope_homo(**kwargs): 47 | return Slope_curv( 48 | line=homo_line, raster=homo_raster, width=100, min_slope=1, **kwargs 49 | ) 50 | 51 | return _slope_homo 52 | 53 | 54 | @pytest.fixture(scope="module") 55 | def tpi_homo(**kwargs): 56 | def _tpi_homo(**kwargs): 57 | return Tpi_curv( 58 | line=homo_line, 59 | raster=homo_raster, 60 | width=100, 61 | tpi_radius=50, 62 | min_tpi=-5, 63 | **kwargs 64 | ) 65 | 66 | return _tpi_homo 67 | 68 | 69 | @pytest.fixture(scope="module") 70 | def base_cir(**kwargs): 71 | def _base_cir(**kwargs): 72 | return Base_cir( 73 | cir_center, 74 | cir_raster, 75 | radius=80, 76 | ng_start=0, 77 | ng_end=300, 78 | ng_stepsize=1, 79 | radial_stepsize=None, 80 | **kwargs 81 | ) 82 | 83 | return _base_cir 84 | 85 | 86 | @pytest.fixture(scope="module") 87 | def orig_cir(**kwargs): 88 | def _orig_cir(**kwargs): 89 | return Orig_cir( 90 | cir_center, 91 | cir_raster, 92 | radius=80, 93 | ng_start=0, 94 | ng_end=300, 95 | ng_stepsize=1, 96 | radial_stepsize=None, 97 | **kwargs 98 | ) 99 | 100 | return _orig_cir 101 | 102 | 103 | @pytest.fixture(scope="module") 104 | def elev_cir(**kwargs): 105 | def _elev_cir(**kwargs): 106 | return Elev_cir( 107 | cir_center, 108 | cir_raster, 109 | radius=80, 110 | min_elev=4, 111 | ng_start=0, 112 | ng_end=300, 113 | ng_stepsize=1, 114 | radial_stepsize=None, 115 | **kwargs 116 | ) 117 | 118 | return _elev_cir 119 | 120 | 121 | @pytest.fixture(scope="module") 122 | def slope_cir(**kwargs): 123 | def _slope_cir(**kwargs): 124 | return Slope_cir( 125 | cir_center, 126 | cir_raster, 127 | radius=80, 128 | min_slope=13, 129 | ng_start=0, 130 | ng_end=300, 131 | ng_stepsize=1, 132 | radial_stepsize=None, 133 | **kwargs 134 | ) 135 | 136 | return _slope_cir 137 | 138 | 139 | @pytest.fixture(scope="module") 140 | def tpi_cir(**kwargs): 141 | def _tpi_cir(**kwargs): 142 | return Tpi_cir( 143 | cir_center, 144 | cir_raster, 145 | radius=80, 146 | tpi_radius=50, 147 | min_tpi=2, 148 | ng_start=0, 149 | ng_end=300, 150 | ng_stepsize=1, 151 | radial_stepsize=None, 152 | **kwargs 153 | ) 154 | 155 | return _tpi_cir 156 | -------------------------------------------------------------------------------- /pyosp/tests/test_cirBase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os, sys 4 | 5 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 6 | dat = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../datasets/") 7 | 8 | 9 | class TestBaseCir: 10 | def test_initial(self, base_cir): 11 | """Test the setup""" 12 | base = base_cir() 13 | assert base.radial_stepsize == base.raster.GetGeoTransform()[1] 14 | assert len(base.distance) == base.radius // base.radial_stepsize + 1 15 | -------------------------------------------------------------------------------- /pyosp/tests/test_cirPost.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | import os, sys 5 | import pyosp 6 | import numpy as np 7 | import pickle 8 | 9 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 10 | dat = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../datasets/") 11 | 12 | 13 | class TestPost: 14 | def test_outPolygon(self, orig_cir): 15 | orig = orig_cir() 16 | polygon = orig.out_polygon() 17 | px, py = polygon.exterior.xy 18 | assert len(px) == 303 19 | assert len(py) == 303 20 | 21 | def test_outpolylines(self, orig_cir): 22 | orig = orig_cir() 23 | polylines = orig.out_polylines() 24 | bounds = polylines.bounds 25 | assert 20.8771 == pytest.approx(bounds[0], 0.001) 26 | assert 179.2956 == pytest.approx(bounds[2], 0.001) 27 | 28 | def test_polyring(self, orig_cir): 29 | orig = orig_cir() 30 | polyring = orig.out_polyring(start=10, end=30) 31 | assert 2052.88 == pytest.approx(polyring.area, 0.01) 32 | 33 | def test_profilePlot(self, orig_cir): 34 | orig = orig_cir() 35 | orig.profile_plot(color="maroon", p_coords=[[40, 40]], s=3, c="k") 36 | assert True 37 | 38 | def test_slicePlot(self, orig_cir): 39 | orig = orig_cir() 40 | orig.slice_plot(angle=200) 41 | assert True 42 | 43 | def test_slicePolyline(self, orig_cir): 44 | orig = orig_cir() 45 | polyline = orig.slice_polyline(angle=200) 46 | bounds = polyline.bounds 47 | assert 25.65404 == pytest.approx(bounds[0], 0.001) 48 | assert 100.08637 == pytest.approx(bounds[2], 0.001) 49 | 50 | def test_hist(self, orig_cir): 51 | orig = orig_cir() 52 | orig.hist(bins=20) 53 | assert True 54 | 55 | def test_sliceHist(self, orig_cir): 56 | orig = orig_cir() 57 | orig.slice_hist(angle=200, bins=20) 58 | assert True 59 | -------------------------------------------------------------------------------- /pyosp/tests/test_cirSP.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | import os, sys 5 | import pyosp 6 | import numpy as np 7 | 8 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 9 | dat = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../datasets/") 10 | 11 | 12 | class TestCir: 13 | def test_elev_cir(self, elev_cir): 14 | """ 15 | Data INSIDE of border should fall in geo-parameter range. 16 | OUTSIDE should not. 17 | """ 18 | elev = elev_cir() 19 | lines = elev.lines 20 | 21 | p_in = [x[-1] for x in lines[90:181]] 22 | gradient = [np.radians(ng) for ng in range(90, 181)] 23 | p_dat_in = [] 24 | p_dat_out = [] 25 | for ind, point in enumerate(p_in): 26 | p_out = [ 27 | point[0] + np.cos(gradient[ind]) * elev.radial_stepsize, 28 | point[1] + np.sin(gradient[ind]) * elev.radial_stepsize, 29 | ] 30 | 31 | dat_in = pyosp.Point_elevation(point, elev.raster).value 32 | dat_out = pyosp.Point_elevation(p_out, elev.raster).value 33 | 34 | p_dat_in.append(dat_in) 35 | p_dat_out.append(dat_out) 36 | 37 | assert all(i < 4 for i in p_dat_out) 38 | 39 | def test_slope_cir(self, slope_cir): 40 | """ 41 | Data INSIDE of border should fall in geo-parameter range. 42 | OUTSIDE should not. 43 | """ 44 | slope = slope_cir() 45 | lines = slope.lines 46 | cell_res = slope.raster.GetGeoTransform()[1] 47 | 48 | p_in = [x[-1] for x in lines[90:181]] 49 | gradient = [np.radians(ng) for ng in range(90, 181)] 50 | p_dat_in = [] 51 | p_dat_out = [] 52 | for ind, point in enumerate(p_in): 53 | p_out = [ 54 | point[0] + np.cos(gradient[ind]) * slope.radial_stepsize, 55 | point[1] + np.sin(gradient[ind]) * slope.radial_stepsize, 56 | ] 57 | 58 | dat_in = pyosp.Geo_slope(point, slope.raster, cell_res).value 59 | dat_out = pyosp.Geo_slope(p_out, slope.raster, cell_res).value 60 | 61 | p_dat_in.append(dat_in) 62 | p_dat_out.append(dat_out) 63 | 64 | assert all(i < 13 for i in p_dat_out) 65 | 66 | def test_tpi_cir(self, tpi_cir): 67 | """ 68 | Data INSIDE of border should fall in geo-parameter range. 69 | OUTSIDE should not. 70 | """ 71 | tpi = tpi_cir() 72 | lines = tpi.lines 73 | 74 | p_in = [x[-1] for x in lines[90:181]] 75 | gradient = [np.radians(ng) for ng in range(90, 181)] 76 | p_dat_in = [] 77 | p_dat_out = [] 78 | for ind, point in enumerate(p_in): 79 | p_out = [ 80 | point[0] + np.cos(gradient[ind]) * tpi.radial_stepsize, 81 | point[1] + np.sin(gradient[ind]) * tpi.radial_stepsize, 82 | ] 83 | 84 | dat_in = pyosp.Tpi(point, tpi.raster, tpi.tpi_radius).value 85 | dat_out = pyosp.Tpi(p_out, tpi.raster, tpi.tpi_radius).value 86 | p_dat_in.append(dat_in) 87 | p_dat_out.append(dat_out) 88 | 89 | assert all(i < 2 for i in p_dat_out) 90 | -------------------------------------------------------------------------------- /pyosp/tests/test_curvBase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | import os, sys 5 | from pyosp import point_coords 6 | 7 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 8 | dat = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../datasets/") 9 | 10 | 11 | class TestBaseHomo: 12 | # @pytest.mark.parametrize('base_homo', [ 13 | # dict(line_stepsize=None, cross_stepsize=None), 14 | # dict(line_stepsize=100) 15 | # ], indirect=True) 16 | def test_initial(self, base_homo): 17 | """Test the setup""" 18 | base = base_homo(line_stepsize=None, cross_stepsize=None) 19 | assert base.line_stepsize == base.cell_res 20 | assert base.cross_stepsize == base.cell_res 21 | assert len(base.distance) == base.line.length // base.line_stepsize + 1 22 | assert len(base.line_p) == len(base.distance) 23 | 24 | def test_segment(self, base_homo): 25 | """Test the start and end of chosen segment: 26 | start == line.length / 2 27 | end == line.length / 4 28 | """ 29 | start_end = os.path.join(dat, "homo_start_end.shp") 30 | coords = point_coords(start_end) 31 | base = base_homo(line_stepsize=None, cross_stepsize=None) 32 | start_ind, end_ind = base._segment(coords[1], coords[0]) 33 | assert abs(start_ind - len(base.distance) / 4) <= 3 34 | assert abs(end_ind - len(base.distance) / 2) <= 3 35 | 36 | # if given values are distances 37 | start_distance = base.distance[len(base.distance) // 4] 38 | end_distance = base.distance[len(base.distance) // 2] 39 | start_ind, end_ind = base._segment(start_distance, end_distance) 40 | assert abs(start_ind - len(base.distance) / 4) <= 3 41 | assert abs(end_ind - len(base.distance) / 2) <= 3 42 | -------------------------------------------------------------------------------- /pyosp/tests/test_curvPost.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | import os, sys 5 | import pyosp 6 | import numpy as np 7 | import pickle 8 | 9 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 10 | dat = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../datasets/") 11 | 12 | 13 | class TestPost: 14 | def test_outPolygon(self, orig_homo): 15 | start_end = os.path.join(dat, "homo_start_end.shp") 16 | coords = pyosp.point_coords(start_end) 17 | orig = orig_homo() 18 | polygon = orig.out_polygon(start=coords[1], end=coords[0]) 19 | px, py = polygon.exterior.xy 20 | assert 108.44225 == pytest.approx(px[0], 0.001) 21 | assert 108.44225 == pytest.approx(px[-1], 0.001) 22 | assert 28.5506 == pytest.approx(py[0], 0.001) 23 | assert 28.5506 == pytest.approx(py[-1], 0.001) 24 | 25 | def test_outPolyline(self, orig_homo): 26 | start_end = os.path.join(dat, "homo_start_end.shp") 27 | coords = pyosp.point_coords(start_end) 28 | orig = orig_homo() 29 | polylines = orig.out_polylines(start=coords[1], end=coords[0]) 30 | lineBounds = polylines.bounds 31 | assert 72.595 == pytest.approx(lineBounds[0], 0.001) 32 | assert 134.606 == pytest.approx(lineBounds[3], 0.001) 33 | 34 | def test_profile_plot(self, orig_homo): 35 | start_end = os.path.join(dat, "homo_start_end.shp") 36 | orig = orig_homo() 37 | orig.profile_plot( 38 | start=0, end=100, color="maroon", points=start_end, s=5, marker="s" 39 | ) 40 | assert True 41 | 42 | def test_density_scatter(self, orig_homo): 43 | start_end = os.path.join(dat, "homo_start_end.shp") 44 | coords = pyosp.point_coords(start_end) 45 | orig = orig_homo() 46 | orig.density_scatter(bins=20, start=coords[1], end=coords[0], cmap="jet", s=1) 47 | assert True 48 | 49 | def test_slice_plot(self, orig_homo): 50 | start_end = os.path.join(dat, "homo_start_end.shp") 51 | coords = pyosp.point_coords(start_end) 52 | orig = orig_homo() 53 | orig.slice_plot(loc=coords[1]) 54 | assert True 55 | 56 | def test_slice_hist(self, orig_homo): 57 | start_end = os.path.join(dat, "homo_start_end.shp") 58 | coords = pyosp.point_coords(start_end) 59 | orig = orig_homo() 60 | orig.slice_hist(loc=coords[1], bins=20) 61 | assert True 62 | 63 | def test_cross_dat(self, orig_homo): 64 | start_end = os.path.join(dat, "homo_start_end.shp") 65 | data_path = os.path.join(dat, "cross_data.txt") 66 | data_valid = pickle.load(open(data_path, "rb")) 67 | coords = pyosp.point_coords(start_end) 68 | orig = orig_homo() 69 | cross_dat = orig.cross_dat(start=coords[1], end=coords[0]) 70 | assert all( 71 | [a == b for a, b in zip(cross_dat["distance"], data_valid["distance"])] 72 | ) 73 | # assert all( 74 | # [ 75 | # a == b 76 | # for a, b in zip( 77 | # [np.nanmean(p) for p in cross_dat["cross_matrix"]], 78 | # [np.nanmean(p) for p in data_valid["cross_matrix"]], 79 | # ) 80 | # ] 81 | # ) 82 | 83 | def test_cross_plot(self, orig_homo): 84 | start_end = os.path.join(dat, "homo_start_end.shp") 85 | orig = orig_homo() 86 | orig.cross_plot(color="maroon", points=start_end) 87 | assert True 88 | 89 | def test_post_tpi(self, orig_homo): 90 | start_end = os.path.join(dat, "homo_start_end.shp") 91 | data_path = os.path.join(dat, "tpi_valid.txt") 92 | data_valid = pickle.load(open(data_path, "rb")) 93 | coords = pyosp.point_coords(start_end) 94 | orig = orig_homo() 95 | post_dat = orig.post_tpi( 96 | radius=50, 97 | min_val=0, 98 | max_val=100, 99 | start=coords[1], 100 | end=coords[0], 101 | cross=True, 102 | ) 103 | mean_valid = np.nanmean(np.hstack(data_valid[1][50])) 104 | mean_post = np.nanmean(np.hstack(post_dat[1][50])) 105 | assert all([a == b for a, b in zip(post_dat[0], data_valid[0])]) 106 | assert 0 == pytest.approx(mean_valid-mean_post, 5.) 107 | 108 | def test_post_slope(self, orig_homo): 109 | start_end = os.path.join(dat, "homo_start_end.shp") 110 | data_path = os.path.join(dat, "slope_valid.txt") 111 | data_valid = pickle.load(open(data_path, "rb")) 112 | coords = pyosp.point_coords(start_end) 113 | orig = orig_homo() 114 | post_dat = orig.post_slope( 115 | min_val=2, max_val=10, start=coords[1], end=coords[0], cross=True 116 | ) 117 | mean_valid = np.nanmean(np.hstack(data_valid[1][50])) 118 | mean_post = np.nanmean(np.hstack(post_dat[1][50])) 119 | assert all([a == b for a, b in zip(post_dat[0], data_valid[0])]) 120 | assert mean_valid == mean_post 121 | 122 | def test_post_elev(self, orig_homo): 123 | start_end = os.path.join(dat, "homo_start_end.shp") 124 | data_path = os.path.join(dat, "elev_valid.txt") 125 | data_valid = pickle.load(open(data_path, "rb")) 126 | coords = pyosp.point_coords(start_end) 127 | orig = orig_homo() 128 | post_dat = orig.post_elev( 129 | min_val=5, max_val=20, start=coords[1], end=coords[0], cross=True 130 | ) 131 | mean_valid = np.nanmean(np.hstack(data_valid[1][50])) 132 | mean_post = np.nanmean(np.hstack(post_dat[1][50])) 133 | assert all([a == b for a, b in zip(post_dat[0], data_valid[0])]) 134 | assert mean_valid == mean_post 135 | -------------------------------------------------------------------------------- /pyosp/tests/test_curvSP.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | import os, sys 5 | import pyosp 6 | import numpy as np 7 | 8 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 9 | dat = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../datasets/") 10 | 11 | 12 | @pytest.fixture() 13 | def dxdy(orig_homo): 14 | orig = orig_homo() 15 | p1 = orig.lines[len(orig.lines) // 2][0] 16 | p2 = orig.lines[len(orig.lines) // 2][1] 17 | slope = (p2[1] - p1[1]) / (p2[0] - p1[0]) 18 | dx = np.sqrt(orig.cross_stepsize ** 2 / (slope ** 2 + 1)) 19 | dy = dx * slope 20 | yield dx, dy 21 | 22 | 23 | class TestCurv: 24 | def test_elev_homo(self, elev_homo, dxdy): 25 | """ 26 | Double sides test. 27 | All points WITHIN polygon should fall in geo-parameter range. 28 | All points OUTSIDE should not. 29 | """ 30 | elev = elev_homo() 31 | lines = elev.lines 32 | dx, dy = dxdy 33 | 34 | points_left = [x[0] for x in lines[50:150]] 35 | points_right = [x[-1] for x in lines[50:150]] 36 | p_dat_in = [] 37 | p_dat_out = [] 38 | for point in points_left: 39 | p_out = [point[0] - dx, point[1] - dy] 40 | 41 | p_elev_in = pyosp.Point_elevation(point, elev.raster).value 42 | p_elev_out = pyosp.Point_elevation(p_out, elev.raster).value 43 | 44 | p_dat_in.append(p_elev_in) 45 | p_dat_out.append(p_elev_out) 46 | 47 | assert all(i >= 0.01 for i in p_dat_in) 48 | assert all(i < 0.01 for i in p_dat_out) 49 | 50 | p_dat_in = [] 51 | p_dat_out = [] 52 | for point in points_right: 53 | p_out = [point[0] + dx, point[1] + dy] 54 | 55 | p_elev_in = pyosp.Point_elevation(point, elev.raster).value 56 | p_elev_out = pyosp.Point_elevation(p_out, elev.raster).value 57 | 58 | p_dat_in.append(p_elev_in) 59 | p_dat_out.append(p_elev_out) 60 | 61 | assert all(i >= 0.01 for i in p_dat_in) 62 | assert all(i < 0.01 for i in p_dat_out) 63 | 64 | def test_slope_homo(self, slope_homo, dxdy): 65 | """ 66 | Double sides test. 67 | All points WITHIN polygon should fall in geo-parameter range. 68 | All points OUTSIDE should not. 69 | """ 70 | slope = slope_homo() 71 | lines = slope.lines 72 | dx, dy = dxdy 73 | 74 | points_left = [x[0] for x in lines[50:150]] 75 | points_right = [x[-1] for x in lines[50:150]] 76 | p_dat_in = [] 77 | p_dat_out = [] 78 | for point in points_left: 79 | p_out = [point[0] - dx, point[1] - dy] 80 | 81 | dat_in = pyosp.Geo_slope(point, slope.raster, slope.cell_res).value 82 | dat_out = pyosp.Geo_slope(p_out, slope.raster, slope.cell_res).value 83 | 84 | p_dat_in.append(dat_in) 85 | p_dat_out.append(dat_out) 86 | 87 | assert all(i >= 1 for i in p_dat_in) 88 | assert all(i < 1 for i in p_dat_out) 89 | 90 | p_dat_in = [] 91 | p_dat_out = [] 92 | for point in points_right: 93 | p_out = [point[0] + dx, point[1] + dy] 94 | 95 | dat_in = pyosp.Geo_slope(point, slope.raster, slope.cell_res).value 96 | dat_out = pyosp.Geo_slope(p_out, slope.raster, slope.cell_res).value 97 | 98 | p_dat_in.append(dat_in) 99 | p_dat_out.append(dat_out) 100 | 101 | assert all(i >= 1 for i in p_dat_in) 102 | assert all(i < 1 for i in p_dat_out) 103 | 104 | def test_tpi_homo(self, tpi_homo, dxdy): 105 | """ 106 | Double sides test. 107 | All points WITHIN polygon should fall in geo-parameter range. 108 | All points OUTSIDE should not. 109 | """ 110 | tpi = tpi_homo() 111 | lines = tpi.lines 112 | dx, dy = dxdy 113 | 114 | points_left = [x[0] for x in lines[50:100]] 115 | points_right = [x[-1] for x in lines[50:100]] 116 | p_dat_in = [] 117 | p_dat_out = [] 118 | for point in points_left: 119 | p_out = [point[0] - dx, point[1] - dy] 120 | 121 | dat_in = pyosp.Tpi(point, tpi.raster, tpi.tpi_radius).value 122 | dat_out = pyosp.Tpi(p_out, tpi.raster, tpi.tpi_radius).value 123 | p_dat_in.append(dat_in) 124 | p_dat_out.append(dat_out) 125 | 126 | assert all(i >= -5 for i in p_dat_in) 127 | assert all(i < -5 for i in p_dat_out) 128 | 129 | p_dat_in = [] 130 | p_dat_out = [] 131 | for point in points_right: 132 | p_out = [point[0] + dx, point[1] + dy] 133 | 134 | dat_in = pyosp.Tpi(point, tpi.raster, tpi.tpi_radius).value 135 | dat_out = pyosp.Tpi(p_out, tpi.raster, tpi.tpi_radius).value 136 | p_dat_in.append(dat_in) 137 | p_dat_out.append(dat_out) 138 | 139 | assert all(i >= -5 for i in p_dat_in) 140 | assert all(i < -5 for i in p_dat_out) 141 | -------------------------------------------------------------------------------- /pyosp/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = [ 4 | "pairwise", 5 | "grouped", 6 | "read_shape", 7 | "point_coords", 8 | "write_polygon", 9 | "write_polylines", 10 | "progressBar", 11 | ] 12 | 13 | from osgeo import ogr 14 | import json 15 | import itertools 16 | from shapely.geometry import shape 17 | import sys 18 | 19 | 20 | def pairwise(iterable): 21 | "s -> (s0,s1), (s1,s2), (s2, s3), ..." 22 | a, b = itertools.tee(iterable) 23 | next(b, None) 24 | return zip(a, b) 25 | 26 | 27 | def grouped(iterable): 28 | "s -> (s0, s1), (s2, s3), (s4, s5), ..." 29 | a = iter(iterable) 30 | return zip(a, a) 31 | 32 | 33 | def read_shape(shapefile): 34 | "Return a shapely object." 35 | file = ogr.Open(shapefile) 36 | layer = file.GetLayer(0) 37 | feature = layer.GetFeature(0) 38 | read = feature.ExportToJson() 39 | outshape = shape(json.loads(read)["geometry"]) 40 | return outshape 41 | 42 | 43 | def point_coords(shapefile): 44 | "Return coordinates from point(s) shapefile" 45 | file = ogr.Open(shapefile) 46 | layer = file.GetLayer(0) 47 | coords = [] 48 | for feature in layer: 49 | read = feature.ExportToJson() 50 | coords.append(json.loads(read)["geometry"]["coordinates"]) 51 | 52 | return coords 53 | 54 | 55 | def write_polygon(poly, out_file): 56 | """Write polygon shapefile to file path. 57 | 58 | :param poly: input polygon 59 | :type poly: shapely polygon object 60 | :param out_file: file path to restore polygon 61 | :type out_file: str 62 | """ 63 | driver = ogr.GetDriverByName("Esri Shapefile") 64 | ds = driver.CreateDataSource(out_file) 65 | layer = ds.CreateLayer("", None, ogr.wkbPolygon) 66 | # Add one attribute 67 | layer.CreateField(ogr.FieldDefn("id", ogr.OFTInteger)) 68 | defn = layer.GetLayerDefn() 69 | 70 | # Create a new feature (attribute and geometry) 71 | feat = ogr.Feature(defn) 72 | feat.SetField("id", 1) 73 | 74 | # Make a geometry, from Shapely object 75 | geom = ogr.CreateGeometryFromWkb(poly.wkb) 76 | feat.SetGeometry(geom) 77 | 78 | layer.CreateFeature(feat) 79 | 80 | # Save and close everything 81 | ds = layer = feat = geom = None 82 | 83 | 84 | def write_polylines(poly, out_file): 85 | """Write polyline shapefile to file path. 86 | 87 | :param poly: input polyline 88 | :type poly: shapely polyline object 89 | :param out_file: file path to restore polyline 90 | :type out_file: str 91 | """ 92 | driver = ogr.GetDriverByName("Esri Shapefile") 93 | ds = driver.CreateDataSource(out_file) 94 | layer = ds.CreateLayer("", None, ogr.wkbLineString) 95 | # Add one attribute 96 | layer.CreateField(ogr.FieldDefn("id", ogr.OFTInteger)) 97 | defn = layer.GetLayerDefn() 98 | 99 | # Create a new feature (attribute and geometry) 100 | feat = ogr.Feature(defn) 101 | feat.SetField("id", 1) 102 | 103 | # Make a geometry, from Shapely object 104 | geom = ogr.CreateGeometryFromWkb(poly.wkb) 105 | feat.SetGeometry(geom) 106 | 107 | layer.CreateFeature(feat) 108 | 109 | # Save and close everything 110 | ds = layer = feat = geom = None 111 | 112 | 113 | def progressBar(current, total, width=25): 114 | """Progress bar, call inside of iteration. 115 | 116 | :param current: current progress 117 | :type current: int 118 | :param total: total iterations 119 | :type total: int 120 | :param width: bar width, defaults to 25 121 | :type width: int, optional 122 | """ 123 | bar_width = width 124 | block = int(round(bar_width * current / total)) 125 | text = "\rProcessing: [{0}] {1} of {2} lineSteps".format( 126 | "#" * block + "-" * (bar_width - block), current, total 127 | ) 128 | 129 | sys.stdout.write(text) 130 | sys.stdout.flush() 131 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | python: 2 | version: 3 3 | 4 | conda: 5 | file: environment.yml 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gdal>=3 2 | matplotlib 3 | numpy 4 | shapely 5 | pytest 6 | scipy 7 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | gdal>=3 2 | matplotlib 3 | numpy 4 | shapely>=1.6 5 | pytest 6 | scipy 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The PyOSP Developers 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License") 4 | 5 | import os 6 | from os.path import realpath, dirname, join 7 | from setuptools import setup, find_packages 8 | import re 9 | 10 | with open('README.md') as readme_file: 11 | readme = readme_file.read() 12 | 13 | DISTNAME = "pyosp" 14 | DESCRIPTION = "An Python Library for Object-oriented Swath Profile Analysis" 15 | AUTHOR = "Yichuan Zhu, Matthew A. Massey, Jason Dortch" 16 | AUTHOR_EMAIL = "yichuan211@gmail.com" 17 | URL = "https://github.com/PyOSP-devs/PyOSP.git" 18 | LICENSE = "Apache License, Version 2.0" 19 | 20 | classifiers=[ 21 | 'Development Status :: 4 - Beta', 22 | 'Intended Audience :: GIS/Science/Research', 23 | 'License :: OSI Approved :: Apache Software License', 24 | 'Natural Language :: English', 25 | "Operating System :: OS Independent", 26 | ] 27 | 28 | PROJECT_ROOT = dirname(realpath(__file__)) 29 | 30 | # version 31 | def get_version(): 32 | VERSIONFILE = join("pyosp", "__init__.py") 33 | lines = open(VERSIONFILE, "rt").readlines() 34 | version_regex = r"^__version__ = ['\"]([^'\"]*)['\"]" 35 | for line in lines: 36 | mo = re.search(version_regex, line, re.M) 37 | if mo: 38 | return mo.group(1) 39 | raise RuntimeError("Unable to find version in %s." % (VERSIONFILE,)) 40 | 41 | # Get the long description from the README file 42 | with open(join(PROJECT_ROOT, "README.md"), encoding="utf-8") as buff: 43 | LONG_DESCRIPTION = buff.read() 44 | 45 | REQUIREMENTS_FILE = join(PROJECT_ROOT, "requirements.txt") 46 | 47 | with open(REQUIREMENTS_FILE) as f: 48 | install_reqs = f.read().splitlines() 49 | 50 | test_reqs = ["pytest", "pytest-cov"] 51 | 52 | setup( 53 | name=DISTNAME, 54 | version=get_version(), 55 | author=AUTHOR, 56 | author_email=AUTHOR_EMAIL, 57 | description=DESCRIPTION, 58 | license=LICENSE, 59 | url='https://github.com/PyOSP-devs/PyOSP.git', 60 | long_description=LONG_DESCRIPTION, 61 | long_description_content_type="text/x-rst", 62 | packages=find_packages(), 63 | include_package_data=True, 64 | package_data={"pyosp": ['./datasets/*']}, 65 | python_requires='>=3.6', 66 | install_requires=install_reqs, 67 | tests_require=test_reqs, 68 | ) 69 | --------------------------------------------------------------------------------