├── .github └── workflows │ └── release_to_pypi.yml ├── .gitignore ├── .readthedocs.yml ├── ChangeLog.MD ├── LICENSE ├── README.md ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── about.rst │ ├── conf.py │ ├── getting_started.md │ ├── index.rst │ ├── license.md │ ├── pages │ ├── advance.md │ ├── basic.md │ ├── changelog.rst │ └── install.rst │ └── reference │ ├── geoTile.rst │ ├── geotile.rst │ └── modules.rst ├── geotile ├── GeoTile.py ├── __init__.py ├── __version__.py └── utils.py ├── requirements.txt └── setup.py /.github/workflows/release_to_pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish geoTile to PyPI / GitHub 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build-n-publish: 10 | name: Build and publish geoTile to PyPI 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout source 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: "3.x" 21 | 22 | - name: Build source and wheel distributions 23 | run: | 24 | python -m pip install --upgrade build twine 25 | python -m build 26 | twine check --strict dist/* 27 | - name: Publish distribution to PyPI 28 | uses: pypa/gh-action-pypi-publish@master 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.PYPI_API_TOKEN }} 32 | 33 | - name: Create GitHub Release 34 | id: create_release 35 | uses: actions/create-release@v1 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 38 | with: 39 | tag_name: ${{ github.ref }} 40 | release_name: ${{ github.ref }} 41 | draft: false 42 | prerelease: false 43 | 44 | - name: Get Asset name 45 | run: | 46 | export PKG=$(ls dist/ | grep tar) 47 | set -- $PKG 48 | echo "name=$1" >> $GITHUB_ENV 49 | - name: Upload Release Asset (sdist) to GitHub 50 | id: upload-release-asset 51 | uses: actions/upload-release-asset@v1 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | with: 55 | upload_url: ${{ steps.create_release.outputs.upload_url }} 56 | asset_path: dist/${{ env.name }} 57 | asset_name: ${{ env.name }} 58 | asset_content_type: application/zip 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # some custom gitignores 10 | test.ipynb 11 | data/ 12 | kamal/ 13 | .vscode/ 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | cover/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | .pybuilder/ 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | .pdm.toml 91 | 92 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 93 | __pypackages__/ 94 | 95 | # Celery stuff 96 | celerybeat-schedule 97 | celerybeat.pid 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Environments 103 | .env 104 | .venv 105 | env/ 106 | venv/ 107 | ENV/ 108 | env.bak/ 109 | venv.bak/ 110 | 111 | # Spyder project settings 112 | .spyderproject 113 | .spyproject 114 | 115 | # Rope project settings 116 | .ropeproject 117 | 118 | # mkdocs documentation 119 | /site 120 | 121 | # mypy 122 | .mypy_cache/ 123 | .dmypy.json 124 | dmypy.json 125 | 126 | # Pyre type checker 127 | .pyre/ 128 | 129 | # pytype static type analyzer 130 | .pytype/ 131 | 132 | # Cython debug symbols 133 | cython_debug/ 134 | 135 | # PyCharm 136 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 137 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 138 | # and can be added to the global gitignore or merged into this file. For a more nuclear 139 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 140 | #.idea/ -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.11" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/source/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | # formats: 27 | # - pdf 28 | # - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | python: 34 | install: 35 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /ChangeLog.MD: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, Tek Kshetri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.13737752.svg)](https://doi.org/10.5281/zenodo.13737752) 2 | [![Downloads](https://pepy.tech/badge/geotile)](https://pepy.tech/project/geotile) 3 | [![PyPI version](https://badge.fury.io/py/geotile.svg)](https://pypi.org/project/geotile/) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/iamtekson/geotile/blob/master/LICENSE) 5 | 6 | # GeoTile 7 | 8 | GeoTile is an open-source python library for creating and manipulating the tiles of the raster dataset. The package will be very useful for managing the raster tiles which can be used for deep learning traing dataset. 9 | 10 | ## Full documentation 11 | 12 | The complete documentation of this package is available here: https://geotile.readthedocs.io/en/latest/ 13 | 14 | ## Installation 15 | 16 | The easy installation of `geotile` is by using `conda` environment, 17 | 18 | ```shell 19 | conda install -c conda-forge geotile 20 | ``` 21 | 22 | If you want to install it for `pip`, check the documentation here: https://geotile.readthedocs.io/en/latest/pages/install.html 23 | 24 | ## Some basic examples 25 | 26 | Please check the complete documentation here: https://geotile.readthedocs.io/en/latest/ 27 | 28 | ```shell 29 | from geotile import GeoTile 30 | gt = GeoTile(r"path/to/raster/data.tif") 31 | 32 | # to generate the tiles of raster 33 | gt.generate_tiles(r'/path/to/output/folder', tile_x=256, tile_y=256, stride_x=256, stride_y=256) 34 | 35 | # to generate the tiles of selected bands only 36 | gt.generate_tiles(r'/path/to/output/folder', bands=[4, 3, 2], tile_x=256, tile_y=256, stride_x=256, stride_y=256) 37 | 38 | # to merge the tiles 39 | from geotile import mosaic 40 | mosaic('/path/to/input/folder/tiles', output_file='path/to/output/file.tif') 41 | 42 | # to generate the raster mask from shapefile 43 | gt.mask('/path/to/shapefile.shp', '/path/to/output/file.tif') 44 | 45 | # to rasterize the shapefile based on column value, 46 | gt.rasterization(input_vector='path/to/shp.shp', out_path='path/to/output.tif' value_col="value_col") 47 | 48 | # to close the file 49 | gt.close() 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/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 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_gallery 2 | myst_parser 3 | nbsphinx 4 | numpydoc 5 | sphinx_toggleprompt 6 | sphinx_rtd_theme 7 | pydata_sphinx_theme 8 | rasterio 9 | geopandas 10 | -------------------------------------------------------------------------------- /docs/source/about.rst: -------------------------------------------------------------------------------- 1 | About GeoTiles 2 | ============== 3 | 4 | GeoTile is an open source python library for creating and manipulating the tiles of the raster dataset. The package will be very useful for managing the raster tiles for the deep learning dataset. The package is written on top of rasterio package. 5 | 6 | Dependencies 7 | ------------ 8 | 9 | The library has the following dependencies: 10 | 11 | - gdal (http://www.gdal.org/) 12 | - numpy (http://www.numpy.org/) 13 | - geopandas (https://pypi.python.org/pypi/geopandas) 14 | - rasterio (https://pypi.python.org/pypi/rasterio) 15 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | import warnings 16 | sys.path.insert(0, os.path.abspath('../../')) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = 'geotile' 22 | copyright = '2022, Tek Kshetri' 23 | author = 'Tek Kshetri' 24 | 25 | # The full version, including alpha/beta/rc tags 26 | release = '0.1.0' 27 | 28 | 29 | # -- General configuration --------------------------------------------------- 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | "sphinx.ext.autosummary", 36 | "sphinx.ext.intersphinx", 37 | "sphinx.ext.autodoc", 38 | "myst_parser", 39 | "nbsphinx", 40 | "numpydoc", 41 | "sphinx_toggleprompt", 42 | ] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # List of patterns, relative to source directory, that match files and 48 | # directories to ignore when looking for source files. 49 | # This pattern also affects html_static_path and html_extra_path. 50 | exclude_patterns = [] 51 | 52 | 53 | # -- Options for HTML output ------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. See the documentation for 56 | # a list of builtin themes. 57 | # 58 | html_theme = 'sphinx_rtd_theme' 59 | 60 | # Add any paths that contain custom static files (such as style sheets) here, 61 | # relative to this directory. They are copied after the builtin static files, 62 | # so a file named "default.css" will overwrite the builtin "default.css". 63 | html_static_path = ['_static'] 64 | 65 | 66 | # -- Extra things ----------------------------------------------------------- 67 | # Fix issue with warnings from numpydoc (see discussion in PR #534) 68 | numpydoc_show_class_members = False 69 | 70 | autosummary_generate = True 71 | 72 | nbsphinx_execute = "always" 73 | nbsphinx_allow_errors = True 74 | nbsphinx_kernel_name = "python3" 75 | 76 | # autodoc_mock_imports = ["rasterio", 'geopandas'] 77 | 78 | # Theme options are theme-specific and customize the look and feel of a theme 79 | # further. For a list of options available for each theme, see the 80 | # documentation. 81 | 82 | # to add the edit page button 83 | html_context = { 84 | "github_user": "iamtekson", 85 | "github_repo": "geotile", 86 | "github_version": "main", 87 | "doc_path": "docs/source", 88 | } 89 | -------------------------------------------------------------------------------- /docs/source/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Installation 4 | 5 | GeoTile is written in pure python but some dependencies are written in C (Gdal, GEOS). These dependencies are not included in the source distribution of GeoTile. To install GeoTile, you need to install the dependencies first. 6 | 7 | The best and easy way to install the GeoTile is `conda` distribution: 8 | 9 | ```shell 10 | conda install -c conda-forge geotile 11 | ``` 12 | 13 | ### Detailed instruction 14 | 15 | Do you prefer `pip install` or installation from source? Or specific version? See [detailed instructions](pages/install). 16 | 17 | ## What now? 18 | 19 | - Now it is time to getting started with [basic function](pages/basic) 20 | - Explore the possibilities of `GeoTile` library, [advance docs](pages/advance) 21 | - If you are familier with basic function of GeoTile library, check the [API Reference](reference/modules) 22 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. GeoTile documentation master file, created by 2 | sphinx-quickstart on Tue Jun 28 10:49:49 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | GeoTiles Documentation 7 | ====================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: GeoTile Documentation 12 | :hidden: 13 | 14 | 15 | Home 16 | About 17 | Getting Started 18 | Installation 19 | Basic Use Case 20 | Advance Use Case in Deep Learning 21 | API reference 22 | Change Log 23 | License 24 | 25 | 26 | GeoTile is an open source python library for creating and manipulating the tiles of the raster dataset. It is based on the `Rasterio`_ library. 27 | 28 | .. _`Rasterio`: https://rasterio.readthedocs.io/en/latest/ 29 | 30 | Motivation 31 | ---------- 32 | 33 | The main motivation to write this library is to make it easier to create tiles of the raster dataset as a deep learning training dataset. 34 | Since the library is extended to the Rasterio package, it also supports some very useful raster analysis function like, reprojection of raster, resampling of raster, raster masking etc. 35 | 36 | Useful links 37 | ------------ 38 | 39 | - `Binary Installers (PyPI) `_ 40 | - `Source Repository (GitHub) `_ 41 | - `Issues & Ideas `_ 42 | 43 | .. automodule:: geotile 44 | :members: 45 | :noindex: 46 | 47 | 48 | Indices and tables 49 | ================== 50 | 51 | * :ref:`genindex` 52 | * :ref:`modindex` 53 | * :ref:`search` 54 | -------------------------------------------------------------------------------- /docs/source/license.md: -------------------------------------------------------------------------------- 1 | # MIT LICENSE 2 | 3 | Copyright (c) 2022, Tek Kshetri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/source/pages/advance.md: -------------------------------------------------------------------------------- 1 | # Advance Use Cases 2 | 3 | The main motivation of this library is to create the easy pipeline to handle the geospatial raster dataset in machine learning and deep learning projects. The library is able to create the required label imagery from the vector data as well as the tiles of the raster data. The library is also able to create the tiles of the predicted data and merge them into single tif file. The below are the some key features/use cases of the library, 4 | 5 | ## 1. Generate masks from vector data 6 | 7 | The library is able to create the labels or masks from the vector data. The vector data can be in any format such as geojson, shapefile, etc. The library is able to create the masks of the vector data in the raster format. The below is the example of creating the mask from the shapefile, 8 | 9 | ```python 10 | from geotile import GeoTile 11 | gt = GeoTile('/path/to/raster/file.tif') 12 | gt.rasterization('/path/to/vector/file.shp', output_folder='/path/to/output/file.tif') 13 | ``` 14 | 15 | If you didn't pass the `value_col` parameter, the library will create the mask of the vector data with binary values, i.e. 0 and 1. If you want to create the mask with the specific values, you can pass the `value_col` parameter as below, 16 | 17 | ```python 18 | from geotile import GeoTile 19 | gt = GeoTile('/path/to/raster/file.tif') 20 | gt.rasterization('/path/to/vector/file.shp', out_path='/path/to/output/file.tif', value_col='class') 21 | ``` 22 | 23 | Using the rasterization function, you don't need to worry about the metadata and extend of the output raster. The output raster will have the same metadata and extend as the input raster assigned in `gt` class. 24 | 25 | > **Note**: Many people confused with the `gt.mask()` function. The `gt.mask()` function is used to clip out the raster data by vector extend which is similar to [extract by mask](https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-analyst/extract-by-mask.htm) in ArcGIS. The `gt.rasterization()` function is used to create the mask of the vector data. 26 | 27 | ## 2. Generate tiles from both imagery and masks 28 | 29 | The library is able to create the tiles from both images and masks. The below is the example of creating the tiles from the image, 30 | 31 | ```python 32 | from geotile import GeoTile 33 | 34 | # create the tiles of the raster imagery 35 | gt_img = GeoTile('/path/to/raster/file.tif') 36 | gt_img.generate_tiles(output_folder='/path/to/output/folder') 37 | 38 | # create the tiles of the raster mask 39 | gt_mask = GeoTile('/path/to/raster/file.tif') 40 | gt_mask.generate_tiles(output_folder='/path/to/output/folder') 41 | ``` 42 | 43 | If you don't want to save the tiles, you have to pass the `save_tiles=False` parameter in the `generate_tiles()` function. In this case, the raster values will be stored in the `gt_img.tile_data` and `gt_mask.tile_data` variables. 44 | The below is the example of creating the tiles without saving them, 45 | 46 | ```python 47 | from geotile import GeoTile 48 | 49 | # create the tiles of the raster imagery 50 | gt_img = GeoTile('/path/to/raster/file.tif') 51 | gt_img.generate_tiles(save_tiles=False) 52 | 53 | # create the tiles of the raster mask 54 | gt_mask = GeoTile('/path/to/raster/file.tif') 55 | gt_mask.generate_tiles(save_tiles=False) 56 | ``` 57 | 58 | ### Preprocess tiles before saving 59 | 60 | If you want to preprocess (such as normalization, removing nan, etc.) the tiles before saving, you can do it as below, 61 | 62 | ```python 63 | # generate the tiles 64 | gt_img.generate_tiles(save_tiles=False) 65 | gt_img.convert_nan_to_zero() 66 | gt_img.normalize_tiles() 67 | 68 | # save the tiles in numpy format; shape of the array will be (num_tiles, tile_x, tile_y, bands) 69 | gt_img.save_numpy(output_folder=r'/path/to/output/folder') 70 | 71 | # save the tiles in tif format 72 | gt_img.save_tiles(output_folder=r'/path/to/output/folder', prefix='tile_') 73 | ``` 74 | 75 | If your main goal is to train the deep learning model, you can only save the tiles as a numpy array and ignore saving tiles as a tif file. The numpy array will be more efficient and faster to load in the deep learning model. 76 | 77 | ## 3. Model prediction on tiles and merge them into single tif file 78 | 79 | You can now use your deep learning model to predict the tiles. The below is the example of predicting the tiles, 80 | 81 | ```python 82 | from geotile import GeoTile 83 | from geotile import mosaic 84 | 85 | # create the tiles of the raster imagery 86 | gt_img = GeoTile('/path/to/raster/file.tif') 87 | gt_img.generate_tiles(save_tiles=False) 88 | 89 | # predict the tiles 90 | pred = model.predict(gt_img.tile_data) 91 | 92 | # you can do the post processing such as thresholding, changing datatype etc on the predicted tiles as well. After that you can assign the predicted tile values to the gt class and save georeferenced tile as below, 93 | gt_img.tile_data = pred 94 | gt_img.save_tiles(output_folder='/path/to/output/folder', prefix='pred_') 95 | 96 | # merge the predicted tiles into single tif file 97 | mosaic('/path/to/output/folder', '/path/to/output/file.tif') 98 | ``` 99 | 100 | ## Discussion on other advance operations 101 | 102 | Some of the advance operations were discussing in following issues, please check them out for more details, 103 | 104 | 1. [Issue #18: Merge/mosaic predicted tiles into single tif file](https://github.com/iamtekson/geotile/issues/18) 105 | 2. [Issue #15: Data augmentation technique](https://github.com/iamtekson/geotile/issues/18) 106 | -------------------------------------------------------------------------------- /docs/source/pages/basic.md: -------------------------------------------------------------------------------- 1 | # Getting Started With Basic Functions 2 | 3 | Now it is time to initialize the library and work with your raster data. First of all, you need to import the library and initialize the library your raster file as below, 4 | 5 | ```python 6 | from geotile import GeoTile 7 | gt = GeoTile('/path/to/raster/file.tif') 8 | ``` 9 | 10 | After initializing the `GeoTile` class, the library will be able to read the file. Now you have access to use the `GeoTile` methods. 11 | 12 | ## Create the raster tiles 13 | 14 | Lets create the tiles of our raster data, 15 | 16 | ```python 17 | gt.generate_tiles(output_folder=r'/path/to/output/folder', prefix='_img') 18 | ``` 19 | 20 | By default, the above function will create the tiles having square shape of `256` and overlapping of `128`. But you can overwrite the shape as your requirements, 21 | 22 | ```python 23 | # to generate the tiles having no overlapping 24 | gt.generate_tiles(output_folder=r'/path/to/output/folder', tile_x=512, tile_y=512, stride_x=512, stride_y=512) 25 | ``` 26 | 27 | Also, if you are working with multi-band dataset, it will help you to create the tiles with only specific bands as well, 28 | 29 | ```python 30 | # only band 3,2,1 will be included in the output tiles 31 | gt.generate_tiles(output_folder=r'/path/to/output/folder', bands=[3,2,1], tile_x=512, tile_y=512, stride_x=512, stride_y=512) 32 | ``` 33 | 34 | If you don't want to save tiles on the disk, you can get the tiles as numpy array as well, 35 | 36 | ```python 37 | gt.generate_tiles(save_tiles=False) 38 | 39 | # get the tiles as numpy array; The shape of the array will be (num_tiles, tile_x, tile_y, bands) 40 | gt.save_numpy(output_folder=r'/path/to/output/folder') 41 | ``` 42 | 43 | ### Preprocess tiles before saving 44 | 45 | If you want to preprocess (such as normalization, suffling, etc.) the tiles before saving, you can do it as below, 46 | 47 | ```python 48 | # generate the tiles 49 | gt.generate_tiles(save_tiles=False) 50 | 51 | # suffle tiles 52 | gt.suffel_tiles() 53 | 54 | # normalize tiles 55 | gt.normalize_tiles() 56 | 57 | # convert nan to zero 58 | gt.convert_nan_to_zero() 59 | 60 | # save the tiles in numpy format; shape of the array will be (num_tiles, tile_x, tile_y, bands) 61 | gt.save_numpy(output_folder=r'/path/to/output/folder') 62 | 63 | # save the tiles in tif format 64 | gt.save_tiles(output_folder=r'/path/to/output/folder', prefix='tile_') 65 | ``` 66 | 67 | ## Merge tiles 68 | 69 | To create the combined image with tiled images, the `mosaic` function will be useful, 70 | 71 | ```python 72 | from geotile import mosaic 73 | mosaic('/path/to/input/folder', '/path/to/output/file.tif') 74 | ``` 75 | 76 | **Note: ** This function will be valid only for the georeferenced rasters. 77 | 78 | ## Generate mask from shapefile 79 | 80 | To generate the raster mask from shapefile, you need to initialize the GeoTile class first, 81 | 82 | ```python 83 | from geotile import GeoTile 84 | gt = GeoTile('/path/to/raster/file.tif') 85 | 86 | # generate shapefile mask 87 | gt.mask('/path/to/shapefile.shp', '/path/to/output/file.tif') 88 | ``` 89 | 90 | The output raster will have simillar metadata as the input raster. 91 | 92 | > **Note:** The above function will generate the raster mask from shapefile. Which is similar to [extract by mask](https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-analyst/extract-by-mask.htm) function in ArcGIS. If you want to make the mask raster as binary, you have to use `gt.rasterization()` function. 93 | 94 | ## Rasterization of shapefile 95 | 96 | To create the raster surface from the shapefile, you can do following thin. If value_col is None, the rasterization will be binary otherwise the rasterization will be the based on value of the column 97 | 98 | ```python 99 | gt.rasterization(input_vector='path/to/shp.shp', out_path='path/to/output.tif' value_col=None) 100 | ``` 101 | 102 | > The generated raster will have same meta information as raster used in `GeoTile` class. 103 | 104 | ## Close the raster file 105 | 106 | ```python 107 | gt.close() 108 | ``` 109 | 110 | ## More functions 111 | 112 | Some of the other functionalities of this packages are as below, 113 | 114 | ```python 115 | # reprojection of raster 116 | gt.reprojection(out_path='path/to/output.tif', out_crs='EPSG:4326', resampling_method='nearest') 117 | 118 | #resampling of raster 119 | gt.resample('/path/to/output/file.tif', upscale_factor=2, resampling_method='bilinear') 120 | ``` 121 | -------------------------------------------------------------------------------- /docs/source/pages/changelog.rst: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | .. include:: ../../../ChangeLog.MD -------------------------------------------------------------------------------- /docs/source/pages/install.rst: -------------------------------------------------------------------------------- 1 | GeoTile Installation 2 | ==================== 3 | 4 | Conda Installation 5 | ------------------ 6 | 7 | The easy and best way to install the GeoTile library is using ``conda`` distribution, 8 | 9 | .. code-block:: python 10 | 11 | conda install -c conda-forge geotile 12 | 13 | Pip Installation 14 | ---------------- 15 | 16 | To install the package with pip installation, you have to install its dependencies first. Following packages are the dependencies of GeoTile: 17 | 18 | - gdal (http://www.gdal.org/) 19 | - numpy (http://www.numpy.org/) 20 | - geopandas (https://pypi.python.org/pypi/geopandas) 21 | - rasterio (https://pypi.python.org/pypi/rasterio) 22 | 23 | Windows Pip Installation 24 | ^^^^^^^^^^^^^^^^^^^^^^^^ 25 | 26 | In windows, the dependencies can be install using ``pipwin`` command, 27 | 28 | .. code:: shell 29 | 30 | pip install pipwin 31 | pipwin install gdal numpy geopandas rasterio 32 | 33 | 34 | Now you can install the library using pip install command, 35 | 36 | .. code:: shell 37 | 38 | pip install geotile 39 | 40 | 41 | Linux Installation 42 | ^^^^^^^^^^^^^^^^^^ 43 | 44 | In Debian/Ubuntu, the dependencies can be install using ``apt-get`` command, 45 | 46 | .. code:: shell 47 | 48 | sudo add-apt-repository ppa:ubuntugis/ppa 49 | sudo apt update -y; sudo apt upgrade -y; 50 | sudo apt install gdal-bin libgdal-dev 51 | pip3 install pygdal=="`gdal-config --version`.*" 52 | 53 | 54 | More Instruction for Dependencies Installation 55 | ---------------------------------------------- 56 | 57 | The following links are the instructions for installing the dependencies of GeoTile: 58 | 59 | 1. `GeoPandas installation `_ 60 | 2. `Rasterio Installation `_ 61 | 62 | -------------------------------------------------------------------------------- /docs/source/reference/geoTile.rst: -------------------------------------------------------------------------------- 1 | geotile package 2 | =============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | geotile.GeoTile module 8 | ---------------------- 9 | 10 | .. automodule:: geotile.GeoTile 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | geotile.utils module 16 | -------------------- 17 | 18 | .. automodule:: geotile.utils 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: geotile 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/source/reference/geotile.rst: -------------------------------------------------------------------------------- 1 | geotile package 2 | =============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | geotile.GeoTile module 8 | ---------------------- 9 | 10 | .. automodule:: geotile.GeoTile 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | geotile.utils module 16 | -------------------- 17 | 18 | .. automodule:: geotile.utils 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: geotile 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/source/reference/modules.rst: -------------------------------------------------------------------------------- 1 | geotile 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | geotile 8 | -------------------------------------------------------------------------------- /geotile/GeoTile.py: -------------------------------------------------------------------------------- 1 | # inbuilt libraries 2 | import os 3 | import itertools 4 | from typing import Optional 5 | import pathlib 6 | import math 7 | 8 | # numpy library 9 | import numpy as np 10 | 11 | # rasterio library 12 | import rasterio as rio 13 | from rasterio import windows 14 | from rasterio.merge import merge 15 | from rasterio.mask import mask 16 | from rasterio.warp import calculate_default_transform, reproject 17 | from rasterio.enums import Resampling 18 | from rasterio.features import rasterize 19 | from rasterio.transform import Affine 20 | 21 | # geopandas library 22 | import geopandas as gpd 23 | 24 | # to check if the input raster dtype is int based or float based 25 | _int_dtypes = ["uint8", "uint16", "uint32", "int8", "int16", "int32"] 26 | 27 | 28 | class GeoTile: 29 | """GeoTile class 30 | 31 | Attributes 32 | ---------- 33 | path : str, python path 34 | Path to the raster file (str) 35 | ds : rasterio.DatasetReader, object 36 | Raster dataset 37 | meta : dict 38 | Raster metadata 39 | height : int 40 | Raster height 41 | width : int 42 | Raster width 43 | crs : str 44 | Raster crs (e.g. 'EPSG:4326'). The will be generated automatically. 45 | In case of non-geographic raster, the crs will be None. 46 | stride_x : int 47 | The stride of the x axis (int), default is 128 48 | stride_y : int 49 | The stride of the y axis (int), default is 128 50 | tile_x : int 51 | The size of the tile in x axis (int), default is 256 52 | tile_y : int 53 | The size of the tile in y axis (int), default is 256 54 | """ 55 | 56 | def __init__(self, path): 57 | """ 58 | Read the raster file 59 | 60 | Parameters 61 | ---------- 62 | path: the path of the raster file 63 | 64 | Returns 65 | ------- 66 | None: Read raster and assign metadata of raster to the class 67 | """ 68 | self._read_raster(path) 69 | 70 | def __del__(self): 71 | self.ds.close() 72 | 73 | def _read_raster(self, path): 74 | """Read the raster file 75 | 76 | Parameters 77 | ---------- 78 | path: the path of the raster file 79 | 80 | Returns 81 | ------- 82 | None: Read raster and assign metadata of raster to the class 83 | """ 84 | self.path = path 85 | self.ds = rio.open(path) 86 | self.meta = self.ds.meta 87 | self.height = self.meta["height"] 88 | self.width = self.meta["width"] 89 | self.meta["crs"] = self.ds.crs 90 | 91 | def _calculate_offset( 92 | self, stride_x: Optional[int] = None, stride_y: Optional[int] = None 93 | ) -> tuple: 94 | """Calculate the offset for the whole dataset 95 | 96 | Parameters 97 | ---------- 98 | tile_x: the size of the tile in x axis 99 | tile_y: the size of the tile in y axis 100 | stride_x: the stride of the x axis 101 | stride_y: the stride of the y axis 102 | 103 | Returns 104 | ------- 105 | tuple: (offset_x, offset_y) 106 | """ 107 | self.stride_x = stride_x 108 | self.stride_y = stride_y 109 | 110 | # offset x and y values calculation 111 | X = [x for x in range(0, self.width, stride_x)] 112 | Y = [y for y in range(0, self.height, stride_y)] 113 | offsets = list(itertools.product(X, Y)) 114 | self.offsets = offsets 115 | 116 | def _windows_transform_to_affine(self, window_transform: Optional[tuple]): 117 | """Convert the window transform to affine transform 118 | 119 | Parameters 120 | ---------- 121 | window_transform: tuple 122 | tuple of window transform 123 | 124 | Returns 125 | ------- 126 | tuple: tuple of affine transform 127 | """ 128 | a, b, c, d, e, f, _, _, _ = window_transform 129 | return Affine(a, b, c, d, e, f) 130 | 131 | def _match_nodata(self, dtype): 132 | """Match the nodata value with the dtype 133 | 134 | Parameters 135 | ---------- 136 | dtype: str 137 | The dtype of the raster 138 | 139 | Returns 140 | ------- 141 | int: The nodata value of the raster 142 | 143 | Examples 144 | -------- 145 | >>> from geotile import GeoTile 146 | >>> gt = GeoTile('/path/to/raster/file.tif') 147 | >>> gt._match_nodata('uint8') 148 | 255 149 | """ 150 | dtypes = { 151 | "uint8": 255, 152 | "uint16": 65535, 153 | "uint32": 4294967295, 154 | "int8": -128, 155 | "int16": -32768, 156 | "int32": -2147483648, 157 | "float32": np.nan, 158 | "float64": np.nan, 159 | } 160 | 161 | return dtypes[dtype] if dtype in dtypes.keys() else None 162 | 163 | def shuffle_tiles(self, random_state: Optional[int] = None): 164 | """Shuffle the tiles 165 | 166 | Parameters 167 | ---------- 168 | random_state: int 169 | Random state for shuffling the tiles 170 | 171 | Returns 172 | ------- 173 | None: Shuffle the tiles. The offsets will be shuffled in place 174 | 175 | Examples 176 | -------- 177 | >>> from geotile import GeoTile 178 | >>> gt = GeoTile('/path/to/raster/file.tif') 179 | >>> gt.shuffle_tiles() 180 | """ 181 | # check if random_state is not None 182 | if random_state is not None: 183 | self.random_state = random_state 184 | np.random.seed(self.random_state) 185 | 186 | assert ( 187 | len(self.offsets) == len(self.tile_data) == len(self.window_transform) 188 | ), "The number of offsets and window data should be same" 189 | 190 | # shuffle the offsets and window data 191 | p = np.random.permutation(len(self.offsets)) 192 | self.offsets = np.array(self.offsets)[p] 193 | self.tile_data = np.array(self.tile_data)[p] 194 | self.window_transform = np.array(self.window_transform)[p] 195 | 196 | def tile_info(self): 197 | """Get the information of the tiles 198 | 199 | Returns 200 | ------- 201 | dict: (tile_x, tile_y, stride_x, stride_y) 202 | 203 | Examples 204 | -------- 205 | >>> from geotile import GeoTile 206 | >>> gt = GeoTile('/path/to/raster/file.tif') 207 | >>> gt.tile_info() 208 | {'tile_x': 256, 'tile_y': 256, 'stride_x': 128, 'stride_y': 128} 209 | """ 210 | return { 211 | "tile_x": self.tile_x, 212 | "tile_y": self.tile_y, 213 | "stride_x": self.stride_x, 214 | "stride_y": self.stride_y, 215 | } 216 | 217 | def get_dtype(self, data_array: np.ndarray): 218 | """Get the appropriate dtype for the data array 219 | 220 | Parameters 221 | ---------- 222 | data_array: np.ndarray 223 | The data array for which the dtype will be calculated 224 | 225 | Returns 226 | ------- 227 | str: The appropriate dtype for the data array 228 | 229 | Examples 230 | -------- 231 | >>> from geotile import GeoTile 232 | >>> gt = GeoTile('/path/to/raster/file.tif') 233 | >>> data_array = np.array([1, 2, 3, 4]) 234 | >>> gt.get_dtype(data_array) 235 | 'uint8' 236 | """ 237 | if isinstance(data_array, np.ndarray): 238 | return str(data_array.dtype) 239 | 240 | else: 241 | return "Input is not a NumPy array." 242 | 243 | def generate_tiles( 244 | self, 245 | output_folder: Optional[str] = "tiles", 246 | suffix: Optional[str] = None, 247 | prefix: Optional[str] = None, 248 | save_tiles: Optional[bool] = True, 249 | save_transform: Optional[bool] = False, 250 | out_bands: Optional[list] = None, 251 | image_format: Optional[str] = None, 252 | dtype: Optional[str] = None, 253 | tile_x: Optional[int] = 256, 254 | tile_y: Optional[int] = 256, 255 | stride_x: Optional[int] = 128, 256 | stride_y: Optional[int] = 128, 257 | ): 258 | """ 259 | Save the tiles to the output folder 260 | 261 | Parameters 262 | ---------- 263 | output_folder : str 264 | Path to the output folder 265 | save_tiles : bool 266 | If True, the tiles will be saved to the output folder else the tiles will be stored in the class 267 | save_transform : bool 268 | If True, the transform will be saved to the output folder in txt file else it will only generate the tiles 269 | suffix : str 270 | The suffix of the tile name (eg. _img) 271 | prefix : str 272 | The prefix of the tile name (eg. img_) 273 | out_bands : list 274 | The bands to save (eg. [3, 2, 1]), if None, the output bands will be same as the input raster bands 275 | image_format : str 276 | The image format (eg. tif), if None, the image format will be the same as the input raster format (eg. tif) 277 | dtype : str, np.dtype 278 | The output dtype (eg. uint8, float32), if None, the dtype will be the same as the input raster 279 | tile_x: int 280 | The size of the tile in x axis, Default value is 256 281 | tile_y: int 282 | The size of the tile in y axis, Default value is 256 283 | stride_x: int 284 | The stride of the x axis, Default value is 128 (1/2 overalapping) 285 | If you want to ignore the overlap, keep it same as tile_x 286 | stride_y: int 287 | The stride of the y axis, Default value is 128 (1/2 overalapping) 288 | If you want to ignore the overlap, keep it same as tile_y 289 | 290 | Returns 291 | ------- 292 | None: save the tiles to the output folder 293 | 294 | Examples 295 | -------- 296 | >>> from geotile import GeoTile 297 | >>> gt = GeoTile('/path/to/raster/file.tif') 298 | >>> gt.generate_raster_tiles('/path/to/output/folder', prefix='img_') 299 | # save the specific bands with other than default size 300 | >>> gt.generate_raster_tiles('/path/to/output/folder', [3, 2, 1], tile_x=512, tile_y=512, stride_x=512, stride_y=512) 301 | """ 302 | 303 | self.tile_x = tile_x 304 | self.tile_y = tile_y 305 | self.stride_x = stride_x 306 | self.stride_y = stride_y 307 | 308 | # create the output folder if it doesn't exist 309 | if not os.path.exists(output_folder) and save_tiles is True: 310 | os.makedirs(output_folder) 311 | 312 | # offset calculation 313 | self._calculate_offset(self.stride_x, self.stride_y) 314 | 315 | # store all the windows data as a list, windows shape: (band, tile_y, tile_x) 316 | self.tile_data = [] 317 | 318 | # store all the transform data as a list 319 | self.window_transform = [] 320 | 321 | # iterate through the offsets and save the tiles 322 | for i, (col_off, row_off) in enumerate(self.offsets): 323 | window = windows.Window( 324 | col_off=col_off, row_off=row_off, width=self.tile_x, height=self.tile_y 325 | ) 326 | transform = windows.transform(window, self.ds.transform) 327 | 328 | # convert the window transform to affine transform and append to the list 329 | transform = self._windows_transform_to_affine(transform) 330 | self.window_transform.append(transform) 331 | 332 | # copy the meta data 333 | meta = self.ds.meta.copy() 334 | nodata = meta["nodata"] 335 | 336 | # update the meta data 337 | meta.update( 338 | {"width": window.width, "height": window.height, "transform": transform} 339 | ) 340 | 341 | # if the output bands is not None, add all bands to the output dataset 342 | # out_bands starts from i+1 because rasterio bands start from 1 343 | if out_bands is None: 344 | out_bands = [i + 1 for i in range(0, self.ds.count)] 345 | 346 | else: 347 | meta.update({"count": len(out_bands)}) 348 | 349 | # read the window data and append to the list 350 | single_tile_data = self.ds.read( 351 | out_bands, window=window, fill_value=nodata, boundless=True 352 | ) 353 | self.tile_data.append(single_tile_data) 354 | 355 | # if data_type, update the meta 356 | if dtype: 357 | meta.update({"dtype": dtype}) 358 | 359 | else: 360 | dtype = self.ds.meta["dtype"] 361 | 362 | if save_tiles: 363 | # check if image_format is None 364 | image_format = image_format or pathlib.Path(self.path).suffix 365 | 366 | # create the output folder if it doesn't exist 367 | if not os.path.exists(output_folder): 368 | os.makedirs(output_folder) 369 | 370 | # Set default values for suffix and prefix if they are None 371 | suffix = suffix or "" 372 | prefix = prefix or "" 373 | 374 | # Construct the tile name and path 375 | tile_name = ( 376 | f"{prefix}{str(i)}{suffix}{image_format}" 377 | if suffix or prefix 378 | else f"tile_{col_off}_{row_off}{image_format}" 379 | ) 380 | 381 | tile_path = os.path.join(output_folder, tile_name) 382 | 383 | if save_transform: 384 | # tile georeference data to reconstruct spatial location from output inference bboxes 385 | geo_reference_tile_worldfile = ( 386 | f"{prefix}{str(i)}{suffix}.txt" 387 | if suffix or prefix 388 | else f"tile_{col_off}_{row_off}.txt" 389 | ) 390 | 391 | geo_reference_tile_worldfile_path = os.path.join( 392 | output_folder, geo_reference_tile_worldfile 393 | ) 394 | 395 | crs = meta["crs"] 396 | crs = crs.to_proj4() 397 | 398 | # raster affine transform information 399 | with open(geo_reference_tile_worldfile_path, "w") as f: 400 | f.write(str(transform.to_gdal())) 401 | f.write("\n") 402 | f.write(crs) 403 | 404 | # save the tiles with new metadata 405 | with rio.open(tile_path, "w", **meta) as outds: 406 | outds.write( 407 | self.ds.read( 408 | out_bands, window=window, fill_value=nodata, boundless=True 409 | ).astype(dtype) 410 | ) 411 | 412 | if not save_tiles: 413 | # convert list to numpy array 414 | self.tile_data = np.array(self.tile_data) 415 | 416 | # dtype conversion 417 | self.tile_data = self.tile_data.astype(dtype) 418 | 419 | # move axis to (n, tile_y, tile_x, band) 420 | self.tile_data = np.moveaxis(self.tile_data, 1, -1) 421 | 422 | def save_tiles( 423 | self, 424 | output_folder: str, 425 | prefix: Optional[str] = None, 426 | suffix: Optional[str] = None, 427 | image_format: Optional[str] = None, 428 | nodata: Optional[int] = None, 429 | dtype: Optional[str] = None, 430 | ): 431 | """Save the tiles to the output folder 432 | 433 | Parameters 434 | ---------- 435 | output_folder : str 436 | Path to the output folder 437 | prefix : str 438 | The prefix of the tile name (eg. img_) 439 | suffix : str 440 | The suffix of the tile name (eg. _img) 441 | image_format : str 442 | The image format (eg. tif), if None, the image format will be the same as the input raster format (eg. tif) 443 | nodata : int, float 444 | The nodata value of the raster, if None, the nodata value will be assigned based on the dtype (either max or min value) 445 | dtype : str, np.dtype 446 | The output dtype (eg. uint8, float32), if None, the dtype will be the same as the input raster 447 | 448 | Returns 449 | ------- 450 | None: save the tiles to the output folder 451 | 452 | Examples 453 | -------- 454 | >>> from geotile import GeoTile 455 | >>> gt = GeoTile('/path/to/raster/file.tif') 456 | >>> gt.save_tiles('/path/to/output/folder', prefix='img_') 457 | """ 458 | # create the output folder if it doesn't exist 459 | if not os.path.exists(output_folder): 460 | os.makedirs(output_folder) 461 | 462 | # meta data 463 | meta = self.meta.copy() 464 | # nodata = meta['nodata'] 465 | meta.update( 466 | { 467 | "width": self.tile_x, 468 | "height": self.tile_y, 469 | } 470 | ) 471 | 472 | # check if image_format is None 473 | image_format = image_format or pathlib.Path(self.path).suffix 474 | 475 | # Set default values for suffix and prefix if they are None 476 | suffix = suffix or "" 477 | prefix = prefix or "" 478 | 479 | # if data_type, update the meta 480 | meta.update({"dtype": dtype or self.get_dtype(self.tile_data)}) 481 | 482 | # if nodata is None, update the meta with appropriate nodata value 483 | meta.update({"nodata": nodata or self._match_nodata(meta["dtype"])}) 484 | 485 | # first check nodata is not None and nodata is instance of numeric value; issue #64 486 | if meta["nodata"] is not None and isinstance(meta["nodata"], (int, float)): 487 | # solve nodata related issue #56 488 | if (np.isnan(meta["nodata"])) and (meta["dtype"] in _int_dtypes): 489 | meta.update({"nodata": None}) 490 | 491 | # iterate through the offsets and windows_data and save the tiles 492 | for i, ((col_off, row_off), wd, wt) in enumerate( 493 | zip(self.offsets, self.tile_data, self.window_transform) 494 | ): 495 | # update meta data with transform 496 | meta.update({"transform": tuple(wt)}) 497 | 498 | # Construct the tile name and path 499 | tile_name = ( 500 | f"{prefix}{str(i)}{suffix}{image_format}" 501 | if suffix or prefix 502 | else f"tile_{col_off}_{row_off}{image_format}" 503 | ) 504 | 505 | tile_path = os.path.join(output_folder, tile_name) 506 | 507 | # move axis to (band, tile_y, tile_x) 508 | wd = np.moveaxis(wd, -1, 0) 509 | 510 | # update the meta with number of bands 511 | meta.update({"count": wd.shape[0]}) 512 | 513 | # save the tiles with new metadata 514 | with rio.open(tile_path, "w", **meta) as outds: 515 | outds.write(wd.astype(meta["dtype"])) 516 | 517 | def merge_tiles( 518 | self, 519 | output_path: str, 520 | image_format: Optional[str] = None, 521 | dtype: Optional[str] = None, 522 | ): 523 | """Merge the tiles and save the merged raster. 524 | Make sure the tiles are generated before merging and all the tiles having similar properties (eg. dtype, crs, transform) 525 | 526 | Parameters 527 | ---------- 528 | output_path : str 529 | Path to the output raster 530 | image_format : str 531 | The image format (eg. tif), if None, the image format will be the same as the input raster format (eg. tif) 532 | dtype : str, np.dtype 533 | The output dtype (eg. uint8, float32), if None, the dtype will be the same as the input raster 534 | meta: dict 535 | The metadata of the output raster. 536 | If provided, the output raster will be created with the provided metadata 537 | else the output raster will be created with the metadata of the input raster or the other given parameters 538 | 539 | Returns 540 | ------- 541 | None: save the merged raster to the output folder 542 | 543 | Examples 544 | -------- 545 | >>> from geotile import GeoTile 546 | >>> gt = GeoTile('/path/to/raster/file.tif') 547 | >>> gt.generate_raster_tiles(save_tiles=False) 548 | >>> gt.merge_tiles('/path/to/output/file.tif') 549 | """ 550 | # if self.tile_data is list, convert it to numpy array 551 | if isinstance(self.tile_data, list): 552 | self.tile_data = np.array(self.tile_data) 553 | 554 | # if data_type, update the meta 555 | if dtype: 556 | self.meta.update({"dtype": dtype}) 557 | 558 | # update the meta with number of bands 559 | self.meta.update({"count": self.meta['count']}) 560 | 561 | # check if image_format is None 562 | image_format = image_format or pathlib.Path(self.path).suffix 563 | 564 | # Added module 565 | x_gen = math.ceil(self.meta['height']/self.stride_x) 566 | y_gen = math.ceil(self.meta['width']/self.stride_y) 567 | new_height, new_width = x_gen*self.stride_x, y_gen*self.stride_y 568 | # print(x_gen, y_gen) 569 | # print(new_height, new_width) 570 | 571 | # Update the new height, width 572 | self.meta.update({"height": new_height}) 573 | self.meta.update({"width": new_width}) 574 | 575 | # Fill in the zero_array 576 | zero_array = np.zeros([self.meta['count'], new_height, new_width]) 577 | tile_data_new = np.moveaxis(self.tile_data, -1, 1) 578 | 579 | # Loop over each block and assign it to the corresponding position in the larger raster 580 | for i, block in enumerate(tile_data_new): 581 | x_offset = (i % x_gen) * self.stride_x 582 | y_offset = (i // y_gen) * self.stride_y 583 | zero_array[:, x_offset:x_offset+self.stride_x, y_offset:y_offset+self.stride_y] = block 584 | 585 | # write the output raster 586 | with rio.open(output_path, "w", **self.meta) as outds: 587 | outds.write(zero_array.astype(self.meta["dtype"])) 588 | 589 | def normalize_tiles(self): 590 | """Normalize the tiles between 0 and 1 (MinMaxScaler) 591 | 592 | Returns 593 | ------- 594 | None: Normalize the tiles. The normalized tiles will be stored in the class 595 | 596 | Examples 597 | -------- 598 | >>> from geotile import GeoTile 599 | >>> gt = GeoTile('/path/to/raster/file.tif') 600 | >>> gt.generate_raster_tiles(save_tiles=False) 601 | >>> gt.normalize_tiles() 602 | """ 603 | # normalize the tiles 604 | # if self.tile_data is list, convert it to numpy array 605 | if isinstance(self.tile_data, list): 606 | self.tile_data = np.array(self.tile_data) 607 | 608 | # if datatype is int based (eg. uint8, uint16, int8, int16), convert those to float32 for normalization 609 | # if not changed, the normalization will only generate 0 and 1 values for the tiles 610 | if self.tile_data.dtype in _int_dtypes: 611 | self.tile_data = self.tile_data.astype("float32") 612 | 613 | # find max and min values in whole tiles on each channel 614 | # my windows_data shape: (n, tile_y, tile_x, band) 615 | max_values = np.max(self.tile_data, axis=(0, 1, 2)) 616 | min_values = np.min(self.tile_data, axis=(0, 1, 2)) 617 | 618 | # Normalize the tiles and update the tile_data for each channel independently 619 | for channel in range(self.tile_data.shape[-1]): 620 | self.tile_data[:, :, :, channel] = ( 621 | self.tile_data[:, :, :, channel] - min_values[channel] 622 | ) / (max_values[channel] - min_values[channel]) 623 | 624 | def standardize_tiles(self): 625 | """Normalize the tiles using z-score (StandardScaler) 626 | 627 | Returns 628 | ------- 629 | None: Normalize the tiles. The normalized tiles will be stored in the class 630 | 631 | Examples 632 | -------- 633 | >>> from geotile import GeoTile 634 | >>> gt = GeoTile('/path/to/raster/file.tif') 635 | >>> gt.generate_raster_tiles(save_tiles=False) 636 | >>> gt.standardize_tiles() 637 | """ 638 | # if self.tile_data is list, convert it to numpy array 639 | if isinstance(self.tile_data, list): 640 | self.tile_data = np.array(self.tile_data) 641 | 642 | # if datatype is int based (eg. uint8, uint16, int8, int16), convert those to float32 for normalization 643 | # if not changed, the normalization will only generate 0 and 1 values for the tiles 644 | if self.tile_data.dtype in _int_dtypes: 645 | self.tile_data = self.tile_data.astype("float32") 646 | 647 | # find mean and std values in whole tiles on each channel 648 | # my windows_data shape: (n, tile_y, tile_x, band) 649 | mean_values = np.mean(self.tile_data, axis=(0, 1, 2)) 650 | std_values = np.std(self.tile_data, axis=(0, 1, 2)) 651 | 652 | # Normalize the tiles and update the tile_data for each channel independently 653 | for channel in range(self.tile_data.shape[-1]): 654 | self.tile_data[:, :, :, channel] = ( 655 | self.tile_data[:, :, :, channel] - mean_values[channel] 656 | ) / std_values[channel] 657 | 658 | def convert_nan_to_zero(self): 659 | """Convert nan values to zero 660 | 661 | Returns 662 | ------- 663 | None: Convert nan values to zero. The converted tiles will be stored in the class 664 | 665 | Examples 666 | -------- 667 | >>> from geotile import GeoTile 668 | >>> gt = GeoTile('/path/to/raster/file.tif') 669 | >>> gt.generate_raster_tiles(save_tiles=False) 670 | >>> gt.convert_nan_to_zero() 671 | """ 672 | # if self.tile_data is list, convert it to numpy array 673 | if isinstance(self.tile_data, list): 674 | self.tile_data = np.array(self.tile_data) 675 | 676 | # convert nan values to zero 677 | self.tile_data = np.nan_to_num(self.tile_data) 678 | 679 | def convert_nodata_to_zero(self): 680 | """Convert nodata values to zero 681 | 682 | Returns 683 | ------- 684 | None: Convert nodata values to zero. The converted tiles will be stored in the class 685 | 686 | Examples 687 | -------- 688 | >>> from geotile import GeoTile 689 | >>> gt = GeoTile('/path/to/raster/file.tif') 690 | >>> gt.generate_raster_tiles(save_tiles=False) 691 | >>> gt.convert_nodata_to_zero() 692 | """ 693 | # if self.tile_data is list, convert it to numpy array 694 | if isinstance(self.tile_data, list): 695 | self.tile_data = np.array(self.tile_data) 696 | 697 | # convert nodata values to zero 698 | self.tile_data[self.tile_data == self.meta["nodata"]] = 0 699 | 700 | def drop_nan_tiles(self): 701 | """Drop the tiles with nan values 702 | 703 | Returns 704 | ------- 705 | None: Drop the tiles with nan values. The dropped tiles will be lost forever 706 | 707 | Examples 708 | -------- 709 | >>> from geotile import GeoTile 710 | >>> gt = GeoTile('/path/to/raster/file.tif') 711 | >>> gt.generate_raster_tiles(save_tiles=False) 712 | >>> gt.drop_nan_tiles() 713 | """ 714 | # if self.tile_data is list, convert it to numpy array 715 | if isinstance(self.tile_data, list): 716 | self.tile_data = np.array(self.tile_data) 717 | 718 | # drop the tiles with nan values and also drop corresponding window transform and offsets 719 | nan_index = np.argwhere(np.isnan(self.tile_data).any(axis=(1, 2, 3))) 720 | self.tile_data = np.delete(self.tile_data, nan_index, axis=0) 721 | self.window_transform = np.delete(self.window_transform, nan_index, axis=0) 722 | self.offsets = np.delete(self.offsets, nan_index, axis=0) 723 | 724 | def drop_zero_value_tiles(self): 725 | """Drop the tiles with all zero values 726 | 727 | Returns 728 | ------- 729 | None: Drop the tiles with all zero values. The dropped tiles will lost forever 730 | 731 | Examples 732 | -------- 733 | >>> from geotile import GeoTile 734 | >>> gt = GeoTile('/path/to/raster/file.tif') 735 | >>> gt.generate_raster_tiles(save_tiles=False) 736 | >>> gt.drop_all_zero_value_tiles() 737 | """ 738 | # if self.tile_data is list, convert it to numpy array 739 | if isinstance(self.tile_data, list): 740 | self.tile_data = np.array(self.tile_data) 741 | 742 | # drop the tiles with nan values and also drop corresponding window transform and offsets 743 | zero_index = np.argwhere(np.all(self.tile_data == 0, axis=(1, 2, 3))) 744 | self.tile_data = np.delete(self.tile_data, zero_index, axis=0) 745 | self.window_transform = np.delete(self.window_transform, zero_index, axis=0) 746 | self.offsets = np.delete(self.offsets, zero_index, axis=0) 747 | 748 | def save_numpy(self, file_name: str, dtype: Optional[str] = None): 749 | """Save the tiles to the output folder 750 | 751 | Parameters 752 | ---------- 753 | file_name : str 754 | Path or name of the output numpy file 755 | 756 | dtype : str, np.dtype 757 | The output dtype (eg. uint8, float32), if None, the dtype will be the same as the input raster 758 | 759 | Returns 760 | ------- 761 | None: save the tiles to the output folder, the shape of the numpy file will be (n, tile_x, tile_y, band) 762 | 763 | Examples 764 | -------- 765 | >>> from geotile import GeoTile 766 | >>> gt = GeoTile('/path/to/raster/file.tif') 767 | >>> gt.generate_raster_tiles(save_tiles=False) 768 | >>> gt.save_numpys('/folder/to/output/file.npy') 769 | """ 770 | # check if the file name path exists or not, if not, create the folder 771 | if not os.path.exists(os.path.dirname(file_name)): 772 | os.makedirs(os.path.dirname(file_name)) 773 | 774 | # if self.tile_data is list, convert it to numpy array 775 | if isinstance(self.tile_data, list): 776 | self.tile_data = np.array(self.tile_data) 777 | 778 | # if data_type is none, get the appropriate dtype 779 | if dtype is None: 780 | dtype = self.get_dtype(self.tile_data) 781 | 782 | # save the numpy file 783 | np.save(file_name, self.tile_data.astype(dtype)) 784 | 785 | def mask( 786 | self, input_vector: str, out_path: str, crop=False, invert=False, **kwargs 787 | ): 788 | """Generate a mask raster from a vector 789 | This tool is similar to QGIS clip raster by mask layer (https://docs.qgis.org/2.8/en/docs/user_manual/processing_algs/gdalogr/gdal_extraction/cliprasterbymasklayer.html) 790 | 791 | Parameters 792 | ---------- 793 | input_vector: str, python path 794 | Path to the input vector (supports: shp, geojson, zip) 795 | All the vector formats supported by geopandas are supported 796 | out_path: Str, python Path 797 | Path to the output location of the mask raster 798 | crop: bool 799 | If True, the mask will be cropped to the extent of the vector 800 | If False, the mask will be the same size as the raster 801 | invert: bool 802 | If True, the mask will be inverted, pixels outside the mask will be filled with 1 and pixels inside the mask will be filled with 0 803 | kwargs: dict 804 | # rasterio.mask.mask (e.g. bounds, res, nodataetc.) 805 | The kwargs from rasterio.mask.mask can be used here: https://rasterio.readthedocs.io/en/latest/api/rasterio.mask.html 806 | 807 | Returns 808 | ------- 809 | out_path 810 | Save the mask as a out_path 811 | 812 | Examples: 813 | >>> from geotile import GeoTile 814 | >>> gt = GeoTile('/path/to/raster/file.tif') 815 | >>> gt.generate_raster_mask('/path/to/vector.shp', '/path/to/output/file.tif') 816 | """ 817 | 818 | # open the input vector 819 | df = gpd.read_file(input_vector) 820 | 821 | # check the coordinate system for both raster and vector and reproject vector if necessary 822 | raster_crs = self.meta["crs"] 823 | if raster_crs != df.crs: 824 | df = df.to_crs(raster_crs) 825 | 826 | # get the bounds of the vector 827 | with rio.open(self.path) as src: 828 | out_image, out_transform = mask( 829 | src, df["geometry"], crop=crop, invert=invert, **kwargs 830 | ) 831 | out_meta = src.meta.copy() 832 | 833 | # update the metadata 834 | out_meta.update( 835 | { 836 | "height": out_image.shape[1], 837 | "width": out_image.shape[2], 838 | "transform": out_transform, 839 | } 840 | ) 841 | 842 | # write the output raster 843 | with rio.open(out_path, "w", **out_meta) as outds: 844 | outds.write(out_image) 845 | 846 | def rasterization( 847 | self, 848 | input_vector: str, 849 | out_path: str, 850 | value_col=None, 851 | no_data: Optional[int] = None, 852 | **kwargs, 853 | ): 854 | """Convert vector shapes into raster 855 | The metadata of the raster will be the same as the raster from GeoTile class. 856 | The raster will be filled with the value of the value_col of the vector. 857 | 858 | Parameters 859 | ---------- 860 | input_vector: str, python path 861 | Path to the input vector (supports: shp, geojson, zip) 862 | All the vector formats supported by geopandas are supported 863 | out_path: str, python path 864 | Path to the output location of the rasterized vector 865 | value_col: str 866 | The column name of the vector to be rasterized 867 | If None, the rasterization will be binary otherwise the rasterization will be the based on value of the column 868 | no_data: int 869 | The no data value of the raster. 870 | Default value is None. 871 | kwargs: dict 872 | # rasterio.rasterize.rasterize (e.g. fill, transform etc.) 873 | The kwargs from rasterio.rasterize can be used here: https://rasterio.readthedocs.io/en/latest/api/rasterio.rasterize.html 874 | 875 | 876 | Returns 877 | ------- 878 | None: save the rasterized vector as a out_path 879 | 880 | Examples: 881 | >>> from geotile import GeoTile 882 | >>> gt = GeoTile('/path/to/raster/file.tif') 883 | >>> gt.rasterize_vector('/path/to/vector.shp', '/path/to/output/file.tif', fill=0) 884 | """ 885 | 886 | # open the input vector 887 | df = gpd.read_file(input_vector) 888 | 889 | # check the coordinate system for both raster and vector and reproject vector if necessary 890 | raster_crs = self.meta["crs"] 891 | if raster_crs != df.crs: 892 | print( 893 | f"CRS of raster doesn't match with vector. Reprojecting the vector ({df.crs}) to the raster coordinate system ({raster_crs})" 894 | ) 895 | df = df.to_crs(raster_crs) 896 | 897 | # if value column is specified, rasterize the vector based on value column else bianary classification 898 | dataset = zip(df["geometry"], df[value_col]) if value_col else df["geometry"] 899 | 900 | # rasterize the vector based on raster metadata 901 | mask = rasterize( 902 | dataset, 903 | self.ds.shape, 904 | transform=self.meta["transform"], 905 | **kwargs, 906 | ) 907 | mask = np.reshape(mask, (1, mask.shape[0], mask.shape[1])) 908 | 909 | # update the metadata 910 | meta = self.meta.copy() 911 | meta.update({"count": 1, "dtype": self.get_dtype(mask), "nodata": no_data}) 912 | 913 | # write the output raster 914 | with rio.open(out_path, "w", **meta) as outds: 915 | outds.write(mask) 916 | 917 | def reprojection( 918 | self, out_path: str, out_crs: str, resampling_method: str = "nearest" 919 | ): 920 | """Reproject a raster to a new coordinate system 921 | 922 | Parameters: 923 | out_path: str, python path 924 | Path to the output location of the reprojected raster 925 | out_crs: str 926 | The coordinate system of the output raster (e.g. 'EPSG:4326') 927 | resampling_method: str 928 | The resampling method to use (e.g. 'bilinear') 929 | It should be one of following, 930 | "nearest", "bilinear", "cubic", "cubic_spline", "lanczos", "average", 931 | "mode", "gauss", "max", "min", "median", "q1", "q3", "std", "sum", "rms" 932 | 933 | Returns: 934 | out_path: str 935 | Path to the output location of the reprojected raster 936 | 937 | Examples: 938 | >>> from geotile import GeoTile 939 | >>> gt = GeoTile('/path/to/raster/file.tif') 940 | >>> gt.reprojection('/path/to/output/file.tif', 'EPSG:4326') 941 | """ 942 | # reproject raster to project crs 943 | with rio.open(self.path) as src: 944 | src_crs = src.crs 945 | transform, width, height = calculate_default_transform( 946 | src_crs, out_crs, src.width, src.height, *src.bounds 947 | ) 948 | kwargs = src.meta.copy() 949 | 950 | kwargs.update( 951 | { 952 | "crs": out_crs, 953 | "transform": transform, 954 | "width": width, 955 | "height": height, 956 | } 957 | ) 958 | 959 | with rio.open(out_path, "w", **kwargs) as dst: 960 | for i in range(1, src.count + 1): 961 | reproject( 962 | source=rio.band(src, i), 963 | destination=rio.band(dst, i), 964 | src_transform=src.transform, 965 | src_crs=src.crs, 966 | dst_transform=transform, 967 | dst_crs=out_crs, 968 | resampling=Resampling[resampling_method], 969 | ) 970 | return out_path 971 | 972 | def resample( 973 | self, out_path: str, upscale_factor: int, resampling_method: str = "bilinear" 974 | ): 975 | """Resample a raster to a new resolution 976 | 977 | 978 | Parameters: 979 | out_path: str, python path 980 | Path to the output location of the resampled raster 981 | upscale_factor: int 982 | The upscale factor of the output raster (e.g. 2, i.e. 10x10 cell size to 5x5 cell size) 983 | If you want to downscale by 2, that mean upscale_factor = 0.5 984 | resampling_method: str 985 | The resampling method to use (e.g. 'bilinear') 986 | It should be one of following, 987 | "nearest", "bilinear", "cubic", "cubic_spline", "lanczos", "average", 988 | "mode", "gauss", "max", "min", "median", "q1", "q3", "std", "sum", "rms" 989 | 990 | Returns: 991 | out_path: str 992 | Path to the output location of the resampled raster 993 | 994 | Examples: 995 | >>> from geotile import GeoTile 996 | >>> gt = GeoTile('/path/to/raster/file.tif') 997 | >>> gt.resample('/path/to/output/file.tif', 2) 998 | """ 999 | # target dataset 1000 | data = self.ds.read( 1001 | out_shape=( 1002 | self.ds.count, 1003 | int(self.ds.height * upscale_factor), 1004 | int(self.ds.width * upscale_factor), 1005 | ), 1006 | resampling=Resampling[resampling_method], 1007 | ) 1008 | 1009 | # scale image transform 1010 | transform = self.ds.transform * self.ds.transform.scale( 1011 | (self.ds.width / data.shape[-1]), (self.ds.height / data.shape[-2]) 1012 | ) 1013 | 1014 | # update metadata 1015 | meta = self.meta.copy() 1016 | meta.update( 1017 | { 1018 | "transform": transform, 1019 | "width": int(self.ds.width * upscale_factor), 1020 | "height": int(self.ds.height * upscale_factor), 1021 | } 1022 | ) 1023 | 1024 | # write the output raster 1025 | with rio.open(out_path, "w", **meta) as outds: 1026 | outds.write(data) 1027 | 1028 | return out_path 1029 | 1030 | def close(self): 1031 | """Close the dataset""" 1032 | self.ds.close() 1033 | -------------------------------------------------------------------------------- /geotile/__init__.py: -------------------------------------------------------------------------------- 1 | from .GeoTile import GeoTile 2 | from .utils import mosaic, vectorize 3 | -------------------------------------------------------------------------------- /geotile/__version__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This information is located in its own file so that it can be loaded 4 | # without importing the main package when its dependencies are not installed. 5 | # See: https://packaging.python.org/guides/single-sourcing-package-version 6 | 7 | __author__ = "Tek Kshetri" 8 | __email__ = "iamtekson@gmail.com" 9 | __version__ = "1.1.0" 10 | -------------------------------------------------------------------------------- /geotile/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | from typing import Optional, Union 4 | 5 | import rasterio as rio 6 | from rasterio.merge import merge 7 | 8 | import fiona 9 | 10 | 11 | # mosaic the tiles 12 | def mosaic(input_folder: str, output_file: str, image_format: Optional[str] = 'tif', **kwargs): 13 | """Mosaic the rasters inside the input folder 14 | 15 | This method is used to merge the tiles into single file 16 | 17 | Parameters 18 | ---------- 19 | input_folder: str, python path 20 | Path to the input folder 21 | output_file: str, python path 22 | Path to the output file 23 | image_format: str 24 | The image format (eg. tif), if None, the image format will be the same as the input raster format. 25 | kwargs: dict 26 | The kwargs from rasterio.merge.merge can be used here: https://rasterio.readthedocs.io/en/latest/api/rasterio.merge.html#rasterio.merge.merge (e.g. bounds, res, nodata etc.) 27 | 28 | Returns 29 | ------- 30 | output_file 31 | Save the mosaic as a output_file. Returns the output_file path 32 | 33 | Examples 34 | -------- 35 | >>> from geotile import mosaic 36 | >>> mosaic('/path/to/input/folder', '/path/to/output/file.tif') 37 | """ 38 | 39 | # get the list of input rasters to merge 40 | search_criteria = "*.{}".format(image_format) 41 | q = os.path.join(input_folder, search_criteria) 42 | input_files = sorted(glob.glob(q)) 43 | 44 | # Open and add all the input rasters to a list 45 | src_files_to_mosaic = [] 46 | for files in input_files: 47 | src = rio.open(files) 48 | src_files_to_mosaic.append(src) 49 | 50 | # Merge the rasters 51 | mosaic, out_trans = merge(src_files_to_mosaic, **kwargs) 52 | 53 | # update the metadata 54 | meta = src.meta.copy() 55 | meta.update({ 56 | "height": mosaic.shape[1], 57 | "width": mosaic.shape[2], 58 | "transform": out_trans, 59 | }) 60 | 61 | # write the output raster 62 | with rio.open(output_file, 'w', **meta) as outds: 63 | outds.write(mosaic) 64 | 65 | return output_file 66 | 67 | 68 | # vectorize the tiles 69 | def vectorize(input_raster: str, output_file:str, band: Optional[int] = 1, raster_values: Optional[Union[str, list]] = 'all', mask: Optional[str] = None): 70 | """Vectorize the raster 71 | 72 | This method is used to vectorize the raster 73 | 74 | Parameters 75 | ---------- 76 | input_raster: str, python path 77 | Path to the input raster 78 | output_file: str, python path 79 | Path to the output file 80 | band: int 81 | The band to be vectorized 82 | raster_values: str, list 83 | The values to be vectorized. Default is 'all' 84 | 85 | Returns 86 | ------- 87 | output_file 88 | Save the vectorized raster as a output_file. Returns the output_file path 89 | 90 | Examples 91 | -------- 92 | >>> from geotile import vectorize 93 | >>> vectorize('/path/to/input/raster.tif', '/path/to/output/file.shp') 94 | >>> vectorize('/path/to/input/raster.tif', '/path/to/output/file.shp', raster_values=[1]) 95 | """ 96 | 97 | # Open the raster 98 | with rio.open(input_raster) as src: 99 | raster = src.read(band) 100 | 101 | # Vectorize the raster 102 | shapes = rio.features.shapes(raster, transform=src.transform, mask=mask) 103 | 104 | # if remove_values is not 'all'; filter out the required records 105 | records = [] 106 | if isinstance(raster_values, list): 107 | for i, (geom, value) in enumerate(shapes): 108 | if value in raster_values: 109 | records.append({ 110 | 'geometry': geom, 111 | 'properties': {'value': value}, 112 | }) 113 | 114 | # if remove_values is None, add all shapes to the record 115 | elif raster_values=='all': 116 | for i, (geom, value) in enumerate(shapes): 117 | records.append({ 118 | 'geometry': geom, 119 | 'properties': {'value': value}, 120 | }) 121 | 122 | # else raise the exception 123 | else: 124 | raise ValueError("remove_values either should be 'all' or list of integers") 125 | 126 | 127 | # Save the vectorized raster 128 | with fiona.open(output_file, 'w', crs=src.crs, driver='ESRI Shapefile', schema={'geometry': 'Polygon', 'properties': [('value', 'int')]}) as dst: 129 | dst.writerecords(records) 130 | 131 | return output_file -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fiona 2 | geopandas 3 | gdal 4 | numpy 5 | rasterio -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Dict 3 | 4 | from setuptools import setup 5 | 6 | HERE = os.path.abspath(os.path.dirname(__file__)) 7 | 8 | about = dict() 9 | 10 | with open(os.path.join(HERE, "geotile", "__version__.py"), "r") as f: 11 | exec(f.read(), about) 12 | 13 | with open("README.md", "r") as fh: 14 | long_description = fh.read() 15 | 16 | setup( 17 | name="geotile", 18 | version=about["__version__"], 19 | author=about["__author__"], 20 | author_email=about["__email__"], 21 | description="Package for working with geographic raster tiles", 22 | py_modules=["geotile"], 23 | license="MIT License", 24 | long_description=long_description, 25 | long_description_content_type="text/markdown", 26 | url="https://github.com/iamtekson/geotile", 27 | packages=["geotile"], 28 | keywords=[ 29 | "geotile", 30 | "geotiling", 31 | "geoTiler", 32 | "geospatial", 33 | "geospatial data", 34 | "geospatial raster tiles", 35 | "raster tiles", 36 | "raster", 37 | "tiles", 38 | "tile", 39 | "tiling python", 40 | "python", 41 | ], 42 | classifiers=[ 43 | "Programming Language :: Python :: 3", 44 | "Intended Audience :: Developers", 45 | "Intended Audience :: Science/Research", 46 | "License :: OSI Approved :: MIT License", 47 | "Operating System :: OS Independent", 48 | ], 49 | install_requires=[ 50 | "gdal", 51 | "numpy", 52 | "geopandas", 53 | "rasterio", 54 | ], 55 | extras_require={"dev": [ 56 | "pytest", 57 | "black", 58 | "flake8", 59 | "sphinx>=1.7", 60 | "pydata-sphinx-theme" 61 | ]}, 62 | python_requires=">=3.6", 63 | ) 64 | --------------------------------------------------------------------------------