├── .gitignore ├── .readthedocs-environment.yml ├── .readthedocs.yml ├── .travis.yml ├── LICENSE.txt ├── README.md ├── cw_geodata ├── __init__.py ├── data │ ├── __init__.py │ ├── aff_gdf_result.csv │ ├── geotiff_labels.geojson │ ├── gj_to_px_result.geojson │ ├── sample.csv │ ├── sample.geojson │ ├── sample_b_from_df2px.tif │ ├── sample_b_mask_inner.tif │ ├── sample_b_mask_outer.tif │ ├── sample_b_mask_outer_10.tif │ ├── sample_c_from_df2px.tif │ ├── sample_c_mask.tif │ ├── sample_fbc_from_df2px.tif │ ├── sample_fp_from_df2px.tif │ ├── sample_fp_mask.tif │ ├── sample_fp_mask_from_geojson.tif │ ├── sample_geotiff.tif │ ├── sample_graph.pkl │ ├── sample_roads.geojson │ ├── split_multi_grouped_result.json │ ├── split_multi_result.csv │ ├── split_multi_result.json │ ├── test_overlap_output.txt │ └── w_multipolygon.csv ├── raster_image │ ├── __init__.py │ └── image.py ├── utils │ ├── __init__.py │ ├── core.py │ └── geo.py └── vector_label │ ├── __init__.py │ ├── graph.py │ ├── mask.py │ └── polygon.py ├── docs ├── Makefile ├── make.bat └── source │ ├── api.rst │ ├── conf.py │ └── index.rst ├── environment.yml ├── setup.py └── tests ├── __init__.py ├── test_imports.py ├── test_raster_image ├── __init__.py └── test_image.py ├── test_utils ├── __init__.py ├── test_core.py └── test_geo.py └── test_vector_label ├── __init__.py ├── test_graph.py ├── test_mask.py └── test_polygon.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # MacOS stuff: 7 | .DS_Store 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # celery beat schedule file 89 | celerybeat-schedule 90 | 91 | # SageMath parsed files 92 | *.sage.py 93 | 94 | # Environments 95 | .env 96 | .venv 97 | env/ 98 | venv/ 99 | ENV/ 100 | env.bak/ 101 | venv.bak/ 102 | 103 | # Spyder project settings 104 | .spyderproject 105 | .spyproject 106 | 107 | # Rope project settings 108 | .ropeproject 109 | 110 | # mkdocs documentation 111 | /site 112 | 113 | # mypy 114 | .mypy_cache/ 115 | .dmypy.json 116 | dmypy.json 117 | 118 | # Pyre type checker 119 | .pyre/ 120 | 121 | # Project-specific 122 | sandbox.ipynb 123 | -------------------------------------------------------------------------------- /.readthedocs-environment.yml: -------------------------------------------------------------------------------- 1 | name: cw-geodata 2 | dependencies: 3 | - python=3.6 4 | - pip 5 | - shapely 6 | - fiona 7 | - pandas 8 | - geopandas 9 | - numpy 10 | - scikit-image 11 | - networkx 12 | - rasterio 13 | - pip: 14 | - affine 15 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | formats: 3 | - htmlzip 4 | 5 | python: 6 | version: 3.6 7 | install: 8 | - method: pip 9 | path: . 10 | conda: 11 | environment: .readthedocs-environment.yml 12 | 13 | build: 14 | image: latest 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "3.6" 5 | 6 | # command to install dependencies 7 | install: 8 | - sudo apt-get update 9 | # We do this conditionally because it saves us some downloading if the 10 | # version is the same. 11 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then 12 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; 13 | else 14 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 15 | fi 16 | - bash miniconda.sh -b -p $HOME/miniconda 17 | - export PATH="$HOME/miniconda/bin:$PATH" 18 | - hash -r 19 | - conda config --set always_yes yes --set changeps1 no 20 | - conda update -q conda 21 | # Useful for debugging any issues with conda 22 | - conda info -a 23 | # switch python version spec in environment.yml to match TRAVIS_PYTHON_VERSION 24 | # annoying workaround to `conda env create python=$TRAVIS_PYTHON_VERSION` not working 25 | - sed -i -E 's/(python=)(.*)/\1'$TRAVIS_PYTHON_VERSION'/' ./environment.yml 26 | - conda env create -n cw-geodata --file=environment.yml 27 | - source activate cw-geodata 28 | - python --version 29 | - pip install -q -e .[test] 30 | - pip install codecov pytest pytest-cov 31 | # command to run tests 32 | script: 33 | - pytest --cov=./ 34 | 35 | after_success: 36 | - codecov 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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 2019 CosmiQ Works, an IQT Lab 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is no longer being updated. Future development of code tools for geospatial machine learning analysis will be done at https://github.com/cosmiq/solaris. 2 | 3 |

CosmiQ Works Geospatial Data Processing Tools for ML

4 |

5 | CosmiQ Works 6 |
7 |
8 |
9 | 10 | 11 | build 12 | docs 13 | license 14 | 15 | 16 |

