Ejemplo n.º 1
0
    def test_get_projection_raises_NansatProjectionError(self, dataset):
        dataset.GetProjection.return_value = ''
        dataset.GetGCPProjection.return_value = ''

        vrt = VRT()
        with self.assertRaises(NansatProjectionError):
            proj = vrt.get_projection()
Ejemplo n.º 2
0
    def test_get_projection_raises_NansatProjectionError(self, dataset):
        dataset.GetProjection.return_value = ''
        dataset.GetGCPProjection.return_value = ''
        dataset.GetMetadata.return_value = {}

        vrt = VRT()
        with self.assertRaises(NansatProjectionError):
            proj = vrt.get_projection()
Ejemplo n.º 3
0
    def test_get_projection_geolocation(self, dataset):
        proj = 'SOME_PROJECTION'
        dataset.GetProjection.return_value = ''
        dataset.GetGCPProjection.return_value = ''
        dataset.GetMetadata.return_value = {'SRS': proj}

        vrt = VRT()
        proj_src = vrt.get_projection()
        self.assertEqual(proj_src, (proj, 'geolocation'))
Ejemplo n.º 4
0
    def test_get_projection_dataset(self, dataset):
        proj = 'SOME_PROJECTION'
        dataset.GetProjection.return_value = proj
        dataset.GetGCPProjection.return_value = ''
        dataset.GetMetadata.return_value = {}

        vrt = VRT()
        proj_src = vrt.get_projection()
        self.assertEqual(proj_src, (proj, 'dataset'))
Ejemplo n.º 5
0
    def test_get_projection_geolocation(self, dataset):
        proj = 'SOME_PROJECTION'
        dataset.GetProjection.return_value = ''
        dataset.GetGCPProjection.return_value = ''
        dataset.GetMetadata.return_value = {'SRS': proj}

        vrt = VRT()
        proj_src = vrt.get_projection()
        self.assertEqual(proj_src, (proj, 'geolocation'))
Ejemplo n.º 6
0
    def test_get_projection_dataset(self, dataset):
        proj = 'SOME_PROJECTION'
        dataset.GetProjection.return_value = proj
        dataset.GetGCPProjection.return_value = ''
        dataset.GetMetadata.return_value = {}

        vrt = VRT()
        proj_src = vrt.get_projection()
        self.assertEqual(proj_src, (proj, 'dataset'))
