├── .gitignore ├── .readthedocs.yml ├── LICENSE.txt ├── MANIFEST ├── README.md ├── docs ├── Makefile ├── api.rst ├── conf.py ├── create-georeferenced-data.rst ├── get-started.rst ├── index.rst ├── load-existing-data.rst ├── make.bat └── rtd_environment.yml ├── georaster ├── __init__.py └── georaster.py ├── requirements.txt ├── setup.cfg ├── setup.py └── test ├── data └── LE71400412000304SGS00_B4_crop.TIF └── test_georaster.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | *.egg-info -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Optionally build your docs in additional formats such as PDF and ePub 13 | formats: all 14 | 15 | conda: 16 | environment: docs/rtd_environment.yml -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Andrew Tedstone, Amaury Dehecq 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 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | README 3 | setup.cfg 4 | setup.py 5 | georaster/__init__.py 6 | georaster/georaster.py 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeoRaster - easy use of geographic rasters in Python # 2 | 3 | **DEPRECATION NOTICE:** As of 19 February 2021, this repository is marked deprecated and read-only. No further maintenance will be undertaken. Our efforts are now focused on https://www.github.com/GlacioHack/GeoUtils , which incorporates a lot of the functionality of GeoRaster but is built on top of rasterio rather than GDAL. Check it out! 4 | 5 | --- 6 | 7 | This package makes it easy to load, query and save geographic raster datasets in the Python programming language. The package uses Geospatial Data Abstraction Library (GDAL, http://www.gdal.org/) bindings, and so in a single command can import any geo-referenced dataset that is understood by GDAL (http://www.gdal.org/formats_list.html), complete with all geo-referencing information and various helper functions. 8 | 9 | GeoRaster is compatible with Python 2.4-3.x. 10 | 11 | There are two basic types of raster: either a single-band dataset, which you load into a `SingleBandRaster` object, or a dataset containing multiple bands to be loaded, which you load into a `MultiBandRaster` object. 12 | 13 | There is also an 'advanced' option where you can load a raster dataset, manually specifying your geo-referencing information. See example below. 14 | 15 | GeoRaster continues to be maintained but is no longer actively developed with new features. [Rasterio](https://rasterio.readthedocs.io/) is a good alternative for many tasks. 16 | 17 | # Full Documentation # 18 | 19 | http://georaster.readthedocs.io 20 | 21 | 22 | # Current release info # 23 | ==================== 24 | 25 | | Name | Downloads | Version | Platforms | 26 | | --- | --- | --- | --- | 27 | | [![Conda Recipe](https://img.shields.io/badge/recipe-georaster-green.svg)](https://anaconda.org/conda-forge/georaster) | [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/georaster.svg)](https://anaconda.org/conda-forge/georaster) | [![Conda Version](https://img.shields.io/conda/vn/conda-forge/georaster.svg)](https://anaconda.org/conda-forge/georaster) | [![Conda Platforms](https://img.shields.io/conda/pn/conda-forge/georaster.svg)](https://anaconda.org/conda-forge/georaster) | 28 | 29 | 30 | # Installation # 31 | 32 | Please install georaster into a virtual environment (e.g. conda) so that its requirements don't cause problems with your system's Python installation. 33 | 34 | ## Conda ## 35 | 36 | This is the preferred method of installation. It will resolve all the complex dependencies associated with GDAL automatically. 37 | 38 | conda install -c conda-forge georaster 39 | 40 | 41 | ## pip ## 42 | 43 | Installation via pip requires your system to already have a working GDAL installation. We do not recommend this option - use conda where possible. 44 | 45 | pip install georaster 46 | 47 | 48 | 49 | 50 | 51 | 52 | # Examples # 53 | 54 | Before doing anything you must import the package. 55 | 56 | import georaster 57 | 58 | The examples below also require matplotlib: 59 | 60 | import matplotlib.pyplot as plt 61 | 62 | 63 | ## Load a GeoTIFF with a single band of data ## 64 | 65 | Load the image with a single command: 66 | 67 | my_image = georaster.SingleBandRaster('myfile.tif') 68 | 69 | The single data band is loaded into the `r` attribute of `my_image`. Use the `extent` attribute of the `my_image` object to set the coordinates of the plotted image: 70 | 71 | plt.imshow(my_image.r,extent=my_image.extent) 72 | 73 | 74 | ## Single band of data, loading a subset area of the image ## 75 | 76 | In lat/lon (WGS84) - note that this will also set the class georeferencing 77 | information to match (i.e. self.nx, .yx, .extent, .xoffset, .yoffset): 78 | 79 | my_image = georaster.SingleBandRaster('myfile.tif',load_data=(lonll,lonur,latll,latur),latlon=True) 80 | 81 | Or in projection system of the image: 82 | 83 | my_image = georaster.SingleBandRaster('myfile.tif',load_data= 84 | (xstart,xend,ystart,yend), 85 | latlon=False) 86 | 87 | 88 | ## Just get the georeferencing info, without also loading data into memory ## 89 | Each class works as a wrapper to the GDAL API. A raster dataset can be loaded without needing to load the actual data as well, which is useful for querying geo-referencing information without memory overheads. Simply set the `load_data` flag to `False`: 90 | 91 | my_image = georaster.SingleBandRaster('myfile.tif',load_data=False) 92 | print(my_image.srs.GetProjParm('central_meridian')) 93 | 94 | (See the 'Accessing geo-referencing information' section below for informtation on `srs`) 95 | 96 | 97 | ## Raster dataset with multiple bands, load just one band ## 98 | For example, load GDAL band 2: 99 | 100 | my_image = georaster.MultiBandRaster('myfile.tif',band=2) 101 | 102 | 103 | ## Raster dataset with multiple bands, loading all bands ## 104 | 105 | my_image = georaster.MultiBandRaster('myfile.tif') 106 | plt.imshow(my_image.r) 107 | 108 | 109 | ## Raster dataset with multiple bands, loading just a couple of them ## 110 | 111 | my_image = georaster.MultiBandRaster('myfile.tif',bands=[1,3]) 112 | plt.imshow(my_image.r[:,:,my_image.gdal_band(3)]) 113 | 114 | 115 | ## Load dataset, providing your own geo-referencing ## 116 | 117 | from osgeo import osr 118 | spatial_ref = osr.SpatialReference() 119 | # This georef example is from http://nsidc.org/data/docs/daac/nsidc0092_greenland_ice_thickness.gd.html 120 | spatial_ref.ImportFromProj4('+proj=stere +lat_0=90 +lat_ts=71 +lon_0=-39 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs') 121 | my_image = georaster.SingleBandRaster('myfile.tif', 122 | geo_transform=(-800000,5000,0,-600000,0,-5000), 123 | spatial_ref=ref) 124 | 125 | 126 | # Accessing and using geo-referencing information # 127 | Once an image is loaded, the object provides the following attributes: 128 | 129 | - Raster.ds : the `GDAL` handle to the dataset, which provides access to all GDAL functions - see http://www.gdal.org/classGDALDataset.html 130 | 131 | - Raster.srs : an `OGR Spatial Reference` object representation of the dataset - see http://www.gdal.org/classOGRSpatialReference.html 132 | 133 | - Raster.proj : a `pyproj` coordinate conversion function between the dataset coordinate system and WGS84 (latitude/longitude) 134 | 135 | - Raster.extent : a `tuple` of the corners of the dataset in its native coordinate system as (left, right, bottom, top). 136 | 137 | - Raster.nx, ny : x and y sizes of the loaded raster area. 138 | 139 | - Raster.xres, yres : x and y pixel resolution of loaded raster area. 140 | 141 | - Raster.x0, y0 : the pixel offsets in x and y of the loaded area. These will be zero unless a subset area has been loaded (using `load_data=(tuple)`). 142 | 143 | - Raster.get_extent_latlon() : returns WGS84 (lat/lon) extent of raster as tuple. 144 | 145 | - Raster.get_extent_projected(pyproj_obj) : returns raster extent in the coordinate system specified by the pyproj object (e.g. provide a Basemap instance). 146 | 147 | - Raster.coord_to_px(x,y,latlon=True/False) : convert x,y coodinates into raster pixel coordinates. 148 | 149 | - Raster.coordinates() : return projected or geographic coordinates of the whole image (or, with optional arguments, a subset of the image) 150 | 151 | 152 | ## Get in touch 153 | 154 | * Report bugs, suggest features or view the code on GitHub. 155 | 156 | 157 | ## License 158 | 159 | GeoRaster is licenced under GNU Lesser General Public License (LGPLv3). 160 | 161 | 162 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GeoRaster.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GeoRaster.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/GeoRaster" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GeoRaster" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api-reference 2 | 3 | API Reference 4 | ============= 5 | 6 | Full information about GeoRaster's functionality is provided on this page. 7 | 8 | .. py:module:: georaster 9 | 10 | SingleBandRaster 11 | ~~~~~~~~~~~~~~~~ 12 | 13 | .. autoclass:: SingleBandRaster 14 | :members: from_array, get_extent_latlon, get_extent_projected, coord_to_px, value_at_coords, coordinates, reproject, interp, save_geotiff, cartopy_proj, intersection 15 | 16 | 17 | 18 | MultiBandRaster 19 | ~~~~~~~~~~~~~~~ 20 | 21 | .. autoclass:: MultiBandRaster 22 | :members: gdal_band, from_array, get_extent_latlon, get_extent_projected, coord_to_px, value_at_coords, coordinates, reproject, interp, save_geotiff, cartopy_proj, intersection 23 | 24 | 25 | 26 | Advanced Functionality 27 | ~~~~~~~~~~~~~~~~~~~~~~ 28 | 29 | There are several additional functions available in both SingleBandRaster and MultiBandRaster that are not listed in the main reference above. 30 | 31 | .. warning:: You should not use these functions without first fully understanding their implications. 32 | 33 | .. autoclass:: __Raster 34 | :members: read_single_band, read_single_band_subset -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # GeoRaster documentation build configuration file, created by 5 | # sphinx-quickstart on Mon Dec 19 12:07:58 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('../georaster/')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.viewcode', 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | # 49 | # source_encoding = 'utf-8-sig' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = 'GeoRaster' 56 | copyright = '2019, Andrew Tedstone, Amaury Dehecq' 57 | author = 'Andrew Tedstone, Amaury Dehecq' 58 | 59 | # The version info for the project you're documenting, acts as replacement for 60 | # |version| and |release|, also used in various other places throughout the 61 | # built documents. 62 | # 63 | # The short X.Y version. 64 | version = '1.26.1' 65 | # The full version, including alpha/beta/rc tags. 66 | release = '1.26.1' 67 | 68 | # The language for content autogenerated by Sphinx. Refer to documentation 69 | # for a list of supported languages. 70 | # 71 | # This is also used if you do content translation via gettext catalogs. 72 | # Usually you set "language" from the command line for these cases. 73 | language = None 74 | 75 | # There are two options for replacing |today|: either, you set today to some 76 | # non-false value, then it is used: 77 | # 78 | # today = '' 79 | # 80 | # Else, today_fmt is used as the format for a strftime call. 81 | # 82 | # today_fmt = '%B %d, %Y' 83 | 84 | # List of patterns, relative to source directory, that match files and 85 | # directories to ignore when looking for source files. 86 | # This patterns also effect to html_static_path and html_extra_path 87 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 88 | 89 | # The reST default role (used for this markup: `text`) to use for all 90 | # documents. 91 | # 92 | # default_role = None 93 | 94 | # If true, '()' will be appended to :func: etc. cross-reference text. 95 | # 96 | # add_function_parentheses = True 97 | 98 | # If true, the current module name will be prepended to all description 99 | # unit titles (such as .. function::). 100 | # 101 | # add_module_names = True 102 | 103 | # If true, sectionauthor and moduleauthor directives will be shown in the 104 | # output. They are ignored by default. 105 | # 106 | # show_authors = False 107 | 108 | # The name of the Pygments (syntax highlighting) style to use. 109 | pygments_style = 'sphinx' 110 | 111 | # A list of ignored prefixes for module index sorting. 112 | # modindex_common_prefix = [] 113 | 114 | # If true, keep warnings as "system message" paragraphs in the built documents. 115 | # keep_warnings = False 116 | 117 | # If true, `todo` and `todoList` produce output, else they produce nothing. 118 | todo_include_todos = False 119 | 120 | 121 | # -- Options for HTML output ---------------------------------------------- 122 | 123 | # The theme to use for HTML and HTML Help pages. See the documentation for 124 | # a list of builtin themes. 125 | # 126 | html_theme = 'default' 127 | 128 | # Theme options are theme-specific and customize the look and feel of a theme 129 | # further. For a list of options available for each theme, see the 130 | # documentation. 131 | # 132 | # html_theme_options = {} 133 | 134 | # Add any paths that contain custom themes here, relative to this directory. 135 | # html_theme_path = [] 136 | 137 | # The name for this set of Sphinx documents. 138 | # " v documentation" by default. 139 | # 140 | # html_title = 'GeoRaster v1.1' 141 | 142 | # A shorter title for the navigation bar. Default is the same as html_title. 143 | # 144 | # html_short_title = None 145 | 146 | # The name of an image file (relative to this directory) to place at the top 147 | # of the sidebar. 148 | # 149 | # html_logo = None 150 | 151 | # The name of an image file (relative to this directory) to use as a favicon of 152 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 153 | # pixels large. 154 | # 155 | # html_favicon = None 156 | 157 | # Add any paths that contain custom static files (such as style sheets) here, 158 | # relative to this directory. They are copied after the builtin static files, 159 | # so a file named "default.css" will overwrite the builtin "default.css". 160 | html_static_path = ['_static'] 161 | 162 | # Add any extra paths that contain custom files (such as robots.txt or 163 | # .htaccess) here, relative to this directory. These files are copied 164 | # directly to the root of the documentation. 165 | # 166 | # html_extra_path = [] 167 | 168 | # If not None, a 'Last updated on:' timestamp is inserted at every page 169 | # bottom, using the given strftime format. 170 | # The empty string is equivalent to '%b %d, %Y'. 171 | # 172 | # html_last_updated_fmt = None 173 | 174 | # If true, SmartyPants will be used to convert quotes and dashes to 175 | # typographically correct entities. 176 | # 177 | # html_use_smartypants = True 178 | 179 | # Custom sidebar templates, maps document names to template names. 180 | # 181 | # html_sidebars = {} 182 | 183 | # Additional templates that should be rendered to pages, maps page names to 184 | # template names. 185 | # 186 | # html_additional_pages = {} 187 | 188 | # If false, no module index is generated. 189 | # 190 | # html_domain_indices = True 191 | 192 | # If false, no index is generated. 193 | # 194 | # html_use_index = True 195 | 196 | # If true, the index is split into individual pages for each letter. 197 | # 198 | # html_split_index = False 199 | 200 | # If true, links to the reST sources are added to the pages. 201 | # 202 | # html_show_sourcelink = True 203 | 204 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 205 | # 206 | # html_show_sphinx = True 207 | 208 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 209 | # 210 | # html_show_copyright = True 211 | 212 | # If true, an OpenSearch description file will be output, and all pages will 213 | # contain a tag referring to it. The value of this option must be the 214 | # base URL from which the finished HTML is served. 215 | # 216 | # html_use_opensearch = '' 217 | 218 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 219 | # html_file_suffix = None 220 | 221 | # Language to be used for generating the HTML full-text search index. 222 | # Sphinx supports the following languages: 223 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 224 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 225 | # 226 | # html_search_language = 'en' 227 | 228 | # A dictionary with options for the search language support, empty by default. 229 | # 'ja' uses this config value. 230 | # 'zh' user can custom change `jieba` dictionary path. 231 | # 232 | # html_search_options = {'type': 'default'} 233 | 234 | # The name of a javascript file (relative to the configuration directory) that 235 | # implements a search results scorer. If empty, the default will be used. 236 | # 237 | # html_search_scorer = 'scorer.js' 238 | 239 | # Output file base name for HTML help builder. 240 | htmlhelp_basename = 'GeoRasterdoc' 241 | 242 | # -- Options for LaTeX output --------------------------------------------- 243 | 244 | latex_elements = { 245 | # The paper size ('letterpaper' or 'a4paper'). 246 | # 247 | # 'papersize': 'letterpaper', 248 | 249 | # The font size ('10pt', '11pt' or '12pt'). 250 | # 251 | # 'pointsize': '10pt', 252 | 253 | # Additional stuff for the LaTeX preamble. 254 | # 255 | # 'preamble': '', 256 | 257 | # Latex figure (float) alignment 258 | # 259 | # 'figure_align': 'htbp', 260 | } 261 | 262 | # Grouping the document tree into LaTeX files. List of tuples 263 | # (source start file, target name, title, 264 | # author, documentclass [howto, manual, or own class]). 265 | latex_documents = [ 266 | (master_doc, 'GeoRaster.tex', 'GeoRaster Documentation', 267 | 'Andrew Tedstone, Amaury Dehecq', 'manual'), 268 | ] 269 | 270 | # The name of an image file (relative to this directory) to place at the top of 271 | # the title page. 272 | # 273 | # latex_logo = None 274 | 275 | # For "manual" documents, if this is true, then toplevel headings are parts, 276 | # not chapters. 277 | # 278 | # latex_use_parts = False 279 | 280 | # If true, show page references after internal links. 281 | # 282 | # latex_show_pagerefs = False 283 | 284 | # If true, show URL addresses after external links. 285 | # 286 | # latex_show_urls = False 287 | 288 | # Documents to append as an appendix to all manuals. 289 | # 290 | # latex_appendices = [] 291 | 292 | # It false, will not define \strong, \code, itleref, \crossref ... but only 293 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 294 | # packages. 295 | # 296 | # latex_keep_old_macro_names = True 297 | 298 | # If false, no module index is generated. 299 | # 300 | # latex_domain_indices = True 301 | 302 | 303 | # -- Options for manual page output --------------------------------------- 304 | 305 | # One entry per manual page. List of tuples 306 | # (source start file, name, description, authors, manual section). 307 | man_pages = [ 308 | (master_doc, 'georaster', 'GeoRaster Documentation', 309 | [author], 1) 310 | ] 311 | 312 | # If true, show URL addresses after external links. 313 | # 314 | # man_show_urls = False 315 | 316 | 317 | # -- Options for Texinfo output ------------------------------------------- 318 | 319 | # Grouping the document tree into Texinfo files. List of tuples 320 | # (source start file, target name, title, author, 321 | # dir menu entry, description, category) 322 | texinfo_documents = [ 323 | (master_doc, 'GeoRaster', 'GeoRaster Documentation', 324 | author, 'GeoRaster', 'One line description of project.', 325 | 'Miscellaneous'), 326 | ] 327 | 328 | # Documents to append as an appendix to all manuals. 329 | # 330 | # texinfo_appendices = [] 331 | 332 | # If false, no module index is generated. 333 | # 334 | # texinfo_domain_indices = True 335 | 336 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 337 | # 338 | # texinfo_show_urls = 'footnote' 339 | 340 | # If true, do not generate a @detailmenu in the "Top" node's menu. 341 | # 342 | # texinfo_no_detailmenu = False 343 | -------------------------------------------------------------------------------- /docs/create-georeferenced-data.rst: -------------------------------------------------------------------------------- 1 | .. _create-georeferenced-data: 2 | 3 | 4 | Create geo-referenced data 5 | -------------------------- 6 | 7 | GeoRaster can be used to add geo-referencing to un-referenced data, and write 8 | those data out to GeoTIFF files. 9 | 10 | 11 | 12 | Add geo-referencing 13 | ~~~~~~~~~~~~~~~~~~~ 14 | 15 | Our starting point is that we have a NumPy array of dimensions *`y*x*bands`*. We also know about the extent and projection of the grid composing the NumPy array. 16 | 17 | Assuming the data themselves already exist, we need to set up the geo-referencing:: 18 | 19 | geoTransform = (tlx, wes, 0, tly, 0, nss) 20 | proj4 = '+proj=stere ...' 21 | 22 | Now we use this information to create a GeoRaster representation of the data. In this case our data, stored in `data_array`, is single-band, so the NumPy array has dimensions *y*x*:: 23 | 24 | im = georaster.SingleBandRaster.from_array(data_array, geoTransform, proj4) 25 | 26 | By default the datatype is set to Float32, but you can use an alternative GDAL datatype by settin the `gdal_dtype` keyword. 27 | 28 | NoData values are assumed to be `np.nan` in the input data array. If this is not the case, specify the value using the `nodata` keyword. 29 | 30 | This also works for multi-band data - simply substitute `MultiBandRaster` in place of `SingleBandRaster` above. 31 | 32 | 33 | 34 | Manipulate 35 | ~~~~~~~~~~ 36 | 37 | You can now use this geo-referenced GeoRaster object as you wish. You can discard it once you're finished with it, or you can... 38 | 39 | 40 | 41 | Save to GeoTIFF 42 | ~~~~~~~~~~~~~~~ 43 | 44 | Simply call the save command as follows:: 45 | 46 | im.save_geotiff('my_filename.tif') 47 | 48 | N.b. you may need to specify an alternative dtype. -------------------------------------------------------------------------------- /docs/get-started.rst: -------------------------------------------------------------------------------- 1 | .. _get-started: 2 | 3 | Getting Started 4 | --------------- 5 | 6 | GeoRaster supports two main types of image: those with a single band of data, and those with multiple bands of data. These two types of image are loaded in using georaster.SingleBandRaster or georaster.MultiBandRaster respectively. Once a raster is loaded in then most of the functions are identical between these two types of GeoRaster object. However, most geo-referenced images have only a single band, so that's what we'll introduce here. 7 | 8 | First up, let's import GeoRaster:: 9 | 10 | import georaster 11 | 12 | The simplest way of using GeoRaster is to open files on your computer which are already georeferenced. This works as follows:: 13 | 14 | my_image = georaster.SingleBandRaster('myfile.tif') 15 | print(my_image.extent) 16 | 17 | In this example, we have opened the file `myfile.tif` and then printed out its extent in the native coordinate system of the file. 18 | 19 | Using matplotlib we can take a look at the image itself:: 20 | 21 | import matplotlib.pyplot as plt 22 | plt.imshow(my_image.r, extent=my_image.extent) 23 | 24 | This will open up a figure window showing the image. 25 | 26 | Here are the most useful attributes of a Single- or MultiBandRaster: 27 | 28 | * im.r - a NumPy array of the raster data. In the case of a SingleBandRaster then this is a 2-D NumPy array. In the case of a MultiBandRaster this is a [y*x*bands] NumPy array (see 'Working with multi-band data below'). 29 | * im.extent - the extent of the raster in the coordinate system of the raster, ordered as `(left, right, bottom, top)` (AKA `(xmin, xmax, ymin, ymax)`) 30 | * im.srs : an OGR SpatialReference representation of the raster's projection. 31 | * im.trans : a GeoTransform tuple describing the raster (see _load-existing-data for more information). 32 | 33 | 34 | 35 | Working with multi-band data 36 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | As introduced above, multi-band data are loaded into `im.r` as a NumPy `[y*x*bands]` array. Use the convenience function `im.gdal_band` to retrieve a specific band according to its number as numbered by GDAL:: 39 | 40 | import matplotlib.pyplot as plt 41 | # Load a raster with 5 bands 42 | im = georaster.MultiBandRaster('my-5band-file.tif') 43 | plt.imshow(im.r[:, :, im.gdal_band(3)]) 44 | 45 | In this case we are displayed the band labelled by GDAL as number 3. See :ref:`_load-existing-data` to find out how to load in only specific bands. 46 | 47 | 48 | 49 | Get geo-referencing without loading image 50 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 51 | 52 | Sometimes it's useful to find out information such as the extent of resolution of an image, without loading the actual image itself into memory. You can do this as follows:: 53 | 54 | my_image = georaster.SingleBandRaster('myfile.tif', load_data=False) 55 | print(my_image.extent) 56 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. GeoRaster documentation master file, created by 2 | sphinx-quickstart on Mon Dec 19 12:07:58 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | GeoRaster - easy use of geographic and projected rasters in Python 7 | ================================================================== 8 | 9 | This package makes it easy to load, query and save geographic raster datasets - such as GeoTIFFs - in the Python programming language. The package uses Geospatial Data Abstraction Library (GDAL) bindings, and so in a single command can import any geo-referenced dataset that is understood by GDAL, complete with all geo-referencing information and various helper functions. 10 | 11 | GeoRaster is compatible with Python 2.4-3.x. 12 | 13 | There are two basic types of raster: either a single-band dataset, which you load into a `SingleBandRaster` object, or a dataset containing multiple bands to be loaded, which you load into a `MultiBandRaster` object. 14 | 15 | .. _GDAL: http://www.gdal.org/ 16 | .. _GDAL formats: http://www.gdal.org/formats_list.html 17 | 18 | 19 | Contents: 20 | 21 | .. toctree:: 22 | :maxdepth: 1 23 | 24 | get-started 25 | load-existing-data 26 | create-georeferenced-data 27 | access-georeferencing 28 | access-data 29 | reprojecting 30 | plotting-options 31 | api 32 | 33 | 34 | 35 | Indices and tables 36 | ================== 37 | 38 | * :ref:`genindex` 39 | * :ref:`modindex` 40 | * :ref:`search` 41 | 42 | -------------------------------------------------------------------------------- /docs/load-existing-data.rst: -------------------------------------------------------------------------------- 1 | .. _loading-data: 2 | 3 | Options for loading rasters 4 | --------------------------- 5 | 6 | As introduced in :ref:`get-started`, we can load in rasters that already exist in files as follows:: 7 | 8 | # For an image with only a single band 9 | single_im = georaster.SingleBandRaster('my-singleband-image.tif') 10 | 11 | # Or, for an image containing several bands 12 | multi_im = georaster.MultiBandRaster('my-multiband-image.tif') 13 | 14 | This basic command loads both the raster metadata, and all the data within that raster. However, this isn't always what's needed. 15 | 16 | 17 | 18 | Load a smaller area of a raster 19 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | 21 | We can specify the coordinates of a specific area of our raster to load in. This applies to both Single- and Multi-BandRasters. These coordinates can be in either Latitude/Longitude (WGS84) or the coordinate system of the raster. 22 | 23 | To use WGS84 coordinates:: 24 | 25 | extent = (lonlowerleft, lonupperright, latllowerleft, latupperright) 26 | im = georaster.SingleBandRaster('myfile.tif', load_data=extent, latlon=True) 27 | 28 | Or to use coordinate system of the image:: 29 | 30 | extent = (xmin, xmax, ymin, ymax) 31 | im = georaster.SingleBandRaster('myfile.tif', load_data=extent) 32 | 33 | 34 | 35 | Load only the information about a raster, not the image itself 36 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | Simply pass the flag `load_data=False` when you create the Single- or Multi-BandRaster object. 39 | 40 | 41 | 42 | Only load specific bands of a multi-band-raster 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 44 | 45 | Supply the numbers of the bands that you want to load as a tuple. These are the bands as numbered by GDAL, so begin from 1 (not 0):: 46 | 47 | im = georaster.MultiBandRaster('myfile.tif', bands=(1, 3)) 48 | 49 | We can then retrieve these individual bands of data as follows:: 50 | 51 | # Plot band 3 52 | plt.imshow(im.r[:, :, im.gdal_band(3)] 53 | 54 | We use the convenience function :py:func:`~georaster.MultiBandRaster.gdal_band`. This converts the original GDAL band number into the location in which the band is stored in `im.r`, which is a multi-dimensional NumPy array. 55 | 56 | 57 | 58 | Load a raster that doesn't have embedded geo-referencing 59 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 60 | 61 | Sometimes we want to import a raster which doesn't have meta-data that GDAL (the library which underlies GeoRaster) can access. Instead, we can provide the geo-referencing manually when loading the image. To do this we need to specify a SpatialReference object and a geoTransform. This works with both Single- and Multi-BandRasters. 62 | 63 | First create the SpatialReference object - here are a few options for specifying the projection:: 64 | 65 | ref = georaster.osr.SpatialReference() 66 | # Import from Proj.4 67 | ref.ImportFromProj4('your proj4 string') 68 | # Or Well-Known-Text 69 | ref.ImportFromWkt('your WKT string') 70 | # Or EPSG code 71 | ref.ImportFromEPSG(3413) 72 | 73 | Now create a geoTransform tuple that describes the position of the raster in space. A geoTransform has the form:: 74 | 75 | trans = (tlx, W-E size, 0, tly, 0, N-S size) 76 | 77 | Where: 78 | 79 | * `tlx`: The X coordinate of the upper-left corner of the raster 80 | * `W-E size`: The size of each cell from west to east 81 | * `tly`: The Y coordinate of the upper-left corner of the raster 82 | * `N-S size`: The size of each cell from north to south. Generally speaking this should be negative. 83 | 84 | Provide these values in the same units as the projection system you have specified, most commonly this is metres. 85 | 86 | Finally we use this information to load the raster with associated geo-referencing:: 87 | 88 | im = georaster.SingleBandRaster('myfile.tif', spatial_ref=ref, 89 | geo_transform=trans) 90 | 91 | For more information on SpatialReference objects, the following links will help: 92 | 93 | * `List of Spatial References `_ 94 | * `GDAL and OGR Cookbook - Projecting `_ 95 | * `GDAL documentation for the Spatial Reference class `_ -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\GeoRaster.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\GeoRaster.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/rtd_environment.yml: -------------------------------------------------------------------------------- 1 | name: georaster_conda 2 | dependencies: 3 | - python>=3.7 4 | - numpy 5 | - scipy 6 | - gdal 7 | - pyproj 8 | - libgfortran>=1 9 | channels: 10 | - conda-forge -------------------------------------------------------------------------------- /georaster/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Author: Andrew Tedstone 3 | # @Date: 2017-01-05 18:14:23 4 | # @Last Modified by: Andrew Tedstone 5 | # @Last Modified time: 2017-01-24 18:23:38 6 | 7 | from .georaster import SingleBandRaster 8 | from .georaster import MultiBandRaster 9 | from .georaster import simple_write_geotiff 10 | from .georaster import __Raster 11 | -------------------------------------------------------------------------------- /georaster/georaster.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | georaster.py 4 | 5 | Classes to simplify reading geographic raster data and associated 6 | georeferencing information. 7 | 8 | There are two classes available: 9 | 10 | SingleBandRaster -- For loading datasets with only a single band of 11 | data 12 | MultiBandRaster -- For loading datasets that contain multiple bands 13 | of data 14 | 15 | Each class works as a wrapper to the GDAL API. A raster dataset can be loaded 16 | into a class without needing to load the actual data as well, which is useful 17 | for querying geo-referencing information without memory overheads. 18 | 19 | Both classes provide comprehensive georeferencing information access via 20 | the following attributes: 21 | 22 | class.ds : the GDAL handle to the dataset, which provides access to 23 | all GDAL functions, e.g. GetProjection, GetGeoTransform. 24 | More information on API: 25 | http://www.gdal.org/classGDALDataset.html 26 | Remember that this is useless if you have provided your 27 | own geo-referencing information via the geo_transform and 28 | spatial_srs arguments! 29 | 30 | class.srs : an OGR Spatial Reference object representation of the 31 | dataset. 32 | More information on API: 33 | http://www.gdal.org/classOGRSpatialReference.html 34 | 35 | class.proj : a pyproj coordinate conversion function between the 36 | dataset coordinate system and lat/lon. 37 | 38 | class.extent : tuple of the corners of the dataset in native coordinate 39 | system, as (left,right,bottom,top). 40 | class.trans : GeoTransform tuple of the dataset. 41 | 42 | Additionally, georeferencing information which requires calculation 43 | can be accessed via a number of functions available in the class. 44 | 45 | 46 | Raster access: 47 | 48 | class.r : numpy array, m*n for SingleBandRaster, m*n*bands for MultiBand. 49 | 50 | 51 | Examples 52 | -------- 53 | 54 | SingleBandRaster with data import: 55 | >>> my_image = georaster.SingleBandRaster('myfile.tif') 56 | >>> plt.imshow(my_image.r,extent=my_image.extent) 57 | The data of a SingleBandRaster is made available via my_image.r as a numpy 58 | array. 59 | 60 | SingleBandRaster, loading a subset area of the image defined 61 | in lat/lon (WGS84) - note that this will also set the class georeferencing 62 | information to match (i.e. self.nx, .yx, .extent, .xoffset, .yoffset): 63 | >>> my_image = georaster.SingleBandRaster('myfile.tif',load_data= 64 | (lonll,lonur,latll,latur), 65 | latlon=True) 66 | Or in image projection system: 67 | >>> my_image = georaster.SingleBandRaster('myfile.tif',load_data= 68 | (xstart,xend,ystart,yend), 69 | latlon=False) 70 | 71 | SingleBandRaster to get some georeferencing info, without 72 | also loading data into memory: 73 | >>> my_image = georaster.SingleBandRaster('myfile.tif',load_data=False) 74 | >>> print(my_image.srs.GetProjParm('central_meridian')) 75 | 76 | MultiBandRaster, loading all bands: 77 | >>> my_image = georaster.MultiBandRaster('myfile.tif') 78 | >>> plt.imshow(my_image.r) 79 | 80 | MultiBandRaster, loading just a couple of bands: 81 | >>> my_image = georaster.MultiBandRaster('myfile.tif',bands=[1,3]) 82 | >>> plt.imshow(my_image.r[:,:,my_image.gdal_band(3)]) 83 | 84 | Override georeferencing provided with file (either Single or MultiBand): 85 | >>> from osgeo import osr 86 | >>> ref = osr.SpatialReference() 87 | >>> ref.ImportFromProj4('your proj4 string, e.g. +proj=stere ...') 88 | >>> my_image = georaster.SingleBandRaster('myfile.tif', 89 | geo_transform=(160,5000,0,-120,0,-5000), 90 | spatial_ref=ref) 91 | 92 | For more georaster examples see the docstrings which accompany each function. 93 | 94 | 95 | Created on Wed Oct 23 12:06:16 2013 96 | 97 | Change history prior to 2016/01/19 is in atedstone/geoutils repo. 98 | 99 | @author: Andrew Tedstone (a.j.tedstone@bristol.ac.uk) 100 | @author: Amaury Dehecq 101 | 102 | """ 103 | 104 | import numpy as np 105 | from scipy import ndimage 106 | import os 107 | try: 108 | from scipy.stats import nanmean 109 | except ImportError: 110 | from numpy import nanmean 111 | from osgeo import osr, gdal, ogr 112 | try: 113 | import pyproj 114 | except ImportError: 115 | import mpl_toolkits.basemap.pyproj as pyproj 116 | 117 | """ Further dependencies, imported if needed: 118 | 119 | - matplotlib 120 | - Loaded by .plot() 121 | 122 | """ 123 | 124 | # By default, GDAL does not raise exceptions - enable them 125 | # See http://trac.osgeo.org/gdal/wiki/PythonGotchas 126 | gdal.UseExceptions() 127 | from warnings import warn 128 | 129 | 130 | """ 131 | Information on map -> pixel conversion 132 | 133 | We use the formulas : 134 | Xgeo = g0 + Xpixel*g1 + Ypixel*g2 135 | Ygeo = g3 + Xpixel*g4 + Ypixel*g5 136 | where g = ds.GetGeoTransform() 137 | given on http://www.gdal.org/gdal_datamodel.html 138 | 139 | (Xgeo, Ygeo) is the position of the upper-left corner of the cell, so the 140 | cell center is located at position (Xpixel+0.5,Ypixel+0.5) 141 | """ 142 | 143 | 144 | 145 | ### Paths to data for testing and tutorial purposes 146 | 147 | test_data_path = os.path.join('/', *(__file__.split('/')[:-2]), 'test', 'data') 148 | 149 | 150 | 151 | ################################################################################ 152 | 153 | class __Raster: 154 | """ 155 | Attributes: 156 | ds_file (str) : filepath and name 157 | 158 | ds : GDAL handle to dataset 159 | 160 | extent (tuple) : extent of raster in order understood by basemap, 161 | [xll,xur,yll,yur], in raster coordinates 162 | 163 | srs : OSR SpatialReference object 164 | 165 | proj : pyproj conversion object raster coordinates<->lat/lon 166 | 167 | nx : x size of the raster. If a subset area of the raster is 168 | loaded then nx will correspond to the x size of the subset area. 169 | 170 | ny : y size of the raster. As nx. 171 | 172 | xres, yres : pixel sizes in x and y dimensions. 173 | 174 | x0, y0 : the offsets in x and y of the loaded raster area. 175 | These will be zero unless a subset area has been loaded. 176 | 177 | """ 178 | # Filepath and name 179 | ds_file = None 180 | # GDAL handle to the dataset 181 | ds = None 182 | # GeoTransform 183 | trans = None 184 | # Extent of raster in order understood by Basemap, (xll,xur,yll,yur) 185 | extent = None 186 | # SRS 187 | srs = None 188 | # pyproj Projection 189 | proj = None 190 | # Raster size 191 | nx = None 192 | ny = None 193 | # Pixel size 194 | xres = None 195 | yres = None 196 | # Subset offsets in pixels 197 | x0 = None 198 | y0 = None 199 | # GDAL datatype of data 200 | dtype = None 201 | 202 | 203 | 204 | def __del__(self): 205 | """ Close the gdal link to the dataset on object destruction """ 206 | self.ds = None 207 | 208 | 209 | 210 | def _load_ds(self,ds_filename,spatial_ref=None,geo_transform=None): 211 | """ Load link to data file and set up georeferencing 212 | 213 | Parameters: 214 | ds_filename : string, path to file 215 | 216 | To use file georeferencing, leave spatial_ref and geo_transform 217 | set as None. To explicitly specify georeferencing: 218 | spatial_ref : OSR SpatialReference instance 219 | geo_transform : geo-transform tuple 220 | 221 | """ 222 | 223 | # Load dataset from a file 224 | if isinstance(ds_filename,str): 225 | self.ds_file = ds_filename 226 | self.ds = gdal.Open(ds_filename,0) 227 | # Or load GDAL Dataset (already in memory) 228 | elif isinstance(ds_filename,gdal.Dataset): 229 | self.ds = ds_filename 230 | self.ds_file = ds_filename.GetDescription() 231 | 232 | # Check that some georeferencing information is available 233 | if self.ds.GetProjection() == '' and spatial_ref is None: 234 | warn('Warning : No georeferencing information associated to image!') 235 | 236 | # If user attempting to use their own georeferencing then make sure 237 | # that they have provided both required arguments 238 | if (spatial_ref is None and geo_transform != None) or \ 239 | (spatial_ref != None and geo_transform is None): 240 | print('You must set both spatial_ref and geo_transform.') 241 | raise RuntimeError 242 | 243 | 244 | ## Start to load the geo-referencing ... 245 | 246 | if spatial_ref is None: 247 | # Use the georeferencing of the file 248 | self.trans = self.ds.GetGeoTransform() 249 | # Spatial Reference System 250 | self.srs = osr.SpatialReference() 251 | self.srs.ImportFromWkt(self.ds.GetProjection()) 252 | else: 253 | # Set up georeferencing using user-provided information 254 | # GeoTransform 255 | self.trans = geo_transform 256 | # Spatial Reference System 257 | self.srs = spatial_ref 258 | 259 | # Create extent tuple in native dataset coordinates 260 | self.extent = (self.trans[0], 261 | self.trans[0] + self.ds.RasterXSize*self.trans[1], 262 | self.trans[3] + self.ds.RasterYSize*self.trans[5], 263 | self.trans[3]) 264 | 265 | # Pixel size 266 | self.xres = self.trans[1] 267 | self.yres = self.trans[5] 268 | 269 | # Raster size 270 | self.nx = self.ds.RasterXSize 271 | self.ny = self.ds.RasterYSize 272 | 273 | # Offset of the first pixel (non-zero if only subset is read) 274 | self.x0 = 0 275 | self.y0 = 0 276 | 277 | # Load projection if there is one 278 | if self.srs.IsProjected(): 279 | self.proj = pyproj.Proj(self.srs.ExportToProj4()) 280 | 281 | 282 | 283 | @classmethod 284 | def from_array(cls, raster, geo_transform, proj4, 285 | gdal_dtype=gdal.GDT_Float32, nodata=None): 286 | """ Create a georaster object from numpy array and georeferencing information. 287 | 288 | :param raster: 2-D NumPy array of raster to load 289 | :type raster: np.array 290 | :param geo_transform: a Geographic Transform tuple of the form \ 291 | (top left x, w-e cell size, 0, top left y, 0, n-s cell size (-ve)) 292 | :type geo_transform: tuple 293 | :param proj4: a proj4 string representing the raster projection 294 | :type proj4: str 295 | :param gdal_dtype: a GDAL data type (default gdal.GDT_Float32) 296 | :type gdal_dtype: int 297 | :param nodata: None or the nodata value for this array 298 | :type nodata: None, int, float, str 299 | 300 | :returns: GeoRaster object 301 | :rtype: GeoRaster 302 | 303 | """ 304 | 305 | if len(raster.shape) > 2: 306 | nbands = raster.shape[2] 307 | else: 308 | nbands = 1 309 | 310 | # Create a GDAL memory raster to hold the input array 311 | mem_drv = gdal.GetDriverByName('MEM') 312 | source_ds = mem_drv.Create('', raster.shape[1], raster.shape[0], 313 | nbands, gdal_dtype) 314 | 315 | # Set geo-referencing 316 | source_ds.SetGeoTransform(geo_transform) 317 | srs = osr.SpatialReference() 318 | srs.ImportFromProj4(proj4) 319 | source_ds.SetProjection(srs.ExportToWkt()) 320 | 321 | # Write input array to the GDAL memory raster 322 | for b in range(0,nbands): 323 | if nbands > 1: 324 | r = raster[:,:,b] 325 | else: 326 | r = raster 327 | source_ds.GetRasterBand(b+1).WriteArray(r) 328 | if nodata != None: 329 | source_ds.GetRasterBand(b+1).SetNoDataValue(nodata) 330 | 331 | # Return a georaster instance instantiated by the GDAL raster 332 | return cls(source_ds) 333 | 334 | 335 | 336 | def get_extent_latlon(self): 337 | """ Return raster extent in lat/lon coordinates. 338 | 339 | LL = lower left, UR = upper right. 340 | 341 | :returns: (lonll, lonur, latll, latur) 342 | :rtype: tuple 343 | 344 | """ 345 | if self.proj != None: 346 | xmin,xmax, ymin, ymax = self.extent 347 | corners = [(xmin,ymin), (xmin,ymax), (xmax,ymax), (xmax,ymin)] 348 | left, bottom = np.inf, np.inf 349 | right, top = -np.inf, -np.inf 350 | for c in corners: 351 | lon, lat = self.proj(c[0],c[1], inverse=True) 352 | left = min(left,lon) 353 | bottom = min(bottom, lat) 354 | right = max(right, lon) 355 | top = max(top, lat) 356 | #left,bottom = self.proj(self.extent[0], self.extent[2], inverse=True) 357 | #right,top = self.proj(self.extent[1], self.extent[3], inverse=True) 358 | return (left, right, bottom, top) 359 | else: 360 | return self.extent 361 | 362 | 363 | 364 | def get_extent_projected(self, pyproj_obj): 365 | """ Return raster extent in a projected coordinate system. 366 | 367 | This is particularly useful for converting raster extent to the 368 | coordinate system of a Basemap instance. 369 | 370 | 371 | :param pyproj_obj: The projection system to convert into. 372 | :type pyproj_obj: pyproj.Proj 373 | 374 | 375 | :returns: extent in requested coordinate system (left, right, bottom, top) 376 | :type: tuple 377 | 378 | :Example: 379 | 380 | >>> from mpl_toolkits.basemap import Basemap 381 | >>> my_im = georaster.SingleBandRaster('myfile.tif') 382 | >>> my_map = Basemap(...) 383 | >>> my_map.imshow(my_im.r,extent=my_im.get_extent_basemap(my_map)) 384 | 385 | """ 386 | if self.proj != None: 387 | xll,yll = self.proj(self.extent[0], self.extent[2], inverse=True) 388 | xur,yur = self.proj(self.extent[1], self.extent[3], inverse=True) 389 | else: 390 | xll,xur,yll,yur = self.extent 391 | 392 | left,bottom = pyproj_obj(xll,yll) 393 | right,top = pyproj_obj(xur,yur) 394 | return (left,right,bottom,top) 395 | 396 | 397 | 398 | def coord_to_px(self, x, y, latlon=False, rounded=True, check_valid=True, cell_type='center'): 399 | """ Convert projected or geographic coordinates into pixel coordinates of raster. 400 | 401 | :param x: x (longitude) coordinate to convert. 402 | :type x: float 403 | :param y: y (latitude) coordinate to convert. 404 | :type y: float 405 | :param latlon: Set as True if provided coordinates are in lat/lon. 406 | :type latlon: boolean 407 | :param rounded: Return rounded pixel coordinates? otherwise return float. 408 | :type rounded: boolean 409 | :param check_valid: Check that all pixels are in the valid range. 410 | :type check_valid: boolean 411 | :param cell_type: Type of cell considered, 'center' if the coordinate is for the center of the cell, 'corner' if it is as the top left corner 412 | :type cell_type: string 413 | 414 | :returns: corresponding pixel coordinates (x, y) of provided projected or geographic coordinates. 415 | :rtype: tuple 416 | 417 | """ 418 | 419 | # Convert list and tuple into numpy.array 420 | if isinstance(x,tuple) or isinstance(x,list): 421 | x = np.array(x) 422 | y = np.array(y) 423 | 424 | # Convert coordinates to map system if provided in lat/lon and image 425 | # is projected (rather than geographic) 426 | if latlon == True and self.proj != None: 427 | x,y = self.proj(x,y) 428 | 429 | # Shift to the centre of the pixel 430 | if cell_type=='center': 431 | x = np.array(x-self.xres/2) 432 | y = np.array(y-self.yres/2) 433 | 434 | g0, g1, g2, g3, g4, g5 = self.trans 435 | if g2 == 0: 436 | xPixel = (x - g0) / float(g1) 437 | yPixel = (y - g3 - xPixel*g4) / float(g5) 438 | else: 439 | xPixel = (y*g2 - x*g5 + g0*g5 - g2*g3) / float(g2*g4 - g1*g5) 440 | yPixel = (x - g0 - xPixel*g1) / float(g2) 441 | 442 | # Round if required 443 | if rounded==True: 444 | xPixel = np.round(xPixel) 445 | yPixel = np.round(yPixel) 446 | 447 | if check_valid==False: 448 | return xPixel, yPixel 449 | 450 | # Check that pixel location is not outside image dimensions 451 | nx = self.ds.RasterXSize 452 | ny = self.ds.RasterYSize 453 | 454 | xPixel_new = np.copy(xPixel) 455 | yPixel_new = np.copy(yPixel) 456 | xPixel_new = np.fmin(xPixel_new,nx) 457 | yPixel_new = np.fmin(yPixel_new,ny) 458 | xPixel_new = np.fmax(xPixel_new,0) 459 | yPixel_new = np.fmax(yPixel_new,0) 460 | 461 | if np.any(xPixel_new!=xPixel) or np.any(yPixel_new!=yPixel): 462 | print("Warning : some points are out of domain for file") 463 | 464 | return xPixel_new, yPixel_new 465 | 466 | 467 | 468 | def read_single_band(self,band=1,downsampl=1): 469 | """ Read in the data of a single band of data within the dataset. 470 | 471 | :param band: number of band to read. 472 | :type band: int 473 | :param downsampl: Reduce the size of the image loaded. Default of 1 \ 474 | specifies no down-sampling. 475 | :type downsampl: int 476 | 477 | :returns: array of specified band 478 | :rtype: np.array 479 | 480 | """ 481 | band = int(band) 482 | 483 | if downsampl == 1: 484 | return self.ds.GetRasterBand(band).ReadAsArray() 485 | else: 486 | down_x = int(np.ceil(self.ds.RasterXSize/downsampl)) 487 | down_y = int(np.ceil(self.ds.RasterYSize/downsampl)) 488 | arr = self.ds.GetRasterBand(band).ReadAsArray( 489 | buf_xsize=down_x, 490 | buf_ysize=down_y) 491 | self.nx = down_x 492 | self.ny = down_y 493 | self.xres = self.xres*downsampl 494 | self.yres = self.yres*downsampl 495 | return arr 496 | 497 | 498 | 499 | def read_single_band_subset(self,bounds,latlon=False,extent=False,band=1, 500 | update_info=False,downsampl=1): 501 | """ Return a subset area of the specified band of the dataset. 502 | 503 | You may supply coordinates either in the raster's current coordinate \ 504 | system or in lat/lon. 505 | 506 | .. warning:: By default (when `update_info=False`), this function does not 507 | update the `Raster` object with the results of this function call. 508 | 509 | :param bounds: The corners of the area to read in (xmin, xmax, ymin, ymax) 510 | :type bounds: tuple 511 | :param latlon: Set as True if bounds provided in lat/lon. 512 | :type latlon: boolean 513 | :param band: number of band in the dataset to read. 514 | :type band: int 515 | :param extent: If True, also return bounds of subset area in the \ 516 | coordinate system of the image. 517 | :type extent: boolean 518 | :param update_info: If True, set the georeferencing information of the object to that of the subset area. 519 | :type update_info: boolean 520 | 521 | :returns: when extent=False, array containing data from the \ 522 | band of the area requested. 523 | :rtype: np.array 524 | 525 | :returns: when extent=True, index 0 of the tuple contains the data \ 526 | and index 1 contains the extent of the area. 527 | :rtype: tuple 528 | 529 | """ 530 | 531 | left,right,bottom,top = bounds 532 | 533 | # Unlike the bounds tuple, which specifies bottom left and top right 534 | # coordinates, here we need top left and bottom right for the numpy 535 | # readAsArray implementation. 536 | # cell_type='corner' because coordinates are extent, therefore corner coordinates rather than cell center. 537 | xpx1,ypx1 = self.coord_to_px(left,bottom,latlon=latlon,cell_type='corner') 538 | xpx2,ypx2 = self.coord_to_px(right,top,latlon=latlon,cell_type='corner') 539 | 540 | if xpx1 > xpx2: 541 | xpx1, xpx2 = xpx2, xpx1 542 | if ypx1 > ypx2: 543 | ypx1, ypx2 = ypx2, ypx1 544 | 545 | # Resulting pixel offsets 546 | x_offset = xpx2 - xpx1 547 | y_offset = ypx2 - ypx1 548 | 549 | # In special case of being called to read a single point, offset 1 px 550 | if x_offset == 0: x_offset = 1 551 | if y_offset == 0: y_offset = 1 552 | 553 | # Read array and return 554 | if downsampl==1: 555 | arr = self.ds.GetRasterBand(int(band)).ReadAsArray( 556 | int(xpx1), 557 | int(ypx1), 558 | int(x_offset), 559 | int(y_offset)) 560 | else: 561 | down_x = int(np.ceil(self.ds.RasterXSize/downsampl)) 562 | down_y = int(np.ceil(self.ds.RasterYSize/downsampl)) 563 | arr = self.ds.GetRasterBand(int(band)).ReadAsArray( 564 | int(xpx1), 565 | int(ypx1), 566 | int(x_offset), 567 | int(y_offset), 568 | buf_xsize=down_x, 569 | buf_ysize=down_y) 570 | 571 | # Update image size 572 | # (top left x, w-e px res, 0, top left y, 0, n-s px res) 573 | trans = self.ds.GetGeoTransform() 574 | left = trans[0] + xpx1*trans[1] 575 | top = trans[3] + ypx1*trans[5] 576 | subset_extent = (left, left + x_offset*trans[1], 577 | top + y_offset*trans[5], top) 578 | if update_info: 579 | self.nx, self.ny = int(np.ceil(x_offset)),int(np.ceil(y_offset)) #arr.shape 580 | self.x0 = int(xpx1) 581 | self.y0 = int(ypx1) 582 | self.extent = subset_extent 583 | self.xres = self.xres*downsampl 584 | self.yres = self.yres*downsampl 585 | self.trans = (left, trans[1], 0, top, 0, trans[5]) 586 | if extent == True: 587 | return (arr,subset_extent) 588 | else: 589 | return arr 590 | 591 | 592 | 593 | def value_at_coords(self,x,y,latlon=False,band=None, 594 | window=None,return_window=False, 595 | reducer_function=np.nanmean): 596 | """ Extract the pixel value(s) at the specified coordinates. 597 | 598 | Extract pixel value of each band in dataset at the specified 599 | coordinates. Alternatively, if band is specified, return only that 600 | band's pixel value. 601 | 602 | Optionally, return mean of pixels within a square window. 603 | 604 | :param x: x (or longitude) coordinate. 605 | :type x: float 606 | :param y: y (or latitude) coordinate. 607 | :type y: float 608 | :param latlon: Set to True if coordinates provided as longitude/latitude. 609 | :type latlon: boolean 610 | :param band: the GDAL Dataset band number to extract from. 611 | :type band: int 612 | :param window: expand area around coordinate to dimensions \ 613 | window * window. window must be odd. 614 | :type window: None, int 615 | :param return_window: If True when window=int, returns (mean,array) \ 616 | where array is the dataset extracted via the specified window size. 617 | :type return_window: boolean 618 | 619 | :returns: When called on a SingleBandRaster or with a specific band \ 620 | set, return value of pixel. 621 | :rtype: float 622 | :returns: If a MultiBandRaster and the band is not specified, a \ 623 | dictionary containing the value of the pixel in each band. 624 | :rtype: dict 625 | :returns: In addition, if return_window=True, return tuple of \ 626 | (values, arrays) 627 | :rtype: tuple 628 | 629 | :examples: 630 | 631 | >>> self.value_at_coords(-48.125,67.8901,window=3) 632 | Returns mean of a 3*3 window: 633 | v v v \ 634 | v c v | = float(mean) 635 | v v v / 636 | (c = provided coordinate, v= value of surrounding coordinate) 637 | 638 | """ 639 | 640 | if window != None: 641 | if window % 2 != 1: 642 | raise ValueError('Window must be an odd number.') 643 | 644 | def format_value(value): 645 | """ Check if valid value has been extracted """ 646 | if type(value) == np.ndarray: 647 | if window != None: 648 | value = reducer_function(value.flatten()) 649 | else: 650 | value = value[0,0] 651 | else: 652 | value = None 653 | return value 654 | 655 | # Convert coordinates to pixel space 656 | xpx,ypx = self.coord_to_px(x,y,latlon=latlon) 657 | # Decide what pixel coordinates to read: 658 | if window != None: 659 | half_win = (window -1) / 2 660 | # Subtract start coordinates back to top left of window 661 | xpx = xpx - half_win 662 | ypx = ypx - half_win 663 | # Offset to read to == window 664 | xo = window 665 | yo = window 666 | else: 667 | # Start reading at xpx,ypx and read 1px each way 668 | xo = 1 669 | yo = 1 670 | 671 | #Make sure coordinates are int 672 | xpx = int(xpx) 673 | ypx = int(ypx) 674 | 675 | # Get values for all bands 676 | if band is None: 677 | 678 | # Deal with SingleBandRaster case 679 | if self.ds.RasterCount == 1: 680 | data = self.ds.GetRasterBand(1).ReadAsArray(xpx,ypx,xo,yo) 681 | value = format_value(data) 682 | win = data 683 | 684 | # Deal with MultiBandRaster case 685 | else: 686 | value = {} 687 | win = {} 688 | for b in range(1,self.ds.RasterCount+1): 689 | data = self.ds.GetRasterBand(b).ReadAsArray(xpx,ypx,xo,yo) 690 | val = format_value(data) 691 | # Store according to GDAL band numbers 692 | value[b] = val 693 | win[b] = data 694 | 695 | # Or just for specified band in MultiBandRaster case 696 | elif isinstance(band,int): 697 | data = self.ds.GetRasterBand(band).ReadAsArray(xpx,ypx,xo,yo) 698 | value = format_value(data) 699 | else: 700 | raise ValueError('Value provided for band was not int or None.') 701 | 702 | if return_window == True: 703 | return (value,win) 704 | else: 705 | return value 706 | 707 | 708 | 709 | def coordinates(self,Xpixels=None,Ypixels=None,latlon=False): 710 | """ Projected (or geographic) coordinates for specified pixels. 711 | 712 | Coordinates returned are for cell centres. 713 | 714 | If Xpixels=None and Ypixels=None (default), a grid with all 715 | coordinates is returned. 716 | 717 | If latlon=True, return the lat/lon coordinates. 718 | 719 | :param Xpixels: x-index of the pixels 720 | :type Xpixels: float, np.array 721 | :param Ypixels: y-index of the pixels 722 | :type Xpixels: float, np.array 723 | :param latlon: If set to True, lat/lon coordinates are returned 724 | :type latlon: boolean 725 | 726 | :returns: (X, Y) where X and Y are 1-D numpy arrays of coordinates 727 | :rtype: tuple 728 | 729 | """ 730 | 731 | if np.size(Xpixels) != np.size(Ypixels): 732 | print("Xpixels and Ypixels must have the same size") 733 | return 1 734 | 735 | if (Xpixels is None) & (Ypixels is None): 736 | Xpixels = np.arange(self.nx) 737 | Ypixels = np.arange(self.ny) 738 | Xpixels, Ypixels = np.meshgrid(Xpixels,Ypixels) 739 | else: 740 | Xpixels = np.array(Xpixels) 741 | Ypixels = np.array(Ypixels) 742 | 743 | # coordinates are at centre-cell, therefore the +0.5 744 | trans = self.trans 745 | Xgeo = trans[0] + (Xpixels+0.5)*trans[1] + (Ypixels+0.5)*trans[2] 746 | Ygeo = trans[3] + (Xpixels+0.5)*trans[4] + (Ypixels+0.5)*trans[5] 747 | 748 | if latlon==True: 749 | Xgeo, Ygeo = self.proj(Xgeo,Ygeo,inverse=True) 750 | 751 | return (Xgeo,Ygeo) 752 | 753 | 754 | 755 | def reproject(self,target_srs,nx=None,ny=None,xmin=None,ymax=None, 756 | xres=None,yres=None,dtype=gdal.GDT_Float32,nodata=None, 757 | interp_type=gdal.GRA_NearestNeighbour,progress=False): 758 | """ 759 | Reproject and resample dataset into another spatial reference system. 760 | 761 | Use to reproject/resample a dataset in-memory (rather than creating a 762 | new file), the function returns a new SingleBand or MultiBandRaster. 763 | 764 | .. warning:: Not tested to work with datasets where you have provided \ 765 | georeferencing information manually by providing geo_transform and \ 766 | spatial_ref when creating your GeoRaster instance. 767 | 768 | :param target_srs: Spatial Reference System to reproject to 769 | :type target_srs: srs.SpatialReference 770 | :param nx: X size of output raster 771 | :type nx: int 772 | :param ny: Y size of output raster 773 | :type ny: int 774 | :param xmin: value of X minimum coordinate (corner) 775 | :type xmin: float 776 | :param ymax: value of Y maximum coordinate (corner) 777 | :type ymax: float 778 | :param xres: pixel size in X dimension 779 | :type xres: float 780 | :param yres: pixel size in Y dimension 781 | :type yres: float 782 | :param dtype: GDAL data type, e.g. gdal.GDT_Byte. 783 | :type dtype: int 784 | :param nodata: NoData value in the input raster, default is None 785 | :type nodata: None, float, int, np.nan 786 | :param interp_type: gdal.GRA_* interpolation algorithm \ 787 | (e.g GRA_NearestNeighbour, GRA_Bilinear, GRA_CubicSpline...), 788 | :type interp_type: int 789 | :param progress: Set to True to display a progress bar 790 | :type progress: boolean 791 | 792 | :returns: A SingleBandRaster or MultiBandRaster object containing the 793 | reprojected image (in memory - not saved to file system) 794 | :rtype: georaster.SingleBandRaster, georaster.MultiBandRaster 795 | 796 | """ 797 | 798 | # Calculate defaults arguments 799 | if (xmin is None) & (ymax is None): 800 | tf = osr.CoordinateTransformation(self.srs,target_srs) 801 | xll, xur, yll, yur = self.extent 802 | xmin, ymax, _ = tf.TransformPoint(xll,yur) 803 | xmax, ymin, _ = tf.TransformPoint(xur,yll) 804 | if (xres is None) & (yres is None): 805 | tf = osr.CoordinateTransformation(self.srs,target_srs) 806 | xll, xur, yll, yur = self.extent 807 | x0, y0, _ = tf.TransformPoint(xll,yur) 808 | x1, y1, _ = tf.TransformPoint(xll+self.xres,yur+self.yres) 809 | xres = x1-x0 810 | yres = y1-y0 811 | if nx is None: 812 | nx = int(np.ceil((xmax-xmin)/xres)) 813 | #nx = self.nx 814 | if ny is None: 815 | ny = int(np.ceil((ymax-ymin)/np.abs(yres))) 816 | #ny = self.ny 817 | 818 | # Create an in-memory raster 819 | mem_drv = gdal.GetDriverByName( 'MEM' ) 820 | target_ds = mem_drv.Create('', nx, ny, 1, dtype) 821 | 822 | # Set the new geotransform 823 | new_geo = ( xmin, xres, 0, \ 824 | ymax, 0 , yres ) 825 | target_ds.SetGeoTransform(new_geo) 826 | target_ds.SetProjection(target_srs.ExportToWkt()) 827 | 828 | # Set the nodata value 829 | if nodata != None: 830 | for b in range(1,target_ds.RasterCount+1): 831 | inBand = target_ds.GetRasterBand(b) 832 | inBand.SetNoDataValue(nodata) 833 | 834 | # Perform the projection/resampling 835 | if progress==True: 836 | res = gdal.ReprojectImage(self.ds, target_ds, None, None, 837 | interp_type, 0.0, 0.0, gdal.TermProgress) 838 | else: 839 | res = gdal.ReprojectImage(self.ds, target_ds, None, None, 840 | interp_type, 0.0, 0.0, None) 841 | 842 | # Load data 843 | if self.ds.RasterCount > 1: 844 | new_raster = MultiBandRaster(target_ds) 845 | else: 846 | new_raster = SingleBandRaster(target_ds) 847 | 848 | # Replace no data value 849 | if new_raster.ds.RasterCount>1: 850 | for b in range(new_raster.ds.RasterCount): 851 | new_raster.r[b,new_raster.r==0] = nodata 852 | else: 853 | new_raster.r[new_raster.r==0] = nodata 854 | 855 | return new_raster 856 | 857 | 858 | 859 | def interp(self, x, y, order=1, latlon=False, bands=0, warning=True, 860 | from_ds=False): 861 | """ 862 | Interpolate raster at points (x,y). 863 | 864 | Values are extracted from self.r, which means that the data must be 865 | loaded in memory with self.read(...). 866 | 867 | x,y may be either in native coordinate system of raster or lat/lon. 868 | 869 | .. warning:: For now, values are considered as known at the \ 870 | upper-left corner, whereas it should be in the centre of the cell. 871 | 872 | .. warning:: Only integer pixel values can be extracted, 873 | subpixel interpolation must be implemented. 874 | 875 | :param x: x coordinate(s) to convert. 876 | :type x: float, np.array 877 | :param y: y coordinate(s) to convert. 878 | :type y: float, np.array 879 | :param order: order of the spline interpolation (range 0-5), \ 880 | 0=nearest-neighbor, 1=bilinear (default), 2-5 does not seem to \ 881 | work with NaNs. 882 | :type order: int 883 | :param latlon: Set as True if input coordinates are in lat/lon. 884 | :type latlon: boolean 885 | :param bands: Bands to extract for MultiBandRaster objects. Can be an 886 | int, list, tuple, numpy array or 'all' to extract all bands 887 | (Default is first band). 888 | :type bands: int, list, tuple, np.array 889 | :param warning: bool, if set to True, will display a warning when 890 | the coordinates fall outside the range 891 | :type warning: bool 892 | :param from_ds: If True extract data directly from dataset (instead of 893 | using in-memory version, if available) 894 | :type from_ds: bool 895 | 896 | :returns: interpolated raster values, same shape as x and y 897 | :rtype: np.array 898 | 899 | """ 900 | 901 | if self.r is None: 902 | from_ds = True 903 | if warning: 904 | print('WARNING: No data loaded into memory. Interpolation \ 905 | will extract extract data directly from dataset.') 906 | 907 | nBands = self.ds.RasterCount 908 | if (bands == 'all') & (nBands > 1): 909 | bands = np.arange(nBands + 1) 910 | 911 | # Get x,y coordinates in the matrix grid 912 | xi, yi = self.coord_to_px(x, y, latlon=latlon, rounded=False) 913 | 914 | # Case coordinates are not an array 915 | if np.ndim(xi) < 1: 916 | xi = np.array([xi,]) 917 | yi = np.array([yi,]) 918 | 919 | # Convert to int for call in ReadAsArray 920 | xi = np.int32(xi) 921 | yi = np.int32(yi) 922 | 923 | # Check that pixel location is not outside image dimensions 924 | if np.any(xi < 0) or np.any(xi >= self.nx) or np.any(yi < 0) or np.any(yi >= self.ny): 925 | if warning: 926 | print('Warning : some of the coordinates are not in dataset \ 927 | extent -> extrapolated value set to 0') 928 | 929 | 930 | # Interpolate data 931 | if nBands == 1: 932 | b = self.ds.GetRasterBand(1) 933 | if from_ds: 934 | z_interp = np.array([b.ReadAsArray(int(xp), int(yp), 1, 1)[0, 0] for (xp, yp) in zip(xi, yi)]) 935 | else: 936 | z_interp = ndimage.map_coordinates(self.r, [yi, xi], 937 | order=order) 938 | 939 | elif nBands > 1: 940 | if type(bands) == int: 941 | if from_ds: 942 | b = self.ds.GetRasterBand(band) 943 | z_interp = np.array([b.ReadAsArray(int(xp),int(yp),1,1)[0,0] for (xp,yp) in zip(xi,yi)]) 944 | else: 945 | z_interp = ndimage.map_coordinates(self.r[:,:,bands], 946 | [yi, xi], order=order) 947 | 948 | elif (type(bands) == list) or (type(bands) == tuple) or (type(bands) == np.ndarray): 949 | z_interp = np.nan * np.zeros((len(xi), len(bands)), dtype='float32') 950 | if from_ds: 951 | for k in xrange(len(bands)): 952 | b = self.ds.GetRasterBand(band[k]) 953 | z_interp[:,k] = np.array([b.ReadAsArray(int(xp),int(yp),1,1)[0,0] for (xp,yp) in zip(xi,yi)]) 954 | else: 955 | for k in xrange(len(bands)): 956 | z_interp[:,k] = ndimage.map_coordinates(self.r[:,:,bands[k]], [yi, xi], order=order) 957 | else: 958 | print("ERROR: argument bands must be of type int, list, \ 959 | tuple or numpy.ndarray") 960 | raise TypeError 961 | else: 962 | print("ERROR: Dimension of dataset must be 1 or more") 963 | raise TypeError 964 | 965 | return z_interp 966 | 967 | 968 | 969 | def save_geotiff(self,filename, dtype=gdal.GDT_Float32, **kwargs): 970 | """ 971 | Save a GeoTIFF of the raster currently loaded. 972 | 973 | Georeferenced and subset according to the current raster. 974 | 975 | :params filename: the path and filename to save the file to. 976 | :type filename: str 977 | :params dtype: GDAL datatype, defaults to Float32. 978 | :type dtype: int 979 | 980 | :returns: 1 on success. 981 | :rtype: int 982 | 983 | """ 984 | 985 | if self.r is None: 986 | raise ValueError('There are no image data loaded. No file can be created.') 987 | 988 | simple_write_geotiff(filename, self.r, self.trans, 989 | wkt=self.srs.ExportToWkt(), dtype=dtype, **kwargs) 990 | 991 | 992 | 993 | def cartopy_proj(self): 994 | """ Return Cartopy Coordinate System for raster 995 | 996 | :returns: Coordinate system of raster express as Cartopy system 997 | :rtype: cartopy.crs.CRS 998 | 999 | """ 1000 | import cartopy.crs as ccrs 1001 | class cg(cartopy.crs.CRS): pass 1002 | return cg(self.srs.ExportToProj4()) 1003 | 1004 | 1005 | 1006 | def intersection(self,filename): 1007 | """ 1008 | Return coordinates of intersection between this image and another. 1009 | If the rasters have different projections, the intersection extent is given in self's projection system. 1010 | :param filename : path to the second image (or another GeoRaster instance) 1011 | :type filename: str, georaster.__Raster 1012 | 1013 | :returns: extent of the intersection between the 2 images \ 1014 | (xmin, xmax, ymin, ymax) in self's coordinate system. 1015 | :rtype: tuple 1016 | """ 1017 | 1018 | ## Check if both files have the same projection ## 1019 | # Get EPSG code of first raster 1020 | self.srs.AutoIdentifyEPSG() 1021 | code1 = self.srs.GetAuthorityCode(None) 1022 | 1023 | # Get EPSG code of 2nd raster 1024 | img = SingleBandRaster(filename, load_data=False) 1025 | img.srs.AutoIdentifyEPSG() 1026 | code2 = img.srs.GetAuthorityCode(None) 1027 | 1028 | # Compare both codes, if not found, use PROJ4 strings 1029 | same_proj=True 1030 | if ((code1 is None) or (code2 is None)): # If code could not be estimated 1031 | warn("Could not identify images projection EPSG, trying with PROJ4") 1032 | proj1 = self.srs.ExportToProj4() 1033 | proj2 = img.srs.ExportToProj4() 1034 | if proj1==proj2: 1035 | pass 1036 | else: 1037 | same_proj=False 1038 | else: 1039 | if code1==code2: 1040 | pass 1041 | else: 1042 | same_proj=False 1043 | 1044 | ## Find envelope of rasters' intersections 1045 | # Create a polygon of the envelope of the first image 1046 | xmin, xmax, ymin, ymax = self.extent 1047 | wkt = "POLYGON ((%f %f, %f %f, %f %f, %f %f, %f %f))" \ 1048 | %(xmin,ymin,xmin,ymax,xmax,ymax,xmax,ymin,xmin,ymin) 1049 | poly1 = ogr.CreateGeometryFromWkt(wkt) 1050 | poly1.AssignSpatialReference(self.srs) 1051 | 1052 | # Create a polygon of the envelope of the second image 1053 | xmin, xmax, ymin, ymax = img.extent 1054 | wkt = "POLYGON ((%f %f, %f %f, %f %f, %f %f, %f %f))" \ 1055 | %(xmin,ymin,xmin,ymax,xmax,ymax,xmax,ymin,xmin,ymin) 1056 | poly2 = ogr.CreateGeometryFromWkt(wkt) 1057 | poly2.AssignSpatialReference(img.srs) 1058 | 1059 | # If coordinate system is different, reproject poly2 into poly1 1060 | if same_proj==False: 1061 | # Since GDAL 3.0, WGS84 uses lat/long rather than the opposite, which causes issue. This ensures that both SRS will use the long/lat order. 1062 | self.srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) 1063 | img.srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) 1064 | tr = osr.CoordinateTransformation(img.srs,self.srs) 1065 | poly2.Transform(tr) 1066 | 1067 | # Compute intersection envelope 1068 | intersect = poly1.Intersection(poly2) 1069 | extent = intersect.GetEnvelope() 1070 | 1071 | # check that intersection is not void 1072 | if intersect.GetArea()==0: 1073 | warn('Warning: Intersection is void') 1074 | return 0 1075 | else: 1076 | return extent 1077 | 1078 | 1079 | 1080 | def plot(self, band=1, nodata=None, 1081 | pmin=None, pmax=None, vmin=None, vmax=None, 1082 | colorbar=True, clabel=None, 1083 | title=None, figsize=None, save=None, dpi=300, 1084 | **kwargs): 1085 | """ Use matplotlib imshow to plot a GeoRaster image. 1086 | 1087 | With MultiBandRasters, this function currently only supports plotting of 1088 | a single band, which can be specified with the `band` parameter. It does 1089 | not support plotting of RGB composites. 1090 | 1091 | Image value bounds can either be chosen automatically, or specified 1092 | by percentiles or values. Provide only one of vmin or pmin, and only one 1093 | of vmax or pmax. 1094 | 1095 | :param band: Band to visualise (default=1) 1096 | :type band: int 1097 | :param nodata: no data value 1098 | :type nodata: float 1099 | :param pmin: Minimum percentile of image to plot 1100 | :type pmin: float 1101 | :param pmax: Maximum percentile of image to plot 1102 | :type pmax: float 1103 | :param vmin: Minimum value of image to plot 1104 | :type vmin: float 1105 | :param vmax: Maximum value of image to plot 1106 | :type vmax: float 1107 | :param colorbar: If True, plot colorbar, if False, do not. 1108 | :type colorbar: bool 1109 | :param clabel: Label to give to colourbar 1110 | :type clabel: str 1111 | :param title: Title to give to plot 1112 | :type title: str 1113 | :param figsize: Figure size (x, y) in inches 1114 | :type figsize: tuple 1115 | :param save: Path and filename to save plot to 1116 | :type save: str 1117 | :param dpi: Dots per inch of saved file 1118 | :type dpi: int 1119 | 1120 | **kwargs are passed to matplotlib.plot.imshow function. 1121 | 1122 | :returns: True on successful execution. 1123 | :rtype: bool 1124 | 1125 | """ 1126 | 1127 | import matplotlib.pyplot as plt 1128 | 1129 | # Only continue if there is some data loaded to plot! 1130 | if self.r is None: 1131 | raise ValueError('No raster data loaded in .r.') 1132 | 1133 | # Choose between percentiles and values min/max 1134 | if (pmin is not None) & (vmin is not None): 1135 | raise ValueError('Both pmin and vmin provided. Set only one.') 1136 | if (pmax is not None) & (vmax is not None): 1137 | raise ValueError('Both pmax and vmax provided. Set only one.') 1138 | 1139 | 1140 | # default vmin 1141 | if (vmin is None) & (pmin is None): 1142 | vmin = np.nanmin(self.r[:,:,band]) 1143 | 1144 | # default vmax 1145 | if (vmax is None) & (pmax is None): 1146 | vmax = np.nanmax(self.r[:,:,band]) 1147 | 1148 | 1149 | # Percentile minimum 1150 | def calc_percentile(data, nodata, perc): 1151 | if nodata != None: 1152 | vmin = np.nanpercentile(data[data.mask==False], perc) 1153 | else: 1154 | vmin = np.nanpercentile(data, perc) 1155 | return vmin 1156 | 1157 | if pmin is not None: 1158 | vmin = calc_percentile(self.r[:,:,band], nodata, perc) 1159 | 1160 | if pmax is not None: 1161 | vmax = calc_percentile(self.r[:,:,band], nodata, perc) 1162 | 1163 | 1164 | # Figsize 1165 | if figsize is None: 1166 | figsize = plt.rcParams['figure.figsize'] 1167 | else: 1168 | print(eval(figsize)) 1169 | print(tuple(eval(figsize))) 1170 | try: 1171 | figsize = tuple(eval(figsize)) 1172 | xfigsize, yfigsize = figsize 1173 | except: 1174 | print("ERROR: figsize must be a tuple of size 2, currently set to %s" %figsize) 1175 | 1176 | # dpi 1177 | if dpi is None: 1178 | dpi = plt.rcParams['figure.dpi'] 1179 | else: 1180 | try: 1181 | dpi = int(dpi) 1182 | except ValueError: 1183 | print("ERROR: dpi must be an integer, currently set to %s" %dpi) 1184 | 1185 | 1186 | 1187 | ## Plot data 1188 | fig = plt.figure(figsize=figsize) 1189 | 1190 | xmin, xmax, ymin, ymax = self.extent 1191 | 1192 | plt.imshow(self.r[:,:,band], extent=(xmin,xmax,ymin,ymax), 1193 | interpolation='nearest', vmin=vmin, vmax=vmax, **kwargs) 1194 | 1195 | # colorbar 1196 | if colorbar is True: 1197 | cb = plt.colorbar() 1198 | 1199 | if clabel is not None: 1200 | cb.set_label(clabel) 1201 | 1202 | # title 1203 | if title is not None: 1204 | plt.title(title) 1205 | 1206 | plt.tight_layout() 1207 | 1208 | # Save 1209 | if save is not None: 1210 | plt.savefig(save, dpi=dpi) 1211 | print("Figure saved to file %s." %save) 1212 | else: 1213 | plt.show() 1214 | 1215 | return True 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | class SingleBandRaster(__Raster): 1222 | """ A geographic raster dataset with one band of data. 1223 | 1224 | Initialise with the file path to a single band raster dataset, of type 1225 | understood by the GDAL library. Datasets in UTM are preferred. 1226 | 1227 | Attributes: 1228 | ds_file : filepath and name 1229 | 1230 | ds : GDAL handle to dataset 1231 | 1232 | extent : extent of raster in order understood by basemap, 1233 | [xll,xur,yll,yur], in raster coordinates 1234 | 1235 | srs : OSR SpatialReference object 1236 | 1237 | proj : pyproj conversion object raster coordinates<->lat/lon 1238 | 1239 | r : numpy band of array data 1240 | 1241 | 1242 | :example: 1243 | 1244 | >>> georaster.SingleBandRaster('myfile.tif',load_data=False) 1245 | 1246 | """ 1247 | 1248 | # Numpy array of band data 1249 | r = None 1250 | # Band datatype 1251 | dtype = None 1252 | 1253 | 1254 | def __init__(self,ds_filename,load_data=True,latlon=True,band=1, 1255 | spatial_ref=None,geo_transform=None,downsampl=1): 1256 | """ Open a single band raster. 1257 | 1258 | :param ds_filename: filename of the dataset to import 1259 | :type ds_filename: str 1260 | 1261 | :param load_data: If True, to import the data into obj.r. If False, 1262 | do not load any data. Provide tuple (left, right, bottom, top) to load 1263 | subset, in which case extent will be set to reflect subset area. 1264 | :type load_data: 1265 | 1266 | 1267 | latlon : default True. Only used if load_data=tuple. Set as False 1268 | if tuple is projected coordinates, True if WGS84. 1269 | band : default 1. Specify GDAL band number to load. If you want to 1270 | load multiple bands at once use MultiBandRaster instead. 1271 | downsampl : default 1. Used to down-sample the image when loading it. 1272 | A value of 2 for example will multiply the resolution by 2. 1273 | 1274 | Optionally, you can manually specify/override the georeferencing. 1275 | To do this you must set both of the following parameters: 1276 | 1277 | spatial_ref : a OSR SpatialReference instance 1278 | geo_transform : a Geographic Transform tuple of the form 1279 | (top left x, w-e cell size, 0, top left y, 0, 1280 | n-s cell size (-ve)) 1281 | 1282 | """ 1283 | 1284 | # Do basic dataset loading - set up georeferencing 1285 | self._load_ds(ds_filename,spatial_ref=spatial_ref, 1286 | geo_transform=geo_transform) 1287 | 1288 | # Import band datatype 1289 | band_tmp = self.ds.GetRasterBand(band) 1290 | self.dtype = gdal.GetDataTypeName(band_tmp.DataType) 1291 | 1292 | # Load entire image 1293 | if load_data == True: 1294 | self.r = self.read_single_band(band,downsampl=downsampl) 1295 | 1296 | # Or load just a subset region 1297 | elif isinstance(load_data,tuple) or isinstance(load_data,list): 1298 | if len(load_data) == 4: 1299 | self.r = self.read_single_band_subset(load_data,latlon=latlon, 1300 | band=band,update_info=True,downsampl=downsampl) 1301 | 1302 | elif load_data == False: 1303 | return 1304 | 1305 | else: 1306 | print('Warning : load_data argument not understood. No data loaded.') 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | class MultiBandRaster(__Raster): 1313 | """ A geographic raster dataset with multiple bands of data. 1314 | 1315 | Initialise with the file path to a single band raster dataset, of type 1316 | understood by the GDAL library. Datasets in UTM are preferred. 1317 | 1318 | Examples: 1319 | [1] Load all bands of a raster: 1320 | >>> georaster.MultiBandRaster("myfile.tif") 1321 | 1322 | [2] Load just bands 1 and 3 of a raster: 1323 | >>> georaster.MultiBandRaster("myfile.tif",load_data=[1,3]) 1324 | 1325 | [3] Don't load the data, just use the class for the georeferencing API: 1326 | >>> georaster.MultiBandRaster("myfile.tif",load_data=False) 1327 | 1328 | Attributes: 1329 | r : The raster data (if loaded). For the example cases above: 1330 | 1331 | [1] - a np array of [rows,cols,bands]. Standard numpy 1332 | slicing is used, ie. the array is zero-referenced. 1333 | E.g., extract band 2, which is the second band 1334 | loaded: 1335 | >>> myRaster.r[:,:,1] 1336 | 1337 | This can be simplified with gdal_band(): 1338 | E.g., extract band 2: 1339 | >>> myRaster.r[:,:,myRaster.gdal_band(2)] 1340 | 1341 | [2] - The same as [1]. The helper function is particularly 1342 | useful in simplifying lookup of bands, e.g.: 1343 | >>> myRaster.r[:,:,myRaster.gdal_band(3)] 1344 | Rather than the less obvious: 1345 | >>> myRaster.r[:,:,1] 1346 | Which corresponds to the actual numpy location of 1347 | that band. 1348 | 1349 | [3] - r is set as None. No data can be accessed. 1350 | 1351 | 1352 | bands : list of GDAL band numbers which have been loaded, in the 1353 | order corresponding to the order stored in 1354 | r[rows,cols,bands]. 1355 | 1356 | ds_file : filepath and name 1357 | ds : GDAL handle to dataset 1358 | extent : extent of raster in order understood by basemap, 1359 | [xll,xur,yll,yur], in raster coordinates 1360 | srs : OSR SpatialReference object 1361 | proj : pyproj conversion object raster coordinates<->lat/lon 1362 | 1363 | """ 1364 | 1365 | # Either a numpy array if just one band, or a dict of numpy arrays if 1366 | # multiple, key is band number 1367 | r = None 1368 | 1369 | # List of GDAL band numbers which have been loaded. 1370 | bands = None 1371 | 1372 | 1373 | def __init__(self,ds_filename,load_data=True,bands='all',latlon=True, 1374 | spatial_ref=None,geo_transform=None,downsampl=1): 1375 | """ Load a multi-band raster. 1376 | 1377 | Parameters: 1378 | ds_filename : filename of dataset to load 1379 | load_data : True, False or tuple (lonll,lonur,latll,latur) 1380 | latlon : When load_data=tuple of coordinates, True if geographic, 1381 | False if projected. 1382 | bands : 'all', or tuple of raster bands. If tuple, 1383 | MultiBandRaster.r will be a numpy array [y,x,b], where bands 1384 | are indexed from 0 to n in the order specified in the tuple. 1385 | 1386 | Optionally, you can manually specify/override the georeferencing. 1387 | To do this you must set both of the following parameters: 1388 | 1389 | spatial_ref : a OSR SpatialReference instance 1390 | geo_transform : a Geographic Transform tuple of the form 1391 | (top left x, w-e cell size, 0, top left y, 0, 1392 | n-s cell size (-ve)) 1393 | downsampl : default 1. Used to down-sample the image when loading it. 1394 | """ 1395 | 1396 | self._load_ds(ds_filename,spatial_ref=spatial_ref, 1397 | geo_transform=geo_transform) 1398 | 1399 | if load_data != False: 1400 | 1401 | # First check which bands to load 1402 | if bands == 'all': 1403 | self.bands = np.arange(1,self.ds.RasterCount+1) 1404 | else: 1405 | if isinstance(bands,tuple): 1406 | self.bands = bands 1407 | else: 1408 | print('bands is not str "all" or of type tuple') 1409 | raise ValueError 1410 | 1411 | # Loading whole dimensions of raster 1412 | if load_data == True: 1413 | self.r = np.zeros((int(np.ceil(self.ds.RasterYSize/downsampl)),int(np.ceil(self.ds.RasterXSize/downsampl)), 1414 | len(self.bands))) 1415 | k = 0 1416 | for b in self.bands: 1417 | self.r[:,:,k] = self.read_single_band(band=b,downsampl=downsampl) 1418 | k += 1 1419 | 1420 | # Loading geographic subset of raster 1421 | elif isinstance(load_data,tuple): 1422 | if len(load_data) == 4: 1423 | k = 0 1424 | for b in self.bands: 1425 | 1426 | # If first band, create a storage object 1427 | if self.r is None: 1428 | (tmp,self.extent) = self.read_single_band_subset(load_data, 1429 | latlon=latlon,extent=True,band=b,update_info=False, downsampl=downsampl) 1430 | self.r = np.zeros((tmp.shape[0],tmp.shape[1], 1431 | len(self.bands))) 1432 | self.r[:,:,k] = tmp 1433 | # Store subsequent bands in kth dimension of store. 1434 | elif b!=self.bands[-1]: 1435 | self.r[:,:,k] = self.read_single_band_subset(load_data, 1436 | latlon=latlon,band=b,downsampl=downsampl) 1437 | else: #update infos at last iteration 1438 | self.r[:,:,k] = self.read_single_band_subset(load_data, update_info=True, latlon=latlon,band=b,downsampl=downsampl) 1439 | 1440 | k += 1 1441 | 1442 | # Don't load any data 1443 | elif load_data == False: 1444 | self.bands = None 1445 | return 1446 | 1447 | else: 1448 | raise ValueError('load_data was not understood (should be one \ 1449 | of True,False or tuple)') 1450 | 1451 | 1452 | 1453 | def gdal_band(self,b): 1454 | """ Return numpy array location index for given GDAL band number. 1455 | 1456 | :param b: GDAL band number to lookup 1457 | :type b: int 1458 | 1459 | :returns: index location of band in self.r 1460 | :rtype: int 1461 | 1462 | :example: 1463 | 1464 | >>> giveMeMyBand2 = myRaster.r[:,:,myRaster.gdal_band(2)] 1465 | 1466 | """ 1467 | 1468 | # Check that more than 1 band has been loaded into memory. 1469 | if self.bands is None: 1470 | raise AttributeError('No data have been loaded.') 1471 | if len(self.bands) == 1: 1472 | raise AttributeError('Only 1 band of data has been loaded.') 1473 | 1474 | if isinstance(b,int): 1475 | return self.bands.index(b) 1476 | else: 1477 | raise ValueError('B is must be an integer.') 1478 | 1479 | 1480 | 1481 | 1482 | def simple_write_geotiff(outfile,raster,geoTransform, 1483 | wkt=None,proj4=None,mask=None,dtype=gdal.GDT_Float32, 1484 | nodata_value=-999, metadata=None, options=None): 1485 | """ Save a GeoTIFF. 1486 | 1487 | One of proj4 or wkt are required. 1488 | 1489 | :param outfile: filename to save image to, if 'none', returns a memory raster 1490 | :type outfile: string 1491 | :param raster: the raster to save, [rows, cols] or [rows, cols, bands] 1492 | :type raster: np.array 1493 | :param geoTransform: (top left x, w-e cell size, 0, top left y, 0, \ 1494 | n-s cell size (-ve)) 1495 | :param proj4: a proj4 string 1496 | :type proj4: string 1497 | :param wkt: a WKT projection string 1498 | :type wkt: string 1499 | :param dtype: gdal.GDT type (Byte : 1, Int32 : 5, Float32 : 6) 1500 | :type dtype: int 1501 | :param nodata_value: The value in the raster to set as the NoData value 1502 | :type nodata_value: float, int 1503 | :param metadata: Metadata to be stored in the file. Pass a dictionary 1504 | with {key1:value1, key2:value2...} 1505 | :type metadata: dict 1506 | :param options: Additional creation options, see e.g. http://www.gdal.org/frmt_gtiff.html. 1507 | Pass a list of key=value ['key1=value1','key2='value2'...] 1508 | Examples of options are ['COMPRESS=LZW', 'TILED=YES', 'BLOCKXSIZE=256'...] 1509 | For compressions, see http://www.digital-geography.com/geotiff-compression-comparison/#.WW1KV47_lP4 1510 | :type options: list 1511 | 1512 | :returns: True or a GDAL memory raster. 1513 | 1514 | Based on http://adventuresindevelopment.blogspot.com/2008/12/python-gdal-adding-geotiff-meta-data.html 1515 | and http://www.gdal.org/gdal_tutorial.html 1516 | """ 1517 | 1518 | # Georeferencing sanity checks 1519 | if wkt != None and proj4 != None: 1520 | raise 'InputError: Both wkt and proj4 specified. Only specify one.' 1521 | if wkt is None and proj4 is None: 1522 | raise 'InputError: One of wkt or proj4 need to be specified.' 1523 | 1524 | # Check if the image is multi-band or not. 1525 | if raster.shape.__len__() == 3: 1526 | nbands = raster.shape[2] 1527 | ydim = raster.shape[0] 1528 | xdim = raster.shape[1] 1529 | elif raster.shape.__len__() == 2: 1530 | nbands = 1 1531 | ydim = raster.shape[0] 1532 | xdim = raster.shape[1] 1533 | 1534 | # Setup geotiff file. 1535 | if outfile!='none': 1536 | driver = gdal.GetDriverByName("GTiff") 1537 | else: 1538 | driver = gdal.GetDriverByName('MEM') 1539 | 1540 | if options is None: 1541 | dst_ds = driver.Create(outfile, xdim, ydim, nbands, dtype) 1542 | else: 1543 | dst_ds = driver.Create(outfile, xdim, ydim, nbands, dtype, 1544 | options=options) 1545 | # Top left x, w-e pixel res, rotation, top left y, rotation, n-s pixel res 1546 | dst_ds.SetGeoTransform(geoTransform) 1547 | 1548 | # Set the reference info 1549 | srs = osr.SpatialReference() 1550 | if wkt != None: 1551 | dst_ds.SetProjection(wkt) 1552 | elif proj4 != None: 1553 | srs.ImportFromProj4(proj4) 1554 | dst_ds.SetProjection( srs.ExportToWkt() ) 1555 | 1556 | # Write the band(s) 1557 | if nbands > 1: 1558 | for band in range(1,nbands+1): 1559 | dst_ds.GetRasterBand(band).WriteArray(raster[:, :, band-1]) 1560 | if mask != None: 1561 | dst_ds.GetRasterBand(band).GetMaskBand().WriteArray(mask) 1562 | else: 1563 | dst_ds.GetRasterBand(1).WriteArray(raster) 1564 | dst_ds.GetRasterBand(1).SetNoDataValue(nodata_value) 1565 | if mask != None: 1566 | dst_ds.GetRasterBand(1).GetMaskBand().WriteArray(mask) 1567 | 1568 | # Add metadata 1569 | if metadata is not None: 1570 | dst_ds.SetMetadata(metadata) 1571 | 1572 | if outfile != 'none': 1573 | # Close data set 1574 | dst_ds = None 1575 | return True 1576 | 1577 | else: 1578 | return dst_ds 1579 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | pyproj -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name = 'georaster', 4 | packages = ['georaster'], # this must be the same as the name above 5 | version = '1.27', 6 | description = 'easy processing and analysis of geographic and projected rasters in Python', 7 | author = 'Andrew Tedstone, Amaury Dehecq', 8 | author_email = 'andrew.tedstone@unifr.ch', 9 | url = 'https://github.com/GeoUtils/georaster', # use the URL to the github repo 10 | download_url = 'https://github.com/GeoUtils/georaster/archive/v1.27.tar.gz', 11 | keywords = ['rasters', 'remote sensing', 'GIS', 'GDAL', 'projections', 'geoTIFF'], # arbitrary keywords 12 | classifiers = [], 13 | license = 'GNU GPL v.3' 14 | ) -------------------------------------------------------------------------------- /test/data/LE71400412000304SGS00_B4_crop.TIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeoUtils/georaster/244fa248e8c98feff2b5c062573dae14d4b31d91/test/data/LE71400412000304SGS00_B4_crop.TIF -------------------------------------------------------------------------------- /test/test_georaster.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test functions for georaster 3 | 4 | Andrew Tedstone, November 2019 5 | """ 6 | 7 | import unittest 8 | 9 | import os 10 | import hashlib 11 | import pathlib 12 | import pyproj 13 | 14 | import georaster 15 | 16 | test_data_landsat = os.path.join(georaster.test_data_path, 'LE71400412000304SGS00_B4_crop.TIF') 17 | test_data_landsat_sha256 = '271fa34e248f016f87109c8e81960caaa737558fbae110ec9e0d9e2d30d80c26' 18 | 19 | class TestTestDataIntegrity(unittest.TestCase): 20 | 21 | def test_integrity(self): 22 | file_sha256 = hashlib.sha256(pathlib.Path(test_data_landsat).read_bytes()).hexdigest() 23 | assert file_sha256 == test_data_landsat_sha256 24 | 25 | 26 | class TestAttributes(unittest.TestCase): 27 | 28 | im = georaster.SingleBandRaster(os.path.join(georaster.test_data_path, 'LE71400412000304SGS00_B4_crop.TIF')) 29 | 30 | def test_extent(self): 31 | left, right, bottom, top = self.im.extent 32 | self.assertAlmostEqual(left , 478000.000, places=3) 33 | self.assertAlmostEqual(right , 502000.000, places=3) 34 | self.assertAlmostEqual(bottom, 3088490.000, places=3) 35 | self.assertAlmostEqual(top , 3108140.000, places=3) 36 | 37 | 38 | 39 | class TestCoordinateTransforms(unittest.TestCase): 40 | """ 41 | Decimal degrees values calculated from Corner Coordinates output by gdalinfo, 42 | converted from DMS to decimal using: 43 | https://www.rapidtables.com/convert/number/degrees-minutes-seconds-to-degrees.html 44 | 45 | """ 46 | 47 | im = georaster.SingleBandRaster(os.path.join(georaster.test_data_path, 'LE71400412000304SGS00_B4_crop.TIF')) 48 | 49 | 50 | def test_get_extent_latlon(self): 51 | left, right, bottom, top = self.im.get_extent_latlon() 52 | self.assertAlmostEqual(left , 86.7760428, places=2) 53 | self.assertAlmostEqual(right , 87.0203598, places=2) 54 | self.assertAlmostEqual(bottom, 27.9211689, places=2) 55 | self.assertAlmostEqual(top , 28.098735, places=2) 56 | 57 | 58 | 59 | def test_get_extent_projected(self): 60 | 61 | # Use example of web mercator projection 62 | test_proj = pyproj.Proj('+init=epsg:3785') 63 | 64 | left, right, bottom, top = self.im.get_extent_projected(test_proj) 65 | # self.assertAlmostEqual(left , 9659864.900, places=2) 66 | # self.assertAlmostEqual(right , 9687063.081, places=2) 67 | # self.assertAlmostEqual(bottom, 3239029.409, places=2) 68 | # self.assertAlmostEqual(top , 3261427.912, places=2) 69 | self.assertAlmostEqual(left , 9659905.707276639, places=2) 70 | self.assertAlmostEqual(right , 9687062.141583452, places=2) 71 | self.assertAlmostEqual(bottom, 3239038.6219950984, places=2) 72 | self.assertAlmostEqual(top , 3261427.72683907, places=2) 73 | 74 | 75 | def test_coord_to_px_latlon(self): 76 | return 77 | 78 | 79 | def test_coord_to_px_projected(self): 80 | return 81 | 82 | 83 | def test_coord_to_px_projected_cellCorner(self): 84 | return 85 | 86 | 87 | def test_coordinates(self): 88 | return 89 | 90 | 91 | 92 | class TestValueRetrieval(unittest.TestCase): 93 | 94 | im = georaster.SingleBandRaster(os.path.join(georaster.test_data_path, 'LE71400412000304SGS00_B4_crop.TIF')) 95 | 96 | def test_value_at_coords_latlon(self): 97 | # 1. Lat/Lon, cell center coordinate. 98 | val = self.im.value_at_coords(86.90271, 28.00108, latlon=True) 99 | assert val == 67 100 | 101 | 102 | 103 | def test_value_at_coords_projected(self): 104 | # 2. Projected, cell center coordinate. 105 | val = self.im.value_at_coords(490434.605, 3097325.642) 106 | assert val == 67 107 | 108 | 109 | 110 | 111 | class TestLoading(unittest.TestCase): 112 | 113 | def test_extent(self): 114 | return 115 | 116 | 117 | def test_fromarray(self): 118 | return 119 | 120 | def test_read_single_band_subset(self): 121 | return 122 | 123 | 124 | if __name__ == '__main__': 125 | unittest.main() --------------------------------------------------------------------------------