├── .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 | 
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ---
13 |
14 | 
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 | 
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 | 
105 |
106 | Plot, for example, elevation based swath profile.
107 |
108 | ```python
109 | elev.profile_plot()
110 | ```
111 |
112 |
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 | x A Id N
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 | x A Id N
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 | x A Id N
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 | x A Id N
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 | A Id N
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 | x A Id N
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 | x A Id N
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 | x A Id N
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 |
--------------------------------------------------------------------------------