Ejemplo n.º 7
0
class Domain(object):
    '''Container for geographical reference of a raster

    A Domain object describes all attributes of geographical
    reference of a raster:
      * width and height (number of pixels)
      * pixel size (e.g. in decimal degrees or in meters)
      * relation between pixel/line coordinates and geographical
        coordinates (e.g. a linear relation)
      * type of data projection (e.g. geographical or stereographic)

    The core of Domain is a GDAL Dataset. It has no bands, but only
    georeference information: rasterXsize, rasterYsize, GeoTransform and
    Projection or GCPs, etc. which fully describe dimentions and spatial
    reference of the grid.

    There are three ways to store geo-reference in a GDAL dataset:
      * Using GeoTransfrom to define linear relationship between raster
        pixel/line and geographical X/Y coordinates
      * Using GCPs (set of Ground Control Points) to define non-linear
        relationship between pixel/line and X/Y
      * Using Geolocation Array - full grids of X/Y coordinates for
        each pixel of a raster
    The relation between X/Y coordinates of the raster and latitude/longitude
    coordinates is defined by projection type and projection parameters.
    These pieces of information are therefore stored in Domain:
      * Type and parameters of projection +
        * GeoTransform, or
        * GCPs, or
        * GeolocationArrays

    Domain has methods for basic operations with georeference information:
      * creating georeference from input options;
      * fetching corner, border or full grids of X/Y coordinates;
      * making map of the georeferenced grid in a PNG or KML file;
      * and some more...

    The main attribute of Domain is a VRT object self.vrt.
    Nansat inherits from Domain and adds bands to self.vrt

    '''
    def __init__(self,
                 srs=None,
                 ext=None,
                 ds=None,
                 lon=None,
                 lat=None,
                 name='',
                 logLevel=None):
        '''Create Domain from GDALDataset or string options or lat/lon grids

        d = Domain(srs, ext)
            Size, extent and spatial reference is given by strings
        d = Domain(ds=GDALDataset):
            Size, extent and spatial reference is copied from input
            GDAL dataset
        d = Domain(srs, ds=GDALDataset):
            Spatial reference is given by srs, but size and extent is
            determined
            from input GDAL dataset
        d = Domain(lon=lonGrid, lat=latGrid)
            Size, extent and spatial reference is given by two grids

        Parameters
        ----------
        srs : PROJ4 or EPSG or WKT or NSR or osr.SpatialReference()
            Input parameter for nansat.NSR()
        ext : string
            some gdalwarp options + additional options
            [http://www.gdal.org/gdalwarp.html]
            Specifies extent, resolution / size
            Available options: (('-te' or '-lle') and ('-tr' or '-ts'))
            (e.g. '-lle -10 30 55 60 -ts 1000 1000' or
            '-te 100 2000 300 10000 -tr 300 200')
            -tr resolutionx resolutiony
            -ts sizex sizey
            -te xmin ymin xmax ymax
            -lle lonmin latmin lonmax latmax
        ds : GDAL dataset
        lat : Numpy array
            Grid with latitudes
        lon : Numpy array
            Grid with longitudes
        name : string, optional
            Name to be added to the Domain object
        logLevel : int, optional, default=30
            level of logging

        Raises
        -------
        ProjectionError : occurs when Projection() is empty
            despite it is required for creating extentDic.
        OptionError : occures when the arguments are not proper.

        Modifies
        ---------
        self.vrt.datasetset : dataset in memory
            dataset is created based on the input arguments

        See Also
        ---------
        Nansat.reproject()
        [http://www.gdal.org/gdalwarp.html]
        [http://trac.osgeo.org/proj/]
        [http://spatialreference.org/]
        [http://www.gdal.org/ogr/osr_tutorial.html]

        '''
        # set default attributes
        self.logger = add_logger('Nansat', logLevel)
        self.name = name

        self.logger.debug('ds: %s' % str(ds))
        self.logger.debug('srs: %s' % srs)
        self.logger.debug('ext: %s' % ext)

        # If too much information is given raise error
        if ds is not None and srs is not None and ext is not None:
            raise OptionError('Ambiguous specification of both '
                              'dataset, srs- and ext-strings.')

        # choose between input opitons:
        # ds
        # ds and srs
        # srs and ext
        # lon and lat

        # if only a dataset is given:
        #     copy geo-reference from the dataset
        if ds is not None and srs is None:
            self.vrt = VRT(gdalDataset=ds)

        # If dataset and srs are given (but not ext):
        #   use AutoCreateWarpedVRT to determine bounds and resolution
        elif ds is not None and srs is not None:
            srs = NSR(srs)
            tmpVRT = gdal.AutoCreateWarpedVRT(ds, None, srs.wkt)
            if tmpVRT is None:
                raise ProjectionError('Could not warp the given dataset'
                                      'to the given SRS.')
            else:
                self.vrt = VRT(gdalDataset=tmpVRT)

        # If SpatialRef and extent string are given (but not dataset)
        elif srs is not None and ext is not None:
            srs = NSR(srs)
            # create full dictionary of parameters
            extentDic = self._create_extentDic(ext)

            # convert -lle to -te
            if 'lle' in extentDic.keys():
                extentDic = self._convert_extentDic(srs, extentDic)

            # get size/extent from the created extet dictionary
            [geoTransform, rasterXSize,
             rasterYSize] = self._get_geotransform(extentDic)
            # create VRT object with given geo-reference parameters
            self.vrt = VRT(srcGeoTransform=geoTransform,
                           srcProjection=srs.wkt,
                           srcRasterXSize=rasterXSize,
                           srcRasterYSize=rasterYSize)
            self.extentDic = extentDic
        elif lat is not None and lon is not None:
            # create self.vrt from given lat/lon
            self.vrt = VRT(lat=lat, lon=lon)
        else:
            raise OptionError('"dataset" or "srsString and extentString" '
                              'or "dataset and srsString" are required')

        self.logger.debug('vrt.dataset: %s' % str(self.vrt.dataset))

    def __repr__(self):
        '''Creates string with basic info about the Domain object

        Modifies
        ---------
        Print size, projection and corner coordinates

        '''
        outStr = 'Domain:[%d x %d]\n' % (self.vrt.dataset.RasterXSize,
                                         self.vrt.dataset.RasterYSize)
        outStr += '-' * 40 + '\n'
        try:
            corners = self.get_corners()
        except:
            self.logger.error('Cannot read projection from source!')
        else:
            outStr += 'Projection:\n'
            outStr += (NSR(self.vrt.get_projection()).ExportToPrettyWkt(1) +
                       '\n')
            outStr += '-' * 40 + '\n'
            outStr += 'Corners (lon, lat):\n'
            outStr += '\t (%6.2f, %6.2f)  (%6.2f, %6.2f)\n' % (
                corners[0][0], corners[1][0], corners[0][2], corners[1][2])
            outStr += '\t (%6.2f, %6.2f)  (%6.2f, %6.2f)\n' % (
                corners[0][1], corners[1][1], corners[0][3], corners[1][3])
        return outStr

    def write_kml(self, xmlFileName=None, kmlFileName=None):
        '''Write KML file with domains

        Convert XML-file with domains into KML-file for GoogleEarth
        or write KML-file with the current Domain

        Parameters
        -----------
        xmlFileName : string, optional
            Name of the XML-file to convert. If only this value is given
            - kmlFileName=xmlFileName+'.kml'

        kmlFileName : string, optional
            Name of the KML-file to generate from the current Domain

        '''
        # test input options
        if xmlFileName is not None and kmlFileName is None:
            # if only input XML-file is given - convert it to KML

            # open XML, get all domains
            xmlFile = file(xmlFileName, 'rb')
            kmlFileName = xmlFileName + '.kml'
            xmlDomains = ElementTree(file=xmlFile).getroot()
            xmlFile.close()

            # convert domains in XML into list of domains
            domains = []
            for xmlDomain in list(xmlDomains):
                # append Domain object to domains list
                domainName = xmlDomain.attrib['name']
                domains.append(Domain(srs=xmlFileName, ext=domainName))

        elif xmlFileName is None and kmlFileName is not None:
            # if only output KML-file is given
            # then convert the current domain to KML
            domains = [self]

        else:
            # otherwise it is potentially error
            raise OptionError('Either xmlFileName(%s)\
             or kmlFileName(%s) are wrong' % (xmlFileName, kmlFileName))

        # open KML, write header
        kmlFile = file(kmlFileName, 'wt')
        kmlFile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        kmlFile.write('<kml xmlns="http://www.opengis.net/kml/2.2" '
                      'xmlns:gx="http://www.google.com/kml/ext/2.2" '
                      'xmlns:kml="http://www.opengis.net/kml/2.2" '
                      'xmlns:atom="http://www.w3.org/2005/Atom">\n')
        kmlFile.write('<Document>\n')
        kmlFile.write('    <name>%s</name>\n' % kmlFileName)
        kmlFile.write('        <Folder><name>%s</name><open>1</open>\n' %
                      kmlFileName)

        # get border of each domain and add to KML
        for domain in list(domains):
            kmlEntry = domain._get_border_kml()
            kmlFile.write(kmlEntry)

        # write footer and close
        kmlFile.write('        </Folder></Document></kml>\n')
        kmlFile.close()

    def write_kml_image(self, kmlFileName=None, kmlFigureName=None):
        '''Create KML file for already projected image

        Write Domain Image into KML-file for GoogleEarth

        Parameters
        -----------
        kmlFileName : string, optional
            Name of the KML-file to generate from the current Domain
        kmlFigureName : string, optional
            Name of the projected image stored in .png format

        Examples
        ---------
        # First of all, reproject an image into Lat/Lon WGS84
          (Simple Cylindrical) projection
        # 1. Cancel previous reprojection
        # 2. Get corners of the image and the pixel resolution
        # 3. Create Domain with stereographic projection,
        #    corner coordinates and resolution 1000m
        # 4. Reproject
        # 5. Write image
        # 6. Write KML for the image
        n.reproject() # 1.
        lons, lats = n.get_corners() # 2.
        srsString = '+proj=latlong +datum=WGS84 +ellps=WGS84 +no_defs'
        extentString = '-lle %f %f %f %f -ts 3000 3000'
        % (min(lons), min(lats), max(lons), max(lats))
        d = Domain(srs=srsString, ext=extentString) # 3.
        n.reproject(d) # 4.
        n.write_figure(fileName=figureName, bands=[3], clim=[0,0.15],
                       cmapName='gray', transparency=0) # 5.
        n.write_kml_image(kmlFileName=oPath + fileName + '.kml',
                          kmlFigureName=figureName) # 6.

        '''
        # test input options
        if kmlFileName is None:
            raise OptionError('kmlFileName(%s) is wrong' % (kmlFileName))

        if kmlFigureName is None:
            raise OptionError('kmlFigureName(%s) is not specified' %
                              (kmlFigureName))

        # open KML, write header
        kmlFile = file(kmlFileName, 'wt')
        kmlFile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        kmlFile.write('<kml xmlns="http://www.opengis.net/kml/2.2" '
                      'xmlns:gx="http://www.google.com/kml/ext/2.2" '
                      'xmlns:kml="http://www.opengis.net/kml/2.2" '
                      'xmlns:atom="http://www.w3.org/2005/Atom">\n')
        kmlFile.write('<GroundOverlay>\n')
        kmlFile.write('    <name>%s</name>\n' % kmlFileName)
        kmlFile.write('    <Icon>\n')
        kmlFile.write('        <href>%s</href>\n' % kmlFigureName)
        kmlFile.write('        <viewBoundScale>0.75</viewBoundScale>\n')
        kmlFile.write('    </Icon>\n')

        # get corner of the domain and add to KML
        domainLon, domainLat = self.get_corners()

        kmlFile.write('    <LatLonBox>\n')
        kmlFile.write('        <north>%s</north>\n' % max(domainLat))
        kmlFile.write('        <south>%s</south>\n' % min(domainLat))
        kmlFile.write('        <east>%s</east>\n' % max(domainLon))
        kmlFile.write('        <west>%s</west>\n' % min(domainLon))
        kmlFile.write('    </LatLonBox>\n')

        # write footer and close
        kmlFile.write('</GroundOverlay>\n')
        kmlFile.write('</kml>')
        kmlFile.close()

    def get_geolocation_grids(self, stepSize=1, dstSRS=NSR()):
        '''Get longitude and latitude grids representing the full data grid

        If GEOLOCATION is not present in the self.vrt.dataset then grids
        are generated by converting pixel/line of each pixel into lat/lon
        If GEOLOCATION is present in the self.vrt.dataset then grids are read
        from the geolocation bands.

        Parameters
        -----------
        stepSize : int
            Reduction factor if output is desired on a reduced grid size

        Returns
        --------
        longitude : numpy array
            grid with longitudes
        latitude : numpy array
            grid with latitudes
        '''

        X = range(0, self.vrt.dataset.RasterXSize, stepSize)
        Y = range(0, self.vrt.dataset.RasterYSize, stepSize)
        Xm, Ym = np.meshgrid(X, Y)

        if len(self.vrt.geolocationArray.d) > 0:
            # if the vrt dataset has geolocationArray
            # read lon,lat grids from geolocationArray
            lon, lat = self.vrt.geolocationArray.get_geolocation_grids()
            longitude, latitude = lon[Ym, Xm], lat[Ym, Xm]
        else:
            # generate lon,lat grids using GDAL Transformer
            lonVec, latVec = self.transform_points(Xm.flatten(),
                                                   Ym.flatten(),
                                                   dstSRS=dstSRS)
            longitude = lonVec.reshape(Xm.shape)
            latitude = latVec.reshape(Xm.shape)

        return longitude, latitude

    def _convert_extentDic(self, dstSRS, extentDic):
        '''Convert -lle option (lat/lon) to -te (proper coordinate system)

        Source SRS from LAT/LON projection and target SRS from dstWKT.
        Create osr.CoordinateTransformation based on these SRSs and
        convert given values in degrees to the destination coordinate
        system given by WKT.
        Add key 'te' and the converted values into the extentDic.

        Parameters
        -----------
        dstSRS : NSR
            Destination Spatial Reference
        extentDic : dictionary
            dictionary with 'lle' key

        Returns
        --------
        extentDic : dictionary
            input dictionary + 'te' key and its values

        '''
        coorTrans = osr.CoordinateTransformation(NSR(), dstSRS)

        # convert lat/lon given by 'lle' to the target coordinate system and
        # add key 'te' and the converted values to extentDic
        x1, y1, _ = coorTrans.TransformPoint(extentDic['lle'][0],
                                             extentDic['lle'][3])
        x2, y2, _ = coorTrans.TransformPoint(extentDic['lle'][2],
                                             extentDic['lle'][3])
        x3, y3, _ = coorTrans.TransformPoint(extentDic['lle'][2],
                                             extentDic['lle'][1])
        x4, y4, _ = coorTrans.TransformPoint(extentDic['lle'][0],
                                             extentDic['lle'][1])

        minX = min([x1, x2, x3, x4])
        maxX = max([x1, x2, x3, x4])
        minY = min([y1, y2, y3, y4])
        maxY = max([y1, y2, y3, y4])

        extentDic['te'] = [minX, minY, maxX, maxY]

        return extentDic

    def _create_extentDic(self, extentString):
        '''Create a dictionary from extentString

        Check if extentString is proper.
            * '-te' and '-lle' take 4 numbers.
            * '-ts' and '-tr' take 2 numbers.
            * the combination should be ('-te' or '-lle') and ('-ts' or '-tr')
        If it is proper, create a dictionary
        Otherwise, raise the error.

        Parameters
        -----------
        extentString : string
            '-te xMin yMin xMax yMax',
            '-tr xResolution yResolution',
            '-ts width height',
            '-lle minlon minlat maxlon maxlat'

        Returns
        --------
        extentDic : dictionary
            has key ('te' or 'lle') and ('tr' or 'ts') and their values.

        Raises
        -------
        OptionError : occurs when the extentString is improper

        '''
        extentDic = {}

        # Find -re text
        str_tr = re.findall('-tr\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?',
                            extentString)
        if str_tr != []:
            # Check the number of -tr elements
            elm_str = str(str_tr[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 3 or elms_str[2] == '-':
                raise OptionError('Domain._create_extentDic():'
                                  '-tr is used as'
                                  '"-tr xResolution yResolution"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_tr[0], '')
            trElem = str(str_tr).split(None)
            trkey = trElem[0].translate(string.maketrans('', ''), "[]-'")
            if trkey != '':
                elements = []
                for i in range(2):
                    elements.append(
                        float(trElem[i + 1].translate(string.maketrans('', ''),
                                                      "'[]'")))
                extentDic[trkey] = elements

        # Find -ts text
        str_ts = re.findall('-ts\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?',
                            extentString)
        if str_ts != []:
            # Check the number of -ts elements
            elm_str = str(str_ts[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 3 or elms_str[2] == '-':
                raise OptionError('Domain._create_extentDic(): '
                                  '"-ts" is used as "-ts width height"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_ts[0], '')
            tsElem = str(str_ts).split(None)
            tskey = tsElem[0].translate(string.maketrans('', ''), "[]-'")
            if tskey != '':
                elements = []
                for i in range(2):
                    elements.append(
                        float(tsElem[i + 1].translate(string.maketrans('', ''),
                                                      "[]'")))
                extentDic[tskey] = elements

        # Find -te text
        str_te = re.findall(
            '-te\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s'
            '+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?', extentString)
        if str_te != []:
            # Check the number of -te elements
            elm_str = str(str_te[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 5:
                raise OptionError('Domain._create_extentDic():'
                                  '-te is used as "-te xMin yMin xMax yMax"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_te[0], '')
            teElem = str(str_te).split(None)
            tekey = teElem[0].translate(string.maketrans('', ''), "[]-'")
            if tekey != '':
                elements = []
                for i in range(4):
                    elements.append(
                        float(teElem[i + 1].translate(string.maketrans('', ''),
                                                      "[]'")))
                extentDic[tekey] = elements

        # Find -lle text
        str_lle = re.findall(
            '-lle\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s'
            '+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?', extentString)
        if str_lle != []:
            # Check the number of -lle elements
            elm_str = str(str_lle[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 5:
                raise OptionError('Domain._create_extentDic():'
                                  '-lle is used as '
                                  '"-lle minlon minlat maxlon maxlat"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_lle[0], '')
            lleElem = str(str_lle).split(None)
            llekey = lleElem[0].translate(string.maketrans('', ''), "[]-'")
            if llekey != '':
                elements = []
                for i in range(4):
                    elements.append(
                        float(lleElem[i + 1].translate(
                            string.maketrans('', ''), "[]'")))
                extentDic[llekey] = elements

        result = re.search('\S', extentString)
        # if there are unnecessary letters, give an error
        if result is not None:
            raise OptionError(
                'Domain._create_extentDic():'
                'extentString is not redable :', extentString)

        # check if one of '-te' and '-lle' is given
        if ('lle' not in extentDic) and ('te' not in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-lle" or "-te" is required.')
        elif ('lle' in extentDic) and ('te' in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-lle" or "-te" should be chosen.')

        # check if one of '-ts' and '-tr' is given
        if ('ts' not in extentDic) and ('tr' not in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-ts" or "-tr" is required.')
        elif ('ts' in extentDic) and ('tr' in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-ts" or "-tr" should be chosen.')
        return extentDic

    def get_border(self, nPoints=10):
        '''Generate two vectors with values of lat/lon for the border of domain

        Parameters
        -----------
        nPoints : int, optional
            Number of points on each border

        Returns
        --------
        lonVec, latVec : lists
            vectors with lon/lat values for each point at the border

        '''
        # prepare vectors with pixels and lines for upper, left, lower
        # and right borders
        sizes = [self.vrt.dataset.RasterXSize, self.vrt.dataset.RasterYSize]

        rcVector1 = [[], []]
        rcVector2 = [[], []]
        # loop for pixels and lines
        for n in range(0, 2):
            step = max(1, sizes[n] / nPoints)
            rcVector1[n] = range(0, sizes[n], step)[0:nPoints]
            rcVector1[n].append(sizes[n])
            rcVector2[n] = rcVector1[n][:]
            rcVector2[n].reverse()

        # coumpund vectors of pixels (col) and lines (row)
        colVector = (rcVector1[0] + [sizes[0]] * len(rcVector1[1]) +
                     rcVector2[0] + [0] * len(rcVector1[1]))
        rowVector = ([0] * len(rcVector1[0]) + rcVector1[1] +
                     [sizes[1]] * len(rcVector1[0]) + rcVector2[1])

        return self.transform_points(colVector, rowVector)

    def _get_border_kml(self, *args, **kwargs):
        '''Generate Placemark entry for KML

        Returns
        --------
        kmlEntry : String
            String with the Placemark entry

        '''
        domainLon, domainLat = self.get_border(*args, **kwargs)

        # convert Border coordinates into KML-like string
        coordinates = ''
        for lon, lat in zip(domainLon, domainLat):
            coordinates += '%f,%f,0 ' % (lon, lat)

        kmlEntry = ''
        # write placemark: name, style, polygon, coordinates
        kmlEntry += '            <Placemark>\n'
        kmlEntry += '                <name>%s</name>\n' % self.name
        kmlEntry += '                <Style>\n'
        kmlEntry += '                    <LineStyle><color>ffffffff</color>'\
                    '</LineStyle>\n'
        kmlEntry += '                    <PolyStyle><fill>0</fill>'\
                    '</PolyStyle>\n'
        kmlEntry += '                </Style>\n'
        kmlEntry += '                <Polygon><tessellate>1</tessellate>'\
                    '<outerBoundaryIs><LinearRing><coordinates>\n'
        kmlEntry += coordinates + '\n'
        kmlEntry += '            </coordinates></LinearRing>'\
                    '</outerBoundaryIs></Polygon></Placemark>\n'

        return kmlEntry

    def get_border_wkt(self, *args, **kwargs):
        '''Creates string with WKT representation of the border polygon

        Returns
        --------
        WKTPolygon : string
            string with WKT representation of the border polygon

        '''
        lonList, latList = self.get_border(*args, **kwargs)

        # apply > 180 deg correction to longitudes
        for ilon, lon in enumerate(lonList):
            lonList[ilon] = copysign(
                acos(cos(lon * pi / 180.)) / pi * 180, sin(lon * pi / 180.))

        polyCont = ','.join(
            str(lon) + ' ' + str(lat) for lon, lat in zip(lonList, latList))
        # outer quotes have to be double and inner - single!
        # wktPolygon = "PolygonFromText('POLYGON((%s))')" % polyCont
        wkt = 'POLYGON((%s))' % polyCont
        return wkt

    def get_border_geometry(self, *args, **kwargs):
        ''' Get OGR Geometry of the border Polygon

        Returns
        -------
        OGR Geometry, type Polygon

        '''

        return ogr.CreateGeometryFromWkt(self.get_border_wkt(*args, **kwargs))

    def overlaps(self, anotherDomain):
        ''' Checks if this Domain overlaps another Domain

        Returns
        -------
        overlaps : bool
            True if Domains overlaps, False otherwise

        '''

        return self.get_border_geometry().Intersects(
            anotherDomain.get_border_geometry())

    def contains(self, anotherDomain):
        ''' Checks if this Domain fully covers another Domain

        Returns
        -------
        contains : bool
            True if this Domain fully covers another Domain, False otherwise

        '''

        return self.get_border_geometry().Contains(
            anotherDomain.get_border_geometry())

    def get_border_postgis(self):
        ''' Get PostGIS formatted string of the border Polygon

        Returns
        -------
        str : 'PolygonFromText(PolygonWKT)'

        '''

        return "PolygonFromText('%s')" % self.get_border_wkt()

    def get_corners(self):
        '''Get coordinates of corners of the Domain

        Returns
        --------
        lonVec, latVec : lists
            vectors with lon/lat values for each corner

        '''

        colVector = [
            0, 0, self.vrt.dataset.RasterXSize, self.vrt.dataset.RasterXSize
        ]
        rowVector = [
            0, self.vrt.dataset.RasterYSize, 0, self.vrt.dataset.RasterYSize
        ]
        return self.transform_points(colVector, rowVector)

    def get_min_max_lat_lon(self):
        '''Get minimum and maximum lat and long values in the geolocation grid

        Returns
        --------
        minLat, maxLat, minLon, maxLon : float
            min/max lon/lat values for the Domain

        '''
        allLongitudes, allLatitudes = self.get_geolocation_grids()
        maxLat = -90
        minLat = 90
        for latitudes in allLatitudes:
            for lat in latitudes:
                if lat > maxLat:
                    maxLat = lat
                if lat < minLat:
                    minLat = lat

        maxLon = -180
        minLon = 180
        for longitudes in allLongitudes:
            for lon in longitudes:
                if lon > maxLon:
                    maxLon = lon
                if lon < minLon:
                    minLon = lon

        return minLat, maxLat, minLon, maxLon

    def get_pixelsize_meters(self):
        '''Returns the pixelsize (deltaX, deltaY) of the domain

        For projected domains, the exact result which is constant
        over the domain is returned.
        For geographic (lon-lat) projections, or domains with no geotransform,
        the haversine formula is used to calculate the pixel size
        in the center of the domain.
        Returns
        --------
        deltaX, deltaY : float
        pixel size in X and Y directions given in meters
        '''

        srs = osr.SpatialReference(self.vrt.dataset.GetProjection())
        if srs.IsProjected:
            if srs.GetAttrValue('unit') == 'metre':
                geoTransform = self.vrt.dataset.GetGeoTransform()
                deltaX = abs(geoTransform[1])
                deltaY = abs(geoTransform[5])
                return deltaX, deltaY

        # Estimate pixel size in center of domain using haversine formula
        centerCol = round(self.vrt.dataset.RasterXSize / 2)
        centerRow = round(self.vrt.dataset.RasterYSize / 2)
        lon00, lat00 = self.transform_points([centerCol], [centerRow])
        lon01, lat01 = self.transform_points([centerCol], [centerRow + 1])
        lon10, lat10 = self.transform_points([centerCol + 1], [centerRow])

        deltaX = haversine(lon00, lat00, lon01, lat01)
        deltaY = haversine(lon00, lat00, lon10, lat10)
        return deltaX[0], deltaY[0]

    def _get_geotransform(self, extentDic):
        '''
        the new coordinates and raster size are calculated based on
        the given extentDic.

        Parameters
        -----------
        extentDic : dictionary
            includes 'te' key and 'ts' or 'tr' key

        Raises
        -------
        OptionError : occurs when maxX - minX < 0 or maxY - minY < 0
        OptionError : occurs when the given resolution is larger than
                     width or height.

        Returns
        --------
        coordinate : list with 6 float
            GeoTransform

        rasterSize : list with two int
            rasterXSize and rasterYSize

        '''
        # recalculate GeoTransform based on extent option
        minX = extentDic['te'][0]
        minY = extentDic['te'][1]
        maxX = extentDic['te'][2]
        maxY = extentDic['te'][3]
        cornerX = minX
        cornerY = maxY
        width = maxX - minX
        height = maxY - minY
        if width <= 0 or height <= 0:
            raise OptionError('The extent is illegal. '
                              '"-te xMin yMin xMax yMax" ')

        if 'tr' in extentDic.keys():
            resolutionX = extentDic['tr'][0]
            resolutionY = -(extentDic['tr'][1])
            if (width < resolutionX or height < resolutionY):
                raise OptionError('"-tr" is too large. '
                                  'width is %s, height is %s ' %
                                  (str(width), str(height)))
            rasterXSize = width / resolutionX
            rasterYSize = abs(height / resolutionY)
        else:
            rasterXSize = extentDic['ts'][0]
            rasterYSize = extentDic['ts'][1]
            resolutionX = width / rasterXSize
            resolutionY = -abs(height / rasterYSize)

        # create a list for GeoTransform
        coordinates = [cornerX, resolutionX, 0.0, cornerY, 0.0, resolutionY]

        return coordinates, int(rasterXSize), int(rasterYSize)

    def transform_points(self, colVector, rowVector, DstToSrc=0, dstSRS=NSR()):
        '''Transform given lists of X,Y coordinates into lon/lat or inverse

        Parameters
        -----------
        colVector : lists
            X and Y coordinates in pixel/line or lon/lat  coordinate system
        DstToSrc : 0 or 1
            0 - forward transform (pix/line => lon/lat)
            1 - inverse transformation
        dstSRS : NSR
            destination spatial reference
            
        Returns
        --------
        X, Y : lists
            X and Y coordinates in lon/lat or pixel/line coordinate system

        '''
        return self.vrt.transform_points(colVector,
                                         rowVector,
                                         DstToSrc,
                                         dstSRS=dstSRS)

    def azimuth_y(self, reductionFactor=1):
        '''Calculate the angle of each pixel position vector with respect to
        the Y-axis (azimuth).

        In general, azimuth is the angle from a reference vector (e.g., the
        direction to North) to the chosen position vector. The azimuth
        increases clockwise from direction to North.
        http://en.wikipedia.org/wiki/Azimuth

        Parameters
        -----------
        reductionFactor : integer
            factor by which the size of the output array is reduced

        Returns
        -------
        azimuth : numpy array
            Values of azimuth in degrees in range 0 - 360

        '''

        lon, lat = self.get_geolocation_grids(reductionFactor)
        a = initial_bearing(lon[1:, :], lat[1:, :], lon[:-1:, :], lat[:-1:, :])
        # Repeat last row once to match size of lon-lat grids
        a = np.vstack((a, a[-1, :]))
        return a

    def shape(self):
        '''Return Numpy-like shape of Domain object (ySize, xSize)

        Returns
        --------
        shape : tuple of two INT
            Numpy-like shape of Domain object (ySize, xSize)

        '''
        return self.vrt.dataset.RasterYSize, self.vrt.dataset.RasterXSize

    def write_map(self,
                  outputFileName,
                  lonVec=None,
                  latVec=None,
                  lonBorder=10.,
                  latBorder=10.,
                  figureSize=(6, 6),
                  dpi=50,
                  projection='cyl',
                  resolution='c',
                  continetsColor='coral',
                  meridians=10,
                  parallels=10,
                  pColor='r',
                  pLine='k',
                  pAlpha=0.5,
                  padding=0.,
                  merLabels=[False, False, False, False],
                  parLabels=[False, False, False, False],
                  pltshow=False,
                  labels=None):
        ''' Create an image with a map of the domain

        Uses Basemap to create a World Map
        Adds a semitransparent patch with outline of the Domain
        Writes to an image file

        Parameters
        -----------
        outputFileName : string
            name of the output file name
        lonVec : [floats] or [[floats]]
            longitudes of patches to display
        latVec : [floats] or [[floats]]
            latitudes of patches to display
        lonBorder : float
            10, horisontal border around patch (degrees of longitude)
        latBorder : float
            10, vertical border around patch (degrees of latitude)
        figureSize : tuple of two integers
            (6, 6), size of the generated figure in inches
        dpi: int
            50, resolution of the output figure (size 6,6 and dpi 50
            produces 300 x 300 figure)
        projection : string, one of Basemap projections
            'cyl', projection of the map
        resolution : string, resolution of the map
            'c', crude
            'l', low
            'i', intermediate
            'h', high
            'f', full
        continetsColor : string or any matplotlib color representation
            'coral', color of continets
        meridians : int
            10, number of meridians to draw
        parallels : int
            10, number of parallels to draw
        pColor : string or any matplotlib color representation
            'r', color of the Domain patch
        pLine : string or any matplotlib color representation
            'k', color of the Domain outline
        pAlpha : float 0 - 1
            0.5, transparency of Domain patch
        padding : float
            0., width of white padding around the map
        merLabels : list of 4 booleans
            where to put meridian labels, see also Basemap.drawmeridians()
        parLables : list of 4 booleans
            where to put parallel labels, see also Basemap.drawparallels()
        labels : list of str
            labels to print on top of patches
        '''
        # if lat/lon vectors are not given as input
        if lonVec is None or latVec is None or len(lonVec) != len(latVec):
            lonVec, latVec = self.get_border()

        # convert vectors to numpy arrays
        lonVec = np.array(lonVec)
        latVec = np.array(latVec)

        # estimate mean/min/max values of lat/lon of the shown area
        # (real lat min max +/- latBorder) and (real lon min max +/- lonBorder)
        minLon = max(-180, lonVec.min() - lonBorder)
        maxLon = min(180, lonVec.max() + lonBorder)
        minLat = max(-90, latVec.min() - latBorder)
        maxLat = min(90, latVec.max() + latBorder)
        meanLon = lonVec.mean()
        meanLat = latVec.mean()

        # generate template map (can be also tmerc)
        plt.figure(num=1, figsize=figureSize, dpi=dpi)
        bmap = Basemap(projection=projection,
                       lat_0=meanLat,
                       lon_0=meanLon,
                       llcrnrlon=minLon,
                       llcrnrlat=minLat,
                       urcrnrlon=maxLon,
                       urcrnrlat=maxLat,
                       resolution=resolution)

        # add content: coastline, continents, meridians, parallels
        bmap.drawcoastlines()
        bmap.fillcontinents(color=continetsColor)
        bmap.drawmeridians(np.linspace(minLon, maxLon, meridians),
                           labels=merLabels,
                           fmt='%2.1f')
        bmap.drawparallels(np.linspace(minLat, maxLat, parallels),
                           labels=parLabels,
                           fmt='%2.1f')

        # convert input lat/lon vectors to arrays of vectors with one row
        # if only one vector was given
        if len(lonVec.shape) == 1:
            lonVec = [lonVec]
            latVec = [latVec]

        for i in range(len(lonVec)):
            # convert lat/lons to map units
            mapX, mapY = bmap(list(lonVec[i].flat), list(latVec[i].flat))

            # from x/y vectors create a Patch to be added to map
            boundary = Polygon(zip(mapX, mapY),
                               alpha=pAlpha,
                               ec=pLine,
                               fc=pColor)

            # add patch to the map
            plt.gca().add_patch(boundary)
            plt.gca().set_aspect('auto')

            if labels is not None and labels[i] is not None:
                plt.text(np.mean(mapX),
                         np.mean(mapY),
                         labels[i],
                         va='center',
                         ha='right',
                         alpha=0.5,
                         fontsize=10)

        # save figure and close
        plt.savefig(outputFileName,
                    bbox_inches='tight',
                    dpi=dpi,
                    pad_inches=padding)
        if pltshow:
            plt.show()
        else:
            plt.close('all')

    def reproject_GCPs(self, srsString=''):
        '''Reproject all GCPs to a new spatial reference system

        Necessary before warping an image if the given GCPs
        are in a coordinate system which has a singularity
        in (or near) the destination area (e.g. poles for lonlat GCPs)

        Parameters
        ----------
        srsString : string
            SRS given as Proj4 string. If empty '+proj=stere' is used

        Modifies
        --------
            Reprojects all GCPs to new SRS and updates GCPProjection
        '''
        if srsString == '':
            lon, lat = self.get_border()
            srsString = '+proj=stere +datum=WGS84 +ellps=WGS84 +lat_0=%f +lon_0=%f +no_defs' % (
                np.nanmedian(lat), np.nanmedian(lon))

        self.vrt.reproject_GCPs(srsString)
Ejemplo n.º 8
0
class Domain(object):
    '''Container for geographical reference of a raster

    A Domain object describes all attributes of geographical
    reference of a raster:
      * width and height (number of pixels)
      * pixel size (e.g. in decimal degrees or in meters)
      * relation between pixel/line coordinates and geographical
        coordinates (e.g. a linear relation)
      * type of data projection (e.g. geographical or stereographic)

    The core of Domain is a GDAL Dataset. It has no bands, but only
    georeference information: rasterXsize, rasterYsize, GeoTransform and
    Projection or GCPs, etc. which fully describe dimentions and spatial
    reference of the grid.

    There are three ways to store geo-reference in a GDAL dataset:
      * Using GeoTransfrom to define linear relationship between raster
        pixel/line and geographical X/Y coordinates
      * Using GCPs (set of Ground Control Points) to define non-linear
        relationship between pixel/line and X/Y
      * Using Geolocation Array - full grids of X/Y coordinates for
        each pixel of a raster
    The relation between X/Y coordinates of the raster and latitude/longitude
    coordinates is defined by projection type and projection parameters.
    These pieces of information are therefore stored in Domain:
      * Type and parameters of projection +
        * GeoTransform, or
        * GCPs, or
        * GeolocationArrays

    Domain has methods for basic operations with georeference information:
      * creating georeference from input options;
      * fetching corner, border or full grids of X/Y coordinates;
      * making map of the georeferenced grid in a PNG or KML file;
      * and some more...

    The main attribute of Domain is a VRT object self.vrt.
    Nansat inherits from Domain and adds bands to self.vrt

    '''
    def __init__(self, srs=None, ext=None, ds=None, lon=None,
                 lat=None, name='', logLevel=None):
        '''Create Domain from GDALDataset or string options or lat/lon grids

        d = Domain(srs, ext)
            Size, extent and spatial reference is given by strings
        d = Domain(ds=GDALDataset):
            Size, extent and spatial reference is copied from input
            GDAL dataset
        d = Domain(srs, ds=GDALDataset):
            Spatial reference is given by srs, but size and extent is
            determined
            from input GDAL dataset
        d = Domain(lon=lonGrid, lat=latGrid)
            Size, extent and spatial reference is given by two grids

        Parameters
        ----------
        srs : PROJ4 or EPSG or WKT or NSR or osr.SpatialReference()
            Input parameter for nansat.NSR()
        ext : string
            some gdalwarp options + additional options
            [http://www.gdal.org/gdalwarp.html]
            Specifies extent, resolution / size
            Available options: (('-te' or '-lle') and ('-tr' or '-ts'))
            (e.g. '-lle -10 30 55 60 -ts 1000 1000' or
            '-te 100 2000 300 10000 -tr 300 200')
            -tr resolutionx resolutiony
            -ts sizex sizey
            -te xmin ymin xmax ymax
            -lle lonmin latmin lonmax latmax
        ds : GDAL dataset
        lat : Numpy array
            Grid with latitudes
        lon : Numpy array
            Grid with longitudes
        name : string, optional
            Name to be added to the Domain object
        logLevel : int, optional, default=30
            level of logging

        Raises
        -------
        ProjectionError : occurs when Projection() is empty
            despite it is required for creating extentDic.
        OptionError : occures when the arguments are not proper.

        Modifies
        ---------
        self.vrt.datasetset : dataset in memory
            dataset is created based on the input arguments

        See Also
        ---------
        Nansat.reproject()
        [http://www.gdal.org/gdalwarp.html]
        [http://trac.osgeo.org/proj/]
        [http://spatialreference.org/]
        [http://www.gdal.org/ogr/osr_tutorial.html]

        '''
        # set default attributes
        self.logger = add_logger('Nansat', logLevel)
        self.name = name

        self.logger.debug('ds: %s' % str(ds))
        self.logger.debug('srs: %s' % srs)
        self.logger.debug('ext: %s' % ext)

        # If too much information is given raise error
        if ds is not None and srs is not None and ext is not None:
            raise OptionError('Ambiguous specification of both '
                              'dataset, srs- and ext-strings.')

        # choose between input opitons:
        # ds
        # ds and srs
        # srs and ext
        # lon and lat

        # if only a dataset is given:
        #     copy geo-reference from the dataset
        if ds is not None and srs is None:
            self.vrt = VRT(gdalDataset=ds)

        # If dataset and srs are given (but not ext):
        #   use AutoCreateWarpedVRT to determine bounds and resolution
        elif ds is not None and srs is not None:
            srs = NSR(srs)
            tmpVRT = gdal.AutoCreateWarpedVRT(ds, None, srs.wkt)
            if tmpVRT is None:
                raise ProjectionError('Could not warp the given dataset'
                                      'to the given SRS.')
            else:
                self.vrt = VRT(gdalDataset=tmpVRT)

        # If SpatialRef and extent string are given (but not dataset)
        elif srs is not None and ext is not None:
            srs = NSR(srs)
            # create full dictionary of parameters
            extentDic = self._create_extentDic(ext)

            # convert -lle to -te
            if 'lle' in extentDic.keys():
                extentDic = self._convert_extentDic(srs, extentDic)

            # get size/extent from the created extet dictionary
            [geoTransform,
             rasterXSize, rasterYSize] = self._get_geotransform(extentDic)
            # create VRT object with given geo-reference parameters
            self.vrt = VRT(srcGeoTransform=geoTransform,
                           srcProjection=srs.wkt,
                           srcRasterXSize=rasterXSize,
                           srcRasterYSize=rasterYSize)
            self.extentDic = extentDic
        elif lat is not None and lon is not None:
            # create self.vrt from given lat/lon
            self.vrt = VRT(lat=lat, lon=lon)
        else:
            raise OptionError('"dataset" or "srsString and extentString" '
                              'or "dataset and srsString" are required')

        self.logger.debug('vrt.dataset: %s' % str(self.vrt.dataset))

    def __repr__(self):
        '''Creates string with basic info about the Domain object

        Modifies
        ---------
        Print size, projection and corner coordinates

        '''
        outStr = 'Domain:[%d x %d]\n' % (self.vrt.dataset.RasterXSize,
                                         self.vrt.dataset.RasterYSize)
        outStr += '-' * 40 + '\n'
        try:
            corners = self.get_corners()
        except:
            self.logger.error('Cannot read projection from source!')
        else:
            outStr += 'Projection:\n'
            outStr += (NSR(self.vrt.get_projection()).ExportToPrettyWkt(1)
                       + '\n')
            outStr += '-' * 40 + '\n'
            outStr += 'Corners (lon, lat):\n'
            outStr += '\t (%6.2f, %6.2f)  (%6.2f, %6.2f)\n' % (corners[0][0],
                                                               corners[1][0],
                                                               corners[0][2],
                                                               corners[1][2])
            outStr += '\t (%6.2f, %6.2f)  (%6.2f, %6.2f)\n' % (corners[0][1],
                                                               corners[1][1],
                                                               corners[0][3],
                                                               corners[1][3])
        return outStr

    def write_kml(self, xmlFileName=None, kmlFileName=None):
        '''Write KML file with domains

        Convert XML-file with domains into KML-file for GoogleEarth
        or write KML-file with the current Domain

        Parameters
        -----------
        xmlFileName : string, optional
            Name of the XML-file to convert. If only this value is given
            - kmlFileName=xmlFileName+'.kml'

        kmlFileName : string, optional
            Name of the KML-file to generate from the current Domain

        '''
        # test input options
        if xmlFileName is not None and kmlFileName is None:
            # if only input XML-file is given - convert it to KML

            # open XML, get all domains
            xmlFile = file(xmlFileName, 'rb')
            kmlFileName = xmlFileName + '.kml'
            xmlDomains = ElementTree(file=xmlFile).getroot()
            xmlFile.close()

            # convert domains in XML into list of domains
            domains = []
            for xmlDomain in list(xmlDomains):
                # append Domain object to domains list
                domainName = xmlDomain.attrib['name']
                domains.append(Domain(srs=xmlFileName, ext=domainName))

        elif xmlFileName is None and kmlFileName is not None:
            # if only output KML-file is given
            # then convert the current domain to KML
            domains = [self]

        else:
            # otherwise it is potentially error
            raise OptionError('Either xmlFileName(%s)\
             or kmlFileName(%s) are wrong' % (xmlFileName, kmlFileName))

        # open KML, write header
        kmlFile = file(kmlFileName, 'wt')
        kmlFile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        kmlFile.write('<kml xmlns="http://www.opengis.net/kml/2.2" '
                      'xmlns:gx="http://www.google.com/kml/ext/2.2" '
                      'xmlns:kml="http://www.opengis.net/kml/2.2" '
                      'xmlns:atom="http://www.w3.org/2005/Atom">\n')
        kmlFile.write('<Document>\n')
        kmlFile.write('    <name>%s</name>\n' % kmlFileName)
        kmlFile.write('        <Folder><name>%s</name><open>1</open>\n'
                      % kmlFileName)

        # get border of each domain and add to KML
        for domain in list(domains):
            kmlEntry = domain._get_border_kml()
            kmlFile.write(kmlEntry)

        # write footer and close
        kmlFile.write('        </Folder></Document></kml>\n')
        kmlFile.close()

    def write_kml_image(self, kmlFileName=None, kmlFigureName=None):
        '''Create KML file for already projected image

        Write Domain Image into KML-file for GoogleEarth

        Parameters
        -----------
        kmlFileName : string, optional
            Name of the KML-file to generate from the current Domain
        kmlFigureName : string, optional
            Name of the projected image stored in .png format

        Examples
        ---------
        # First of all, reproject an image into Lat/Lon WGS84
          (Simple Cylindrical) projection
        # 1. Cancel previous reprojection
        # 2. Get corners of the image and the pixel resolution
        # 3. Create Domain with stereographic projection,
        #    corner coordinates and resolution 1000m
        # 4. Reproject
        # 5. Write image
        # 6. Write KML for the image
        n.reproject() # 1.
        lons, lats = n.get_corners() # 2.
        srsString = '+proj=latlong +datum=WGS84 +ellps=WGS84 +no_defs'
        extentString = '-lle %f %f %f %f -ts 3000 3000'
        % (min(lons), min(lats), max(lons), max(lats))
        d = Domain(srs=srsString, ext=extentString) # 3.
        n.reproject(d) # 4.
        n.write_figure(fileName=figureName, bands=[3], clim=[0,0.15],
                       cmapName='gray', transparency=0) # 5.
        n.write_kml_image(kmlFileName=oPath + fileName + '.kml',
                          kmlFigureName=figureName) # 6.

        '''
        # test input options
        if kmlFileName is not None:
            # if only output KML-file is given
            # then convert the current domain to KML
            domains = [self]
        else:
            # otherwise it is potentially error
            raise OptionError('kmlFileName(%s) is wrong' % (kmlFileName))

        if kmlFigureName is None:
            raise OptionError('kmlFigureName(%s) is not specified'
                              % (kmlFigureName))

        # open KML, write header
        kmlFile = file(kmlFileName, 'wt')
        kmlFile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        kmlFile.write('<kml xmlns="http://www.opengis.net/kml/2.2" '
                      'xmlns:gx="http://www.google.com/kml/ext/2.2" '
                      'xmlns:kml="http://www.opengis.net/kml/2.2" '
                      'xmlns:atom="http://www.w3.org/2005/Atom">\n')
        kmlFile.write('<GroundOverlay>\n')
        kmlFile.write('    <name>%s</name>\n' % kmlFileName)
        kmlFile.write('    <Icon>\n')
        kmlFile.write('        <href>%s</href>\n' % kmlFigureName)
        kmlFile.write('        <viewBoundScale>0.75</viewBoundScale>\n')
        kmlFile.write('    </Icon>\n')

        # get corner of the domain and add to KML
        domainLon, domainLat = self.get_corners()

        kmlFile.write('    <LatLonBox>\n')
        kmlFile.write('        <north>%s</north>\n' % max(domainLat))
        kmlFile.write('        <south>%s</south>\n' % min(domainLat))
        kmlFile.write('        <east>%s</east>\n' % max(domainLon))
        kmlFile.write('        <west>%s</west>\n' % min(domainLon))
        kmlFile.write('    </LatLonBox>\n')

        # write footer and close
        kmlFile.write('</GroundOverlay>\n')
        kmlFile.write('</kml>')
        kmlFile.close()

    def get_geolocation_grids(self, stepSize=1):
        '''Get longitude and latitude grids representing the full data grid

        If GEOLOCATION is not present in the self.vrt.dataset then grids
        are generated by converting pixel/line of each pixel into lat/lon
        If GEOLOCATION is present in the self.vrt.dataset then grids are read
        from the geolocation bands.

        Parameters
        -----------
        stepSize : int
            Reduction factor if output is desired on a reduced grid size

        Returns
        --------
        longitude : numpy array
            grid with longitudes
        latitude : numpy array
            grid with latitudes
        '''

        X = range(0, self.vrt.dataset.RasterXSize, stepSize)
        Y = range(0, self.vrt.dataset.RasterYSize, stepSize)
        Xm, Ym = np.meshgrid(X, Y)

        if len(self.vrt.geolocationArray.d) > 0:
            # if the vrt dataset has geolocationArray
            # read lon,lat grids from geolocationArray
            lon, lat = self.vrt.geolocationArray.get_geolocation_grids()
            longitude, latitude = lon[Ym, Xm], lat[Ym, Xm]
        else:
            # generate lon,lat grids using GDAL Transformer
            lonVec, latVec = self.transform_points(Xm.flatten(), Ym.flatten())
            longitude = lonVec.reshape(Xm.shape)
            latitude = latVec.reshape(Xm.shape)

        return longitude, latitude

    def _convert_extentDic(self, dstSRS, extentDic):
        '''Convert -lle option (lat/lon) to -te (proper coordinate system)

        Source SRS from LAT/LON projection and target SRS from dstWKT.
        Create osr.CoordinateTransformation based on these SRSs and
        convert given values in degrees to the destination coordinate
        system given by WKT.
        Add key 'te' and the converted values into the extentDic.

        Parameters
        -----------
        dstSRS : NSR
            Destination Spatial Reference
        extentDic : dictionary
            dictionary with 'lle' key

        Returns
        --------
        extentDic : dictionary
            input dictionary + 'te' key and its values

        '''
        coorTrans = osr.CoordinateTransformation(NSR(), dstSRS)

        # convert lat/lon given by 'lle' to the target coordinate system and
        # add key 'te' and the converted values to extentDic
        x1, y1, z1 = coorTrans.TransformPoint(extentDic['lle'][0],
                                              extentDic['lle'][3])
        x2, y2, z2 = coorTrans.TransformPoint(extentDic['lle'][2],
                                              extentDic['lle'][3])
        x3, y3, z3 = coorTrans.TransformPoint(extentDic['lle'][2],
                                              extentDic['lle'][1])
        x4, y4, z4 = coorTrans.TransformPoint(extentDic['lle'][0],
                                              extentDic['lle'][1])

        minX = min([x1, x2, x3, x4])
        maxX = max([x1, x2, x3, x4])
        minY = min([y1, y2, y3, y4])
        maxY = max([y1, y2, y3, y4])

        extentDic['te'] = [minX, minY, maxX, maxY]

        return extentDic

    def _create_extentDic(self, extentString):
        '''Create a dictionary from extentString

        Check if extentString is proper.
            * '-te' and '-lle' take 4 numbers.
            * '-ts' and '-tr' take 2 numbers.
            * the combination should be ('-te' or '-lle') and ('-ts' or '-tr')
        If it is proper, create a dictionary
        Otherwise, raise the error.

        Parameters
        -----------
        extentString : string
            '-te xMin yMin xMax yMax',
            '-tr xResolution yResolution',
            '-ts width height',
            '-lle minlon minlat maxlon maxlat'

        Returns
        --------
        extentDic : dictionary
            has key ('te' or 'lle') and ('tr' or 'ts') and their values.

        Raises
        -------
        OptionError : occurs when the extentString is improper

        '''
        extentDic = {}

        # Find -re text
        str_tr = re.findall('-tr\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?',
                            extentString)
        if str_tr != []:
            # Check the number of -tr elements
            elm_str = str(str_tr[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 3 or elms_str[2] == '-':
                raise OptionError('Domain._create_extentDic():'
                                  '-tr is used as'
                                  '"-tr xResolution yResolution"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_tr[0], '')
            trElem = str(str_tr).split(None)
            trkey = trElem[0].translate(string.maketrans('', ''), "[]-'")
            if trkey != '':
                elements = []
                for i in range(2):
                    elements.append(float(trElem[i + 1].
                                          translate(string.maketrans('', ''),
                                                    "'[]'")))
                extentDic[trkey] = elements

        # Find -ts text
        str_ts = re.findall('-ts\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?',
                            extentString)
        if str_ts != []:
            # Check the number of -ts elements
            elm_str = str(str_ts[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 3 or elms_str[2] == '-':
                raise OptionError('Domain._create_extentDic(): '
                                  '"-ts" is used as "-ts width height"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_ts[0], '')
            tsElem = str(str_ts).split(None)
            tskey = tsElem[0].translate(string.maketrans('', ''), "[]-'")
            if tskey != '':
                elements = []
                for i in range(2):
                    elements.append(float(tsElem[i + 1].
                                          translate(string.maketrans('', ''),
                                                    "[]'")))
                extentDic[tskey] = elements

        # Find -te text
        str_te = re.findall('-te\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s'
                            '+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?',
                            extentString)
        if str_te != []:
            # Check the number of -te elements
            elm_str = str(str_te[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 5:
                raise OptionError('Domain._create_extentDic():'
                                  '-te is used as "-te xMin yMin xMax yMax"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_te[0], '')
            teElem = str(str_te).split(None)
            tekey = teElem[0].translate(string.maketrans('', ''), "[]-'")
            if tekey != '':
                elements = []
                for i in range(4):
                    elements.append(float(teElem[i + 1].
                                          translate(string.maketrans('', ''),
                                                    "[]'")))
                extentDic[tekey] = elements

        # Find -lle text
        str_lle = re.findall('-lle\s+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s'
                             '+[-+]?\d*[.\d*]*\s+[-+]?\d*[.\d*]*\s?',
                             extentString)
        if str_lle != []:
            # Check the number of -lle elements
            elm_str = str(str_lle[0].rstrip())
            elms_str = elm_str.split(None)
            if len(elms_str) != 5:
                raise OptionError('Domain._create_extentDic():'
                                  '-lle is used as '
                                  '"-lle minlon minlat maxlon maxlat"')
            # Add the key and value to extentDic
            extentString = extentString.replace(str_lle[0], '')
            lleElem = str(str_lle).split(None)
            llekey = lleElem[0].translate(string.maketrans('', ''), "[]-'")
            if llekey != '':
                elements = []
                for i in range(4):
                    elements.append(float(lleElem[i + 1].
                                          translate(string.maketrans('', ''),
                                                    "[]'")))
                extentDic[llekey] = elements

        result = re.search('\S', extentString)
        # if there are unnecessary letters, give an error
        if result is not None:
            raise OptionError('Domain._create_extentDic():'
                              'extentString is not redable :',
                              extentString)

        # check if one of '-te' and '-lle' is given
        if ('lle' not in extentDic) and ('te' not in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-lle" or "-te" is required.')
        elif ('lle' in extentDic) and ('te' in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-lle" or "-te" should be chosen.')

        # check if one of '-ts' and '-tr' is given
        if ('ts' not in extentDic) and ('tr' not in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-ts" or "-tr" is required.')
        elif ('ts' in extentDic) and ('tr' in extentDic):
            raise OptionError('Domain._create_extentDic():'
                              '"-ts" or "-tr" should be chosen.')
        return extentDic

    def get_border(self, nPoints=10):
        '''Generate two vectors with values of lat/lon for the border of domain

        Parameters
        -----------
        nPoints : int, optional
            Number of points on each border

        Returns
        --------
        lonVec, latVec : lists
            vectors with lon/lat values for each point at the border

        '''
        # prepare vectors with pixels and lines for upper, left, lower
        # and right borders
        sizes = [self.vrt.dataset.RasterXSize, self.vrt.dataset.RasterYSize]

        rcVector1 = [[], []]
        rcVector2 = [[], []]
        # loop for pixels and lines
        for n in range(0, 2):
            step = max(1, sizes[n] / nPoints)
            rcVector1[n] = range(0, sizes[n], step)[0:nPoints]
            rcVector1[n].append(sizes[n])
            rcVector2[n] = rcVector1[n][:]
            rcVector2[n].reverse()

        # coumpund vectors of pixels (col) and lines (row)
        colVector = (rcVector1[0] + [sizes[0]] * len(rcVector1[1]) +
                     rcVector2[0] + [0] * len(rcVector1[1]))
        rowVector = ([0] * len(rcVector1[0]) + rcVector1[1] +
                     [sizes[1]] * len(rcVector1[0]) + rcVector2[1])

        return self.transform_points(colVector, rowVector)

    def _get_border_kml(self):
        '''Generate Placemark entry for KML

        Returns
        --------
        kmlEntry : String
            String with the Placemark entry

        '''
        domainLon, domainLat = self.get_border()

        # convert Border coordinates into KML-like string
        coordinates = ''
        for lon, lat in zip(domainLon, domainLat):
            coordinates += '%f,%f,0 ' % (lon, lat)

        kmlEntry = ''
        # write placemark: name, style, polygon, coordinates
        kmlEntry += '            <Placemark>\n'
        kmlEntry += '                <name>%s</name>\n' % self.name
        kmlEntry += '                <Style>\n'
        kmlEntry += '                    <LineStyle><color>ffffffff</color>'\
                    '</LineStyle>\n'
        kmlEntry += '                    <PolyStyle><fill>0</fill>'\
                    '</PolyStyle>\n'
        kmlEntry += '                </Style>\n'
        kmlEntry += '                <Polygon><tessellate>1</tessellate>'\
                    '<outerBoundaryIs><LinearRing><coordinates>\n'
        kmlEntry += coordinates + '\n'
        kmlEntry += '            </coordinates></LinearRing>'\
                    '</outerBoundaryIs></Polygon></Placemark>\n'

        return kmlEntry

    def get_border_wkt(self):
        '''Creates string with WKT representation of the border polygon

        Returns
        --------
        WKTPolygon : string
            string with WKT representation of the border polygon

        '''
        lonList, latList = self.get_border()

        # apply > 180 deg correction to longitudes
        for ilon, lon in enumerate(lonList):
            lonList[ilon] = copysign(acos(cos(lon * pi / 180.)) / pi * 180,
                                     sin(lon * pi / 180.))

        polyCont = ','.join(str(lon) + ' ' + str(lat)
                            for lon, lat in zip(lonList, latList))
        # outer quotes have to be double and inner - single!
        #wktPolygon = "PolygonFromText('POLYGON((%s))')" % polyCont
        wkt = 'POLYGON((%s))' % polyCont
        return wkt

    def get_border_geometry(self):
        ''' Get OGR Geometry of the border Polygon

        Returns
        -------
        OGR Geometry, type Polygon

        '''

        return ogr.CreateGeometryFromWkt(self.get_border_wkt())

    def overlaps(self, anotherDomain):
        ''' Checks if this Domain overlaps another Domain

        Returns
        -------
        overlaps : bool
            True if Domains overlaps, False otherwise

        '''

        return self.get_border_geometry().Intersects(
                anotherDomain.get_border_geometry())

    def contains(self, anotherDomain):
        ''' Checks if this Domain fully covers another Domain

        Returns
        -------
        contains : bool
            True if this Domain fully covers another Domain, False otherwise

        '''

        return self.get_border_geometry().Contains(
                anotherDomain.get_border_geometry())

    def get_border_postgis(self):
        ''' Get PostGIS formatted string of the border Polygon

        Returns
        -------
        str : 'PolygonFromText(PolygonWKT)'

        '''

        return "PolygonFromText('%s')" % self.get_border_wkt()

    def get_corners(self):
        '''Get coordinates of corners of the Domain

        Returns
        --------
        lonVec, latVec : lists
            vectors with lon/lat values for each corner

        '''

        colVector = [0, 0, self.vrt.dataset.RasterXSize,
                     self.vrt.dataset.RasterXSize]
        rowVector = [0, self.vrt.dataset.RasterYSize, 0,
                     self.vrt.dataset.RasterYSize]
        return self.transform_points(colVector, rowVector)

    def get_pixelsize_meters(self):
        '''Returns the pixelsize (deltaX, deltaY) of the domain

        For projected domains, the exact result which is constant
        over the domain is returned.
        For geographic (lon-lat) projections, or domains with no geotransform,
        the haversine formula is used to calculate the pixel size
        in the center of the domain.
        Returns
        --------
        deltaX, deltaY : float
        pixel size in X and Y directions given in meters
        '''

        srs = osr.SpatialReference(self.vrt.dataset.GetProjection())
        if srs.IsProjected:
            if srs.GetAttrValue('unit') == 'metre':
                geoTransform = self.vrt.dataset.GetGeoTransform()
                deltaX = abs(geoTransform[1])
                deltaY = abs(geoTransform[5])
                return deltaX, deltaY

        # Estimate pixel size in center of domain using haversine formula
        centerCol = round(self.vrt.dataset.RasterXSize/2)
        centerRow = round(self.vrt.dataset.RasterYSize/2)
        lon00, lat00 = self.transform_points([centerCol], [centerRow])
        lon01, lat01 = self.transform_points([centerCol], [centerRow + 1])
        lon10, lat10 = self.transform_points([centerCol + 1], [centerRow])

        deltaX = haversine(lon00, lat00, lon01, lat01)
        deltaY = haversine(lon00, lat00, lon10, lat10)
        return deltaX[0], deltaY[0]

    def _get_geotransform(self, extentDic):
        '''
        the new coordinates and raster size are calculated based on
        the given extentDic.

        Parameters
        -----------
        extentDic : dictionary
            includes 'te' key and 'ts' or 'tr' key

        Raises
        -------
        OptionError : occurs when maxX - minX < 0 or maxY - minY < 0
        OptionError : occurs when the given resolution is larger than
                     width or height.

        Returns
        --------
        coordinate : list with 6 float
            GeoTransform

        rasterSize : list with two int
            rasterXSize and rasterYSize

        '''
        # recalculate GeoTransform based on extent option
        minX = extentDic['te'][0]
        minY = extentDic['te'][1]
        maxX = extentDic['te'][2]
        maxY = extentDic['te'][3]
        cornerX = minX
        cornerY = maxY
        width = maxX - minX
        height = maxY - minY
        if width <= 0 or height <= 0:
            raise OptionError('The extent is illegal. '
                              '"-te xMin yMin xMax yMax" ')

        if 'tr' in extentDic.keys():
            resolutionX = extentDic['tr'][0]
            resolutionY = -(extentDic['tr'][1])
            if (width < resolutionX or height < resolutionY):
                raise OptionError('"-tr" is too large. '
                                  'width is %s, height is %s '
                                  % (str(width), str(height)))
            rasterXSize = width / resolutionX
            rasterYSize = abs(height / resolutionY)
        else:
            rasterXSize = extentDic['ts'][0]
            rasterYSize = extentDic['ts'][1]
            resolutionX = width / rasterXSize
            resolutionY = -abs(height / rasterYSize)

        # create a list for GeoTransform
        coordinates = [cornerX, resolutionX, 0.0, cornerY, 0.0, resolutionY]

        return coordinates, int(rasterXSize), int(rasterYSize)

    def transform_points(self, colVector, rowVector, DstToSrc=0):

        '''Transform given lists of X,Y coordinates into lon/lat or inverse

        Parameters
        -----------
        colVector : lists
            X and Y coordinates in pixel/line or lon/lat  coordinate system
        DstToSrc : 0 or 1
            0 - forward transform (pix/line => lon/lat)
            1 - inverse transformation

        Returns
        --------
        X, Y : lists
            X and Y coordinates in lon/lat or pixel/line coordinate system

        '''
        return self.vrt.transform_points(colVector, rowVector, DstToSrc)

    def azimuth_y(self, reductionFactor=1):
        '''Calculate the azimuth of 'upward' direction in each pixel

        Generaly speaking, azimuth is angle from the reference vector
        (direction to North) to the chosen direction. Azimuth increases
        clockwise from direction to North. http://en.wikipedia.org/wiki/Azimuth

        Here we calcluate azimuth of 'upward' direction.
        'Upward' direction coincides with Y-axis direction (and hence is
        opposite to the ROW-axis direction). For lon-lat (cylindrical,
        Plate Caree) and Mercator projections 'upward' direction coincides with
        direction to North, hence azimuth is 0.

        Parameters
        -----------
        reductionFactor : integer
            factor by which the size of the output array is reduced

        Returns
        -------
        azimuth        : numpy array
            Values of azimuth in degrees in range 0 - 360

        '''

        lon, lat = self.get_geolocation_grids(reductionFactor)
        a = initial_bearing(lon[1:, :], lat[1:, :],
                            lon[:-1:, :], lat[:-1:, :])
        # Repeat last row once to match size of lon-lat grids
        a = np.vstack((a, a[-1, :]))
        return a

    def shape(self):
        '''Return Numpy-like shape of Domain object (ySize, xSize)

        Returns
        --------
        shape : tuple of two INT
            Numpy-like shape of Domain object (ySize, xSize)

        '''
        return self.vrt.dataset.RasterYSize, self.vrt.dataset.RasterXSize

    def write_map(self, outputFileName,
                  lonVec=None, latVec=None, lonBorder=10., latBorder=10.,
                  figureSize=(6, 6), dpi=50, projection='cyl', resolution='c',
                  continetsColor='coral', meridians=10, parallels=10,
                  pColor='r', pLine='k', pAlpha=0.5, padding=0.,
                  merLabels=[False, False, False, False],
                  parLabels=[False, False, False, False],
                  pltshow=False):
        ''' Create an image with a map of the domain

        Uses Basemap to create a World Map
        Adds a semitransparent patch with outline of the Domain
        Writes to an image file

        Parameters
        -----------
        outputFileName : string
            name of the output file name
        lonBorder : float
            10, horisontal border around patch (degrees of longitude)
        latBorder : float
            10, vertical border around patch (degrees of latitude)
        figureSize : tuple of two integers
            (6, 6), size of the generated figure in inches
        dpi: int
            50, resolution of the output figure (size 6,6 and dpi 50
            produces 300 x 300 figure)
        projection : string, one of Basemap projections
            'cyl', projection of the map
        resolution : string, resolution of the map
            'c', crude
            'l', low
            'i', intermediate
            'h', high
            'f', full
        continetsColor : string or any matplotlib color representation
            'coral', color of continets
        meridians : int
            10, number of meridians to draw
        parallels : int
            10, number of parallels to draw
        pColor : string or any matplotlib color representation
            'r', color of the Domain patch
        pLine : string or any matplotlib color representation
            'k', color of the Domain outline
        pAlpha : float 0 - 1
            0.5, transparency of Domain patch
        padding : float
            0., width of white padding around the map
        merLabels : list of 4 booleans
            where to put meridian labels, see also Basemap.drawmeridians()
        parLables : list of 4 booleans
            where to put parallel labels, see also Basemap.drawparallels()

        '''
        # if lat/lon vectors are not given as input
        if lonVec is None or latVec is None or len(lonVec) != len(latVec):
            try:
                # get lon/lat from Domain/Nansat object
                lonVec, latVec = self.get_border()
            except:
                print('Domain/Nansat object is not given'
                      'and lat/lon vectors=None')
                return

        # convert vectors to numpy arrays
        lonVec = np.array(lonVec)
        latVec = np.array(latVec)

        # estimate mean/min/max values of lat/lon of the shown area
        # (real lat min max +/- latBorder) and (real lon min max +/- lonBorder)
        minLon = max(-180, lonVec.min() - lonBorder)
        maxLon = min(180, lonVec.max() + lonBorder)
        minLat = max(-90, latVec.min() - latBorder)
        maxLat = min(90, latVec.max() + latBorder)
        meanLon = lonVec.mean()
        meanLat = latVec.mean()

        # generate template map (can be also tmerc)
        plt.figure(num=1, figsize=figureSize, dpi=dpi)
        bmap = Basemap(projection=projection,
                       lat_0=meanLat, lon_0=meanLon,
                       llcrnrlon=minLon, llcrnrlat=minLat,
                       urcrnrlon=maxLon, urcrnrlat=maxLat,
                       resolution=resolution)

        # add content: coastline, continents, meridians, parallels
        bmap.drawcoastlines()
        bmap.fillcontinents(color=continetsColor)
        bmap.drawmeridians(np.linspace(minLon, maxLon, meridians))
        bmap.drawparallels(np.linspace(minLat, maxLat, parallels))

        # convert input lat/lon vectors to arrays of vectors with one row
        # if only one vector was given
        if len(lonVec.shape) == 1:
            lonVec = lonVec.reshape(1, lonVec.shape[0])
            latVec = latVec.reshape(1, latVec.shape[0])

        for lonSubVec, latSubVec in zip(lonVec, latVec):
            # convert lat/lons to map units
            mapX, mapY = bmap(list(lonSubVec.flat), list(latSubVec.flat))

            # from x/y vectors create a Patch to be added to map
            boundary = Polygon(zip(mapX, mapY),
                               alpha=pAlpha, ec=pLine, fc=pColor)

            # add patch to the map
            plt.gca().add_patch(boundary)
            plt.gca().set_aspect('auto')

        # save figure and close
        plt.savefig(outputFileName, bbox_inches='tight',
                    dpi=dpi, pad_inches=padding)
        if pltshow:
            plt.show()
        else:
            plt.close('all')

    def reproject_GCPs(self, srsString):
        '''Reproject all GCPs to a new spatial reference system

        Necessary before warping an image if the given GCPs
        are in a coordinate system which has a singularity
        in (or near) the destination area (e.g. poles for lonlat GCPs)

        Parameters
        ----------
        srsString : string
            SRS given as Proj4 string

        Modifies
        --------
            Reprojects all GCPs to new SRS and updates GCPProjection
        '''
        self.vrt.reproject_GCPs(srsString)