# -*- coding:utf-8 -*- # This file is part of BlenderGIS # ***** GPL LICENSE BLOCK ***** # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # All rights reserved. # ***** GPL LICENSE BLOCK ***** import os import logging log = logging.getLogger(__name__) from ..lib import Tyf #geotags reader from .georef import GeoRef from .npimg import NpImage from .img_utils import getImgFormat, getImgDim from ..utils import XY as xy from ..errors import OverlapError from ..checkdeps import HAS_GDAL if HAS_GDAL: from osgeo import gdal class GeoRaster(): '''A class to represent a georaster file''' def __init__(self, path, subBoxGeo=None, useGDAL=False): ''' subBoxGeo : a BBOX object in CRS coordinate space useGDAL : use GDAL (if available) for extract raster informations ''' self.path = path self.wfPath = self._getWfPath() self.format = None #image file format (jpeg, tiff, png ...) self.size = None #raster dimension (width, height) in pixel self.depth = None #8, 16, 32 self.dtype = None #int, uint, float self.nbBands = None #number of bands self.noData = None self.georef = None if not useGDAL or not HAS_GDAL: self.format = getImgFormat(path) if self.format not in ['TIFF', 'BMP', 'PNG', 'JPEG', 'JPEG2000']: raise IOError("Unsupported format {}".format(self.format)) if self.isTiff: self._fromTIFF() if not self.isGeoref and self.hasWorldFile: self.georef = GeoRef.fromWorldFile(self.wfPath, self.size) else: pass else: # Try to read file header w, h = getImgDim(self.path) if w is None or h is None: raise IOError("Unable to read raster size") else: self.size = xy(w, h) #georef if self.hasWorldFile: self.georef = GeoRef.fromWorldFile(self.wfPath, self.size) #TODO add function to extract dtype, nBands & depth from jpg, png, bmp or jpeg2000 else: self._fromGDAL() if not self.isGeoref: raise IOError("Unable to read georef infos from worldfile or geotiff tags") if subBoxGeo is not None: self.georef.setSubBoxGeo(subBoxGeo) #GeoGef delegation by composition instead of inheritance #this special method is called whenever the requested attribute or method is not found in the object def __getattr__(self, attr): return getattr(self.georef, attr) ############################################ # Initialization Helpers ############################################ def _getWfPath(self): '''Try to find a worlfile path for this raster''' ext = self.path[-3:].lower() extTest = [] extTest.append(ext[0] + ext[2] +'w')# tfx, jgw, pgw ... extTest.append(extTest[0]+'x')# tfwx extTest.append(ext+'w')# tifw extTest.append('wld')#*.wld extTest.extend( [ext.upper() for ext in extTest] ) for wfExt in extTest: pathTest = self.path[0:len(self.path)-3] + wfExt if os.path.isfile(pathTest): return pathTest return None def _fromTIFF(self): '''Use Tyf to extract raster infos from geotiff tags''' if not self.isTiff or not self.fileExists: return tif = Tyf.open(self.path)[0] #Warning : Tyf object does not support k in dict test syntax nor get() method, use try block instead self.size = xy(tif['ImageWidth'], tif['ImageLength']) self.nbBands = tif['SamplesPerPixel'] self.depth = tif['BitsPerSample'] if self.nbBands > 1: self.depth = self.depth[0] sampleFormatMap = {1:'uint', 2:'int', 3:'float', None:'uint', 6:'complex'} try: self.dtype = sampleFormatMap[tif['SampleFormat']] except KeyError: self.dtype = 'uint' try: self.noData = float(tif['GDAL_NODATA']) except KeyError: self.noData = None #Get Georef try: self.georef = GeoRef.fromTyf(tif) except Exception as e: log.warning('Cannot extract georefencing informations from tif tags')#, exc_info=True) pass def _fromGDAL(self): '''Use GDAL to extract raster infos and init''' if self.path is None or not self.fileExists: raise IOError("Cannot find file on disk") ds = gdal.Open(self.path, gdal.GA_ReadOnly) self.size = xy(ds.RasterXSize, ds.RasterYSize) self.format = ds.GetDriver().ShortName if self.format in ['JP2OpenJPEG', 'JP2ECW', 'JP2KAK', 'JP2MrSID'] : self.format = 'JPEG2000' self.nbBands = ds.RasterCount b1 = ds.GetRasterBand(1) #first band (band index does not count from 0) self.noData = b1.GetNoDataValue() ddtype = gdal.GetDataTypeName(b1.DataType)#Byte, UInt16, Int16, UInt32, Int32, Float32, Float64 if ddtype == "Byte": self.dtype = 'uint' self.depth = 8 else: self.dtype = ddtype[0:len(ddtype)-2].lower() self.depth = int(ddtype[-2:]) #Get Georef self.georef = GeoRef.fromGDAL(ds) #Close (gdal has no garbage collector) ds, b1 = None, None ####################################### # Dynamic properties ####################################### @property def fileExists(self): '''Test if the file exists on disk''' return os.path.isfile(self.path) @property def baseName(self): if self.path is not None: folder, fileName = os.path.split(self.path) baseName, ext = os.path.splitext(fileName) return baseName @property def isTiff(self): '''Flag if the image format is TIFF''' if self.format in ['TIFF', 'GTiff']: return True else: return False @property def hasWorldFile(self): return self.wfPath is not None @property def isGeoref(self): '''Flag if georef parameters have been extracted''' if self.georef is not None: if self.origin is not None and self.pxSize is not None and self.rotation is not None: return True else: return False else: return False @property def isOneBand(self): return self.nbBands == 1 @property def isFloat(self): return self.dtype in ['Float', 'float'] @property def ddtype(self): ''' Get data type and depth in a concatenate string like 'int8', 'int16', 'uint16', 'int32', 'uint32', 'float32' ... Can be used to define numpy or gdal data type ''' if self.dtype is None or self.depth is None: return None else: return self.dtype + str(self.depth) def __repr__(self): return '\n'.join([ '* Paths infos :', ' path {}'.format(self.path), ' worldfile {}'.format(self.wfPath), ' format {}'.format(self.format), "* Data infos :", " size {}".format(self.size), " bit depth {}".format(self.depth), " data type {}".format(self.dtype), " number of bands {}".format(self.nbBands), " nodata value {}".format(self.noData), "* Georef & Geometry : \n{}".format(self.georef) ]) ####################################### # Methods ####################################### def toGDAL(self): '''Get GDAL dataset''' return gdal.Open(self.path, gdal.GA_ReadOnly) def readAsNpArray(self, subset=True): '''Read raster pixels values as Numpy Array''' if subset and self.subBoxGeo is not None: #georef = GeoRef(self.size, self.pxSize, self.subBoxGeoOrigin, rot=self.rotation, pxCenter=True) img = NpImage(self.path, subBoxPx=self.subBoxPx, noData=self.noData, georef=self.georef, adjustGeoref=True) else: img = NpImage(self.path, noData=self.noData, georef=self.georef) return img