17 | 18 | __This package is currently under active development. Check back soon for a mature version.__ 19 | 20 | - [Installation Instructions](#installation-instructions) 21 | - [API Documentation](https://cw-geodata.readthedocs.io/) 22 | - [Dependencies](#dependencies) 23 | - [License](#license) 24 | --- 25 | This package is built to: 26 | - Enable management and interconversion of geospatial data files without requiring understanding of coordinate reference systems, geospatial transforms, etc. 27 | - Enable creation of training targets for segmentation and object detection from geospatial vector data (_i.e._ geojsons of labels) without requiring understanding of ML training target formats. 28 | 29 | ## Installation Instructions 30 | Several packages require binaries to be installed before pip installing the other packages. We recommend creating a conda environment and installing dependencies there from [environment.yml](./environment.yml), then using `pip` to install this package. 31 | 32 | First, clone this repo to your computer and navigate into the folder: 33 | ``` 34 | git clone https://github.com/cosmiq/cw-geodata.git 35 | cd cw-geodata 36 | ``` 37 | Next, create a [conda](https://anaconda.com/distribution/) environment with dependencies installed as defined in the environment.yml file. 38 | ``` 39 | conda env create -f environment.yml 40 | source activate cw-geodata 41 | ``` 42 | Finally, use `pip` to install this package. 43 | ``` 44 | pip install . 45 | ``` 46 | For bleeding-edge versions (use at your own risk), `pip install` from the dev branch of this repository: 47 | ``` 48 | pip install --upgrade git+https://github.com/CosmiQ/cw-geodata.git@dev 49 | ``` 50 | 51 | ## API Documentation 52 | API documentation can be found [here](https://cw-geodata.readthedocs.io) 53 | 54 | ## Dependencies 55 | All dependencies can be found in [environment.yml](./environment.yml) 56 | 57 | ## License 58 | See [LICENSE](./LICENSE.txt). 59 | -------------------------------------------------------------------------------- /cw_geodata/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/__init__.py -------------------------------------------------------------------------------- /cw_geodata/data/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import rasterio 3 | import gdal 4 | import geopandas as gpd 5 | 6 | data_dir = os.path.abspath(os.path.dirname(__file__)) 7 | 8 | 9 | def sample_load_rasterio(): 10 | return rasterio.open(os.path.join(data_dir, 'sample_geotiff.tif')) 11 | 12 | 13 | def sample_load_gdal(): 14 | return gdal.Open(os.path.join(data_dir, 'sample_geotiff.tif')) 15 | 16 | 17 | def sample_load_geojson(): 18 | return gpd.read_file(os.path.join(data_dir, 'sample.geojson')) 19 | 20 | 21 | def sample_load_csv(): 22 | return pd.read_file(os.path.join(data_dir, 'sample.csv')) 23 | -------------------------------------------------------------------------------- /cw_geodata/data/sample.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::32616" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736348.0, 3722762.5 ], [ 736353.0, 3722762.0 ], [ 736354.0, 3722759.0 ], [ 736352.0, 3722755.5 ], [ 736348.5, 3722755.5 ], [ 736346.0, 3722757.5 ], [ 736348.0, 3722762.5 ] ] ] } }, 6 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736301.0, 3722760.5 ], [ 736310.5, 3722760.0 ], [ 736314.0, 3722758.0 ], [ 736315.0, 3722752.0 ], [ 736310.5, 3722746.5 ], [ 736308.0, 3722746.0 ], [ 736306.0, 3722750.0 ], [ 736301.0, 3722752.0 ], [ 736301.0, 3722760.5 ] ] ] } }, 7 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736306.5, 3722614.0 ], [ 736334.5, 3722611.0 ], [ 736339.5, 3722610.0 ], [ 736339.5, 3722608.5 ], [ 736335.0, 3722603.0 ], [ 736316.0, 3722603.5 ], [ 736304.0, 3722607.5 ], [ 736306.5, 3722614.0 ] ] ] } }, 8 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736359.5, 3722611.5 ], [ 736379.5, 3722608.5 ], [ 736398.0, 3722609.0 ], [ 736399.5, 3722600.0 ], [ 736396.5, 3722596.5 ], [ 736380.5, 3722600.5 ], [ 736373.5, 3722598.5 ], [ 736365.0, 3722600.5 ], [ 736364.0, 3722598.5 ], [ 736360.0, 3722600.0 ], [ 736356.0, 3722605.0 ], [ 736356.0, 3722609.5 ], [ 736359.5, 3722611.5 ] ] ] } }, 9 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736412.5, 3722602.0 ], [ 736429.5, 3722598.0 ], [ 736439.0, 3722601.5 ], [ 736443.5, 3722600.0 ], [ 736454.0, 3722600.0 ], [ 736455.0, 3722594.0 ], [ 736453.5, 3722591.5 ], [ 736444.0, 3722589.5 ], [ 736440.5, 3722587.0 ], [ 736432.5, 3722589.5 ], [ 736427.5, 3722587.5 ], [ 736419.5, 3722587.5 ], [ 736416.0, 3722589.0 ], [ 736410.0, 3722596.0 ], [ 736409.5, 3722600.0 ], [ 736412.5, 3722602.0 ] ] ] } }, 10 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736311.5, 3722591.0 ], [ 736323.5, 3722589.0 ], [ 736325.5, 3722582.0 ], [ 736324.5, 3722579.5 ], [ 736320.5, 3722577.5 ], [ 736302.5, 3722579.0 ], [ 736301.0, 3722580.5 ], [ 736301.0, 3722588.5 ], [ 736311.5, 3722591.0 ] ] ] } }, 11 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736544.0, 3722576.0 ], [ 736547.5, 3722576.0 ], [ 736551.5, 3722573.0 ], [ 736552.0, 3722564.0 ], [ 736550.0, 3722561.0 ], [ 736543.5, 3722559.0 ], [ 736538.5, 3722561.0 ], [ 736537.5, 3722570.5 ], [ 736540.0, 3722575.0 ], [ 736544.0, 3722576.0 ] ] ] } }, 12 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736508.0, 3722564.5 ], [ 736513.5, 3722562.0 ], [ 736515.0, 3722557.0 ], [ 736510.0, 3722539.0 ], [ 736495.0, 3722539.0 ], [ 736491.0, 3722537.0 ], [ 736485.5, 3722537.0 ], [ 736478.5, 3722540.0 ], [ 736472.5, 3722549.0 ], [ 736471.0, 3722556.5 ], [ 736473.0, 3722559.0 ], [ 736497.5, 3722559.0 ], [ 736505.0, 3722561.5 ], [ 736508.0, 3722564.5 ] ] ] } }, 13 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736552.0, 3722551.0 ], [ 736557.0, 3722550.0 ], [ 736559.0, 3722548.0 ], [ 736560.0, 3722543.0 ], [ 736554.5, 3722539.5 ], [ 736547.5, 3722538.5 ], [ 736542.5, 3722535.0 ], [ 736536.0, 3722533.0 ], [ 736534.0, 3722537.0 ], [ 736535.0, 3722543.0 ], [ 736538.0, 3722548.0 ], [ 736552.0, 3722551.0 ] ] ] } }, 14 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736361.0, 3722579.0 ], [ 736365.0, 3722579.0 ], [ 736364.0, 3722522.0 ], [ 736360.0, 3722525.0 ], [ 736357.5, 3722544.0 ], [ 736358.0, 3722565.5 ], [ 736359.5, 3722569.0 ], [ 736358.0, 3722575.5 ], [ 736361.0, 3722579.0 ] ] ] } }, 15 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736319.0, 3722572.0 ], [ 736326.0, 3722570.0 ], [ 736328.0, 3722566.0 ], [ 736330.5, 3722565.0 ], [ 736329.5, 3722547.5 ], [ 736331.0, 3722528.5 ], [ 736330.0, 3722524.5 ], [ 736326.5, 3722520.5 ], [ 736320.0, 3722523.0 ], [ 736318.0, 3722527.0 ], [ 736317.5, 3722570.5 ], [ 736319.0, 3722572.0 ] ] ] } }, 16 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736414.0, 3722573.0 ], [ 736417.5, 3722572.5 ], [ 736420.0, 3722568.0 ], [ 736421.0, 3722556.0 ], [ 736418.5, 3722538.0 ], [ 736424.0, 3722532.5 ], [ 736424.0, 3722527.0 ], [ 736422.5, 3722525.5 ], [ 736412.0, 3722524.0 ], [ 736410.5, 3722521.5 ], [ 736407.0, 3722520.5 ], [ 736383.5, 3722521.0 ], [ 736376.5, 3722528.5 ], [ 736378.0, 3722532.5 ], [ 736402.0, 3722532.0 ], [ 736410.0, 3722539.0 ], [ 736411.0, 3722544.0 ], [ 736408.5, 3722553.5 ], [ 736409.0, 3722569.0 ], [ 736414.0, 3722573.0 ] ] ] } }, 17 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736451.0, 3722575.0 ], [ 736455.0, 3722574.0 ], [ 736457.0, 3722569.0 ], [ 736455.5, 3722555.5 ], [ 736457.0, 3722531.5 ], [ 736454.5, 3722525.0 ], [ 736454.5, 3722516.5 ], [ 736449.0, 3722518.0 ], [ 736449.0, 3722524.0 ], [ 736446.0, 3722525.5 ], [ 736443.5, 3722547.0 ], [ 736445.0, 3722564.5 ], [ 736443.0, 3722569.0 ], [ 736446.0, 3722574.0 ], [ 736451.0, 3722575.0 ] ] ] } }, 18 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736733.5, 3722519.5 ], [ 736735.5, 3722519.0 ], [ 736738.0, 3722512.5 ], [ 736738.0, 3722510.0 ], [ 736736.0, 3722508.0 ], [ 736732.0, 3722510.0 ], [ 736730.5, 3722514.5 ], [ 736732.0, 3722519.0 ], [ 736733.5, 3722519.5 ] ] ] } }, 19 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736448.0, 3722488.5 ], [ 736450.5, 3722488.0 ], [ 736450.5, 3722484.5 ], [ 736448.0, 3722485.5 ], [ 736448.0, 3722488.5 ] ] ] } }, 20 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736412.0, 3722492.0 ], [ 736418.0, 3722492.0 ], [ 736422.0, 3722487.5 ], [ 736423.5, 3722481.5 ], [ 736422.0, 3722478.5 ], [ 736415.0, 3722478.0 ], [ 736408.0, 3722484.5 ], [ 736408.0, 3722490.0 ], [ 736412.0, 3722492.0 ] ] ] } }, 21 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736477.0, 3722491.5 ], [ 736483.0, 3722490.0 ], [ 736482.5, 3722482.5 ], [ 736476.5, 3722474.0 ], [ 736473.0, 3722474.0 ], [ 736469.5, 3722477.5 ], [ 736469.0, 3722482.5 ], [ 736470.0, 3722486.0 ], [ 736477.0, 3722491.5 ] ] ] } }, 22 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736498.0, 3722478.0 ], [ 736500.0, 3722477.5 ], [ 736500.5, 3722473.5 ], [ 736496.0, 3722470.5 ], [ 736494.0, 3722474.5 ], [ 736498.0, 3722478.0 ] ] ] } }, 23 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736744.0, 3722505.0 ], [ 736750.0, 3722505.0 ], [ 736751.0, 3722503.5 ], [ 736751.0, 3722460.0 ], [ 736749.5, 3722459.5 ], [ 736746.0, 3722460.5 ], [ 736741.5, 3722466.0 ], [ 736739.5, 3722492.0 ], [ 736745.0, 3722497.0 ], [ 736745.5, 3722500.5 ], [ 736742.5, 3722502.0 ], [ 736744.0, 3722505.0 ] ] ] } }, 24 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736700.0, 3722476.5 ], [ 736706.0, 3722476.5 ], [ 736708.0, 3722474.0 ], [ 736708.0, 3722465.0 ], [ 736705.5, 3722461.5 ], [ 736696.0, 3722463.0 ], [ 736692.0, 3722468.5 ], [ 736693.5, 3722474.0 ], [ 736700.0, 3722476.5 ] ] ] } }, 25 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736417.5, 3722456.5 ], [ 736422.0, 3722456.5 ], [ 736422.5, 3722454.5 ], [ 736418.0, 3722453.5 ], [ 736417.5, 3722456.5 ] ] ] } }, 26 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736395.5, 3722456.0 ], [ 736401.5, 3722456.0 ], [ 736404.0, 3722452.5 ], [ 736406.5, 3722453.0 ], [ 736406.5, 3722449.0 ], [ 736404.0, 3722449.0 ], [ 736402.0, 3722446.5 ], [ 736396.0, 3722447.0 ], [ 736394.0, 3722448.5 ], [ 736395.5, 3722456.0 ] ] ] } }, 27 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736311.5, 3722494.5 ], [ 736314.5, 3722494.5 ], [ 736318.0, 3722491.5 ], [ 736319.0, 3722484.0 ], [ 736318.0, 3722479.0 ], [ 736316.0, 3722478.5 ], [ 736316.0, 3722475.5 ], [ 736319.0, 3722472.5 ], [ 736316.0, 3722465.0 ], [ 736316.0, 3722462.0 ], [ 736318.5, 3722459.0 ], [ 736318.5, 3722454.5 ], [ 736315.5, 3722452.5 ], [ 736316.0, 3722446.0 ], [ 736314.5, 3722444.5 ], [ 736310.0, 3722445.0 ], [ 736308.5, 3722454.0 ], [ 736308.0, 3722470.5 ], [ 736309.5, 3722480.0 ], [ 736307.5, 3722492.0 ], [ 736311.5, 3722494.5 ] ] ] } }, 28 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736364.0, 3722484.5 ], [ 736365.0, 3722445.5 ], [ 736363.0, 3722439.0 ], [ 736356.0, 3722439.0 ], [ 736353.0, 3722454.5 ], [ 736357.5, 3722463.0 ], [ 736362.5, 3722467.5 ], [ 736357.5, 3722473.0 ], [ 736356.5, 3722478.0 ], [ 736364.0, 3722484.5 ] ] ] } }, 29 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736494.5, 3722447.5 ], [ 736501.0, 3722447.0 ], [ 736501.5, 3722444.0 ], [ 736498.5, 3722442.0 ], [ 736495.0, 3722442.0 ], [ 736495.0, 3722439.0 ], [ 736490.5, 3722439.0 ], [ 736492.0, 3722446.0 ], [ 736494.5, 3722447.5 ] ] ] } }, 30 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736399.0, 3722441.5 ], [ 736403.0, 3722439.0 ], [ 736397.0, 3722439.0 ], [ 736399.0, 3722441.5 ] ] ] } }, 31 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736472.0, 3722466.5 ], [ 736476.0, 3722465.5 ], [ 736477.5, 3722458.0 ], [ 736477.0, 3722443.5 ], [ 736475.5, 3722439.0 ], [ 736469.5, 3722439.0 ], [ 736467.0, 3722456.0 ], [ 736468.5, 3722463.5 ], [ 736472.0, 3722466.5 ] ] ] } }, 32 | { "type": "Feature", "properties": { "rasterVal": 1.0, "conf": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 736660.0, 3722528.5 ], [ 736672.5, 3722528.5 ], [ 736679.5, 3722525.5 ], [ 736688.0, 3722527.5 ], [ 736698.5, 3722524.5 ], [ 736704.0, 3722515.5 ], [ 736705.0, 3722503.5 ], [ 736702.0, 3722501.5 ], [ 736702.0, 3722498.0 ], [ 736704.0, 3722496.0 ], [ 736701.5, 3722491.5 ], [ 736694.0, 3722489.0 ], [ 736684.0, 3722482.0 ], [ 736674.0, 3722482.5 ], [ 736668.0, 3722479.0 ], [ 736666.0, 3722465.5 ], [ 736664.5, 3722463.5 ], [ 736665.5, 3722457.0 ], [ 736664.0, 3722452.5 ], [ 736664.0, 3722440.0 ], [ 736650.5, 3722439.0 ], [ 736649.0, 3722445.5 ], [ 736644.0, 3722452.0 ], [ 736642.5, 3722471.5 ], [ 736650.0, 3722501.0 ], [ 736656.0, 3722515.5 ], [ 736660.0, 3722519.5 ], [ 736657.5, 3722528.0 ], [ 736660.0, 3722528.5 ] ], [ [ 736700.0, 3722500.0 ], [ 736698.0, 3722503.0 ], [ 736695.0, 3722502.5 ], [ 736697.0, 3722499.0 ], [ 736700.0, 3722500.0 ] ] ] } } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /cw_geodata/data/sample_b_from_df2px.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_b_from_df2px.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_b_mask_inner.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_b_mask_inner.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_b_mask_outer.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_b_mask_outer.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_b_mask_outer_10.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_b_mask_outer_10.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_c_from_df2px.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_c_from_df2px.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_c_mask.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_c_mask.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_fbc_from_df2px.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_fbc_from_df2px.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_fp_from_df2px.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_fp_from_df2px.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_fp_mask.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_fp_mask.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_fp_mask_from_geojson.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_fp_mask_from_geojson.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_geotiff.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_geotiff.tif -------------------------------------------------------------------------------- /cw_geodata/data/sample_graph.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/data/sample_graph.pkl -------------------------------------------------------------------------------- /cw_geodata/data/sample_roads.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 14655, "road_type": "5", "origarea": 0, "origlen": 0.0017089240319089305, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.305334270350002, 36.158651982430001 ], [ -115.30533142486, 36.158449259580003 ], [ -115.305334246699999, 36.158405521070001 ], [ -115.305358232339998, 36.158351906109999 ], [ -115.305387861650004, 36.158325098630002 ], [ -115.305568459400007, 36.158171308370001 ], [ -115.305710962310002, 36.1580457365 ], [ -115.305754700820003, 36.158009052579999 ], [ -115.305761755419994, 36.157982245100001 ], [ -115.30576316634, 36.157942739349998 ], [ -115.305764577260007, 36.157628104209998 ], [ -115.30576316634, 36.157506765100003 ], [ -115.305756111739996, 36.157477135779999 ], [ -115.305637880419994, 36.15715224209 ] ] } }, 6 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 19941, "road_type": "5", "origarea": 0, "origlen": 0.0021840289174551468, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.3040076, 36.158656103202638 ], [ -115.304718257900007, 36.1586538711 ] ] } }, 7 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "1", "lane_number": "1", "one_way_ty": "2", "paved": "1", "road_id": 16932, "road_type": "5", "origarea": 0, "origlen": 0.00061020691230463111, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304794494109998, 36.158316912030003 ], [ -115.304854175469998, 36.158295077390001 ], [ -115.304859998040001, 36.158216472680003 ], [ -115.304842530320002, 36.158165525180003 ], [ -115.304800316680002, 36.158142234899998 ], [ -115.304727534549997, 36.158140779260002 ], [ -115.304664941910005, 36.158180081609999 ], [ -115.304654752410002, 36.158228117820002 ], [ -115.304664941910005, 36.158274698390002 ], [ -115.304696966050003, 36.15830672253 ], [ -115.304771203830001, 36.158327101529999 ] ] } }, 8 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "1", "lane_number": "1", "one_way_ty": "2", "paved": "1", "road_id": 20674, "road_type": "5", "origarea": 0, "origlen": 2.5421704359567551e-05, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304794494109998, 36.158316912030003 ], [ -115.304771203830001, 36.158327101529999 ] ] } }, 9 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "1", "lane_number": "1", "one_way_ty": "2", "paved": "1", "road_id": 4759, "road_type": "5", "origarea": 0, "origlen": 0.00036751717873743738, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304777087779996, 36.158654716450002 ], [ -115.304819240040004, 36.158599306729997 ], [ -115.304810506180004, 36.158344569240001 ], [ -115.304771203830001, 36.158327101529999 ] ] } }, 10 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "1", "lane_number": "1", "one_way_ty": "2", "paved": "1", "road_id": 277, "road_type": "5", "origarea": 0, "origlen": 0.00038473841342077095, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304777087779996, 36.158654716450002 ], [ -115.304718800689997, 36.158612407509999 ], [ -115.304718800689997, 36.158363492600003 ], [ -115.304771203830001, 36.158327101529999 ] ] } }, 11 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 21517, "road_type": "5", "origarea": 0, "origlen": 9.5177264571465376e-05, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304813425340001, 36.158655238599998 ], [ -115.304777087779996, 36.158654716450002 ], [ -115.304718257900007, 36.1586538711 ] ] } }, 12 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "1", "lane_number": "1", "one_way_ty": "2", "paved": "1", "road_id": 9198, "road_type": "5", "origarea": 0, "origlen": 0.00028644810990776168, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304777087779996, 36.158654716450002 ], [ -115.304714433759997, 36.158708479929999 ], [ -115.304712978119994, 36.158829298279997 ], [ -115.304763937019999, 36.158894891229998 ] ] } }, 13 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "1", "lane_number": "1", "one_way_ty": "2", "paved": "1", "road_id": 13669, "road_type": "5", "origarea": 0, "origlen": 0.00026815643391558204, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304763937019999, 36.158894891229998 ], [ -115.304816328749993, 36.158806008 ], [ -115.304816328749993, 36.158704113 ], [ -115.304777087779996, 36.158654716450002 ] ] } }, 14 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "3", "lane_number": "3", "one_way_ty": "2", "paved": "1", "road_id": 15490, "road_type": "2", "origarea": 0, "origlen": 0.0017015904383822285, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.307235461239998, 36.158885233829999 ], [ -115.305533891859994, 36.158893699350003 ] ] } }, 15 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "4", "lane_number": "4", "one_way_ty": "2", "paved": "1", "road_id": 22773, "road_type": "2", "origarea": 0, "origlen": 0.00091145533203984097, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.305533891859994, 36.158893699350003 ], [ -115.304812210250006, 36.158894816500002 ], [ -115.304763937019999, 36.158894891229998 ], [ -115.304716265750002, 36.158894965019996 ], [ -115.304622437619997, 36.158895110270002 ] ] } }, 16 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "3", "lane_number": "3", "one_way_ty": "2", "paved": "1", "road_id": 11013, "road_type": "2", "origarea": 0, "origlen": 0.00075908014504109467, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304622437619997, 36.158895110270002 ], [ -115.3040076, 36.158897395911893 ] ] } }, 17 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 13039, "road_type": "2", "origarea": 0, "origlen": 0.00014077602261271154, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304763937019999, 36.158894891229998 ], [ -115.304762782699996, 36.159035662519997 ] ] } }, 18 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "3", "lane_number": "3", "one_way_ty": "2", "paved": "1", "road_id": 13547, "road_type": "2", "origarea": 0, "origlen": 0.00076778803435859967, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.306466509909995, 36.159031264040003 ], [ -115.307234296139995, 36.159032928590001 ] ] } }, 19 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "3", "lane_number": "3", "one_way_ty": "2", "paved": "1", "road_id": 10504, "road_type": "2", "origarea": 0, "origlen": 0.0015294496788786783, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.306466509909995, 36.159031264040003 ], [ -115.304937068170005, 36.159036191939997 ] ] } }, 20 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "4", "lane_number": "4", "one_way_ty": "2", "paved": "1", "road_id": 22869, "road_type": "2", "origarea": 0, "origlen": 0.00092556313019524479, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304937068170005, 36.159036191939997 ], [ -115.304762782699996, 36.159035662519997 ], [ -115.304011509310001, 36.159033380419999 ] ] } }, 21 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "3", "lane_number": "3", "one_way_ty": "2", "paved": "1", "road_id": 9733, "road_type": "2", "origarea": 0, "origlen": 0.0010807946163444123, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304011509310001, 36.159033380419999 ], [ -115.3040076, 36.159033399520865 ] ] } }, 22 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 2207, "road_type": "5", "origarea": 0, "origlen": 0.0021187288976393381, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304031262180004, 36.159379055789998 ], [ -115.304038316779994, 36.159325440830003 ], [ -115.30406371334, 36.159292989679997 ], [ -115.304101808179993, 36.159271825879998 ], [ -115.304165299570002, 36.159261949440001 ], [ -115.305879567229994, 36.159256305760003 ], [ -115.305933182190003, 36.159261949440001 ], [ -115.30596281151, 36.159295811520003 ], [ -115.305974098869996, 36.159346604630002 ], [ -115.305972513970005, 36.159396974929997 ] ] } }, 23 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 14360, "road_type": "5", "origarea": 0, "origlen": 0.00060620635352942218, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.306909865430001, 36.159582676630002 ], [ -115.306901073229994, 36.159404452350003 ], [ -115.306918004270003, 36.159325440830003 ], [ -115.306957510030003, 36.159277469560003 ], [ -115.307028056020002, 36.159249251159999 ], [ -115.307236872160004, 36.15924642932 ] ] } }, 24 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 18886, "road_type": "5", "origarea": 0, "origlen": 0.00017671102624952563, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304031262180004, 36.159379055789998 ], [ -115.304035910479996, 36.159555705670002 ] ] } }, 25 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "4", "lane_number": "4", "one_way_ty": "2", "paved": "1", "road_id": 4414, "road_type": "5", "origarea": 0, "origlen": 0.00055551672896427928, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.305960011426549, 36.159887699800002 ], [ -115.305972513970005, 36.159396974929997 ] ] } }, 26 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "4", "lane_number": "4", "one_way_ty": "2", "paved": "1", "road_id": 8866, "road_type": "5", "origarea": 0, "origlen": 0.00040571587428926529, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.306898346152181, 36.159887699800002 ], [ -115.306908096840004, 36.159764841159998 ], [ -115.306909865430001, 36.159582676630002 ] ] } }, 27 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 5865, "road_type": "5", "origarea": 0, "origlen": 0.00056254711252776385, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304035910479996, 36.159555705670002 ], [ -115.304028602435167, 36.159887699800002 ] ] } }, 28 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 4214, "road_type": "5", "origarea": 0, "origlen": 0.0030770784666620775, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304540695628134, 36.156377699799997 ], [ -115.304225969130002, 36.15646409531 ], [ -115.3040076, 36.156523805617901 ] ] } }, 29 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 6668, "road_type": "5", "origarea": 0, "origlen": 0.00030445562381666708, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304564589899996, 36.157148391450001 ], [ -115.304675347110006, 36.157431986349998 ] ] } }, 30 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 11117, "road_type": "5", "origarea": 0, "origlen": 0.00028852803692290427, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304475211440007, 36.157495147520002 ], [ -115.304364239280005, 36.157228813880003 ] ] } }, 31 | { "type": "Feature", "properties": { "bridge_typ": "2", "heading": "0", "lane_numbe": "2", "lane_number": "2", "one_way_ty": "2", "paved": "1", "road_id": 17863, "road_type": "5", "origarea": 0, "origlen": 0.006987667279550927, "partialDec": 1, "truncated": 0 }, "geometry": { "type": "LineString", "coordinates": [ [ -115.304813425340001, 36.158655238599998 ], [ -115.305334270350002, 36.158651982430001 ], [ -115.305488036970004, 36.158651021129998 ], [ -115.30614834747, 36.158652432049998 ], [ -115.30689472409, 36.158653842969997 ], [ -115.306967386470006, 36.158653137510001 ], [ -115.307004775839999, 36.158647493830003 ], [ -115.307027350560006, 36.158634090089997 ], [ -115.307047808899995, 36.158612220830001 ], [ -115.307061212639994, 36.158561427720002 ], [ -115.307018885039994, 36.157412233469998 ], [ -115.307016063199995, 36.157364262190001 ], [ -115.306996310320002, 36.157331811040002 ], [ -115.306963859169997, 36.15729794896 ], [ -115.30691588789, 36.157268319640004 ], [ -115.30674798842, 36.157173788009999 ], [ -115.306716948190001, 36.157155446049998 ], [ -115.306622416549999, 36.157121583970003 ], [ -115.30652647399999, 36.157103242010002 ], [ -115.306252755540001, 36.15704116154 ], [ -115.306186442309993, 36.157029874179997 ], [ -115.306077801480001, 36.157032696020003 ], [ -115.305988913530001, 36.157051037979997 ], [ -115.305847821539999, 36.157096187409998 ], [ -115.305702496790005, 36.157134282249999 ], [ -115.305637880419994, 36.15715224209 ], [ -115.305235482309996, 36.157264086879998 ], [ -115.304985749490001, 36.157337454710003 ], [ -115.304788220700004, 36.157388247829999 ], [ -115.304675347110006, 36.157431986349998 ], [ -115.304569528119998, 36.15747290302 ], [ -115.304475211440007, 36.157495147520002 ], [ -115.304419970609999, 36.157508176020002 ], [ -115.304277467700004, 36.157543449019997 ], [ -115.3040076, 36.157610573470834 ] ] } } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /cw_geodata/data/split_multi_grouped_result.json: -------------------------------------------------------------------------------- 1 | {"type": "FeatureCollection", "features": [{"id": "0", "type": "Feature", "properties": {"field_1": 1, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "8086", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "137.5869833444336", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[742959.5157261142, 3739469.858595584], [742964.2412947685, 3739469.934550551], [742964.2835617226, 3739467.18306186], [742968.7404872194, 3739467.252177057], [742968.7827553968, 3739464.50068832], [742970.1818431471, 3739464.5252224], [742970.3714329029, 3739451.621851874], [742963.6070103869, 3739451.527265817], [742963.4608848467, 3739461.268512606], [742959.6434285438, 3739461.204586885], [742959.5157261142, 3739469.858595584]]]}}, {"id": "1", "type": "Feature", "properties": {"field_1": 2, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "8229", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "232.75674536334253", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[743020.5285401859, 3739472.034202539], [743027.7523358399, 3739473.017358276], [743027.9560968133, 3739471.568572332], [743030.4316793848, 3739471.909114651], [743031.0890603127, 3739467.208761358], [743031.6330556386, 3739464.048288816], [743033.5432770674, 3739464.385528093], [743035.4323498101, 3739453.545475867], [743022.4840836683, 3739451.306581199], [743020.9414738066, 3739460.1909273], [743022.0743392245, 3739460.841333462], [743020.5285401859, 3739472.034202539]]]}}, {"id": "2", "type": "Feature", "properties": {"field_1": 3, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "8228", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "115.83593626400437", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[743003.1399996518, 3739467.617796136], [743015.3542409713, 3739467.740245839], [743015.3977939934, 3739462.391609387], [743017.5941185456, 3739462.414260812], [743017.6373293742, 3739457.442983567], [743011.2706792641, 3739457.380694342], [743011.2414975561, 3739460.709668207], [743005.2546582168, 3739460.657057802], [743005.2795396517, 3739457.860729276], [743003.231432164, 3739457.841856197], [743003.1399996518, 3739467.617796136]]]}}, {"id": "3", "type": "Feature", "properties": {"field_1": 4, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "7812", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "1117.803722432596", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[742737.216587933, 3739529.428720969], [742737.2726519796, 3739527.953976202], [742741.2542792484, 3739528.121883995], [742741.9995199341, 3739510.127116858], [742744.0181279983, 3739510.211778471], [742744.1330298374, 3739507.517637256], [742742.058839861, 3739507.431561362], [742742.5669390478, 3739495.113465066], [742744.3449797556, 3739495.180906138], [742745.0815230057, 3739477.163720175], [742722.4685948562, 3739476.244281095], [742720.3770908483, 3739527.479686408], [742728.5166346187, 3739527.808876015], [742728.4659341584, 3739529.072875594], [742737.216587933, 3739529.428720969]]]}}, {"id": "4", "type": "Feature", "properties": {"field_1": 5, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120819", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "868.6465770175508", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[742779.0589662758, 3739517.785098479], [742785.7264630584, 3739518.043576586], [742785.7797116076, 3739516.679750225], [742787.9002177101, 3739516.767014552], [742788.018007018, 3739513.595687717], [742789.4963797061, 3739512.689894631], [742794.7850222825, 3739512.85779129], [742794.7384953512, 3739514.321680113], [742800.8142568704, 3739514.520712196], [742800.8579616819, 3739513.167741883], [742804.8404431387, 3739513.302400339], [742806.719179487, 3739511.962838294], [742809.5193322457, 3739513.754459812], [742810.8216116317, 3739515.39696687], [742810.8425385487, 3739518.216655498], [742815.6076734723, 3739518.193654652], [742817.1555002977, 3739514.559269692], [742818.4727785089, 3739513.427399684], [742820.7698578427, 3739513.496967576], [742821.0155526869, 3739505.30103076], [742818.8296341306, 3739505.234292389], [742818.9082965751, 3739502.872199133], [742817.049548522, 3739503.79050431], [742814.6007570606, 3739498.755805385], [742814.9873573856, 3739494.492515358], [742817.0303021728, 3739493.257025897], [742819.6744798073, 3739492.436405651], [742805.5144255088, 3739491.920608293], [742805.3067322085, 3739497.531435517], [742789.7855063399, 3739496.958818469], [742787.9062348148, 3739496.134058065], [742787.21331656, 3739494.95102441], [742788.1948983755, 3739493.533130954], [742788.6153025262, 3739492.311837214], [742788.6233625122, 3739490.902464679], [742775.3039837473, 3739490.86316813], [742776.4324709803, 3739492.778722805], [742776.4256696098, 3739495.231436581], [742779.1062013684, 3739497.352975133], [742778.8313719494, 3739499.41040163], [742777.2480186591, 3739502.255856039], [742776.2833758653, 3739503.008239368], [742773.9964049477, 3739502.905642527], [742773.7390761343, 3739508.282126087], [742779.373780194, 3739510.878410715], [742779.8477320484, 3739514.109192995], [742779.1370028395, 3739515.811456004], [742779.0589662758, 3739517.785098479]]]}}, {"id": "5", "type": "Feature", "properties": {"field_1": 1, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "7813", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "446.8580438238858", "origlen": "0", "partialDec": "0.4599659863913662", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[742693.3122179032, 3739539.0], [742694.5911374168, 3739534.692852943], [742701.8596583691, 3739536.831200783], [742703.3902353786, 3739531.686894269], [742687.859493556, 3739527.118546354], [742687.4643547137, 3739528.440377403], [742683.2411743457, 3739527.200840465], [742681.172764875, 3739530.167156974], [742679.1856038158, 3739536.498545914], [742677.5853192207, 3739536.002776217], [742676.6595880231, 3739539.0], [742693.3122179032, 3739539.0]]]}}, {"id": "6", "type": "Feature", "properties": {"field_1": 2, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120818", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "921.0009706238087", "origlen": "0", "partialDec": "0.13212004440894673", "truncated": "1"}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[742820.9164519846, 3739539.0], [742819.2061650158, 3739537.419993884], [742819.3015388706, 3739534.037214165], [742810.5031569091, 3739533.735573504], [742810.4563450023, 3739535.210554277], [742808.8447725036, 3739535.158436355], [742808.7962669341, 3739536.699968245], [742807.3359817594, 3739538.716122539], [742806.6252731845, 3739539.0], [742820.9164519846, 3739539.0]]], [[[742784.7538719983, 3739539.0], [742786.5276360018, 3739535.73363368], [742785.0281770587, 3739534.918539529], [742785.0537910719, 3739532.455205162], [742775.2400383659, 3739532.349736623], [742775.1763434813, 3739538.130713525], [742775.8829740167, 3739539.0], [742784.7538719983, 3739539.0]]]]}}, {"id": "7", "type": "Feature", "properties": {"field_1": 3, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "122421", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "628.0617074431335", "origlen": "0", "partialDec": "0.6854550148892067", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[743051.0, 3739240.429075356], [743019.7339072112, 3739241.220183459], [743020.0951837152, 3739255.058807612], [743051.0, 3739254.279686034], [743051.0, 3739240.429075356]]]}}, {"id": "8", "type": "Feature", "properties": {"field_1": 4, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "122449", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "164.34510745720252", "origlen": "0", "partialDec": "0.3268898818394317", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[743046.4084919207, 3739315.608520228], [743051.0, 3739315.560417519], [743051.0, 3739304.00325697], [743046.2950427994, 3739304.051510714], [743046.4084919207, 3739315.608520228]]]}}, {"id": "9", "type": "Feature", "properties": {"field_1": 6, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "84910", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "67.0691122589702", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[741360.3983888365, 3743875.358136298], [741367.4885894872, 3743871.975018986], [741362.9199707231, 3743862.502813682], [741357.5458472944, 3743865.074794827], [741360.0725632336, 3743870.299843532], [741360.3983888365, 3743875.358136298]]]}}, {"id": "10", "type": "Feature", "properties": {"field_1": 5, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120818", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "921.0009706238087", "origlen": "0", "partialDec": "0.13212004440894673", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[742820.9164519846, 3739539.0], [742819.2061650158, 3739537.419993884], [742819.3015388706, 3739534.037214165], [742810.5031569091, 3739533.735573504], [742810.4563450023, 3739535.210554277], [742808.8447725036, 3739535.158436355], [742808.7962669341, 3739536.699968245], [742807.3359817594, 3739538.716122539], [742806.6252731845, 3739539.0], [742820.9164519846, 3739539.0]]]}}, {"id": "11", "type": "Feature", "properties": {"field_1": 6, "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120818", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "921.0009706238087", "origlen": "0", "partialDec": "0.13212004440894673", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[742784.7538719983, 3739539.0], [742786.5276360018, 3739535.73363368], [742785.0281770587, 3739534.918539529], [742785.0537910719, 3739532.455205162], [742775.2400383659, 3739532.349736623], [742775.1763434813, 3739538.130713525], [742775.8829740167, 3739539.0], [742784.7538719983, 3739539.0]]]}}]} -------------------------------------------------------------------------------- /cw_geodata/data/split_multi_result.csv: -------------------------------------------------------------------------------- 1 | ,field_1,access,addr_house,addr_hou_1,addr_inter,admin_leve,aerialway,aeroway,amenity,area,barrier,bicycle,boundary,brand,bridge,building,constructi,covered,culvert,cutting,denominati,disused,embankment,foot,generator_,harbour,highway,historic,horse,intermitte,junction,landuse,layer,leisure,lock,man_made,military,motorcar,name,natural,office,oneway,operator,osm_id,place,population,power,power_sour,public_tra,railway,ref,religion,route,service,shop,sport,surface,tags,toll,tourism,tower_type,tunnel,water,waterway,wetland,width,wood,z_order,tracktype,way_area,origarea,origlen,partialDec,truncated,geometry 2 | 0,660,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,8086,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,137.5869833444336,0,1.0,0,"POLYGON ((742959.5157261142 3739469.858595584, 742964.2412947685 3739469.934550551, 742964.2835617226 3739467.18306186, 742968.7404872194 3739467.252177057, 742968.7827553968 3739464.50068832, 742970.1818431471 3739464.5252224, 742970.3714329029 3739451.621851874, 742963.6070103869 3739451.527265817, 742963.4608848467 3739461.268512606, 742959.6434285438 3739461.204586885, 742959.5157261142 3739469.858595584))" 3 | 1,661,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,8229,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,232.75674536334253,0,1.0,0,"POLYGON ((743020.5285401859 3739472.034202539, 743027.7523358399 3739473.017358276, 743027.9560968133 3739471.568572332, 743030.4316793848 3739471.909114651, 743031.0890603127 3739467.208761358, 743031.6330556386 3739464.048288816, 743033.5432770674 3739464.385528093, 743035.4323498101 3739453.545475867, 743022.4840836683 3739451.306581199, 743020.9414738066 3739460.1909273, 743022.0743392245 3739460.841333462, 743020.5285401859 3739472.034202539))" 4 | 2,662,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,8228,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,115.83593626400437,0,1.0,0,"POLYGON ((743003.1399996518 3739467.617796136, 743015.3542409713 3739467.740245839, 743015.3977939934 3739462.391609387, 743017.5941185456 3739462.414260812, 743017.6373293742 3739457.442983567, 743011.2706792641 3739457.380694342, 743011.2414975561 3739460.709668207, 743005.2546582168 3739460.657057802, 743005.2795396517 3739457.860729276, 743003.231432164 3739457.841856197, 743003.1399996518 3739467.617796136))" 5 | 3,663,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,7812,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,1117.803722432596,0,1.0,0,"POLYGON ((742737.216587933 3739529.428720969, 742737.2726519796 3739527.953976202, 742741.2542792484 3739528.121883995, 742741.9995199341 3739510.127116858, 742744.0181279983 3739510.211778471, 742744.1330298374 3739507.517637256, 742742.058839861 3739507.431561362, 742742.5669390478 3739495.113465066, 742744.3449797556 3739495.180906138, 742745.0815230057 3739477.163720175, 742722.4685948562 3739476.244281095, 742720.3770908483 3739527.479686408, 742728.5166346187 3739527.808876015, 742728.4659341584 3739529.072875594, 742737.216587933 3739529.428720969))" 6 | 4,664,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,120819,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,868.6465770175508,0,1.0,0,"POLYGON ((742779.0589662758 3739517.785098479, 742785.7264630584 3739518.043576586, 742785.7797116076 3739516.679750225, 742787.9002177101 3739516.767014552, 742788.018007018 3739513.595687717, 742789.4963797061 3739512.689894631, 742794.7850222825 3739512.85779129, 742794.7384953512 3739514.321680113, 742800.8142568704 3739514.520712196, 742800.8579616819 3739513.167741883, 742804.8404431387 3739513.302400339, 742806.719179487 3739511.962838294, 742809.5193322457 3739513.754459812, 742810.8216116317 3739515.39696687, 742810.8425385487 3739518.216655498, 742815.6076734723 3739518.193654652, 742817.1555002977 3739514.559269692, 742818.4727785089 3739513.427399684, 742820.7698578427 3739513.496967576, 742821.0155526869 3739505.30103076, 742818.8296341306 3739505.234292389, 742818.9082965751 3739502.872199133, 742817.049548522 3739503.79050431, 742814.6007570606 3739498.755805385, 742814.9873573856 3739494.492515358, 742817.0303021728 3739493.257025897, 742819.6744798073 3739492.436405651, 742805.5144255088 3739491.920608293, 742805.3067322085 3739497.531435517, 742789.7855063399 3739496.958818469, 742787.9062348148 3739496.134058065, 742787.21331656 3739494.95102441, 742788.1948983755 3739493.533130954, 742788.6153025262 3739492.311837214, 742788.6233625122 3739490.902464679, 742775.3039837473 3739490.86316813, 742776.4324709803 3739492.778722805, 742776.4256696098 3739495.231436581, 742779.1062013684 3739497.352975133, 742778.8313719494 3739499.41040163, 742777.2480186591 3739502.255856039, 742776.2833758653 3739503.008239368, 742773.9964049477 3739502.905642527, 742773.7390761343 3739508.282126087, 742779.373780194 3739510.878410715, 742779.8477320484 3739514.109192995, 742779.1370028395 3739515.811456004, 742779.0589662758 3739517.785098479))" 7 | 5,665,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,7813,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,446.8580438238858,0,0.4599659863913662,1,"POLYGON ((742693.3122179032 3739539, 742694.5911374168 3739534.692852943, 742701.8596583691 3739536.831200783, 742703.3902353786 3739531.686894269, 742687.859493556 3739527.118546354, 742687.4643547137 3739528.440377403, 742683.2411743457 3739527.200840465, 742681.172764875 3739530.167156974, 742679.1856038158 3739536.498545914, 742677.5853192207 3739536.002776217, 742676.6595880231 3739539, 742693.3122179032 3739539))" 8 | 6,666,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,120818,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,921.0009706238087,0,0.13212004440894673,1,"MULTIPOLYGON (((742820.9164519846 3739539, 742819.2061650158 3739537.419993884, 742819.3015388706 3739534.037214165, 742810.5031569091 3739533.735573504, 742810.4563450023 3739535.210554277, 742808.8447725036 3739535.158436355, 742808.7962669341 3739536.699968245, 742807.3359817594 3739538.716122539, 742806.6252731845 3739539, 742820.9164519846 3739539)), ((742784.7538719983 3739539, 742786.5276360018 3739535.73363368, 742785.0281770587 3739534.918539529, 742785.0537910719 3739532.455205162, 742775.2400383659 3739532.349736623, 742775.1763434813 3739538.130713525, 742775.8829740167 3739539, 742784.7538719983 3739539)))" 9 | 7,667,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,122421,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,628.0617074431335,0,0.6854550148892067,1,"POLYGON ((743051 3739240.429075356, 743019.7339072112 3739241.220183459, 743020.0951837152 3739255.058807612, 743051 3739254.279686034, 743051 3739240.429075356))" 10 | 8,668,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,122449,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,164.34510745720252,0,0.3268898818394317,1,"POLYGON ((743046.4084919207 3739315.608520228, 743051 3739315.560417519, 743051 3739304.00325697, 743046.2950427994 3739304.051510714, 743046.4084919207 3739315.608520228))" 11 | 9,669,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,84910,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,67.0691122589702,0,1.0,0,"POLYGON ((741360.3983888365 3743875.358136298, 741367.4885894872 3743871.975018986, 741362.9199707231 3743862.502813682, 741357.5458472944 3743865.074794827, 741360.0725632336 3743870.299843532, 741360.3983888365 3743875.358136298))" 12 | 10,666,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,120818,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,921.0009706238087,0,0.13212004440894673,1,"POLYGON ((742820.9164519846 3739539, 742819.2061650158 3739537.419993884, 742819.3015388706 3739534.037214165, 742810.5031569091 3739533.735573504, 742810.4563450023 3739535.210554277, 742808.8447725036 3739535.158436355, 742808.7962669341 3739536.699968245, 742807.3359817594 3739538.716122539, 742806.6252731845 3739539, 742820.9164519846 3739539))" 13 | 11,666,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,120818,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,921.0009706238087,0,0.13212004440894673,1,"POLYGON ((742784.7538719983 3739539, 742786.5276360018 3739535.73363368, 742785.0281770587 3739534.918539529, 742785.0537910719 3739532.455205162, 742775.2400383659 3739532.349736623, 742775.1763434813 3739538.130713525, 742775.8829740167 3739539, 742784.7538719983 3739539))" 14 | -------------------------------------------------------------------------------- /cw_geodata/data/split_multi_result.json: -------------------------------------------------------------------------------- 1 | {"type": "FeatureCollection", "features": [{"id": "0", "type": "Feature", "properties": {"field_1": "660", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "8086", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "137.5869833444336", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[742959.5157261142, 3739469.858595584], [742964.2412947685, 3739469.934550551], [742964.2835617226, 3739467.18306186], [742968.7404872194, 3739467.252177057], [742968.7827553968, 3739464.50068832], [742970.1818431471, 3739464.5252224], [742970.3714329029, 3739451.621851874], [742963.6070103869, 3739451.527265817], [742963.4608848467, 3739461.268512606], [742959.6434285438, 3739461.204586885], [742959.5157261142, 3739469.858595584]]]}}, {"id": "1", "type": "Feature", "properties": {"field_1": "661", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "8229", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "232.75674536334253", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[743020.5285401859, 3739472.034202539], [743027.7523358399, 3739473.017358276], [743027.9560968133, 3739471.568572332], [743030.4316793848, 3739471.909114651], [743031.0890603127, 3739467.208761358], [743031.6330556386, 3739464.048288816], [743033.5432770674, 3739464.385528093], [743035.4323498101, 3739453.545475867], [743022.4840836683, 3739451.306581199], [743020.9414738066, 3739460.1909273], [743022.0743392245, 3739460.841333462], [743020.5285401859, 3739472.034202539]]]}}, {"id": "2", "type": "Feature", "properties": {"field_1": "662", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "8228", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "115.83593626400437", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[743003.1399996518, 3739467.617796136], [743015.3542409713, 3739467.740245839], [743015.3977939934, 3739462.391609387], [743017.5941185456, 3739462.414260812], [743017.6373293742, 3739457.442983567], [743011.2706792641, 3739457.380694342], [743011.2414975561, 3739460.709668207], [743005.2546582168, 3739460.657057802], [743005.2795396517, 3739457.860729276], [743003.231432164, 3739457.841856197], [743003.1399996518, 3739467.617796136]]]}}, {"id": "3", "type": "Feature", "properties": {"field_1": "663", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "7812", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "1117.803722432596", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[742737.216587933, 3739529.428720969], [742737.2726519796, 3739527.953976202], [742741.2542792484, 3739528.121883995], [742741.9995199341, 3739510.127116858], [742744.0181279983, 3739510.211778471], [742744.1330298374, 3739507.517637256], [742742.058839861, 3739507.431561362], [742742.5669390478, 3739495.113465066], [742744.3449797556, 3739495.180906138], [742745.0815230057, 3739477.163720175], [742722.4685948562, 3739476.244281095], [742720.3770908483, 3739527.479686408], [742728.5166346187, 3739527.808876015], [742728.4659341584, 3739529.072875594], [742737.216587933, 3739529.428720969]]]}}, {"id": "4", "type": "Feature", "properties": {"field_1": "664", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120819", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "868.6465770175508", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[742779.0589662758, 3739517.785098479], [742785.7264630584, 3739518.043576586], [742785.7797116076, 3739516.679750225], [742787.9002177101, 3739516.767014552], [742788.018007018, 3739513.595687717], [742789.4963797061, 3739512.689894631], [742794.7850222825, 3739512.85779129], [742794.7384953512, 3739514.321680113], [742800.8142568704, 3739514.520712196], [742800.8579616819, 3739513.167741883], [742804.8404431387, 3739513.302400339], [742806.719179487, 3739511.962838294], [742809.5193322457, 3739513.754459812], [742810.8216116317, 3739515.39696687], [742810.8425385487, 3739518.216655498], [742815.6076734723, 3739518.193654652], [742817.1555002977, 3739514.559269692], [742818.4727785089, 3739513.427399684], [742820.7698578427, 3739513.496967576], [742821.0155526869, 3739505.30103076], [742818.8296341306, 3739505.234292389], [742818.9082965751, 3739502.872199133], [742817.049548522, 3739503.79050431], [742814.6007570606, 3739498.755805385], [742814.9873573856, 3739494.492515358], [742817.0303021728, 3739493.257025897], [742819.6744798073, 3739492.436405651], [742805.5144255088, 3739491.920608293], [742805.3067322085, 3739497.531435517], [742789.7855063399, 3739496.958818469], [742787.9062348148, 3739496.134058065], [742787.21331656, 3739494.95102441], [742788.1948983755, 3739493.533130954], [742788.6153025262, 3739492.311837214], [742788.6233625122, 3739490.902464679], [742775.3039837473, 3739490.86316813], [742776.4324709803, 3739492.778722805], [742776.4256696098, 3739495.231436581], [742779.1062013684, 3739497.352975133], [742778.8313719494, 3739499.41040163], [742777.2480186591, 3739502.255856039], [742776.2833758653, 3739503.008239368], [742773.9964049477, 3739502.905642527], [742773.7390761343, 3739508.282126087], [742779.373780194, 3739510.878410715], [742779.8477320484, 3739514.109192995], [742779.1370028395, 3739515.811456004], [742779.0589662758, 3739517.785098479]]]}}, {"id": "5", "type": "Feature", "properties": {"field_1": "665", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "7813", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "446.8580438238858", "origlen": "0", "partialDec": "0.4599659863913662", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[742693.3122179032, 3739539.0], [742694.5911374168, 3739534.692852943], [742701.8596583691, 3739536.831200783], [742703.3902353786, 3739531.686894269], [742687.859493556, 3739527.118546354], [742687.4643547137, 3739528.440377403], [742683.2411743457, 3739527.200840465], [742681.172764875, 3739530.167156974], [742679.1856038158, 3739536.498545914], [742677.5853192207, 3739536.002776217], [742676.6595880231, 3739539.0], [742693.3122179032, 3739539.0]]]}}, {"id": "6", "type": "Feature", "properties": {"field_1": "666", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120818", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "921.0009706238087", "origlen": "0", "partialDec": "0.13212004440894673", "truncated": "1"}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[742820.9164519846, 3739539.0], [742819.2061650158, 3739537.419993884], [742819.3015388706, 3739534.037214165], [742810.5031569091, 3739533.735573504], [742810.4563450023, 3739535.210554277], [742808.8447725036, 3739535.158436355], [742808.7962669341, 3739536.699968245], [742807.3359817594, 3739538.716122539], [742806.6252731845, 3739539.0], [742820.9164519846, 3739539.0]]], [[[742784.7538719983, 3739539.0], [742786.5276360018, 3739535.73363368], [742785.0281770587, 3739534.918539529], [742785.0537910719, 3739532.455205162], [742775.2400383659, 3739532.349736623], [742775.1763434813, 3739538.130713525], [742775.8829740167, 3739539.0], [742784.7538719983, 3739539.0]]]]}}, {"id": "7", "type": "Feature", "properties": {"field_1": "667", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "122421", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "628.0617074431335", "origlen": "0", "partialDec": "0.6854550148892067", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[743051.0, 3739240.429075356], [743019.7339072112, 3739241.220183459], [743020.0951837152, 3739255.058807612], [743051.0, 3739254.279686034], [743051.0, 3739240.429075356]]]}}, {"id": "8", "type": "Feature", "properties": {"field_1": "668", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "122449", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "164.34510745720252", "origlen": "0", "partialDec": "0.3268898818394317", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[743046.4084919207, 3739315.608520228], [743051.0, 3739315.560417519], [743051.0, 3739304.00325697], [743046.2950427994, 3739304.051510714], [743046.4084919207, 3739315.608520228]]]}}, {"id": "9", "type": "Feature", "properties": {"field_1": "669", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "84910", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "67.0691122589702", "origlen": "0", "partialDec": "1.0", "truncated": "0"}, "geometry": {"type": "Polygon", "coordinates": [[[741360.3983888365, 3743875.358136298], [741367.4885894872, 3743871.975018986], [741362.9199707231, 3743862.502813682], [741357.5458472944, 3743865.074794827], [741360.0725632336, 3743870.299843532], [741360.3983888365, 3743875.358136298]]]}}, {"id": "10", "type": "Feature", "properties": {"field_1": "666", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120818", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "921.0009706238087", "origlen": "0", "partialDec": "0.13212004440894673", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[742820.9164519846, 3739539.0], [742819.2061650158, 3739537.419993884], [742819.3015388706, 3739534.037214165], [742810.5031569091, 3739533.735573504], [742810.4563450023, 3739535.210554277], [742808.8447725036, 3739535.158436355], [742808.7962669341, 3739536.699968245], [742807.3359817594, 3739538.716122539], [742806.6252731845, 3739539.0], [742820.9164519846, 3739539.0]]]}}, {"id": "11", "type": "Feature", "properties": {"field_1": "666", "access": "", "addr_house": "", "addr_hou_1": "", "addr_inter": "", "admin_leve": "", "aerialway": "", "aeroway": "", "amenity": "", "area": "", "barrier": "", "bicycle": "", "boundary": "", "brand": "", "bridge": "", "building": "yes", "constructi": "", "covered": "", "culvert": "", "cutting": "", "denominati": "", "disused": "", "embankment": "", "foot": "", "generator_": "", "harbour": "", "highway": "", "historic": "", "horse": "", "intermitte": "", "junction": "", "landuse": "", "layer": "", "leisure": "", "lock": "", "man_made": "", "military": "", "motorcar": "", "name": "Occlusion", "natural": "", "office": "", "oneway": "", "operator": "", "osm_id": "120818", "place": "", "population": "", "power": "", "power_sour": "", "public_tra": "", "railway": "", "ref": "", "religion": "", "route": "", "service": "", "shop": "", "sport": "", "surface": "", "tags": "\"security:classification\"=>\"UNCLASSIFIED\",\"source\"=>\"Unknown\"", "toll": "", "tourism": "", "tower_type": "", "tunnel": "", "water": "", "waterway": "", "wetland": "", "width": "", "wood": "", "z_order": "-999999", "tracktype": "", "way_area": "-999999.0", "origarea": "921.0009706238087", "origlen": "0", "partialDec": "0.13212004440894673", "truncated": "1"}, "geometry": {"type": "Polygon", "coordinates": [[[742784.7538719983, 3739539.0], [742786.5276360018, 3739535.73363368], [742785.0281770587, 3739534.918539529], [742785.0537910719, 3739532.455205162], [742775.2400383659, 3739532.349736623], [742775.1763434813, 3739538.130713525], [742775.8829740167, 3739539.0], [742784.7538719983, 3739539.0]]]}}]} -------------------------------------------------------------------------------- /cw_geodata/data/w_multipolygon.csv: -------------------------------------------------------------------------------- 1 | ,access,addr_house,addr_hou_1,addr_inter,admin_leve,aerialway,aeroway,amenity,area,barrier,bicycle,boundary,brand,bridge,building,constructi,covered,culvert,cutting,denominati,disused,embankment,foot,generator_,harbour,highway,historic,horse,intermitte,junction,landuse,layer,leisure,lock,man_made,military,motorcar,name,natural,office,oneway,operator,osm_id,place,population,power,power_sour,public_tra,railway,ref,religion,route,service,shop,sport,surface,tags,toll,tourism,tower_type,tunnel,water,waterway,wetland,width,wood,z_order,tracktype,way_area,origarea,origlen,partialDec,truncated,geometry 2 | 660,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,8086,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,137.5869833444336,0,1.0,0,"POLYGON ((742959.5157261142 3739469.858595584, 742964.2412947685 3739469.934550551, 742964.2835617226 3739467.18306186, 742968.7404872194 3739467.252177057, 742968.7827553968 3739464.50068832, 742970.1818431471 3739464.5252224, 742970.3714329029 3739451.621851874, 742963.6070103869 3739451.527265817, 742963.4608848467 3739461.268512606, 742959.6434285438 3739461.204586885, 742959.5157261142 3739469.858595584))" 3 | 661,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,8229,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,232.75674536334253,0,1.0,0,"POLYGON ((743020.5285401859 3739472.034202539, 743027.7523358399 3739473.017358276, 743027.9560968133 3739471.568572332, 743030.4316793848 3739471.909114651, 743031.0890603127 3739467.208761358, 743031.6330556386 3739464.048288816, 743033.5432770674 3739464.385528093, 743035.4323498101 3739453.545475867, 743022.4840836683 3739451.306581199, 743020.9414738066 3739460.1909273, 743022.0743392245 3739460.841333462, 743020.5285401859 3739472.034202539))" 4 | 662,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,8228,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,115.83593626400437,0,1.0,0,"POLYGON ((743003.1399996518 3739467.617796136, 743015.3542409713 3739467.740245839, 743015.3977939934 3739462.391609387, 743017.5941185456 3739462.414260812, 743017.6373293742 3739457.442983567, 743011.2706792641 3739457.380694342, 743011.2414975561 3739460.709668207, 743005.2546582168 3739460.657057802, 743005.2795396517 3739457.860729276, 743003.231432164 3739457.841856197, 743003.1399996518 3739467.617796136))" 5 | 663,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,7812,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,1117.803722432596,0,1.0,0,"POLYGON ((742737.216587933 3739529.428720969, 742737.2726519796 3739527.953976202, 742741.2542792484 3739528.121883995, 742741.9995199341 3739510.127116858, 742744.0181279983 3739510.211778471, 742744.1330298374 3739507.517637256, 742742.058839861 3739507.431561362, 742742.5669390478 3739495.113465066, 742744.3449797556 3739495.180906138, 742745.0815230057 3739477.163720175, 742722.4685948562 3739476.244281095, 742720.3770908483 3739527.479686408, 742728.5166346187 3739527.808876015, 742728.4659341584 3739529.072875594, 742737.216587933 3739529.428720969))" 6 | 664,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,120819,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,868.6465770175508,0,1.0,0,"POLYGON ((742779.0589662758 3739517.785098479, 742785.7264630584 3739518.043576586, 742785.7797116076 3739516.679750225, 742787.9002177101 3739516.767014552, 742788.018007018 3739513.595687717, 742789.4963797061 3739512.689894631, 742794.7850222825 3739512.85779129, 742794.7384953512 3739514.321680113, 742800.8142568704 3739514.520712196, 742800.8579616819 3739513.167741883, 742804.8404431387 3739513.302400339, 742806.719179487 3739511.962838294, 742809.5193322457 3739513.754459812, 742810.8216116317 3739515.39696687, 742810.8425385487 3739518.216655498, 742815.6076734723 3739518.193654652, 742817.1555002977 3739514.559269692, 742818.4727785089 3739513.427399684, 742820.7698578427 3739513.496967576, 742821.0155526869 3739505.30103076, 742818.8296341306 3739505.234292389, 742818.9082965751 3739502.872199133, 742817.049548522 3739503.79050431, 742814.6007570606 3739498.755805385, 742814.9873573856 3739494.492515358, 742817.0303021728 3739493.257025897, 742819.6744798073 3739492.436405651, 742805.5144255088 3739491.920608293, 742805.3067322085 3739497.531435517, 742789.7855063399 3739496.958818469, 742787.9062348148 3739496.134058065, 742787.21331656 3739494.95102441, 742788.1948983755 3739493.533130954, 742788.6153025262 3739492.311837214, 742788.6233625122 3739490.902464679, 742775.3039837473 3739490.86316813, 742776.4324709803 3739492.778722805, 742776.4256696098 3739495.231436581, 742779.1062013684 3739497.352975133, 742778.8313719494 3739499.41040163, 742777.2480186591 3739502.255856039, 742776.2833758653 3739503.008239368, 742773.9964049477 3739502.905642527, 742773.7390761343 3739508.282126087, 742779.373780194 3739510.878410715, 742779.8477320484 3739514.109192995, 742779.1370028395 3739515.811456004, 742779.0589662758 3739517.785098479))" 7 | 665,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,7813,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,446.8580438238858,0,0.4599659863913662,1,"POLYGON ((742693.3122179032 3739539, 742694.5911374168 3739534.692852943, 742701.8596583691 3739536.831200783, 742703.3902353786 3739531.686894269, 742687.859493556 3739527.118546354, 742687.4643547137 3739528.440377403, 742683.2411743457 3739527.200840465, 742681.172764875 3739530.167156974, 742679.1856038158 3739536.498545914, 742677.5853192207 3739536.002776217, 742676.6595880231 3739539, 742693.3122179032 3739539))" 8 | 666,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,120818,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,921.0009706238087,0,0.13212004440894673,1,"MULTIPOLYGON (((742820.9164519846 3739539, 742819.2061650158 3739537.419993884, 742819.3015388706 3739534.037214165, 742810.5031569091 3739533.735573504, 742810.4563450023 3739535.210554277, 742808.8447725036 3739535.158436355, 742808.7962669341 3739536.699968245, 742807.3359817594 3739538.716122539, 742806.6252731845 3739539, 742820.9164519846 3739539)), ((742784.7538719983 3739539, 742786.5276360018 3739535.73363368, 742785.0281770587 3739534.918539529, 742785.0537910719 3739532.455205162, 742775.2400383659 3739532.349736623, 742775.1763434813 3739538.130713525, 742775.8829740167 3739539, 742784.7538719983 3739539)))" 9 | 667,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,122421,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,628.0617074431335,0,0.6854550148892067,1,"POLYGON ((743051 3739240.429075356, 743019.7339072112 3739241.220183459, 743020.0951837152 3739255.058807612, 743051 3739254.279686034, 743051 3739240.429075356))" 10 | 668,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,122449,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,164.34510745720252,0,0.3268898818394317,1,"POLYGON ((743046.4084919207 3739315.608520228, 743051 3739315.560417519, 743051 3739304.00325697, 743046.2950427994 3739304.051510714, 743046.4084919207 3739315.608520228))" 11 | 669,,,,,,,,,,,,,,,yes,,,,,,,,,,,,,,,,,,,,,,,Occlusion,,,,,84910,,,,,,,,,,,,,,"""security:classification""=>""UNCLASSIFIED"",""source""=>""Unknown""",,,,,,,,,,-999999,,-999999.0,67.0691122589702,0,1.0,0,"POLYGON ((741360.3983888365 3743875.358136298, 741367.4885894872 3743871.975018986, 741362.9199707231 3743862.502813682, 741357.5458472944 3743865.074794827, 741360.0725632336 3743870.299843532, 741360.3983888365 3743875.358136298))" 12 | -------------------------------------------------------------------------------- /cw_geodata/raster_image/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/raster_image/__init__.py -------------------------------------------------------------------------------- /cw_geodata/raster_image/image.py: -------------------------------------------------------------------------------- 1 | from osgeo import gdal 2 | import rasterio 3 | from affine import Affine 4 | 5 | 6 | def get_geo_transform(raster_src): 7 | """Get the geotransform for a raster image source. 8 | 9 | Arguments 10 | --------- 11 | raster_src : str, :class:`rasterio.DatasetReader`, or `osgeo.gdal.Dataset` 12 | Path to a raster image with georeferencing data to apply to `geom`. 13 | Alternatively, an opened :class:`rasterio.Band` object or 14 | :class:`osgeo.gdal.Dataset` object can be provided. Required if not 15 | using `affine_obj`. 16 | 17 | Returns 18 | ------- 19 | transform : :class:`affine.Affine` 20 | An affine transformation object to the image's location in its CRS. 21 | """ 22 | 23 | if isinstance(raster_src, str): 24 | affine_obj = rasterio.open(raster_src).transform 25 | elif isinstance(raster_src, rasterio.DatasetReader): 26 | affine_obj = raster_src.transform 27 | elif isinstance(raster_src, gdal.Dataset): 28 | affine_obj = Affine.from_gdal(*raster_src.GetGeoTransform()) 29 | 30 | return affine_obj 31 | -------------------------------------------------------------------------------- /cw_geodata/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/utils/__init__.py -------------------------------------------------------------------------------- /cw_geodata/utils/core.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import geopandas as gpd 3 | import rasterio 4 | 5 | 6 | def _check_rasterio_im_load(im): 7 | """Check if `im` is already loaded in; if not, load it in.""" 8 | if isinstance(im, str): 9 | return rasterio.open(im) 10 | elif isinstance(im, rasterio.DatasetReader): 11 | return im 12 | else: 13 | raise ValueError( 14 | "{} is not an accepted image format for rasterio.".format(im)) 15 | 16 | 17 | def _check_df_load(df): 18 | """Check if `df` is already loaded in, if not, load from file.""" 19 | if isinstance(df, str): 20 | if df.lower().endswith('json'): 21 | return _check_gdf_load(df) 22 | else: 23 | return pd.read_csv(df) 24 | elif isinstance(df, pd.DataFrame): 25 | return df 26 | else: 27 | raise ValueError("{} is not an accepted DataFrame format.".format(df)) 28 | 29 | 30 | def _check_gdf_load(gdf): 31 | """Check if `gdf` is already loaded in, if not, load from geojson.""" 32 | if isinstance(gdf, str): 33 | return gpd.read_file(gdf) 34 | elif isinstance(gdf, gpd.GeoDataFrame): 35 | return gdf 36 | else: 37 | raise ValueError( 38 | "{} is not an accepted GeoDataFrame format.".format(gdf)) 39 | -------------------------------------------------------------------------------- /cw_geodata/utils/geo.py: -------------------------------------------------------------------------------- 1 | from .core import _check_gdf_load 2 | import numpy as np 3 | import pandas as pd 4 | from affine import Affine 5 | import geopandas as gpd 6 | import os 7 | import rasterio 8 | from rasterio.enums import Resampling 9 | import ogr 10 | import shapely 11 | from shapely.errors import WKTReadingError 12 | from shapely.wkt import loads 13 | from shapely.geometry import MultiLineString, MultiPolygon, mapping, shape 14 | from shapely.ops import cascaded_union 15 | from warnings import warn 16 | 17 | 18 | def list_to_affine(xform_mat): 19 | """Create an Affine from a list or array-formatted [a, b, d, e, xoff, yoff] 20 | 21 | Arguments 22 | --------- 23 | xform_mat : `list` or :class:`numpy.array` 24 | A `list` of values to convert to an affine object. 25 | 26 | Returns 27 | ------- 28 | aff : :class:`affine.Affine` 29 | An affine transformation object. 30 | """ 31 | # first make sure it's not in gdal order 32 | if len(xform_mat) > 6: 33 | xform_mat = xform_mat[0:6] 34 | if rasterio.transform.tastes_like_gdal(xform_mat): 35 | return Affine.from_gdal(*xform_mat) 36 | else: 37 | return Affine(*xform_mat) 38 | 39 | 40 | def geometries_internal_intersection(polygons): 41 | """Get the intersection geometries between all geometries in a set. 42 | 43 | Arguments 44 | --------- 45 | polygons : `list`-like 46 | A `list`-like containing geometries. These will be placed in a 47 | :class:`geopandas.GeoSeries` object to take advantage of `rtree` 48 | spatial indexing. 49 | 50 | Returns 51 | ------- 52 | intersect_list 53 | A `list` of geometric intersections between polygons in `polygons`, in 54 | the same CRS as the input. 55 | """ 56 | # convert `polygons` to geoseries and get spatialindex 57 | # TODO: Implement test to see if `polygon` items are actual polygons or 58 | # WKT strings 59 | if isinstance(polygons, gpd.GeoSeries): 60 | gs = polygons 61 | else: 62 | gs = gpd.GeoSeries(polygons).reset_index(drop=True) 63 | sindex = gs.sindex 64 | gs_bboxes = gs.apply(lambda x: x.bounds) 65 | 66 | # find indices of polygons that overlap in gs 67 | intersect_lists = gs_bboxes.apply(lambda x: list(sindex.intersection(x))) 68 | intersect_lists = intersect_lists.dropna() 69 | # drop all objects that only have self-intersects 70 | # first, filter down to the ones that have _some_ intersection with others 71 | intersect_lists = intersect_lists[ 72 | intersect_lists.apply(lambda x: len(x) > 1)] 73 | # the below is a royal pain to follow. what it does is create a dataframe 74 | # with two columns: 'gs_idx' and 'intersectors'. 'gs_idx' corresponds to 75 | # a polygon's original index in gs, and 'intersectors' gives a list of 76 | # gs indices for polygons that intersect with its bbox. 77 | intersect_lists.name = 'intersectors' 78 | intersect_lists.index.name = 'gs_idx' 79 | intersect_lists = intersect_lists.reset_index() 80 | # first, we get rid of self-intersection indices in 'intersectors': 81 | intersect_lists['intersectors'] = intersect_lists.apply( 82 | lambda x: [i for i in x['intersectors'] if i != x['gs_idx']], 83 | axis=1) 84 | # for each row, we next create a union of the polygons in 'intersectors', 85 | # and find the intersection of that with the polygon at gs[gs_idx]. this 86 | # (Multi)Polygon output corresponds to all of the intersections for the 87 | # polygon at gs[gs_idx]. we add that to a list of intersections stored in 88 | # output_polys. 89 | output_polys = [] 90 | _ = intersect_lists.apply(lambda x: output_polys.append( 91 | gs[x['gs_idx']].intersection(cascaded_union(gs[x['intersectors']])) 92 | ), axis=1) 93 | # we then generate the union of all of these intersections and return it. 94 | return cascaded_union(output_polys) 95 | 96 | 97 | def split_multi_geometries(gdf, obj_id_col=None, group_col=None, 98 | geom_col='geometry'): 99 | """Split apart MultiPolygon or MultiLineString geometries. 100 | 101 | Arguments 102 | --------- 103 | gdf : :class:`geopandas.GeoDataFrame` or `str` 104 | A :class:`geopandas.GeoDataFrame` or path to a geojson containing 105 | geometries. 106 | obj_id_col : str, optional 107 | If one exists, the name of the column that uniquely identifies each 108 | geometry (e.g. the ``"BuildingId"`` column in many SpaceNet datasets). 109 | This will be tracked so multiple objects don't get produced with 110 | the same ID. Note that object ID column will be renumbered on output. 111 | If passed, `group_col` must also be provided. 112 | group_col : str, optional 113 | A column to identify groups for sequential numbering (for example, 114 | ``'ImageId'`` for sequential number of ``'BuildingId'``). Must be 115 | provided if `obj_id_col` is passed. 116 | geom_col : str, optional 117 | The name of the column in `gdf` that corresponds to geometry. Defaults 118 | to ``'geometry'``. 119 | 120 | Returns 121 | ------- 122 | :class:`geopandas.GeoDataFrame` 123 | A `geopandas.GeoDataFrame` that's identical to the input, except with 124 | the multipolygons split into separate rows, and the object ID column 125 | renumbered (if one exists). 126 | 127 | """ 128 | if obj_id_col and not group_col: 129 | raise ValueError('group_col must be provided if obj_id_col is used.') 130 | gdf2 = _check_gdf_load(gdf) 131 | # drop duplicate columns (happens if loading a csv with geopandas) 132 | gdf2 = gdf2.loc[:, ~gdf2.columns.duplicated()] 133 | # check if the values in gdf2[geometry] are polygons; if strings, do loads 134 | if isinstance(gdf2[geom_col][0], str): 135 | gdf2[geom_col] = gdf2[geom_col].apply(loads) 136 | split_geoms_gdf = pd.concat( 137 | gdf2.apply(_split_multigeom_row, axis=1, geom_col=geom_col).tolist()) 138 | gdf2.drop(index=split_geoms_gdf.index.unique()) # remove multipolygons 139 | gdf2 = gpd.GeoDataFrame(pd.concat([gdf2, split_geoms_gdf], 140 | ignore_index=True), crs=gdf2.crs) 141 | 142 | if obj_id_col: 143 | gdf2[obj_id_col] = gdf2.groupby(group_col).cumcount()+1 144 | 145 | return gdf2 146 | 147 | 148 | def get_subgraph(G, node_subset): 149 | """ 150 | Create a subgraph from G. Code almost directly copied from osmnx. 151 | 152 | Arguments 153 | --------- 154 | G : :class:`networkx.MultiDiGraph` 155 | A graph to be subsetted 156 | node_subset : `list`-like 157 | The subset of nodes to induce a subgraph of `G` 158 | 159 | Returns 160 | ------- 161 | G2 : :class:`networkx`.MultiDiGraph 162 | The subgraph of G that includes node_subset 163 | """ 164 | 165 | node_subset = set(node_subset) 166 | 167 | # copy nodes into new graph 168 | G2 = G.fresh_copy() 169 | G2.add_nodes_from((n, G.nodes[n]) for n in node_subset) 170 | 171 | # copy edges to new graph, including parallel edges 172 | if G2.is_multigraph: 173 | G2.add_edges_from( 174 | (n, nbr, key, d) 175 | for n, nbrs in G.adj.items() if n in node_subset 176 | for nbr, keydict in nbrs.items() if nbr in node_subset 177 | for key, d in keydict.items()) 178 | else: 179 | G2.add_edges_from( 180 | (n, nbr, d) 181 | for n, nbrs in G.adj.items() if n in node_subset 182 | for nbr, d in nbrs.items() if nbr in node_subset) 183 | 184 | # update graph attribute dict, and return graph 185 | G2.graph.update(G.graph) 186 | return G2 187 | 188 | 189 | def _split_multigeom_row(gdf_row, geom_col): 190 | new_rows = [] 191 | if isinstance(gdf_row[geom_col], MultiPolygon) \ 192 | or isinstance(gdf_row[geom_col], MultiLineString): 193 | new_polys = _split_multigeom(gdf_row[geom_col]) 194 | for poly in new_polys: 195 | row_w_poly = gdf_row.copy() 196 | row_w_poly[geom_col] = poly 197 | new_rows.append(row_w_poly) 198 | return pd.DataFrame(new_rows) 199 | 200 | 201 | def _split_multigeom(multigeom): 202 | return list(multigeom) 203 | 204 | 205 | def _reduce_geom_precision(geom, precision=2): 206 | geojson = mapping(geom) 207 | geojson['coordinates'] = np.round(np.array(geojson['coordinates']), 208 | precision) 209 | 210 | return shape(geojson) 211 | 212 | 213 | def _check_wkt_load(x): 214 | """Check if an object is a loaded polygon or not. If not, load it.""" 215 | if isinstance(x, str): 216 | try: 217 | x = loads(x) 218 | except WKTReadingError: 219 | warn('{} is not a WKT-formatted string.'.format(x)) 220 | 221 | return x 222 | 223 | 224 | 225 | # PRETEND THIS ISN'T HERE AT THE MOMENT 226 | # class CoordTransformer(object): 227 | # """A transformer class to change coordinate space using affine transforms. 228 | # 229 | # Notes 230 | # ----- 231 | # This class will take in an image or geometric object (Shapely or GDAL) 232 | # and transform its coordinate space based on `dest_obj` . `dest_obj` 233 | # should be an instance of :class:`rasterio.DatasetReader` . 234 | # 235 | # Arguments 236 | # --------- 237 | # src_obj 238 | # A source image or geometric object to transform. The function will 239 | # first try to extract georegistration information from this object 240 | # if it exists; if it doesn't, it will assume unit (pixel) coords. 241 | # dest_obj 242 | # Object with a destination coordinate reference system to apply to 243 | # `src_obj` . This can be in the form of an ``[a, b, d, e, xoff, yoff]`` 244 | # `list` , an :class:`affine.Affine` instance, or a source 245 | # :class:`geopandas.GeoDataFrame` or geotiff with `crs` metadata to 246 | # produce the transform from, or even just a crs string. 247 | # src_crs : optional 248 | # Source coordinate reference in the form of a :class:`rasterio.crs.CRS` 249 | # object or an epsg string. Only needed if the source object provided 250 | # does not have CRS metadata attached to it. 251 | # src_transform : :class:`affine.Affine` or :class:`list` 252 | # The source affine transformation matrix as a :class:`affine.Affine` 253 | # object or in an ``[a, b, c, d, xoff, yoff]`` `list`. Required if 254 | # `src_obj` is a :class:`numpy.array` . 255 | # dest_transform : :class:`affine.Affine` or :class:`list` 256 | # The destination affine transformation matrix as a 257 | # :class:`affine.Affine` object or in an ``[a, b, c, d, xoff, yoff]`` 258 | # `list` . Required if `dest_obj` is a :class:`numpy.array` . 259 | # """ 260 | # def __init__(self, src_obj=None, dest_obj=None, src_crs=None, 261 | # src_transform=None, dest_transform=None): 262 | # self.src_obj = src_obj 263 | # self.src_type = None 264 | # self.dest_obj = dest_obj 265 | # self.dest_type = None 266 | # self.get_obj_types() # replaces the None values above 267 | # self.src_crs = src_crs 268 | # if isinstance(self.src_crs, dict): 269 | # self.src_crs = self.src_crs['init'] 270 | # if not self.src_crs: 271 | # self.src_crs = self._get_crs(self.src_obj, self.src_type) 272 | # self.dest_crs = self._get_crs(self.dest_obj, self.dest_type) 273 | # self.src_transform = src_transform 274 | # self.dest_transform = dest_transform 275 | # 276 | # def __repr__(self): 277 | # print('CoordTransformer for {}'.format(self.src_obj)) 278 | # 279 | # def load_src_obj(self, src_obj, src_crs=None): 280 | # """Load in a new source object for transformation.""" 281 | # self.src_obj = src_obj 282 | # self.src_type = None # replaced in self._get_src_crs() 283 | # self.src_type = self._get_type(self.src_obj) 284 | # self.src_crs = src_crs 285 | # if self.src_crs is None: 286 | # self.src_crs = self._get_crs(self.src_obj, self.src_type) 287 | # 288 | # def load_dest_obj(self, dest_obj): 289 | # """Load in a new destination object for transformation.""" 290 | # self.dest_obj = dest_obj 291 | # self.dest_type = None 292 | # self.dest_type = self._get_type(self.dest_obj) 293 | # self.dest_crs = self._get_crs(self.dest_obj, self.dest_type) 294 | # 295 | # def load_src_crs(self, src_crs): 296 | # """Load in a new source coordinate reference system.""" 297 | # self.src_crs = self._get_crs(src_crs) 298 | # 299 | # def get_obj_types(self): 300 | # if self.src_obj is not None: 301 | # self.src_type = self._get_type(self.src_obj) 302 | # if self.src_type is None: 303 | # warn('The src_obj type is not compatible with this package.') 304 | # if self.dest_obj is not None: 305 | # self.dest_type = self._get_type(self.dest_obj) 306 | # if self.dest_type is None: 307 | # warn('The dest_obj type is not compatible with this package.') 308 | # elif self.dest_type == 'shapely Geometry': 309 | # warn('Shapely geometries cannot provide a destination CRS.') 310 | # 311 | # @staticmethod 312 | # def _get_crs(obj, obj_type): 313 | # """Get the destination coordinate reference system.""" 314 | # # get the affine transformation out of dest_obj 315 | # if obj_type == "transform matrix": 316 | # return Affine(obj) 317 | # elif obj_type == 'Affine': 318 | # return obj 319 | # elif obj_type == 'GeoTIFF': 320 | # return rasterio.open(obj).crs 321 | # elif obj_type == 'GeoDataFrame': 322 | # if isinstance(obj, str): # if it's a path to a gdf 323 | # return gpd.read_file(obj).crs 324 | # else: # assume it's a GeoDataFrame object 325 | # return obj.crs 326 | # elif obj_type == 'epsg string': 327 | # if obj.startswith('{init'): 328 | # return rasterio.crs.CRS.from_string( 329 | # obj.lstrip('{init: ').rstrip('}')) 330 | # elif obj.lower().startswith('epsg'): 331 | # return rasterio.crs.CRS.from_string(obj) 332 | # elif obj_type == 'OGR Geometry': 333 | # return get_crs_from_ogr(obj) 334 | # elif obj_type == 'shapely Geometry': 335 | # raise TypeError('Cannot extract a coordinate system from a ' + 336 | # 'shapely.Geometry') 337 | # else: 338 | # raise TypeError('Cannot extract CRS from this object type.') 339 | # 340 | # @staticmethod 341 | # def _get_type(obj): 342 | # if isinstance(obj, gpd.GeoDataFrame): 343 | # return 'GeoDataFrame' 344 | # elif isinstance(obj, str): 345 | # if os.path.isfile(obj): 346 | # if os.path.splitext(obj)[1].lower() in ['tif', 'tiff', 347 | # 'geotiff']: 348 | # return 'GeoTIFF' 349 | # elif os.path.splitext(obj)[1] in ['csv', 'geojson']: 350 | # # assume it can be loaded as a geodataframe 351 | # return 'GeoDataFrame' 352 | # else: # assume it's a crs string 353 | # if obj.startswith('{init'): 354 | # return "epsg string" 355 | # elif obj.lower().startswith('epsg'): 356 | # return "epsg string" 357 | # else: 358 | # raise ValueError('{} is not an accepted crs type.'.format( 359 | # obj)) 360 | # elif isinstance(obj, ogr.Geometry): 361 | # # ugh. Try to get the EPSG code out. 362 | # return 'OGR Geometry' 363 | # elif isinstance(obj, shapely.Geometry): 364 | # return "shapely Geometry" 365 | # elif isinstance(obj, list): 366 | # return "transform matrix" 367 | # elif isinstance(obj, Affine): 368 | # return "Affine transform" 369 | # elif isinstance(obj, np.array): 370 | # return "numpy array" 371 | # else: 372 | # return None 373 | # 374 | # def transform(self, output_loc): 375 | # """Transform `src_obj` from `src_crs` to `dest_crs`. 376 | # 377 | # Arguments 378 | # --------- 379 | # output_loc : `str` or `var` 380 | # Object or location to output transformed src_obj to. If it's a 381 | # string, it's assumed to be a path. 382 | # """ 383 | # if not self.src_crs or not self.dest_crs: 384 | # raise AttributeError('The source or destination CRS is missing.') 385 | # if not self.src_obj: 386 | # raise AttributeError('The source object to transform is missing.') 387 | # if isinstance(output_loc, str): 388 | # out_file = True 389 | # if self.src_type == 'GeoTIFF': 390 | # return rasterio.warp.reproject(rasterio.open(self.src_obj), 391 | # output_loc, 392 | # src_transform=self.src_transform, 393 | # src_crs=self.src_crs, 394 | # dst_trasnform=self.dest_transform, 395 | # dst_crs=self.dest_crs, 396 | # resampling=Resampling.bilinear) 397 | # elif self.src_type == 'GeoDataFrame': 398 | # if isinstance(self.src_obj, str): 399 | # # load the gdf and transform it 400 | # tmp_src = gpd.read_file(self.src_obj).to_crs(self.dest_crs) 401 | # else: 402 | # # just transform it 403 | # tmp_src = self.src_obj.to_crs(self.dest_crs) 404 | # if out_file: 405 | # # save to file 406 | # if output_loc.lower().endswith('json'): 407 | # tmp_src.to_file(output_loc, driver="GeoJSON") 408 | # else: 409 | # tmp_src.to_file(output_loc) # ESRI shapefile 410 | # return 411 | # else: 412 | # # assign to the variable and return 413 | # output_loc = tmp_src 414 | # return output_loc 415 | # elif self.src_type == 'OGR Geometry': 416 | # dest_sr = ogr.SpatialReference().ImportFromEPSG( 417 | # int(self.dest_crs.lstrip('epsg'))) 418 | # output_loc = self.src_obj.TransformTo(dest_sr) 419 | # return output_loc 420 | # elif self.src_type == 'shapely Geometry': 421 | # if self.dest_type not in [ 422 | # 'Affine transform', 'transform matrix' 423 | # ] and not self.dest_transform: 424 | # raise ValueError('Transforming shapely objects requires ' + 425 | # 'an affine transformation matrix.') 426 | # elif self.dest_type == 'Affine transform': 427 | # output_loc = shapely.affinity.affine_transform( 428 | # self.src_obj, [self.dest_obj.a, self.dest_obj.b, 429 | # self.dest_obj.d, self.dest_obj.e, 430 | # self.dest_obj.xoff, self.dest_obj.yoff] 431 | # ) 432 | # return output_loc 433 | # elif self.dest_type == 'transform matrix': 434 | # output_loc = shapely.affinity.affine_transform(self.src_obj, 435 | # self.dest_obj) 436 | # return output_loc 437 | # else: 438 | # if isinstance(self.dest_transform, Affine): 439 | # xform_mat = [self.dest_transform.a, self.dest_transform.b, 440 | # self.dest_transform.d, self.dest_transform.e, 441 | # self.dest_transform.xoff, 442 | # self.dest_transform.yoff] 443 | # else: 444 | # xform_mat = self.dest_transform 445 | # output_loc = shapely.affinity.affine_transform(self.src_obj, 446 | # xform_mat) 447 | # return output_loc 448 | # elif self.src_type == 'numpy array': 449 | # return rasterio.warp.reproject( 450 | # self.src_obj, output_loc, src_transform=self.src_transform, 451 | # src_crs=self.src_crs, dst_transform=self.dest_transform, 452 | # dst_crs=self.dest_crs) 453 | # 454 | # 455 | # def get_crs_from_ogr(annoying_OGR_geometry): 456 | # """Get a CRS from an :class:`osgeo.ogr.Geometry` object. 457 | # 458 | # Arguments 459 | # --------- 460 | # annoying_OGR_geometry: :class:`osgeo.ogr.Geometry` 461 | # An OGR object which stores crs information in an annoying fashion. 462 | # 463 | # Returns 464 | # ------- 465 | # An extremely clear, easy to work with ``'epsg[number]'`` string. 466 | # """ 467 | # srs = annoying_OGR_geometry.GetSpatialReference() 468 | # result_of_ID = srs.AutoIdentifyEPSG() # if success, returns 0 469 | # if result_of_ID == 0: 470 | # return 'epsg:' + str(srs.GetAuthorityCode(None)) 471 | # else: 472 | # raise ValueError('Could not determine EPSG code.') 473 | -------------------------------------------------------------------------------- /cw_geodata/vector_label/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/cw_geodata/vector_label/__init__.py -------------------------------------------------------------------------------- /cw_geodata/vector_label/graph.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | import numpy as np 3 | import geopandas as gpd 4 | from ..utils.geo import get_subgraph 5 | import shapely 6 | from shapely.geometry import Point 7 | import networkx as nx 8 | import geopandas as gpd 9 | import fiona 10 | from multiprocessing import Pool 11 | 12 | 13 | class Node(object): 14 | """An object to hold node attributes. 15 | 16 | Attributes 17 | ---------- 18 | idx : int 19 | The numerical index of the node. Used as a unique identifier 20 | when the nodes are added to the graph. 21 | x : `int` or `float` 22 | Numeric x location of the node, in either a geographic CRS or in pixel 23 | coordinates. 24 | y : `int` or `float` 25 | Numeric y location of the node, in either a geographic CRS or in pixel 26 | coordinates. 27 | 28 | """ 29 | 30 | def __init__(self, idx, x, y): 31 | self.idx = idx 32 | self.x = x 33 | self.y = y 34 | 35 | def __repr__(self): 36 | return 'Node {} at ({}, {})'.format(self.idx, self.x, self.y) 37 | 38 | 39 | class Edge(object): 40 | """An object to hold edge attributes. 41 | 42 | Attributes 43 | ---------- 44 | nodes : 2-`tuple` of :class:`Node` s 45 | :class:`Node` instances connected by the edge. 46 | weight : int or float 47 | The weight of the edge. 48 | 49 | """ 50 | 51 | def __init__(self, nodes, edge_weight=None): 52 | self.nodes = nodes 53 | self.weight = edge_weight 54 | 55 | def __repr__(self): 56 | return 'Edge between {} and {} with weight {}'.format(self.nodes[0], 57 | self.nodes[1], 58 | self.weight) 59 | 60 | def set_edge_weight(self, normalize_factor=None, inverse=False): 61 | """Get the edge weight based on Euclidean distance between nodes. 62 | 63 | Note 64 | ---- 65 | This method does not account for spherical deformation (i.e. does not 66 | use the Haversine equation). It is a simple linear distance. 67 | 68 | Arguments 69 | --------- 70 | normalize_factor : `int` or `float`, optional 71 | a number to multiply (or divide, if 72 | ``inverse=True``) the Euclidean distance by. Defaults to ``None`` 73 | (no normalization) 74 | inverse : bool, optional 75 | if ``True``, the Euclidean distance weight will be divided by 76 | ``normalize_factor`` instead of multiplied by it. 77 | """ 78 | weight = np.linalg.norm( 79 | np.array((self.nodes[0].x, self.nodes[0].y)) - 80 | np.array((self.nodes[1].x, self.nodes[1].y))) 81 | 82 | if normalize_factor is not None: 83 | if inverse: 84 | weight = weight/normalize_factor 85 | else: 86 | weight = weight*normalize_factor 87 | self.weight = weight 88 | 89 | def get_node_idxs(self): 90 | """Return the Node.idx for the nodes in the edge.""" 91 | return (self.nodes[0].idx, self.nodes[1].idx) 92 | 93 | 94 | class Path(object): 95 | """An object to hold :class:`Edge` s with common properties. 96 | 97 | Attributes 98 | ---------- 99 | edges : `list` of :class:`Edge` s 100 | A `list` of :class:`Edge` s 101 | properties : dict 102 | A dictionary of property: value pairs that provide relevant metadata 103 | about edges along the path (e.g. road type, speed limit, etc.) 104 | 105 | """ 106 | 107 | def __init__(self, edges=None, properties=None): 108 | self.edges = edges 109 | if properties is None: 110 | properties = {} 111 | self.properties = properties 112 | 113 | def __repr__(self): 114 | return 'Path including {}'.format([e for e in self.edges]) 115 | 116 | def add_edge(self, edge): 117 | """Add an edge to the path.""" 118 | self.edges.append(edge) 119 | 120 | def set_edge_weights(self, data_key=None, inverse=False, overwrite=True): 121 | """Calculate edge weights for all edges in the Path.""" 122 | for edge in self.edges: 123 | if not overwrite and edge.weight is not None: 124 | continue 125 | if data_key is not None: 126 | edge.set_edge_weight( 127 | normalize_factor=self.properties[data_key], 128 | inverse=inverse) 129 | else: 130 | edge.set_edge_weight() 131 | 132 | def add_data(self, property, value): 133 | """Add a property: value pair to the Path.properties attribute.""" 134 | self.properties[property] = value 135 | 136 | def __iter__(self): 137 | """Iterate through edges in the path.""" 138 | yield from self.edges 139 | 140 | 141 | def geojson_to_graph(geojson, graph_name=None, retain_all=True, 142 | valid_road_types=None, road_type_field='type', edge_idx=0, 143 | first_node_idx=0, weight_norm_field=None, inverse=False, 144 | workers=1, verbose=False): 145 | """Convert a geojson of path strings to a network graph. 146 | 147 | Arguments 148 | --------- 149 | geojson : str 150 | Path to a geojson file (or any other OGR-compatible vector file) to 151 | load network edges and nodes from. 152 | graph_name : str, optional 153 | Name of the graph. If not provided, graph will be named ``'unnamed'`` . 154 | retain_all : bool, optional 155 | If ``True`` , the entire graph will be returned even if some parts are 156 | not connected. Defaults to ``True``. 157 | valid_road_types : :class:`list` of :class:`int` s, optional 158 | The road types to permit in the graph. If not provided, it's assumed 159 | that all road types are permitted. The possible values are integers 160 | ``1``-``7``, which map as follows:: 161 | 162 | 1: Motorway 163 | 2: Primary 164 | 3: Secondary 165 | 4: Tertiary 166 | 5: Residential 167 | 6: Unclassified 168 | 7: Cart track 169 | 170 | road_type_field : str, optional 171 | The name of the property in the vector data that delineates road type. 172 | Defaults to ``'type'`` . 173 | edge_idx : int, optional 174 | The first index to use for an edge. This can be set to a higher value 175 | so that a graph's edge indices don't overlap with existing values in 176 | another graph. 177 | first_node_idx : int, optional 178 | The first index to use for a node. This can be set to a higher value 179 | so that a graph's node indices don't overlap with existing values in 180 | another graph. 181 | weight_norm_field : str, optional 182 | The name of a field in `geojson` to pass to argument ``data_key`` in 183 | :func:`Path.set_edge_weights`. Defaults to ``None``, in which case 184 | no weighting is performed (weights calculated solely using Euclidean 185 | distance.) 186 | workers : int, optional 187 | Number of parallel processes to run for parallelization. Defaults to 1. 188 | Should not be greater than the number of CPUs available. 189 | verbose : bool, optional 190 | Verbose print output. Defaults to ``False`` . 191 | 192 | Returns 193 | ------- 194 | G : :class:`networkx.MultiDiGraph` 195 | A :class:`networkx.MultiDiGraph` containing all of the nodes and edges 196 | from the geojson (or only the largest connected component if 197 | `retain_all` = ``False``). Edge lengths are weighted based on 198 | geographic distance. 199 | 200 | """ 201 | # due to an annoying feature of loading these graphs, the numeric road 202 | # type identifiers are presented as string versions. we therefore reformat 203 | # the valid_road_types list as strings. 204 | if valid_road_types is not None: 205 | valid_road_types = [str(i) for i in valid_road_types] 206 | 207 | # create the graph as a MultiGraph and set the original CRS to EPSG 4326 208 | 209 | # extract nodes and paths 210 | nodes, paths = get_nodes_paths(geojson, 211 | valid_road_types=valid_road_types, 212 | first_node_idx=first_node_idx, 213 | road_type_field=road_type_field, 214 | workers=workers, verbose=verbose) 215 | # nodes is a dict of node_idx: node_params (e.g. location, metadata) 216 | # pairs. 217 | # paths is a dict of path dicts. the path key is the path_idx. 218 | # each path dict has a list of node_idxs as well as properties metadata. 219 | 220 | # initialize the graph object 221 | G = nx.MultiDiGraph(name=graph_name, crs={'init': 'epsg:4326'}) 222 | if not nodes: # if there are no nodes in the graph 223 | return G 224 | if verbose: 225 | print("nodes:", nodes) 226 | print("paths:", paths) 227 | # add each osm node to the graph 228 | for node in nodes: 229 | G.add_node(node, **{'x': node.x, 'y': node.y}) 230 | # add each path to the graph 231 | for path in paths: 232 | # calculate edge length using euclidean distance and a weighting term 233 | path.set_edge_weights(data_key=weight_norm_field, inverse=inverse) 234 | edges = [(*edge.nodes, edge.weight) for edge in path] 235 | if verbose: 236 | print(edges) 237 | G.add_weighted_edges_from(edges) 238 | if not retain_all: 239 | # keep only largest connected component of graph unless retain_all 240 | # code modified from osmnx.core.get_largest_component & induce_subgraph 241 | largest_cc = max(nx.weakly_connected_components(G), key=len) 242 | G = get_subgraph(G, largest_cc) 243 | 244 | return G 245 | 246 | 247 | def get_nodes_paths(vector_file, first_node_idx=0, node_gdf=gpd.GeoDataFrame(), 248 | valid_road_types=None, road_type_field='type', workers=1, 249 | verbose=False): 250 | """ 251 | Extract nodes and paths from a vector file. 252 | 253 | Arguments 254 | --------- 255 | vector_file : str 256 | Path to an OGR-compatible vector file containing line segments (e.g., 257 | JSON response from from the Overpass API, or a SpaceNet GeoJSON). 258 | first_path_idx : int, optional 259 | The first index to use for a path. This can be set to a higher value 260 | so that a graph's path indices don't overlap with existing values in 261 | another graph. 262 | first_node_idx : int, optional 263 | The first index to use for a node. This can be set to a higher value 264 | so that a graph's node indices don't overlap with existing values in 265 | another graph. 266 | node_gdf : :class:`geopandas.GeoDataFrame` , optional 267 | A :class:`geopandas.GeoDataFrame` containing nodes to add to the graph. 268 | New nodes will be added to this object incrementally during the 269 | function call. 270 | valid_road_types : :class:`list` of :class:`int` s, optional 271 | The road types to permit in the graph. If not provided, it's assumed 272 | that all road types are permitted. The possible values are integers 273 | ``1``-``7``, which map as follows:: 274 | 275 | 1: Motorway 276 | 2: Primary 277 | 3: Secondary 278 | 4: Tertiary 279 | 5: Residential 280 | 6: Unclassified 281 | 7: Cart track 282 | 283 | road_type_field : str, optional 284 | The name of the attribute containing road type information in 285 | `vector_file`. Defaults to ``'type'``. 286 | workers : int, optional 287 | Number of worker processes to use for parallelization. Defaults to 1. 288 | Should not exceed the number of CPUs available. 289 | verbose : bool, optional 290 | Verbose print output. Defaults to ``False``. 291 | 292 | Returns 293 | ------- 294 | nodes, paths : `tuple` of `dict` s 295 | nodes : list 296 | A `list` of :class:`Node` s to be added to the graph. 297 | paths : list 298 | A list of :class:`Path` s containing the :class:`Edge` s and 299 | :class:`Node` s to be added to the graph. 300 | 301 | """ 302 | if valid_road_types is None: 303 | valid_road_types = ['1', '2', '3', '4', '5', '6', '7'] 304 | 305 | with fiona.open(vector_file, 'r') as source: 306 | 307 | with Pool(processes=workers) as pool: 308 | node_list = pool.map(_get_all_nodes, source, 309 | chunksize=10) 310 | pool.close() 311 | source.close() 312 | 313 | # convert to geoseries and drop duplicates (have to flatten first) 314 | node_series = gpd.GeoSeries([i for sublist in node_list for i in sublist]) 315 | # NOTE: It is ESSENTIAL to use keep='last' in the line below; otherwise, it 316 | # misses a duplicate if it includes the first element of the series. 317 | node_series = node_series.drop_duplicates(keep='last') 318 | node_series = node_series.reset_index(drop=True) 319 | node_series.name = 'geometry' 320 | node_series.index.name = 'node_idx' 321 | node_gdf = gpd.GeoDataFrame(node_series.reset_index()) 322 | node_gdf['node'] = node_gdf.apply( 323 | lambda p: Node(p['node_idx'], p['geometry'].x, p['geometry'].y), 324 | axis=1) 325 | 326 | # create another parallelized operation to iterate through edges 327 | # _init_worker passes the node_series to every process in the pool 328 | with fiona.open(vector_file, 'r') as source: 329 | with Pool( 330 | processes=workers, initializer=_init_worker, 331 | initargs=(node_gdf, valid_road_types, road_type_field) 332 | ) as pool: 333 | zipped_edges_properties = pool.map(parallel_linestring_to_path, 334 | source, chunksize=10) 335 | pool.close() 336 | source.close() 337 | 338 | nodes = node_gdf['node'].tolist() 339 | paths = [] 340 | # it would've been better to do this within the multiprocessing pool but 341 | # it's REALLY hard to share objects in memory across processes without 342 | # copies being made (and therefore nodes being duplicated) 343 | for edges, properties in zipped_edges_properties: 344 | path = Path( 345 | edges=[Edge((nodes[edge[0]], nodes[edge[1]])) for edge in edges], 346 | properties=properties 347 | ) 348 | paths.append(path) 349 | return nodes, paths 350 | 351 | 352 | def parallel_linestring_to_path(feature): 353 | """Read in a feature line from a fiona-opened shapefile and get the edges. 354 | 355 | Arguments 356 | --------- 357 | feature : dict 358 | An item from a :class:`fiona.open` iterable with the key ``'geometry'`` 359 | containing :class:`shapely.geometry.line.LineString` s or 360 | :class:`shapely.geometry.line.MultiLineString` s. 361 | 362 | Returns 363 | ------- 364 | A list of :class:`Path` s containing all edges in the LineString or 365 | MultiLineString. 366 | 367 | Notes 368 | ----- 369 | This function depends on ``node_series`` and ``valid_road_types``, which 370 | are passed by an initializer as items in ``var_dict``. 371 | 372 | """ 373 | 374 | properties = feature['properties'] 375 | # TODO: create more adjustable filter 376 | if var_dict['road_type_field'] in properties: 377 | road_type = properties[var_dict['road_type_field']] 378 | elif 'highway' in properties: 379 | road_type = properties['highway'] 380 | elif 'road_type' in properties: 381 | road_type = properties['road_type'] 382 | else: 383 | road_type = 'None' 384 | 385 | geom = feature['geometry'] 386 | if geom['type'] == 'LineString' or \ 387 | geom['type'] == 'MultiLineString': 388 | if road_type not in var_dict['valid_road_types'] or \ 389 | 'LINESTRING EMPTY' in properties.values(): 390 | return 391 | 392 | if geom['type'] == 'LineString': 393 | linestring = shapely.geometry.shape(geom) 394 | edges = linestring_to_edges(linestring, var_dict['node_gdf']) 395 | 396 | elif geom['type'] == 'MultiLineString': 397 | # do the same thing as above, but do it for each piece 398 | edges = [] 399 | for linestring in shapely.geometry.shape(geom): 400 | edge_set, node_idx, node_gdf = linestring_to_edges( 401 | linestring, var_dict['node_gdf']) 402 | edges.extend(edge_set) 403 | 404 | return edges, properties 405 | 406 | 407 | def linestring_to_edges(linestring, node_gdf): 408 | """Collect nodes in a linestring and add them to an edge. 409 | 410 | Arguments 411 | --------- 412 | linestring : :class:`shapely.geometry.LineString` 413 | A :class:`shapely.geometry.LineString` object to extract nodes and 414 | edges from. 415 | node_series : :class:`geopandas.GeoSeries` 416 | A :class:`geopandas.GeoSeries` containing a 417 | :class:`shapely.geometry.point.Point` for every node to be added to the 418 | graph. 419 | 420 | Returns 421 | ------- 422 | edges : list 423 | A list of :class:`Edge` s from ``linestring``. 424 | 425 | """ 426 | edges = [] 427 | nodes = [] 428 | 429 | for point in linestring.coords: 430 | point_shp = shapely.geometry.shape(Point(point)) 431 | nodes.append( 432 | node_gdf.node_idx[node_gdf.distance(point_shp) == 0.0].values[0] 433 | ) 434 | if len(nodes) > 1: 435 | edges.append(nodes[-2:]) 436 | 437 | return edges 438 | 439 | 440 | def _get_all_nodes(feature): 441 | """Create a list of node geometries from a geojson of (multi)linestrings. 442 | 443 | Note 444 | ---- 445 | This function is intended to be used with pool.imap_unordered for 446 | parallelization. 447 | 448 | Returns 449 | ------- 450 | A list of :class:`shapely.geometry.Point` instances. DUPLICATES CAN EXIST. 451 | """ 452 | points = [] 453 | geom = feature['geometry'] 454 | if geom['type'] == 'LineString': 455 | linestring = shapely.geometry.shape(geom) 456 | points.extend(_get_linestring_points(linestring)) 457 | elif geom['type'] == 'MultiLineString': 458 | for linestring in shapely.geometry.shape(geom): 459 | points.extend(_get_linestring_points(linestring)) 460 | 461 | return points 462 | 463 | 464 | def _get_linestring_points(linestring): 465 | points = [] 466 | for point in linestring.coords: 467 | points.append(shapely.geometry.shape(Point(point))) 468 | return points 469 | 470 | 471 | def _init_worker(node_gdf, valid_road_types, road_type_field): 472 | the_dict = {'node_gdf': node_gdf, 473 | 'valid_road_types': valid_road_types, 474 | 'road_type_field': road_type_field} 475 | global var_dict 476 | var_dict = the_dict 477 | -------------------------------------------------------------------------------- /cw_geodata/vector_label/mask.py: -------------------------------------------------------------------------------- 1 | from ..utils.core import _check_df_load, _check_rasterio_im_load 2 | from ..utils.geo import geometries_internal_intersection, _check_wkt_load 3 | import numpy as np 4 | import pandas as pd 5 | import rasterio 6 | from rasterio import features 7 | from affine import Affine 8 | from skimage.morphology import square, erosion, dilation 9 | 10 | 11 | def df_to_px_mask(df, channels=['footprint'], out_file=None, reference_im=None, 12 | geom_col='geometry', affine_obj=None, shape=(900, 900), 13 | out_type='int', burn_value=255, **kwargs): 14 | """Convert a dataframe of geometries to a pixel mask. 15 | 16 | Arguments 17 | --------- 18 | df : :class:`pandas.DataFrame` or :class:`geopandas.GeoDataFrame` 19 | A :class:`pandas.DataFrame` or :class:`geopandas.GeoDataFrame` instance 20 | with a column containing geometries (identified by `geom_col`). If the 21 | geometries in `df` are not in pixel coordinates, then `affine` or 22 | `reference_im` must be passed to provide the transformation to convert. 23 | channels : list, optional 24 | The mask channels to generate. There are three values that this can 25 | contain: 26 | 27 | - ``"footprint"``: Create a full footprint mask, with 0s at pixels 28 | that don't fall within geometries and `burn_value` at pixels that 29 | do. 30 | - ``"boundary"``: Create a mask with geometries outlined. Use 31 | `boundary_width` to set how thick the boundary will be drawn. 32 | - ``"contact"``: Create a mask with regions between >= 2 closely 33 | juxtaposed geometries labeled. Use `contact_spacing` to set the 34 | maximum spacing between polygons to be labeled. 35 | 36 | Each channel correspond to its own `shape` plane in the output. 37 | out_file : str, optional 38 | Path to an image file to save the output to. Must be compatible with 39 | :class:`rasterio.DatasetReader`. If provided, a `reference_im` must be 40 | provided (for metadata purposes). 41 | reference_im : :class:`rasterio.DatasetReader` or `str`, optional 42 | An image to extract necessary coordinate information from: the 43 | affine transformation matrix, the image extent, etc. If provided, 44 | `affine_obj` and `shape` are ignored. 45 | geom_col : str, optional 46 | The column containing geometries in `df`. Defaults to ``"geometry"``. 47 | affine_obj : `list` or :class:`affine.Affine`, optional 48 | Affine transformation to use to convert from geo coordinates to pixel 49 | space. Only provide this argument if `df` is a 50 | :class:`geopandas.GeoDataFrame` with coordinates in a georeferenced 51 | coordinate space. Ignored if `reference_im` is provided. 52 | shape : tuple, optional 53 | An ``(x_size, y_size)`` tuple defining the pixel extent of the output 54 | mask. Ignored if `reference_im` is provided. 55 | burn_value : `int` or `float` 56 | The value to use for labeling objects in the mask. Defaults to 255 (the 57 | max value for ``uint8`` arrays). The mask array will be set to the same 58 | dtype as `burn_value`. 59 | kwargs 60 | Additional arguments to pass to `boundary_mask` or `contact_mask`. See 61 | those functions for requirements. 62 | 63 | Returns 64 | ------- 65 | mask : :class:`numpy.array` 66 | A pixel mask with 0s for non-object pixels and `burn_value` at object 67 | pixels. `mask` dtype will coincide with `burn_value`. Shape will be 68 | ``(shape[0], shape[1], len(channels))``, with channels ordered per the 69 | provided `channels` `list`. 70 | 71 | """ 72 | if isinstance(channels, str): # e.g. if "contact", not ["contact"] 73 | channels = [channels] 74 | 75 | mask_dict = {} 76 | if 'footprint' in channels: 77 | mask_dict['footprint'] = footprint_mask( 78 | df=df, reference_im=reference_im, geom_col=geom_col, 79 | affine_obj=affine_obj, shape=shape, out_type=out_type, 80 | burn_value=burn_value 81 | ) 82 | if 'boundary' in channels: 83 | mask_dict['boundary'] = boundary_mask( 84 | footprint_msk=mask_dict.get('footprint', None), 85 | reference_im=reference_im, geom_col=geom_col, 86 | boundary_width=kwargs.get('boundary_width', 3), 87 | boundary_type=kwargs.get('boundary_type', 'inner'), 88 | burn_value=burn_value, df=df, affine_obj=affine_obj, 89 | shape=shape, out_type=out_type 90 | ) 91 | if 'contact' in channels: 92 | mask_dict['contact'] = contact_mask( 93 | df=df, reference_im=reference_im, geom_col=geom_col, 94 | affine_obj=affine_obj, shape=shape, out_type=out_type, 95 | contact_spacing=kwargs.get('contact_spacing', 10), 96 | burn_value=burn_value 97 | ) 98 | 99 | output_arr = np.stack([mask_dict[c] for c in channels], axis=-1) 100 | 101 | if reference_im: 102 | reference_im = _check_rasterio_im_load(reference_im) 103 | if out_file: 104 | meta = reference_im.meta.copy() 105 | meta.update(count=output_arr.shape[-1]) 106 | meta.update(dtype='uint8') 107 | with rasterio.open(out_file, 'w', **meta) as dst: 108 | # I hate band indexing. 109 | for c in range(1, 1 + output_arr.shape[-1]): 110 | dst.write(output_arr[:, :, c-1], indexes=c) 111 | 112 | return output_arr 113 | 114 | 115 | def footprint_mask(df, out_file=None, reference_im=None, geom_col='geometry', 116 | do_transform=False, affine_obj=None, shape=(900, 900), 117 | out_type='int', burn_value=255, burn_field=None): 118 | """Convert a dataframe of geometries to a pixel mask. 119 | 120 | Arguments 121 | --------- 122 | df : :class:`pandas.DataFrame` or :class:`geopandas.GeoDataFrame` 123 | A :class:`pandas.DataFrame` or :class:`geopandas.GeoDataFrame` instance 124 | with a column containing geometries (identified by `geom_col`). If the 125 | geometries in `df` are not in pixel coordinates, then `affine` or 126 | `reference_im` must be passed to provide the transformation to convert. 127 | out_file : str, optional 128 | Path to an image file to save the output to. Must be compatible with 129 | :class:`rasterio.DatasetReader`. If provided, a `reference_im` must be 130 | provided (for metadata purposes). 131 | reference_im : :class:`rasterio.DatasetReader` or `str`, optional 132 | An image to extract necessary coordinate information from: the 133 | affine transformation matrix, the image extent, etc. If provided, 134 | `affine_obj` and `shape` are ignored. 135 | geom_col : str, optional 136 | The column containing geometries in `df`. Defaults to ``"geometry"``. 137 | do_transform : bool, optional 138 | Should the values in `df` be transformed from geospatial coordinates 139 | to pixel coordinates? Defaults to no (False). If True, either 140 | `reference_im` or `affine_obj` must be provided as a source for the 141 | the required affine transformation matrix. 142 | affine_obj : `list` or :class:`affine.Affine`, optional 143 | Affine transformation to use to convert from geo coordinates to pixel 144 | space. Only provide this argument if `df` is a 145 | :class:`geopandas.GeoDataFrame` with coordinates in a georeferenced 146 | coordinate space. Ignored if `reference_im` is provided or if 147 | ``do_transform=False``. 148 | shape : tuple, optional 149 | An ``(x_size, y_size)`` tuple defining the pixel extent of the output 150 | mask. Ignored if `reference_im` is provided. 151 | out_type : 'float' or 'int' 152 | burn_value : `int` or `float`, optional 153 | The value to use for labeling objects in the mask. Defaults to 255 (the 154 | max value for ``uint8`` arrays). The mask array will be set to the same 155 | dtype as `burn_value`. Ignored if `burn_field` is provided. 156 | burn_field : str, optional 157 | Name of a column in `df` that provides values for `burn_value` for each 158 | independent object. If provided, `burn_value` is ignored. 159 | 160 | Returns 161 | ------- 162 | mask : :class:`numpy.array` 163 | A pixel mask with 0s for non-object pixels and `burn_value` at object 164 | pixels. `mask` dtype will coincide with `burn_value`. 165 | 166 | """ 167 | 168 | # start with required checks and pre-population of values 169 | if out_file and not reference_im: 170 | raise ValueError( 171 | 'If saving output to file, `reference_im` must be provided.') 172 | df = _check_df_load(df) 173 | df[geom_col] = df[geom_col].apply(_check_wkt_load) # load in geoms if wkt 174 | if not do_transform: 175 | affine_obj = Affine(1, 0, 0, 0, 1, 0) # identity transform 176 | 177 | if reference_im: 178 | reference_im = _check_rasterio_im_load(reference_im) 179 | shape = reference_im.shape 180 | if do_transform: 181 | affine_obj = reference_im.transform 182 | 183 | # extract geometries and pair them with burn values 184 | if burn_field: 185 | if out_type == 'int': 186 | feature_list = list(zip(df[geom_col], 187 | df[burn_field].astype('uint8'))) 188 | else: 189 | feature_list = list(zip(df[geom_col], 190 | df[burn_field].astype('uint8'))) 191 | else: 192 | feature_list = list(zip(df[geom_col], [burn_value]*len(df))) 193 | 194 | output_arr = features.rasterize(shapes=feature_list, out_shape=shape, 195 | transform=affine_obj) 196 | if out_file: 197 | meta = reference_im.meta.copy() 198 | meta.update(count=1) 199 | if out_type == 'int': 200 | meta.update(dtype='uint8') 201 | with rasterio.open(out_file, 'w', **meta) as dst: 202 | dst.write(output_arr, indexes=1) 203 | 204 | return output_arr 205 | 206 | 207 | def boundary_mask(footprint_msk=None, out_file=None, reference_im=None, 208 | boundary_width=3, boundary_type='inner', burn_value=255, 209 | **kwargs): 210 | """Convert a dataframe of geometries to a pixel mask. 211 | 212 | Notes 213 | ----- 214 | This function requires creation of a footprint mask before it can operate; 215 | therefore, if there is no footprint mask already present, it will create 216 | one. In that case, additional arguments for :func:`footprint_mask` (e.g. 217 | ``df``) must be passed. 218 | 219 | Arguments 220 | --------- 221 | footprint_msk : :class:`numpy.array`, optional 222 | A filled in footprint mask created using :func:`footprint_mask`. If not 223 | provided, one will be made by calling :func:`footprint_mask` before 224 | creating the boundary mask, and the required arguments for that 225 | function must be provided as kwargs. 226 | out_file : str, optional 227 | Path to an image file to save the output to. Must be compatible with 228 | :class:`rasterio.DatasetReader`. If provided, a `reference_im` must be 229 | provided (for metadata purposes). 230 | reference_im : :class:`rasterio.DatasetReader` or `str`, optional 231 | An image to extract necessary coordinate information from: the 232 | affine transformation matrix, the image extent, etc. If provided, 233 | `affine_obj` and `shape` are ignored 234 | boundary_width : int, optional 235 | The width of the boundary to be created in pixels. Defaults to 3. 236 | boundary_type : ``"inner"`` or ``"outer"``, optional 237 | Where to draw the boundaries: within the object (``"inner"``) or 238 | outside of it (``"outer"``). Defaults to ``"inner"``. 239 | burn_value : `int`, optional 240 | The value to use for labeling objects in the mask. Defaults to 255 (the 241 | max value for ``uint8`` arrays). The mask array will be set to the same 242 | dtype as `burn_value`. Ignored if `burn_field` is provided. 243 | **kwargs : optional 244 | Additional arguments to pass to :func:`footprint_mask` if one needs to 245 | be created. 246 | 247 | Returns 248 | ------- 249 | boundary_mask : :class:`numpy.array` 250 | A pixel mask with 0s for non-object pixels and the same value as the 251 | footprint mask `burn_value` for the boundaries of each object. 252 | 253 | Note: This function draws the boundaries within the edge of the object. 254 | 255 | """ 256 | if out_file and not reference_im: 257 | raise ValueError( 258 | 'If saving output to file, `reference_im` must be provided.') 259 | if reference_im: 260 | reference_im = _check_rasterio_im_load(reference_im) 261 | # need to have a footprint mask for this function, so make it if not given 262 | if footprint_msk is None: 263 | footprint_msk = footprint_mask(reference_im=reference_im, 264 | burn_value=burn_value, **kwargs) 265 | 266 | # perform dilation or erosion of `footprint_mask` to get the boundary 267 | strel = square(boundary_width) 268 | if boundary_type == 'outer': 269 | boundary_mask = dilation(footprint_msk, strel) 270 | elif boundary_type == 'inner': 271 | boundary_mask = erosion(footprint_msk, strel) 272 | # use xor operator between border and footprint mask to get _just_ boundary 273 | boundary_mask = boundary_mask ^ footprint_msk 274 | # scale the `True` values to burn_value and return 275 | boundary_mask = boundary_mask > 0 # need to binarize to get burn val right 276 | output_arr = boundary_mask.astype('uint8')*burn_value 277 | 278 | if out_file: 279 | meta = reference_im.meta.copy() 280 | meta.update(count=1) 281 | meta.update(dtype='uint8') 282 | with rasterio.open(out_file, 'w', **meta) as dst: 283 | dst.write(output_arr, indexes=1) 284 | 285 | return output_arr 286 | 287 | 288 | def contact_mask(df, out_file=None, reference_im=None, geom_col='geometry', 289 | affine_obj=None, shape=(900, 900), out_type='int', 290 | contact_spacing=10, burn_value=255): 291 | """Create a pixel mask labeling closely juxtaposed objects. 292 | 293 | Notes 294 | ----- 295 | This function identifies pixels in an image that do not correspond to 296 | objects, but fall within `contact_spacing` of >1 labeled object. 297 | 298 | Arguments 299 | --------- 300 | df : :class:`pandas.DataFrame` or :class:`geopandas.GeoDataFrame` 301 | A :class:`pandas.DataFrame` or :class:`geopandas.GeoDataFrame` instance 302 | with a column containing geometries (identified by `geom_col`). If the 303 | geometries in `df` are not in pixel coordinates, then `affine` or 304 | `reference_im` must be passed to provide the transformation to convert. 305 | out_file : str, optional 306 | Path to an image file to save the output to. Must be compatible with 307 | :class:`rasterio.DatasetReader`. If provided, a `reference_im` must be 308 | provided (for metadata purposes). 309 | reference_im : :class:`rasterio.DatasetReader` or `str`, optional 310 | An image to extract necessary coordinate information from: the 311 | affine transformation matrix, the image extent, etc. If provided, 312 | `affine_obj` and `shape` are ignored. 313 | geom_col : str, optional 314 | The column containing geometries in `df`. Defaults to ``"geometry"``. 315 | affine_obj : `list` or :class:`affine.Affine`, optional 316 | Affine transformation to use to convert from geo coordinates to pixel 317 | space. Only provide this argument if `df` is a 318 | :class:`geopandas.GeoDataFrame` with coordinates in a georeferenced 319 | coordinate space. Ignored if `reference_im` is provided. 320 | shape : tuple, optional 321 | An ``(x_size, y_size)`` tuple defining the pixel extent of the output 322 | mask. Ignored if `reference_im` is provided. 323 | out_type : 'float' or 'int' 324 | contact_spacing : `int` or `float`, optional 325 | The desired maximum distance between adjacent polygons to be labeled 326 | as contact. `contact_spacing` will be in the same units as `df` 's 327 | geometries, not necessarily in pixel units. 328 | burn_value : `int` or `float`, optional 329 | The value to use for labeling objects in the mask. Defaults to 255 (the 330 | max value for ``uint8`` arrays). The mask array will be set to the same 331 | dtype as `burn_value`. 332 | 333 | """ 334 | if out_file and not reference_im: 335 | raise ValueError( 336 | 'If saving output to file, `reference_im` must be provided.') 337 | df = _check_df_load(df) 338 | df[geom_col] = df[geom_col].apply(_check_wkt_load) # load in geoms if wkt 339 | if reference_im: 340 | reference_im = _check_rasterio_im_load(reference_im) 341 | # grow geometries by half `contact_spacing` to find overlaps 342 | buffered_geoms = df[geom_col].apply(lambda x: x.buffer(contact_spacing/2)) 343 | # create a single multipolygon that covers all of the intersections 344 | intersect_poly = geometries_internal_intersection(buffered_geoms) 345 | # create a small df containing the intersections to make a footprint from 346 | df_for_footprint = pd.DataFrame({'shape_name': ['overlap'], 347 | 'geometry': [intersect_poly]}) 348 | # use `footprint_mask` to create the overlap mask 349 | contact_msk = footprint_mask(df_for_footprint, reference_im=reference_im, 350 | geom_col='geometry', affine_obj=affine_obj, 351 | shape=shape, out_type=out_type, 352 | burn_value=burn_value) 353 | footprint_msk = footprint_mask(df, reference_im=reference_im, 354 | geom_col=geom_col, affine_obj=affine_obj, 355 | shape=shape, out_type=out_type, 356 | burn_value=burn_value) 357 | contact_msk[footprint_msk > 0] = 0 358 | contact_msk = contact_msk > 0 359 | output_arr = contact_msk.astype('uint8')*burn_value 360 | 361 | if out_file: 362 | meta = reference_im.meta.copy() 363 | meta.update(count=1) 364 | if out_type == 'int': 365 | meta.update(dtype='uint8') 366 | with rasterio.open(out_file, 'w', **meta) as dst: 367 | dst.write(output_arr, indexes=1) 368 | 369 | return output_arr 370 | -------------------------------------------------------------------------------- /cw_geodata/vector_label/polygon.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shapely 3 | from affine import Affine 4 | import rasterio 5 | from rasterio.warp import transform_bounds 6 | from ..utils.geo import list_to_affine, _reduce_geom_precision 7 | from ..utils.core import _check_gdf_load 8 | from ..raster_image.image import get_geo_transform 9 | from shapely.geometry import box, Polygon 10 | import pandas as pd 11 | import geopandas as gpd 12 | from rtree.core import RTreeError 13 | 14 | 15 | def convert_poly_coords(geom, raster_src=None, affine_obj=None, inverse=False, 16 | precision=None): 17 | """Georegister geometry objects currently in pixel coords or vice versa. 18 | 19 | Arguments 20 | --------- 21 | geom : :class:`shapely.geometry.shape` or str 22 | A :class:`shapely.geometry.shape`, or WKT string-formatted geometry 23 | object currently in pixel coordinates. 24 | raster_src : str, optional 25 | Path to a raster image with georeferencing data to apply to `geom`. 26 | Alternatively, an opened :class:`rasterio.Band` object or 27 | :class:`osgeo.gdal.Dataset` object can be provided. Required if not 28 | using `affine_obj`. 29 | affine_obj: list or :class:`affine.Affine` 30 | An affine transformation to apply to `geom` in the form of an 31 | ``[a, b, d, e, xoff, yoff]`` list or an :class:`affine.Affine` object. 32 | Required if not using `raster_src`. 33 | inverse : bool, optional 34 | If true, will perform the inverse affine transformation, going from 35 | geospatial coordinates to pixel coordinates. 36 | precision : int, optional 37 | Decimal precision for the polygon output. If not provided, rounding 38 | is skipped. 39 | 40 | Returns 41 | ------- 42 | out_geom 43 | A geometry in the same format as the input with its coordinate system 44 | transformed to match the destination object. 45 | """ 46 | 47 | if not raster_src and not affine_obj: 48 | raise ValueError("Either raster_src or affine_obj must be provided.") 49 | 50 | if raster_src is not None: 51 | affine_xform = get_geo_transform(raster_src) 52 | else: 53 | if isinstance(affine_obj, Affine): 54 | affine_xform = affine_obj 55 | else: 56 | # assume it's a list in either gdal or "standard" order 57 | # (list_to_affine checks which it is) 58 | if len(affine_obj) == 9: # if it's straight from rasterio 59 | affine_obj = affine_obj[0:6] 60 | affine_xform = list_to_affine(affine_obj) 61 | 62 | if inverse: # geo->px transform 63 | affine_xform = ~affine_xform 64 | 65 | if isinstance(geom, str): 66 | # get the polygon out of the wkt string 67 | g = shapely.wkt.loads(geom) 68 | elif isinstance(geom, shapely.geometry.base.BaseGeometry): 69 | g = geom 70 | else: 71 | raise TypeError('The provided geometry is not an accepted format. ' + 72 | 'This function can only accept WKT strings and ' + 73 | 'shapely geometries.') 74 | 75 | xformed_g = shapely.affinity.affine_transform(g, [affine_xform.a, 76 | affine_xform.b, 77 | affine_xform.d, 78 | affine_xform.e, 79 | affine_xform.xoff, 80 | affine_xform.yoff]) 81 | if isinstance(geom, str): 82 | # restore to wkt string format 83 | xformed_g = shapely.wkt.dumps(xformed_g) 84 | if precision is not None: 85 | xformed_g = _reduce_geom_precision(xformed_g, precision=precision) 86 | 87 | return xformed_g 88 | 89 | 90 | def affine_transform_gdf(gdf, affine_obj, inverse=False, geom_col="geometry", 91 | precision=None): 92 | """Perform an affine transformation on a GeoDataFrame. 93 | 94 | Arguments 95 | --------- 96 | gdf : :class:`geopandas.GeoDataFrame`, :class:`pandas.DataFrame`, or `str` 97 | A GeoDataFrame, pandas DataFrame with a ``"geometry"`` column (or a 98 | different column containing geometries, identified by `geom_col` - 99 | note that this column will be renamed ``"geometry"`` for ease of use 100 | with geopandas), or the path to a saved file in .geojson or .csv 101 | format. 102 | affine_obj : list or :class:`affine.Affine` 103 | An affine transformation to apply to `geom` in the form of an 104 | ``[a, b, d, e, xoff, yoff]`` list or an :class:`affine.Affine` object. 105 | inverse : bool, optional 106 | Use this argument to perform the inverse transformation. 107 | geom_col : str, optional 108 | The column in `gdf` corresponding to the geometry. Defaults to 109 | ``'geometry'``. 110 | precision : int, optional 111 | Decimal precision to round the geometries to. If not provided, no 112 | rounding is performed. 113 | """ 114 | if isinstance(gdf, str): # assume it's a geojson 115 | if gdf.lower().endswith('json'): 116 | gdf = gpd.read_file(gdf) 117 | elif gdf.lower().endswith('csv'): 118 | gdf = pd.read_csv(gdf) 119 | gdf = gdf.rename(columns={geom_col: 'geometry'}) 120 | if not isinstance(gdf['geometry'][0], Polygon): 121 | gdf['geometry'] = gdf['geometry'].apply(shapely.wkt.loads) 122 | else: 123 | raise ValueError( 124 | "The file format is incompatible with this function.") 125 | gdf["geometry"] = gdf["geometry"].apply(convert_poly_coords, 126 | affine_obj=affine_obj, 127 | inverse=inverse) 128 | if precision is not None: 129 | gdf['geometry'] = gdf['geometry'].apply( 130 | _reduce_geom_precision, precision=precision) 131 | return gdf 132 | 133 | 134 | def georegister_px_df(df, im_fname=None, affine_obj=None, crs=None, 135 | geom_col='geometry', precision=None): 136 | """Convert a dataframe of geometries in pixel coordinates to a geo CRS. 137 | 138 | Arguments 139 | --------- 140 | df : :class:`pandas.DataFrame` 141 | A :class:`pandas.DataFrame` with polygons in a column named 142 | ``"geometry"``. 143 | im_fname : str, optional 144 | A filename or :class:`rasterio.DatasetReader` object containing an 145 | image that has the same bounds as the pixel coordinates in `df`. If 146 | not provided, `affine_obj` and `crs` must both be provided. 147 | affine_obj : `list` or :class:`affine.Affine`, optional 148 | An affine transformation to apply to `geom` in the form of an 149 | ``[a, b, d, e, xoff, yoff]`` list or an :class:`affine.Affine` object. 150 | Required if not using `raster_src`. 151 | crs : dict, optional 152 | The coordinate reference system for the output GeoDataFrame. Required 153 | if not providing a raster image to extract the information from. Format 154 | should be ``{'init': 'epsgxxxx'}``, replacing xxxx with the EPSG code. 155 | geom_col : str, optional 156 | The column containing geometry in `df`. If not provided, defaults to 157 | ``"geometry"``. 158 | precision : int, optional 159 | The decimal precision for output geometries. If not provided, the 160 | vertex locations won't be rounded. 161 | 162 | """ 163 | if im_fname is not None: 164 | affine_obj = rasterio.open(im_fname).transform 165 | crs = rasterio.open(im_fname).crs 166 | else: 167 | if not affine_obj or not crs: 168 | raise ValueError( 169 | 'If an image path is not provided, ' + 170 | 'affine_obj and crs must be.') 171 | tmp_df = affine_transform_gdf(df, affine_obj, geom_col=geom_col, 172 | precision=precision) 173 | 174 | return gpd.GeoDataFrame(tmp_df, crs=crs) 175 | 176 | 177 | def geojson_to_px_gdf(geojson, im_path, precision=None): 178 | """Convert a geojson or set of geojsons from geo coords to px coords. 179 | 180 | Arguments 181 | --------- 182 | geojson : str 183 | Path to a geojson. This function will also accept a 184 | :class:`pandas.DataFrame` or :class:`geopandas.GeoDataFrame` with a 185 | column named ``'geometry'`` in this argument. 186 | im_path : str 187 | Path to a georeferenced image (ie a GeoTIFF) that geolocates to the 188 | same geography as the `geojson`(s). If a directory, the bounds of each 189 | GeoTIFF will be loaded in and all overlapping geometries will be 190 | transformed. This function will also accept a 191 | :class:`osgeo.gdal.Dataset` or :class:`rasterio.DatasetReader` with 192 | georeferencing information in this argument. 193 | precision : int, optional 194 | The decimal precision for output geometries. If not provided, the 195 | vertex locations won't be rounded. 196 | 197 | Returns 198 | ------- 199 | output_df : :class:`pandas.DataFrame` 200 | A :class:`pandas.DataFrame` with all geometries in `geojson` that 201 | overlapped with the image at `im_path` converted to pixel coordinates. 202 | Additional columns are included with the filename of the source 203 | geojson (if available) and images for reference. 204 | 205 | """ 206 | # get the bbox and affine transforms for the image 207 | if isinstance(im_path, str): 208 | bbox = box(*rasterio.open(im_path).bounds) 209 | affine_obj = rasterio.open(im_path).transform 210 | im_crs = rasterio.open(im_path).crs 211 | 212 | else: 213 | bbox = box(im_path.bounds) 214 | affine_obj = im_path.transform 215 | im_crs = im_path.crs 216 | 217 | # make sure the geo vector data is loaded in as geodataframe(s) 218 | gdf = _check_gdf_load(geojson) 219 | 220 | overlap_gdf = get_overlapping_subset(gdf, bbox=bbox, bbox_crs=im_crs) 221 | transformed_gdf = affine_transform_gdf(overlap_gdf, affine_obj=affine_obj, 222 | inverse=True, precision=precision) 223 | transformed_gdf['image_fname'] = os.path.split(im_path)[1] 224 | 225 | return transformed_gdf 226 | 227 | 228 | def get_overlapping_subset(gdf, im=None, bbox=None, bbox_crs=None): 229 | """Extract a subset of geometries in a GeoDataFrame that overlap with `im`. 230 | 231 | Notes 232 | ----- 233 | This function uses RTree's spatialindex, which is much faster (but slightly 234 | less accurate) than direct comparison of each object for overlap. 235 | 236 | Arguments 237 | --------- 238 | gdf : :class:`geopandas.GeoDataFrame` 239 | A :class:`geopandas.GeoDataFrame` instance or a path to a geojson. 240 | im : :class:`rasterio.DatasetReader` or `str`, optional 241 | An image object loaded with `rasterio` or a path to a georeferenced 242 | image (i.e. a GeoTIFF). 243 | bbox : `list` or :class:`shapely.geometry.Polygon`, optional 244 | A bounding box (either a :class:`shapely.geometry.Polygon` or a 245 | ``[bottom, left, top, right]`` `list`) from an image. Has no effect 246 | if `im` is provided (`bbox` is inferred from the image instead.) If 247 | `bbox` is passed and `im` is not, a `bbox_crs` should be provided to 248 | ensure correct geolocation - if it isn't, it will be assumed to have 249 | the same crs as `gdf`. 250 | 251 | Returns 252 | ------- 253 | output_gdf : :class:`geopandas.GeoDataFrame` 254 | A :class:`geopandas.GeoDataFrame` with all geometries in `gdf` that 255 | overlapped with the image at `im`. 256 | Coordinates are kept in the CRS of `gdf`. 257 | 258 | """ 259 | if not im and not bbox: 260 | raise ValueError('Either `im` or `bbox` must be provided.') 261 | if isinstance(gdf, str): 262 | gdf = gpd.read_file(gdf) 263 | if isinstance(im, str): 264 | im = rasterio.open(im) 265 | sindex = gdf.sindex 266 | # use transform_bounds in case the crs is different - no effect if not 267 | if im: 268 | bbox = transform_bounds(im.crs, gdf.crs, *im.bounds) 269 | else: 270 | if isinstance(bbox, Polygon): 271 | bbox = bbox.bounds 272 | if not bbox_crs: 273 | bbox_crs = gdf.crs 274 | bbox = transform_bounds(bbox_crs, gdf.crs, *bbox) 275 | try: 276 | intersectors = list(sindex.intersection(bbox)) 277 | except RTreeError: 278 | intersectors = [] 279 | 280 | return gdf.iloc[intersectors, :] 281 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /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=source 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% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | .. title:: API reference 2 | 3 | CosmiQ Works GeoData API reference 4 | ===================================== 5 | 6 | .. contents:: 7 | 8 | cw-geodata class and function list 9 | ---------------------------------- 10 | 11 | .. autosummary:: 12 | 13 | cw_geodata.raster_image.image.get_geo_transform 14 | cw_geodata.vector_label.polygon.affine_transform_gdf 15 | cw_geodata.vector_label.polygon.convert_poly_coords 16 | cw_geodata.vector_label.polygon.geojson_to_px_gdf 17 | cw_geodata.vector_label.polygon.georegister_px_df 18 | cw_geodata.vector_label.polygon.get_overlapping_subset 19 | cw_geodata.vector_label.graph.geojson_to_graph 20 | cw_geodata.vector_label.graph.get_nodes_paths 21 | cw_geodata.vector_label.graph.process_linestring 22 | cw_geodata.vector_label.mask.boundary_mask 23 | cw_geodata.vector_label.mask.contact_mask 24 | cw_geodata.vector_label.mask.df_to_px_mask 25 | cw_geodata.vector_label.mask.footprint_mask 26 | cw_geodata.utils.geo.geometries_internal_intersection 27 | cw_geodata.utils.geo.list_to_affine 28 | cw_geodata.utils.geo.split_multi_geometries 29 | 30 | 31 | 32 | Raster/Image functionality 33 | -------------------------- 34 | 35 | Image submodule 36 | ~~~~~~~~~~~~~~~ 37 | 38 | .. automodule:: cw_geodata.raster_image.image 39 | :members: 40 | 41 | Vector/Label functionality 42 | -------------------------- 43 | 44 | Polygon submodule 45 | ~~~~~~~~~~~~~~~~~ 46 | 47 | .. automodule:: cw_geodata.vector_label.polygon 48 | :members: 49 | 50 | Graph submodule 51 | ~~~~~~~~~~~~~~~ 52 | 53 | .. automodule:: cw_geodata.vector_label.graph 54 | :members: 55 | 56 | Mask submodule 57 | ~~~~~~~~~~~~~~ 58 | 59 | .. automodule:: cw_geodata.vector_label.mask 60 | :members: 61 | 62 | Utility functions 63 | ----------------- 64 | 65 | Core utility submodule 66 | ~~~~~~~~~~~~~~~~~~~~~~ 67 | 68 | .. automodule:: cw_geodata.utils.core 69 | :members: 70 | 71 | Geo utility submodule 72 | ~~~~~~~~~~~~~~~~~~~~~ 73 | 74 | .. automodule:: cw_geodata.utils.geo 75 | :members: 76 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'cw-geodata' 23 | copyright = '2019, CosmiQ Works' 24 | author = 'Nick Weir, CosmiQ Works' 25 | 26 | # The short X.Y version 27 | version = '0.0' 28 | # The full version, including alpha/beta/rc tags 29 | release = '0.0.3' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.napoleon', 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.doctest', 45 | 'sphinx.ext.intersphinx', 46 | 'sphinx.ext.mathjax', 47 | 'sphinx.ext.viewcode', 48 | 'sphinx.ext.githubpages', 49 | 'sphinx.ext.autosummary' 50 | ] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix(es) of source filenames. 56 | # You can specify multiple suffix as a list of string: 57 | # 58 | # source_suffix = ['.rst', '.md'] 59 | source_suffix = '.rst' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This pattern also affects html_static_path and html_extra_path. 74 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = None 78 | 79 | 80 | # -- Options for HTML output ------------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'sphinx_rtd_theme' 86 | 87 | # Theme options are theme-specific and customize the look and feel of a theme 88 | # further. For a list of options available for each theme, see the 89 | # documentation. 90 | # 91 | # html_theme_options = {} 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | html_static_path = ['_static'] 97 | 98 | # Custom sidebar templates, must be a dictionary that maps document names 99 | # to template names. 100 | # 101 | # The default sidebars (for documents that don't match any pattern) are 102 | # defined by theme itself. Builtin themes are using these templates by 103 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 104 | # 'searchbox.html']``. 105 | # 106 | # html_sidebars = {} 107 | 108 | 109 | # -- Options for HTMLHelp output --------------------------------------------- 110 | 111 | # Output file base name for HTML help builder. 112 | htmlhelp_basename = 'cw-geodatadoc' 113 | 114 | 115 | # -- Options for LaTeX output ------------------------------------------------ 116 | 117 | latex_elements = { 118 | # The paper size ('letterpaper' or 'a4paper'). 119 | # 120 | # 'papersize': 'letterpaper', 121 | 122 | # The font size ('10pt', '11pt' or '12pt'). 123 | # 124 | # 'pointsize': '10pt', 125 | 126 | # Additional stuff for the LaTeX preamble. 127 | # 128 | # 'preamble': '', 129 | 130 | # Latex figure (float) alignment 131 | # 132 | # 'figure_align': 'htbp', 133 | } 134 | 135 | # Grouping the document tree into LaTeX files. List of tuples 136 | # (source start file, target name, title, 137 | # author, documentclass [howto, manual, or own class]). 138 | latex_documents = [ 139 | (master_doc, 'cw-geodata.tex', 'cw-geodata Documentation', 140 | 'CosmiQ Works', 'manual'), 141 | ] 142 | 143 | 144 | # -- Options for manual page output ------------------------------------------ 145 | 146 | # One entry per manual page. List of tuples 147 | # (source start file, name, description, authors, manual section). 148 | man_pages = [ 149 | (master_doc, 'cw-geodata', 'cw-geodata Documentation', 150 | [author], 1) 151 | ] 152 | 153 | 154 | # -- Options for Texinfo output ---------------------------------------------- 155 | 156 | # Grouping the document tree into Texinfo files. List of tuples 157 | # (source start file, target name, title, author, 158 | # dir menu entry, description, category) 159 | texinfo_documents = [ 160 | (master_doc, 'cw-geodata', 'cw-geodata Documentation', 161 | author, 'cw-geodata', 'CosmiQ Works Tools for coversion between CV and GeoSpatial formats.', 162 | 'Miscellaneous'), 163 | ] 164 | 165 | 166 | # -- Options for Epub output ------------------------------------------------- 167 | 168 | # Bibliographic Dublin Core info. 169 | epub_title = project 170 | 171 | # The unique identifier of the text. This can be a ISBN number 172 | # or the project homepage. 173 | # 174 | # epub_identifier = '' 175 | 176 | # A unique identification for the text. 177 | # 178 | # epub_uid = '' 179 | 180 | # A list of files that should not be packed into the epub file. 181 | epub_exclude_files = ['search.html'] 182 | 183 | 184 | # -- Extension configuration ------------------------------------------------- 185 | 186 | # -- Options for intersphinx extension --------------------------------------- 187 | 188 | # Example configuration for intersphinx: refer to the Python standard library. 189 | intersphinx_mapping = intersphinx_mapping = { 190 | "python": ('https://docs.python.org/', None), 191 | "rasterio": ('https://rasterio.readthedocs.io/en/latest/', None), 192 | "pandas": ('http://pandas.pydata.org/pandas-docs/stable/', None), 193 | "geopandas": ('http://geopandas.org/', None), 194 | "rtree": ('http://toblerity.org/rtree/', None), 195 | "shapely": ('https://shapely.readthedocs.io/en/stable/', None), 196 | "networkx": ('https://networkx.github.io/documentation/stable/', None) 197 | } 198 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | CosmiQ Works Evaluation (`cw-geodata `__) Documentation 2 | ============================================================================================= 3 | :Author: `CosmiQ Works `__ 4 | :Release: |release| 5 | :Copyright: 2018, CosmiQ Works 6 | :License: This work is licensed under an `Apache 2.0 License`__. 7 | 8 | .. __: https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | .. toctree:: 11 | :name: mastertoc 12 | :maxdepth: 3 13 | :glob: 14 | 15 | api 16 | 17 | Indices and tables 18 | ------------------ 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: cw-geodata 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - python=3.6 7 | - pip 8 | - shapely 9 | - pandas 10 | - geopandas 11 | - numpy 12 | - scipy 13 | - scikit-image 14 | - rtree 15 | - networkx 16 | - rasterio 17 | - pip: 18 | - affine 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | version = '0.0.3' 3 | 4 | # Runtime requirements. 5 | inst_reqs = ["shapely", "rtree", "geopandas", "pandas", "networkx"] 6 | 7 | extra_reqs = { 8 | 'test': ['mock', 'pytest', 'pytest-cov', 'codecov']} 9 | 10 | setup(name='cw_geodata', 11 | version=version, 12 | description=u"""Geospatial raster and vector data processing for ML""", 13 | classifiers=[ 14 | 'Intended Audience :: Information Technology', 15 | 'Intended Audience :: Science/Research', 16 | 'License :: OSI Approved :: BSD License', 17 | 'Programming Language :: Python :: 3.6', 18 | 'Programming Language :: Python :: 2.7', 19 | 'Topic :: Scientific/Engineering :: GIS'], 20 | keywords='spacenet machinelearning gis geojson', 21 | author=u"Nicholas Weir", 22 | author_email='nweir@iqt.org', 23 | url='https://github.com/CosmiQ/cw-geodata', 24 | license='Apache-2.0', 25 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 26 | zip_safe=False, 27 | include_package_data=True, 28 | install_requires=inst_reqs, 29 | extras_require=extra_reqs, 30 | entry_points={} 31 | ) 32 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_imports.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class TestImports(object): 5 | def test_imports(self): 6 | from cw_geodata.utils import core, geo 7 | from cw_geodata.raster_image import image 8 | from cw_geodata.vector_label import polygon, graph, mask 9 | -------------------------------------------------------------------------------- /tests/test_raster_image/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/tests/test_raster_image/__init__.py -------------------------------------------------------------------------------- /tests/test_raster_image/test_image.py: -------------------------------------------------------------------------------- 1 | import os 2 | from cw_geodata.data import data_dir, sample_load_rasterio, sample_load_gdal 3 | from cw_geodata.raster_image.image import get_geo_transform 4 | from affine import Affine 5 | 6 | 7 | class TestGetGeoTransform(object): 8 | """Tests for cw_geodata.raster_image.image.get_geo_transform.""" 9 | 10 | def test_get_from_file(self): 11 | affine_obj = get_geo_transform(os.path.join(data_dir, 12 | 'sample_geotiff.tif')) 13 | assert affine_obj == Affine(0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0) 14 | 15 | def test_get_from_opened_raster(self): 16 | src_obj = sample_load_rasterio() 17 | affine_obj = get_geo_transform(src_obj) 18 | assert affine_obj == Affine(0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0) 19 | src_obj.close() 20 | 21 | def test_get_from_gdal(self): 22 | src_obj = sample_load_gdal() 23 | affine_obj = get_geo_transform(src_obj) 24 | assert affine_obj == Affine(0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0) 25 | -------------------------------------------------------------------------------- /tests/test_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/tests/test_utils/__init__.py -------------------------------------------------------------------------------- /tests/test_utils/test_core.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import geopandas as gpd 5 | import rasterio 6 | from cw_geodata.data import data_dir 7 | from cw_geodata.utils.core import _check_df_load, _check_gdf_load 8 | from cw_geodata.utils.core import _check_rasterio_im_load 9 | 10 | 11 | class TestLoadCheckers(object): 12 | """Test objects for checking loading of various objects.""" 13 | 14 | def test_unloaded_geojson(self): 15 | geojson_path = os.path.join(data_dir, 'sample.geojson') 16 | truth_gdf = gpd.read_file(geojson_path) 17 | test_gdf = _check_gdf_load(geojson_path) 18 | 19 | assert truth_gdf.equals(test_gdf) 20 | 21 | def test_loaded_geojson(self): 22 | geojson_path = os.path.join(data_dir, 'sample.geojson') 23 | truth_gdf = gpd.read_file(geojson_path) 24 | test_gdf = _check_gdf_load(truth_gdf.copy()) 25 | 26 | assert truth_gdf.equals(test_gdf) 27 | 28 | def test_unloaded_df(self): 29 | csv_path = os.path.join(data_dir, 'sample.csv') 30 | truth_df = pd.read_csv(csv_path) 31 | test_df = _check_df_load(csv_path) 32 | 33 | assert truth_df.equals(test_df) 34 | 35 | def test_loaded_df(self): 36 | csv_path = os.path.join(data_dir, 'sample.csv') 37 | truth_df = pd.read_csv(csv_path) 38 | test_df = _check_df_load(truth_df.copy()) 39 | 40 | assert truth_df.equals(test_df) 41 | 42 | def test_unloaded_image(self): 43 | im_path = os.path.join(data_dir, 'sample_geotiff.tif') 44 | truth_im = rasterio.open(im_path) 45 | test_im = _check_rasterio_im_load(im_path) 46 | 47 | assert truth_im.profile == test_im.profile 48 | assert np.array_equal(truth_im.read(1), test_im.read(1)) 49 | 50 | truth_im.close() # need to close the rasterio datasetreader objects 51 | test_im.close() 52 | 53 | def test_loaded_image(self): 54 | im_path = os.path.join(data_dir, 'sample_geotiff.tif') 55 | truth_im = rasterio.open(im_path) 56 | test_im = _check_rasterio_im_load(truth_im) 57 | 58 | assert truth_im.profile == test_im.profile 59 | assert np.array_equal(truth_im.read(1), test_im.read(1)) 60 | 61 | truth_im.close() # need to close the rasterio datasetreader objects 62 | test_im.close() 63 | -------------------------------------------------------------------------------- /tests/test_utils/test_geo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import geopandas as gpd 4 | import shapely 5 | from affine import Affine 6 | from shapely.wkt import loads 7 | from cw_geodata.data import data_dir 8 | from cw_geodata.utils.geo import list_to_affine, split_multi_geometries, \ 9 | geometries_internal_intersection 10 | 11 | 12 | class TestCoordTransformer(object): 13 | """Tests for the utils.geo.CoordTransformer.""" 14 | 15 | def test_convert_image_crs(self): 16 | pass 17 | 18 | 19 | class TestListToAffine(object): 20 | """Tests for utils.geo.list_to_affine().""" 21 | 22 | def test_rasterio_order_list(self): 23 | truth_affine_obj = Affine(0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0) 24 | affine_list = [0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0] 25 | test_affine = list_to_affine(affine_list) 26 | 27 | assert truth_affine_obj == test_affine 28 | 29 | def test_gdal_order_list(self): 30 | truth_affine_obj = Affine(0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0) 31 | gdal_affine_list = [733601.0, 0.5, 0.0, 3725139.0, 0.0, -0.5] 32 | test_affine = list_to_affine(gdal_affine_list) 33 | 34 | assert truth_affine_obj == test_affine 35 | 36 | 37 | class TestGeometriesInternalIntersection(object): 38 | """Tests for utils.geo.geometries_internal_intersection.""" 39 | 40 | def test_no_overlap(self): 41 | """Test creation of an overlap object with no intersection.""" 42 | poly_df = pd.read_csv(os.path.join(data_dir, 'sample.csv')) 43 | polygons = poly_df['PolygonWKT_Pix'].apply(loads).values 44 | preds = geometries_internal_intersection(polygons) 45 | # there's no overlap, so result should be an empty GeometryCollection 46 | assert preds == shapely.geometry.collection.GeometryCollection() 47 | 48 | def test_with_overlap(self): 49 | poly_df = pd.read_csv(os.path.join(data_dir, 'sample.csv')) 50 | # expand the polygons to generate some overlap 51 | polygons = poly_df['PolygonWKT_Pix'].apply( 52 | lambda x: loads(x).buffer(15)).values 53 | preds = geometries_internal_intersection(polygons) 54 | with open(os.path.join(data_dir, 'test_overlap_output.txt'), 'r') as f: 55 | truth = f.read() 56 | f.close() 57 | truth = loads(truth) 58 | # set a threshold for how good overlap with truth has to be in case of 59 | # rounding errors 60 | assert truth.intersection(preds).area/truth.area > 0.99 61 | 62 | 63 | class TestSplitMultiGeometries(object): 64 | """Test for splittling MultiPolygons.""" 65 | 66 | def test_simple_split_multipolygon(self): 67 | output = split_multi_geometries(os.path.join(data_dir, 68 | 'w_multipolygon.csv')) 69 | expected = gpd.read_file(os.path.join( 70 | data_dir, 'split_multi_result.json')).drop(columns='id') 71 | 72 | assert expected.equals(output) 73 | 74 | def test_grouped_split_multipolygon(self): 75 | output = split_multi_geometries( 76 | os.path.join(data_dir, 'w_multipolygon.csv'), obj_id_col='field_1', 77 | group_col='truncated') 78 | expected = gpd.read_file(os.path.join( 79 | data_dir, 'split_multi_grouped_result.json')).drop(columns='id') 80 | 81 | assert expected.equals(output) 82 | -------------------------------------------------------------------------------- /tests/test_vector_label/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CosmiQ/cw-geodata/19a290c20ec4672dc2e9be37f451d136166bb28a/tests/test_vector_label/__init__.py -------------------------------------------------------------------------------- /tests/test_vector_label/test_graph.py: -------------------------------------------------------------------------------- 1 | import os 2 | from cw_geodata.data import data_dir 3 | from cw_geodata.vector_label.graph import geojson_to_graph 4 | import pickle 5 | import networkx as nx 6 | 7 | 8 | class TestGeojsonToGraph(object): 9 | """Tests for cw_geodata.vector_label.graph.geojson_to_graph.""" 10 | 11 | def test_graph_creation(self): 12 | """Test if a newly created graph is identical to an existing one.""" 13 | with open(os.path.join(data_dir, 'sample_graph.pkl'), 'rb') as f: 14 | truth_graph = pickle.load(f) 15 | f.close() 16 | output_graph = geojson_to_graph(os.path.join(data_dir, 17 | 'sample_roads.geojson')) 18 | 19 | assert nx.is_isomorphic(truth_graph, output_graph) 20 | -------------------------------------------------------------------------------- /tests/test_vector_label/test_mask.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | from affine import Affine 5 | from shapely.geometry import Polygon 6 | import skimage 7 | import rasterio 8 | from cw_geodata.data import data_dir 9 | from cw_geodata.vector_label.mask import footprint_mask, boundary_mask, \ 10 | contact_mask, df_to_px_mask 11 | 12 | 13 | class TestFootprintMask(object): 14 | """Tests for cw_geodata.vector_label.mask.footprint_mask.""" 15 | 16 | def test_make_mask(self): 17 | """test creating a basic mask using a csv input.""" 18 | output_mask = footprint_mask(os.path.join(data_dir, 'sample.csv'), 19 | geom_col="PolygonWKT_Pix") 20 | truth_mask = skimage.io.imread(os.path.join(data_dir, 21 | 'sample_fp_mask.tif')) 22 | 23 | assert np.array_equal(output_mask, truth_mask) 24 | 25 | def test_make_mask_w_output_file(self): 26 | """test creating a basic mask and saving the output to a file.""" 27 | output_mask = footprint_mask( 28 | os.path.join(data_dir, 'sample.csv'), 29 | geom_col="PolygonWKT_Pix", 30 | reference_im=os.path.join(data_dir, "sample_geotiff.tif"), 31 | out_file=os.path.join(data_dir, 'test_out.tif') 32 | ) 33 | truth_mask = skimage.io.imread(os.path.join(data_dir, 34 | 'sample_fp_mask.tif')) 35 | saved_output_mask = skimage.io.imread(os.path.join(data_dir, 36 | 'test_out.tif')) 37 | 38 | assert np.array_equal(output_mask, truth_mask) 39 | assert np.array_equal(saved_output_mask, truth_mask) 40 | # clean up 41 | os.remove(os.path.join(data_dir, 'test_out.tif')) 42 | 43 | def test_make_mask_w_file_and_transform(self): 44 | """Test creating a mask using a geojson and an affine xform.""" 45 | output_mask = footprint_mask( 46 | os.path.join(data_dir, 'geotiff_labels.geojson'), 47 | reference_im=os.path.join(data_dir, 'sample_geotiff.tif'), 48 | do_transform=True, 49 | out_file=os.path.join(data_dir, 'test_out.tif') 50 | ) 51 | truth_mask = skimage.io.imread( 52 | os.path.join(data_dir, 'sample_fp_mask_from_geojson.tif') 53 | ) 54 | saved_output_mask = skimage.io.imread(os.path.join(data_dir, 55 | 'test_out.tif')) 56 | 57 | assert np.array_equal(output_mask, truth_mask) 58 | assert np.array_equal(saved_output_mask, truth_mask) 59 | # clean up 60 | os.remove(os.path.join(data_dir, 'test_out.tif')) 61 | 62 | 63 | class TestBoundaryMask(object): 64 | """Tests for cw_geodata.vector_label.mask.boundary_mask.""" 65 | 66 | def test_make_inner_mask_from_fp(self): 67 | """test creating a boundary mask using an existing footprint mask.""" 68 | fp_mask = skimage.io.imread(os.path.join(data_dir, 69 | 'sample_fp_mask.tif')) 70 | output_mask = boundary_mask(fp_mask) 71 | truth_mask = skimage.io.imread(os.path.join(data_dir, 72 | 'sample_b_mask_inner.tif')) 73 | 74 | assert np.array_equal(output_mask, truth_mask) 75 | 76 | def test_make_outer_mask_from_fp(self): 77 | """test creating a boundary mask using an existing footprint mask.""" 78 | fp_mask = skimage.io.imread(os.path.join(data_dir, 79 | 'sample_fp_mask.tif')) 80 | output_mask = boundary_mask(fp_mask, boundary_type="outer") 81 | truth_mask = skimage.io.imread(os.path.join(data_dir, 82 | 'sample_b_mask_outer.tif')) 83 | 84 | assert np.array_equal(output_mask, truth_mask) 85 | 86 | def test_make_thick_outer_mask_from_fp(self): 87 | """test creating a 10-px thick boundary mask.""" 88 | fp_mask = skimage.io.imread(os.path.join(data_dir, 89 | 'sample_fp_mask.tif')) 90 | output_mask = boundary_mask(fp_mask, boundary_type="outer", 91 | boundary_width=10) 92 | truth_mask = skimage.io.imread( 93 | os.path.join(data_dir, 'sample_b_mask_outer_10.tif') 94 | ) 95 | 96 | assert np.array_equal(output_mask, truth_mask) 97 | 98 | def test_make_binary_and_fp(self): 99 | """test creating a boundary mask and a fp mask together.""" 100 | output_mask = boundary_mask(df=os.path.join(data_dir, 'sample.csv'), 101 | geom_col="PolygonWKT_Pix") 102 | truth_mask = skimage.io.imread(os.path.join(data_dir, 103 | 'sample_b_mask_inner.tif')) 104 | 105 | assert np.array_equal(output_mask, truth_mask) 106 | 107 | 108 | class TestContactMask(object): 109 | """Tests for cw_geodata.vector_label.mask.contact_mask.""" 110 | 111 | def test_make_contact_mask_w_save(self): 112 | """test creating a contact point mask.""" 113 | output_mask = contact_mask( 114 | os.path.join(data_dir, 'sample.csv'), geom_col="PolygonWKT_Pix", 115 | contact_spacing=10, 116 | reference_im=os.path.join(data_dir, "sample_geotiff.tif"), 117 | out_file=os.path.join(data_dir, 'test_out.tif') 118 | ) 119 | truth_mask = skimage.io.imread(os.path.join(data_dir, 120 | 'sample_c_mask.tif')) 121 | saved_output_mask = skimage.io.imread(os.path.join(data_dir, 122 | 'test_out.tif')) 123 | 124 | assert np.array_equal(output_mask, truth_mask) 125 | assert np.array_equal(saved_output_mask, truth_mask) 126 | os.remove(os.path.join(data_dir, 'test_out.tif')) # clean up after 127 | 128 | 129 | class TestDFToPxMask(object): 130 | """Tests for cw_geodata.vector_label.mask.df_to_px_mask.""" 131 | 132 | def test_basic_footprint_w_save(self): 133 | output_mask = df_to_px_mask( 134 | os.path.join(data_dir, 'sample.csv'), 135 | geom_col='PolygonWKT_Pix', 136 | reference_im=os.path.join(data_dir, "sample_geotiff.tif"), 137 | out_file=os.path.join(data_dir, 'test_out.tif')) 138 | truth_mask = skimage.io.imread(os.path.join(data_dir, 139 | 'sample_fp_from_df2px.tif') 140 | ) 141 | saved_output_mask = skimage.io.imread(os.path.join(data_dir, 142 | 'test_out.tif')) 143 | 144 | assert np.array_equal(output_mask, truth_mask) 145 | assert np.array_equal(saved_output_mask, truth_mask[:, :, 0]) 146 | os.remove(os.path.join(data_dir, 'test_out.tif')) # clean up after 147 | 148 | def test_border_footprint_w_save(self): 149 | output_mask = df_to_px_mask( 150 | os.path.join(data_dir, 'sample.csv'), channels=['boundary'], 151 | geom_col='PolygonWKT_Pix', 152 | reference_im=os.path.join(data_dir, "sample_geotiff.tif"), 153 | out_file=os.path.join(data_dir, 'test_out.tif')) 154 | truth_mask = skimage.io.imread(os.path.join(data_dir, 155 | 'sample_b_from_df2px.tif') 156 | ) 157 | saved_output_mask = skimage.io.imread(os.path.join(data_dir, 158 | 'test_out.tif')) 159 | 160 | assert np.array_equal(output_mask, truth_mask) 161 | assert np.array_equal(saved_output_mask, truth_mask[:, :, 0]) 162 | os.remove(os.path.join(data_dir, 'test_out.tif')) # clean up after 163 | 164 | def test_contact_footprint_w_save(self): 165 | output_mask = df_to_px_mask( 166 | os.path.join(data_dir, 'sample.csv'), channels=['contact'], 167 | geom_col='PolygonWKT_Pix', 168 | reference_im=os.path.join(data_dir, "sample_geotiff.tif"), 169 | out_file=os.path.join(data_dir, 'test_out.tif')) 170 | truth_mask = skimage.io.imread(os.path.join(data_dir, 171 | 'sample_c_from_df2px.tif') 172 | ) 173 | saved_output_mask = skimage.io.imread(os.path.join(data_dir, 174 | 'test_out.tif')) 175 | 176 | assert np.array_equal(output_mask, truth_mask) 177 | assert np.array_equal(saved_output_mask, truth_mask[:, :, 0]) 178 | os.remove(os.path.join(data_dir, 'test_out.tif')) # clean up after 179 | 180 | def test_all_three_w_save(self): 181 | """Test creating a 3-channel mask.""" 182 | output_mask = df_to_px_mask( 183 | os.path.join(data_dir, 'sample.csv'), 184 | channels=['footprint', 'boundary', 'contact'], 185 | boundary_type='outer', boundary_width=5, contact_spacing=15, 186 | geom_col='PolygonWKT_Pix', 187 | reference_im=os.path.join(data_dir, "sample_geotiff.tif"), 188 | out_file=os.path.join(data_dir, 'test_out.tif')) 189 | truth_mask = skimage.io.imread( 190 | os.path.join(data_dir, 'sample_fbc_from_df2px.tif') 191 | ) 192 | saved_output_mask = skimage.io.imread(os.path.join(data_dir, 193 | 'test_out.tif')) 194 | 195 | assert np.array_equal(output_mask, truth_mask) 196 | assert np.array_equal(saved_output_mask, truth_mask) 197 | os.remove(os.path.join(data_dir, 'test_out.tif')) # clean up after 198 | -------------------------------------------------------------------------------- /tests/test_vector_label/test_polygon.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | from affine import Affine 4 | from shapely.geometry import Polygon 5 | from shapely.wkt import loads, dumps 6 | import geopandas as gpd 7 | import rasterio 8 | from cw_geodata.data import data_dir 9 | from cw_geodata.vector_label.polygon import convert_poly_coords, \ 10 | affine_transform_gdf, georegister_px_df, geojson_to_px_gdf 11 | 12 | square = Polygon([(10, 20), (10, 10), (20, 10), (20, 20)]) 13 | forward_result = loads("POLYGON ((733606 3725129, 733606 3725134, 733611 3725134, 733611 3725129, 733606 3725129))") 14 | reverse_result = loads("POLYGON ((-1467182 7450238, -1467182 7450258, -1467162 7450258, -1467162 7450238, -1467182 7450238))") 15 | # note that the xform below is the same as in cw_geodata/data/sample_geotiff.tif 16 | aff = Affine(0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0) 17 | affine_list = [0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0] 18 | long_affine_list = [0.5, 0.0, 733601.0, 0.0, -0.5, 3725139.0, 19 | 0.0, 0.0, 1.0] 20 | gdal_affine_list = [733601.0, 0.5, 0.0, 3725139.0, 0.0, -0.5] 21 | 22 | 23 | class TestConvertPolyCoords(object): 24 | """Test the convert_poly_coords functionality.""" 25 | 26 | def test_square_pass_affine(self): 27 | """Test both forward and inverse affine transforms when passed affine obj.""" 28 | xform_result = convert_poly_coords(square, affine_obj=aff) 29 | assert xform_result == forward_result 30 | rev_xform_result = convert_poly_coords(square, 31 | affine_obj=aff, 32 | inverse=True) 33 | assert rev_xform_result == reverse_result 34 | 35 | def test_square_pass_raster(self): 36 | """Test forward affine transform when passed a raster reference.""" 37 | raster_src = os.path.join(data_dir, 'sample_geotiff.tif') 38 | xform_result = convert_poly_coords(square, raster_src=raster_src) 39 | assert xform_result == forward_result 40 | 41 | def test_square_pass_list(self): 42 | """Test forward and reverse affine transform when passed a list.""" 43 | fwd_xform_result = convert_poly_coords(square, 44 | affine_obj=affine_list) 45 | assert fwd_xform_result == forward_result 46 | rev_xform_result = convert_poly_coords(square, 47 | affine_obj=affine_list, 48 | inverse=True) 49 | assert rev_xform_result == reverse_result 50 | 51 | def test_square_pass_gdal_list(self): 52 | """Test forward affine transform when passed a list in gdal order.""" 53 | fwd_xform_result = convert_poly_coords(square, 54 | affine_obj=gdal_affine_list 55 | ) 56 | assert fwd_xform_result == forward_result 57 | 58 | def test_square_pass_long_list(self): 59 | """Test forward affine transform when passed a full 9-element xform.""" 60 | fwd_xform_result = convert_poly_coords( 61 | square, affine_obj=long_affine_list 62 | ) 63 | assert fwd_xform_result == forward_result 64 | 65 | 66 | class TestAffineTransformGDF(object): 67 | """Test the affine_transform_gdf functionality.""" 68 | 69 | def test_transform_csv(self): 70 | truth_gdf = pd.read_csv(os.path.join(data_dir, 'aff_gdf_result.csv')) 71 | input_df = os.path.join(data_dir, 'sample.csv') 72 | output_gdf = affine_transform_gdf(input_df, aff, 73 | geom_col="PolygonWKT_Pix", 74 | precision=0) 75 | output_gdf['geometry'] = output_gdf['geometry'].apply(dumps, trim=True) 76 | assert output_gdf.equals(truth_gdf) 77 | 78 | 79 | class TestGeoregisterPxDF(object): 80 | """Test the georegister_px_df functionality.""" 81 | 82 | def test_transform_using_raster(self): 83 | input_df = os.path.join(data_dir, 'sample.csv') 84 | input_im = os.path.join(data_dir, 'sample_geotiff.tif') 85 | output_gdf = georegister_px_df(input_df, im_fname=input_im, 86 | geom_col='PolygonWKT_Pix', precision=0) 87 | truth_df = pd.read_csv(os.path.join(data_dir, 'aff_gdf_result.csv')) 88 | truth_df['geometry'] = truth_df['geometry'].apply(loads) 89 | truth_gdf = gpd.GeoDataFrame( 90 | truth_df, 91 | crs=rasterio.open(os.path.join(data_dir, 'sample_geotiff.tif')).crs 92 | ) 93 | 94 | assert truth_gdf.equals(output_gdf) 95 | 96 | def test_transform_using_aff_crs(self): 97 | input_df = os.path.join(data_dir, 'sample.csv') 98 | crs = rasterio.open(os.path.join(data_dir, 'sample_geotiff.tif')).crs 99 | output_gdf = georegister_px_df(input_df, affine_obj=aff, crs=crs, 100 | geom_col='PolygonWKT_Pix', precision=0) 101 | truth_df = pd.read_csv(os.path.join(data_dir, 'aff_gdf_result.csv')) 102 | truth_df['geometry'] = truth_df['geometry'].apply(loads) 103 | truth_gdf = gpd.GeoDataFrame( 104 | truth_df, 105 | crs=rasterio.open(os.path.join(data_dir, 'sample_geotiff.tif')).crs 106 | ) 107 | 108 | assert truth_gdf.equals(output_gdf) 109 | 110 | 111 | class TestGeojsonToPxGDF(object): 112 | """Tests for geojson_to_px_gdf.""" 113 | 114 | def test_transform_to_px_coords(self): 115 | output_gdf = geojson_to_px_gdf( 116 | os.path.join(data_dir, 'geotiff_labels.geojson'), 117 | os.path.join(data_dir, 'sample_geotiff.tif'), 118 | precision=0 119 | ) 120 | truth_gdf = gpd.read_file(os.path.join(data_dir, 121 | 'gj_to_px_result.geojson')) 122 | truth_subset = truth_gdf[['geometry']] 123 | output_subset = output_gdf[['geometry']].reset_index(drop=True) 124 | 125 | assert truth_subset.equals(output_subset) 126 | --------------------------------------------------------------------------------