コード例 #1
0
def test_handler(auxdata_dem_cases):
    with bbox({
            'xmin': 11.5,
            'xmax': 11.9,
            'ymin': 51.1,
            'ymax': 51.5
    },
              crs=4326) as box:
        with DEMHandler([box]) as handler:
            for demType, reference in auxdata_dem_cases:
                result = handler.remote_ids(demType=demType, extent=box.extent)
                assert result == reference

    with bbox({
            'xmin': -11.9,
            'xmax': -11.5,
            'ymin': -51.5,
            'ymax': -51.1
    },
              crs=4326) as box:
        with DEMHandler([box]) as handler:
            cases = [('AW3D30', ['S055W015/S052W012.tar.gz']),
                     ('SRTM 1Sec HGT', ['S52W012.SRTMGL1.hgt.zip']),
                     ('SRTM 3Sec', ['srtm_34_23.zip']),
                     ('TDX90m',
                      ['90mdem/DEM/S52/W010/TDM1_DEM__30_S52W012.zip'])]
            for demType, reference in cases:
                result = handler.remote_ids(demType=demType, extent=box.extent)
                assert result == reference
コード例 #2
0
def test_dissolve(tmpdir, travis, testdata):
    scene = Raster(testdata['tif'])
    bbox1 = scene.bbox()
    # retrieve extent and shift its coordinates by one unit
    ext = bbox1.extent
    for key in ext.keys():
        ext[key] += 1
    # create new bbox shapefile with modified extent
    bbox2_name = os.path.join(str(tmpdir), 'bbox2.shp')
    bbox(ext, bbox1.srs, bbox2_name)
    # assert intersection between the two bboxes and combine them into one
    with Vector(bbox2_name) as bbox2:
        assert intersect(bbox1, bbox2) is not None
        bbox1.addvector(bbox2)
        # write combined bbox into new shapefile
        bbox3_name = os.path.join(str(tmpdir), 'bbox3.shp')
        bbox1.write(bbox3_name)
    bbox1.close()

    if not travis and platform.system() != 'Windows':
        # dissolve the geometries in bbox3 and write the result to new bbox4
        # this test is currently disabled for Travis as the current sqlite3 version on Travis seems to not support
        # loading gdal as extension; Travis CI setup: Ubuntu 14.04 (Trusty), sqlite3 version 3.8.2 (2018-06-04)
        bbox4_name = os.path.join(str(tmpdir), 'bbox4.shp')
        dissolve(bbox3_name, bbox4_name, field='area')
        assert os.path.isfile(bbox4_name)
コード例 #3
0
ファイル: test_snap.py プロジェクト: tyler-c2s/pyroSAR
 def test_shp(self, tmpdir, testdata):
     scene = testdata['s1']
     ext = {'xmin': 12, 'xmax': 13, 'ymin': 53, 'ymax': 54}
     with bbox(ext, 4326) as new:
         with pytest.raises(RuntimeError):
             geocode(scene, str(tmpdir), shapefile=new, test=True)
     
     with identify(scene).bbox() as box:
         ext = box.extent
     ext['xmax'] -= 1
     with bbox(ext, 4326) as new:
         geocode(scene, str(tmpdir), shapefile=new, test=True)
コード例 #4
0
ファイル: test_auxdata.py プロジェクト: tyler-c2s/pyroSAR
def test_autoload(auxdata_dem_cases, travis):
    # delete all target files to test downloading them again
    home = os.path.expanduser('~')
    demdir = os.path.join(home, '.snap', 'auxdata', 'dem')
    locals = [
        os.path.join(demdir, x, os.path.basename(y[0]))
        for x, y in auxdata_dem_cases
    ]
    for item in locals:
        if os.path.isfile(item):
            os.remove(item)
    with bbox({
            'xmin': 11.5,
            'xmax': 11.9,
            'ymin': 51,
            'ymax': 51.5
    }, crs=4326) as box:
        # if the following is run in a loop, it is not possible to see which demType failed
        # Travis CI does not support ftp access;
        # see https://blog.travis-ci.com/2018-07-23-the-tale-of-ftp-at-travis-ci
        if not travis:
            files = dem_autoload([box], 'AW3D30')
            assert len(files) == 1
            files = dem_autoload([box], 'AW3D30', product='stk')
            assert len(files) == 1
        files = dem_autoload([box], 'SRTM 1Sec HGT')
        assert len(files) == 1
        files = dem_autoload([box], 'SRTM 3Sec')
        assert len(files) == 1
        with pytest.raises(RuntimeError):
            files = dem_autoload([box], 'TDX90m')
        with pytest.raises(RuntimeError):
            dem_autoload([box], 'AW3D30', product='foobar')
コード例 #5
0
ファイル: test_auxdata.py プロジェクト: tyler-c2s/pyroSAR
def test_handler(auxdata_dem_cases):
    with bbox({
            'xmin': 11.5,
            'xmax': 11.9,
            'ymin': 51.1,
            'ymax': 51.5
    },
              crs=4326) as box:
        with DEMHandler([box]) as handler:
            for demType, reference in auxdata_dem_cases:
                result = handler.remote_ids(demType=demType, extent=box.extent)
                assert result == reference

    with bbox({
            'xmin': -11.9,
            'xmax': -11.5,
            'ymin': -51.5,
            'ymax': -51.1
    },
              crs=4326) as box:
        with DEMHandler([box]) as handler:
            cases = [('AW3D30', ['S055W015/S052W012.tar.gz']),
                     ('SRTM 1Sec HGT', ['S52W012.SRTMGL1.hgt.zip']),
                     ('SRTM 3Sec', ['srtm_34_23.zip']),
                     ('TDX90m',
                      ['90mdem/DEM/S52/W010/TDM1_DEM__30_S52W012.zip'])]
            for demType, reference in cases:
                result = handler.remote_ids(demType=demType, extent=box.extent)
                assert result == reference
    with pytest.raises(RuntimeError):
        test = DEMHandler('foobar')
    ext_utm = {
        'xmin': -955867,
        'xmax': -915536,
        'ymin': -5915518,
        'ymax': -5863678
    }
    with bbox(ext_utm, crs=32632) as box:
        with pytest.raises(RuntimeError):
            test = DEMHandler([box])
コード例 #6
0
def test_dem_create(tmpdir):
    with bbox({
            'xmin': 11.5,
            'xmax': 11.9,
            'ymin': 51,
            'ymax': 51.5
    }, crs=4326) as box:
        with pytest.raises(RuntimeError):
            files = dem_autoload([box], 'foobar')
        vrt = dem_autoload([box], 'SRTM 3Sec', vrt='/vsimem/test.vrt')
    out = os.path.join(str(tmpdir), 'srtm.tif')
    dem_create(src=vrt, dst=out, t_srs=32632, tr=(90, 90))
    assert os.path.isfile(out)
コード例 #7
0
def test_Raster_subset(testdata):
    with Raster(testdata['tif']) as ras:
        ext = ras.bbox().extent
        xres, yres = ras.res
        ext['xmin'] += xres
        ext['xmax'] -= xres
        ext['ymin'] += yres
        ext['ymax'] -= yres
        with bbox(ext, ras.proj4) as vec:
            with ras[vec] as sub:
                xres, yres = ras.res
                assert sub.geo['xmin'] - ras.geo['xmin'] == xres
                assert ras.geo['xmax'] - sub.geo['xmax'] == xres
                assert sub.geo['ymin'] - ras.geo['ymin'] == xres
                assert ras.geo['ymax'] - sub.geo['ymax'] == xres
コード例 #8
0
ファイル: util.py プロジェクト: flaviasmendes/pyroSAR
def geocode(infile,
            outdir,
            t_srs=4326,
            tr=20,
            polarizations='all',
            shapefile=None,
            scaling='dB',
            geocoding_type='Range-Doppler',
            removeS1BorderNoise=True,
            removeS1BorderNoiseMethod='pyroSAR',
            removeS1ThermalNoise=True,
            offset=None,
            allow_RES_OSV=False,
            demName='SRTM 1Sec HGT',
            externalDEMFile=None,
            externalDEMNoDataValue=None,
            externalDEMApplyEGM=True,
            terrainFlattening=True,
            basename_extensions=None,
            test=False,
            export_extra=None,
            groupsize=1,
            cleanup=True,
            tmpdir=None,
            gpt_exceptions=None,
            gpt_args=None,
            returnWF=False,
            nodataValueAtSea=True,
            demResamplingMethod='BILINEAR_INTERPOLATION',
            imgResamplingMethod='BILINEAR_INTERPOLATION',
            alignToStandardGrid=False,
            standardGridOriginX=0,
            standardGridOriginY=0,
            speckleFilter=False,
            refarea='gamma0'):
    """
    wrapper function for geocoding SAR images using ESA SNAP

    Parameters
    ----------
    infile: str or ~pyroSAR.drivers.ID or list
        the SAR scene(s) to be processed; multiple scenes are treated as consecutive acquisitions, which will be
        mosaicked with SNAP's SliceAssembly operator
    outdir: str
        The directory to write the final files to.
    t_srs: int, str or osr.SpatialReference
        A target geographic reference system in WKT, EPSG, PROJ4 or OPENGIS format.
        See function :func:`spatialist.auxil.crsConvert()` for details.
        Default: `4326 <http://spatialreference.org/ref/epsg/4326/>`_.
    tr: int or float, optional
        The target resolution in meters. Default is 20
    polarizations: list or str
        The polarizations to be processed; can be a string for a single polarization, e.g. 'VV', or a list of several
        polarizations, e.g. ['VV', 'VH']. With the special value 'all' (default) all available polarizations are
        processed.
    shapefile: str or :py:class:`~spatialist.vector.Vector` or dict or None
        A vector geometry for subsetting the SAR scene to a test site.
    scaling: {'dB', 'db', 'linear'}, optional
        Should the output be in linear or decibel scaling? Default is 'dB'.
    geocoding_type: {'Range-Doppler', 'SAR simulation cross correlation'}, optional
        The type of geocoding applied; can be either 'Range-Doppler' (default) or 'SAR simulation cross correlation'
    removeS1BorderNoise: bool, optional
        Enables removal of S1 GRD border noise (default).
    removeS1BorderNoiseMethod: str
        the border noise removal method to be applied, See :func:`pyroSAR.S1.removeGRDBorderNoise` for details; one of the following:
         - 'ESA': the pure implementation as described by ESA
         - 'pyroSAR': the ESA method plus the custom pyroSAR refinement
    removeS1ThermalNoise: bool, optional
        Enables removal of S1 thermal noise (default).
    offset: tuple, optional
        A tuple defining offsets for left, right, top and bottom in pixels, e.g. (100, 100, 0, 0); this variable is
        overridden if a shapefile is defined. Default is None.
    allow_RES_OSV: bool
        (only applies to Sentinel-1) Also allow the less accurate RES orbit files to be used?
        The function first tries to download a POE file for the scene.
        If this fails and RES files are allowed, it will download the RES file.
        The selected OSV type is written to the workflow XML file.
        Processing is aborted if the correction fails (Apply-Orbit-File parameter continueOnFail set to false).
    demName: str
        the name of the auto-download DEM. Default is 'SRTM 1Sec HGT'. Is ignored when `externalDEMFile` is not None.
    externalDEMFile: str or None, optional
        The absolute path to an external DEM file. Default is None. Overrides `demName`.
    externalDEMNoDataValue: int, float or None, optional
        The no data value of the external DEM. If not specified (default) the function will try to read it from the
        specified external DEM.
    externalDEMApplyEGM: bool, optional
        Apply Earth Gravitational Model to external DEM? Default is True.
    terrainFlattening: bool
        apply topographic normalization on the data?
    basename_extensions: list of str or None
        names of additional parameters to append to the basename, e.g. ['orbitNumber_rel']
    test: bool, optional
        If set to True the workflow xml file is only written and not executed. Default is False.
    export_extra: list or None
        a list of image file IDs to be exported to outdir. The following IDs are currently supported:
         - incidenceAngleFromEllipsoid
         - localIncidenceAngle
         - projectedLocalIncidenceAngle
         - DEM
         - layoverShadowMask
         - scatteringArea
    groupsize: int
        the number of workers executed together in one gpt call
    cleanup: bool
        should all files written to the temporary directory during function execution be deleted after processing?
    tmpdir: str or None
        path of custom temporary directory, useful to separate output folder and temp folder. If `None`, the `outdir`
        location will be used. The created subdirectory will be deleted after processing.
    gpt_exceptions: dict or None
        a dictionary to override the configured GPT executable for certain operators;
        each (sub-)workflow containing this operator will be executed with the define executable;
        
         - e.g. ``{'Terrain-Flattening': '/home/user/snap/bin/gpt'}``
    gpt_args: list or None
        a list of additional arguments to be passed to the gpt call
        
        - e.g. ``['-x', '-c', '2048M']`` for increased tile cache size and intermediate clearing
    returnWF: bool
        return the full name of the written workflow XML file?
    nodataValueAtSea: bool
        mask pixels acquired over sea? The sea mask depends on the selected DEM.
    demResamplingMethod: str
        one of the following:
         - 'NEAREST_NEIGHBOUR'
         - 'BILINEAR_INTERPOLATION'
         - 'CUBIC_CONVOLUTION'
         - 'BISINC_5_POINT_INTERPOLATION'
         - 'BISINC_11_POINT_INTERPOLATION'
         - 'BISINC_21_POINT_INTERPOLATION'
         - 'BICUBIC_INTERPOLATION'
    imgResamplingMethod: str
        the resampling method for geocoding the SAR image; the options are identical to demResamplingMethod
    speckleFilter: str
        one of the following:
         - 'Boxcar'
         - 'Median'
         - 'Frost'
         - 'Gamma Map'
         - 'Refined Lee'
         - 'Lee'
         - 'Lee Sigma'
    refarea: str or list
        'sigma0', 'gamma0' or a list of both
    alignToStandardGrid: bool
        align all processed images to a common grid?
    standardGridOriginX: int or float
        the x origin value for grid alignment
    standardGridOriginY: int or float
        the y origin value for grid alignment
    
    Returns
    -------
    str or None
        either the name of the workflow file if `returnWF == True` or None otherwise

    Note
    ----
    If only one polarization is selected and not extra products are defined the results are directly written to GeoTiff.
    Otherwise the results are first written to a folder containing ENVI files and then transformed to GeoTiff files
    (one for each polarization/extra product).
    If GeoTiff would directly be selected as output format for multiple polarizations then a multilayer GeoTiff
    is written by SNAP which is considered an unfavorable format
    
    
    .. figure:: figures/snap_geocode.png
        :scale: 25%
        :align: center
        
        Workflow diagram for function geocode for processing a Sentinel-1 Ground Range
        Detected (GRD) scene to radiometrically terrain corrected (RTC) backscatter.
        An additional Subset node might be inserted in case a vector geometry is provided.

    Examples
    --------
    geocode a Sentinel-1 scene and export the local incidence angle map with it

    >>> from pyroSAR.snap import geocode
    >>> filename = 'S1A_IW_GRDH_1SDV_20180829T170656_20180829T170721_023464_028DE0_F7BD.zip'
    >>> geocode(infile=filename, outdir='outdir', tr=20, scaling='dB',
    >>>         export_extra=['DEM', 'localIncidenceAngle'], t_srs=4326)

    See Also
    --------
    :class:`pyroSAR.drivers.ID`,
    :class:`spatialist.vector.Vector`,
    :func:`spatialist.auxil.crsConvert()`
    """

    if isinstance(infile, pyroSAR.ID):
        id = infile
    elif isinstance(infile, str):
        id = pyroSAR.identify(infile)
    elif isinstance(infile, list):
        ids = pyroSAR.identify_many(infile, verbose=False, sortkey='start')
        id = ids[0]
    else:
        raise TypeError("'infile' must be of type str, list or pyroSAR.ID")

    if id.is_processed(outdir):
        print('scene {} already processed'.format(id.outname_base()))
        return
    # print(os.path.basename(id.scene))
    if not os.path.isdir(outdir):
        os.makedirs(outdir)
    ############################################
    # general setup

    if id.sensor in ['ASAR', 'ERS1', 'ERS2']:
        formatName = 'ENVISAT'
    elif id.sensor in ['S1A', 'S1B']:
        if id.product == 'SLC':
            raise RuntimeError('Sentinel-1 SLC data is not supported yet')
        formatName = 'SENTINEL-1'
    else:
        raise RuntimeError('sensor not supported (yet)')

    # several options like resampling are modified globally for the whole workflow at the end of this function
    # this list gathers IDs of nodes for which this should not be done because they are configured individually
    resampling_exceptions = []
    ######################
    # print('- assessing polarization selection')
    if isinstance(polarizations, str):
        if polarizations == 'all':
            polarizations = id.polarizations
        else:
            if polarizations in id.polarizations:
                polarizations = [polarizations]
            else:
                raise RuntimeError(
                    'polarization {} does not exists in the source product'.
                    format(polarizations))
    elif isinstance(polarizations, list):
        polarizations = [x for x in polarizations if x in id.polarizations]
    else:
        raise RuntimeError('polarizations must be of type str or list')

    bandnames = dict()
    bandnames['int'] = ['Intensity_' + x for x in polarizations]
    bandnames['beta0'] = ['Beta0_' + x for x in polarizations]
    bandnames['gamma0'] = ['Gamma0_' + x for x in polarizations]
    bandnames['sigma0'] = ['Sigma0_' + x for x in polarizations]
    ############################################
    ############################################
    # parse base workflow
    # print('- parsing base workflow')
    workflow = parse_recipe('base')
    ############################################
    # Read node configuration
    # print('-- configuring Read Node')
    read = workflow['Read']
    read.parameters['file'] = id.scene
    read.parameters['formatName'] = formatName
    readers = [read.id]

    if isinstance(infile, list):
        for i in range(1, len(infile)):
            readn = parse_node('Read')
            readn.parameters['file'] = ids[i].scene
            readn.parameters['formatName'] = formatName
            workflow.insert_node(readn,
                                 before=read.id,
                                 resetSuccessorSource=False)
            readers.append(readn.id)
        sliceAssembly = parse_node('SliceAssembly')
        sliceAssembly.parameters['selectedPolarisations'] = polarizations
        workflow.insert_node(sliceAssembly, before=readers)
        read = sliceAssembly
    ############################################
    # Remove-GRD-Border-Noise node configuration
    # print('-- configuring Remove-GRD-Border-Noise Node')
    if id.sensor in ['S1A', 'S1B'] and removeS1BorderNoise:
        bn = parse_node('Remove-GRD-Border-Noise')
        workflow.insert_node(bn, before=read.id)
        bn.parameters['selectedPolarisations'] = polarizations
    ############################################
    # ThermalNoiseRemoval node configuration
    # print('-- configuring ThermalNoiseRemoval Node')
    if id.sensor in ['S1A', 'S1B'] and removeS1ThermalNoise:
        for reader in readers:
            tn = parse_node('ThermalNoiseRemoval')
            workflow.insert_node(tn, before=reader)
            tn.parameters['selectedPolarisations'] = polarizations
    ############################################
    # orbit file application node configuration
    # print('-- configuring Apply-Orbit-File Node')
    orbit_lookup = {
        'ENVISAT': 'DELFT Precise (ENVISAT, ERS1&2) (Auto Download)',
        'SENTINEL-1': 'Sentinel Precise (Auto Download)'
    }
    orbitType = orbit_lookup[formatName]
    if formatName == 'ENVISAT' and id.acquisition_mode == 'WSM':
        orbitType = 'DORIS Precise VOR (ENVISAT) (Auto Download)'

    if formatName == 'SENTINEL-1':
        match = id.getOSV(osvType='POE', returnMatch=True)
        if match is None and allow_RES_OSV:
            id.getOSV(osvType='RES')
            orbitType = 'Sentinel Restituted (Auto Download)'

    orb = workflow['Apply-Orbit-File']
    orb.parameters['orbitType'] = orbitType
    orb.parameters['continueOnFail'] = False
    ############################################
    # calibration node configuration
    # print('-- configuring Calibration Node')
    cal = workflow['Calibration']
    cal.parameters['selectedPolarisations'] = polarizations
    cal.parameters['sourceBands'] = bandnames['int']
    if isinstance(refarea, str):
        refarea = [refarea]
    if terrainFlattening:
        if 'gamma0' not in refarea:
            raise RuntimeError(
                'if terrain flattening is applied refarea must be gamma0')
        cal.parameters['outputBetaBand'] = True
        if 'sigma0' in refarea:
            cal.parameters['outputSigmaBand'] = True
    else:
        refarea_options = ['sigma0', 'gamma0']
        for opt in refarea:
            if opt not in refarea_options:
                message = '{0} must be one of the following:\n- {1}'
                raise ValueError(
                    message.format('refarea', '\n- '.join(refarea_options)))
            cal.parameters['output{}Band'.format(opt[:-1].capitalize())] = True
    last = cal.id
    ############################################
    # terrain flattening node configuration
    # print('-- configuring Terrain-Flattening Node')
    if terrainFlattening:
        tf = parse_node('Terrain-Flattening')
        workflow.insert_node(tf, before=last)
        if id.sensor in ['ERS1', 'ERS2'] or (id.sensor == 'ASAR'
                                             and id.acquisition_mode != 'APP'):
            tf.parameters['sourceBands'] = 'Beta0'
        else:
            tf.parameters['sourceBands'] = bandnames['beta0']
        if 'reGridMethod' in tf.parameters.keys():
            if externalDEMFile is None:
                tf.parameters['reGridMethod'] = True
            else:
                tf.parameters['reGridMethod'] = False
        last = tf.id
    ############################################
    # speckle filtering node configuration
    speckleFilter_options = [
        'Boxcar', 'Median', 'Frost', 'Gamma Map', 'Refined Lee', 'Lee',
        'Lee Sigma'
    ]

    if speckleFilter:
        message = '{0} must be one of the following:\n- {1}'
        if speckleFilter not in speckleFilter_options:
            raise ValueError(
                message.format('speckleFilter',
                               '\n- '.join(speckleFilter_options)))
        sf = parse_node('Speckle-Filter')
        workflow.insert_node(sf, before=last)
        sf.parameters['sourceBands'] = None
        sf.parameters['filter'] = speckleFilter
        last = sf.id
    ############################################
    # configuration of node sequence for specific geocoding approaches
    bands = dissolve([bandnames[opt] for opt in refarea])
    if geocoding_type == 'Range-Doppler':
        tc = parse_node('Terrain-Correction')
        workflow.insert_node(tc, before=last)
        tc.parameters['sourceBands'] = bands
    elif geocoding_type == 'SAR simulation cross correlation':
        sarsim = parse_node('SAR-Simulation')
        workflow.insert_node(sarsim, before=last)
        sarsim.parameters['sourceBands'] = bands

        workflow.insert_node(parse_node('Cross-Correlation'),
                             before='SAR-Simulation')

        tc = parse_node('SARSim-Terrain-Correction')
        workflow.insert_node(tc, before='Cross-Correlation')
    else:
        raise RuntimeError('geocode_type not recognized')

    tc.parameters['alignToStandardGrid'] = alignToStandardGrid
    tc.parameters['standardGridOriginX'] = standardGridOriginX
    tc.parameters['standardGridOriginY'] = standardGridOriginY
    ############################################
    # Multilook node configuration

    try:
        image_geometry = id.meta['image_geometry']
        incidence = id.meta['incidence']
    except KeyError:
        raise RuntimeError(
            'This function does not yet support sensor {}'.format(id.sensor))

    rlks, azlks = multilook_factors(sp_rg=id.spacing[0],
                                    sp_az=id.spacing[1],
                                    tr_rg=tr,
                                    tr_az=tr,
                                    geometry=image_geometry,
                                    incidence=incidence)

    if azlks > 1 or rlks > 1:
        workflow.insert_node(parse_node('Multilook'), before='Calibration')
        ml = workflow['Multilook']
        ml.parameters['nAzLooks'] = azlks
        ml.parameters['nRgLooks'] = rlks
        ml.parameters['sourceBands'] = None
    ############################################
    # merge sigma0 and gamma0 bands to pass them to Terrain-Correction
    if len(refarea) > 1 and terrainFlattening:
        bm = parse_node('BandMerge')
        workflow.insert_node(bm, before=[tf.source, tf.id])
        sources = bm.source
        gamma_index = sources.index('Terrain-Flattening')
        sigma_index = abs(gamma_index - 1)
        s1_id = os.path.basename(os.path.splitext(id.scene)[0])
        bands_long = []
        for band in bands:
            comp = [band + '::']
            if shapefile is not None:
                comp.append('Subset_')
            comp.append(s1_id)
            if band.startswith('Gamma'):
                comp.append('_' + workflow.suffix(stop=sources[gamma_index]))
            else:
                comp.append('_' + workflow.suffix(stop=sources[sigma_index]))
            bands_long.append(''.join(comp))
        bm.parameters['sourceBands'] = bands_long
        bm.parameters['geographicError'] = 0.0
        ############################################
    # specify spatial resolution and coordinate reference system of the output dataset
    # print('-- configuring CRS')
    tc.parameters['pixelSpacingInMeter'] = tr

    try:
        t_srs = crsConvert(t_srs, 'epsg')
    except TypeError:
        raise RuntimeError("format of parameter 't_srs' not recognized")

    # the EPSG code 4326 is not supported by SNAP and thus the WKT string has to be defined;
    # in all other cases defining EPSG:{code} will do
    if t_srs == 4326:
        t_srs = 'GEOGCS["WGS84(DD)",' \
                'DATUM["WGS84",' \
                'SPHEROID["WGS84", 6378137.0, 298.257223563]],' \
                'PRIMEM["Greenwich", 0.0],' \
                'UNIT["degree", 0.017453292519943295],' \
                'AXIS["Geodetic longitude", EAST],' \
                'AXIS["Geodetic latitude", NORTH]]'
    else:
        t_srs = 'EPSG:{}'.format(t_srs)

    tc.parameters['mapProjection'] = t_srs
    ############################################
    # (optionally) add node for conversion from linear to db scaling
    # print('-- configuring LinearToFromdB Node')
    if scaling not in ['dB', 'db', 'linear']:
        raise RuntimeError(
            'scaling must be  a string of either "dB", "db" or "linear"')

    if scaling in ['dB', 'db']:
        lin2db = parse_node('LinearToFromdB')
        workflow.insert_node(lin2db, before=tc.id)
        lin2db.parameters['sourceBands'] = bands

    ############################################
    # (optionally) add subset node and add bounding box coordinates of defined shapefile
    # print('-- configuring Subset Node')
    if shapefile:
        # print('--- read')
        if isinstance(shapefile, dict):
            ext = shapefile
        else:
            if isinstance(shapefile, Vector):
                shp = shapefile.clone()
            elif isinstance(shapefile, str):
                shp = Vector(shapefile)
            else:
                raise TypeError(
                    "argument 'shapefile' must be either a dictionary, a Vector object or a string."
                )
            # reproject the geometry to WGS 84 latlon
            shp.reproject(4326)
            ext = shp.extent
            shp.close()
        # add an extra buffer of 0.01 degrees
        buffer = 0.01
        ext['xmin'] -= buffer
        ext['ymin'] -= buffer
        ext['xmax'] += buffer
        ext['ymax'] += buffer
        # print('--- create bbox')
        with bbox(ext, 4326) as bounds:
            # print('--- intersect')
            inter = intersect(id.bbox(), bounds)
            if not inter:
                raise RuntimeError(
                    'no bounding box intersection between shapefile and scene')
            inter.close()
            wkt = bounds.convert2wkt()[0]

        subset = parse_node('Subset')
        workflow.insert_node(subset, before=read.id)
        subset.parameters['region'] = [0, 0, id.samples, id.lines]
        subset.parameters['geoRegion'] = wkt
        subset.parameters['copyMetadata'] = True
    ############################################
    # (optionally) configure subset node for pixel offsets
    if offset and not shapefile:
        subset = parse_node('Subset')
        workflow.insert_node(subset, before=read.id)

        # left, right, top and bottom offset in pixels
        l, r, t, b = offset

        subset_values = [l, t, id.samples - l - r, id.lines - t - b]
        subset.parameters['region'] = subset_values
        subset.parameters['geoRegion'] = ''
    ############################################
    # parametrize write node
    # print('-- configuring Write Node')
    # create a suffix for the output file to identify processing steps performed in the workflow
    suffix = workflow.suffix()
    if tmpdir is None:
        tmpdir = outdir
    basename = os.path.join(tmpdir, id.outname_base(basename_extensions))
    outname = basename + '_' + suffix

    write = workflow['Write']
    write.parameters['file'] = outname
    write.parameters['formatName'] = 'ENVI'
    ############################################
    ############################################
    if export_extra is not None:
        tc_options = [
            'incidenceAngleFromEllipsoid', 'localIncidenceAngle',
            'projectedLocalIncidenceAngle', 'DEM'
        ]
        tc_write = None
        tc_selection = []
        for item in export_extra:
            if item in tc_options:
                if tc_write is None:
                    tc_write = parse_node('Write')
                    workflow.insert_node(tc_write,
                                         before=tc.id,
                                         resetSuccessorSource=False)
                    tc_write.parameters['file'] = outname
                    tc_write.parameters['formatName'] = 'ENVI'
                key = 'save{}{}'.format(item[0].upper(), item[1:])
                tc.parameters[key] = True
                tc_selection.append(item)
            elif item == 'layoverShadowMask':
                sarsim = parse_node('SAR-Simulation')
                sarsim.parameters['saveLayoverShadowMask'] = True
                workflow.insert_node(sarsim,
                                     after=tc.id,
                                     resetSuccessorSource=False)
                sarsim_select = parse_node('BandSelect')
                sarsim_select.parameters['sourceBands'] = 'layover_shadow_mask'
                workflow.insert_node(sarsim_select,
                                     before=sarsim.id,
                                     resetSuccessorSource=False)

                sarsim_tc = parse_node('Terrain-Correction')
                workflow.insert_node(sarsim_tc, before=sarsim_select.id)
                sarsim_tc.parameters[
                    'alignToStandardGrid'] = alignToStandardGrid
                sarsim_tc.parameters[
                    'standardGridOriginX'] = standardGridOriginX
                sarsim_tc.parameters[
                    'standardGridOriginY'] = standardGridOriginY
                sarsim_tc.parameters[
                    'imgResamplingMethod'] = 'NEAREST_NEIGHBOUR'
                resampling_exceptions.append(sarsim_tc.id)

                sarsim_write = parse_node('Write')
                sarsim_write.parameters['file'] = outname
                sarsim_write.parameters['formatName'] = 'ENVI'
                workflow.insert_node(sarsim_write,
                                     before=sarsim_tc.id,
                                     resetSuccessorSource=False)
            elif item == 'scatteringArea':
                area_select = parse_node('BandSelect')
                workflow.insert_node(area_select,
                                     before=tf.source,
                                     resetSuccessorSource=False)
                area_select.parameters['sourceBands'] = bandnames['beta0']

                area_merge1 = parse_node('BandMerge')
                workflow.insert_node(area_merge1,
                                     before=[tf.id, area_select.id],
                                     resetSuccessorSource=False)

                math = parse_node('BandMaths')
                math.element.attrib[
                    'class'] = '"com.bc.ceres.binding.dom.XppDomElement"'
                workflow.insert_node(math,
                                     before=area_merge1.id,
                                     resetSuccessorSource=False)

                # math = parse_node('BandMaths')
                # workflow.insert_node(math, before=[tf.id, area_select.id], resetSuccessorSource=False)
                math.parameters.clear_variables()
                exp = math.parameters['targetBands'][0]
                exp['name'] = 'scatteringArea_VV'
                exp['type'] = 'float32'
                exp['expression'] = 'Beta0_VV / Gamma0_VV'
                exp['noDataValue'] = 0.0

                area_merge2 = parse_node('BandMerge')
                workflow.insert_node(area_merge2,
                                     before=[tf.id, math.id],
                                     resetSuccessorSource=False)
                tc.source = area_merge2.id

                # modify Terrain-Correction source bands
                tc_bands = tc.parameters['sourceBands'] + ',scatteringArea_VV'
                tc.parameters['sourceBands'] = tc_bands

                # add scattering Area to list of band directly written from Terrain-Correction
                tc_selection.append('scatteringArea_VV')
            else:
                raise RuntimeError(
                    "ID '{}' not valid for argument 'export_extra'".format(
                        item))
        if len(tc_selection) > 0:
            tc_select = parse_node('BandSelect')
            workflow.insert_node(tc_select, after=tc_write.id)
            tc_select.parameters['sourceBands'] = tc_selection
    ############################################
    ############################################
    # select DEM type
    # print('-- configuring DEM')
    dempar = {
        'externalDEMFile': externalDEMFile,
        'externalDEMApplyEGM': externalDEMApplyEGM
    }
    if externalDEMFile is not None:
        if os.path.isfile(externalDEMFile):
            if externalDEMNoDataValue is None:
                with Raster(externalDEMFile) as dem:
                    dempar['externalDEMNoDataValue'] = dem.nodata
                if dempar['externalDEMNoDataValue'] is None:
                    raise RuntimeError(
                        'Cannot read NoData value from DEM file. '
                        'Please specify externalDEMNoDataValue')
            else:
                dempar['externalDEMNoDataValue'] = externalDEMNoDataValue
            dempar['reGridMethod'] = False
        else:
            raise RuntimeError('specified externalDEMFile does not exist')
        dempar['demName'] = 'External DEM'
    else:
        dempar['demName'] = demName
        dempar['externalDEMFile'] = None
        dempar['externalDEMNoDataValue'] = 0

    for key, value in dempar.items():
        workflow.set_par(key, value)

    # download the EGM lookup table if necessary
    if dempar['externalDEMApplyEGM']:
        get_egm96_lookup()
    ############################################
    ############################################
    # configure the resampling methods

    options = [
        'NEAREST_NEIGHBOUR', 'BILINEAR_INTERPOLATION', 'CUBIC_CONVOLUTION',
        'BISINC_5_POINT_INTERPOLATION', 'BISINC_11_POINT_INTERPOLATION',
        'BISINC_21_POINT_INTERPOLATION', 'BICUBIC_INTERPOLATION'
    ]

    message = '{0} must be one of the following:\n- {1}'
    if demResamplingMethod not in options:
        raise ValueError(
            message.format('demResamplingMethod', '\n- '.join(options)))
    if imgResamplingMethod not in options:
        raise ValueError(
            message.format('imgResamplingMethod', '\n- '.join(options)))

    workflow.set_par('demResamplingMethod', demResamplingMethod)
    workflow.set_par('imgResamplingMethod',
                     imgResamplingMethod,
                     exceptions=resampling_exceptions)
    ############################################
    ############################################
    # additional parameter settings applied to the whole workflow

    workflow.set_par('nodataValueAtSea', nodataValueAtSea)
    ############################################
    ############################################
    # write workflow to file and optionally execute it
    # print('- writing workflow to file')

    wf_name = outname.replace(tmpdir, outdir) + '_proc.xml'
    workflow.write(wf_name)

    # execute the newly written workflow
    if not test:
        try:
            groups = groupbyWorkers(wf_name, groupsize)
            gpt(wf_name,
                groups=groups,
                cleanup=cleanup,
                gpt_exceptions=gpt_exceptions,
                gpt_args=gpt_args,
                removeS1BorderNoiseMethod=removeS1BorderNoiseMethod,
                outdir=outdir)
        except RuntimeError as e:
            print(str(e))
            with open(wf_name.replace('_proc.xml', '_error.log'), 'w') as log:
                log.write(str(e))
    if returnWF:
        return wf_name
コード例 #9
0
ファイル: util.py プロジェクト: sheecegardezi/pyroSAR
def geocode(infile, outdir, t_srs=4326, tr=20, polarizations='all', shapefile=None, scaling='dB',
            geocoding_type='Range-Doppler', removeS1BorderNoise=True, removeS1ThermalNoise=True, offset=None,
            externalDEMFile=None, externalDEMNoDataValue=None, externalDEMApplyEGM=True, terrainFlattening=True,
            basename_extensions=None, test=False, export_extra=None, groupsize=2, cleanup=True,
            gpt_exceptions=None, returnWF=False,
            demResamplingMethod='BILINEAR_INTERPOLATION', imgResamplingMethod='BILINEAR_INTERPOLATION',
            speckleFilter=False, refarea='gamma0'):
    """
    wrapper function for geocoding SAR images using ESA SNAP

    Parameters
    ----------
    infile: str or ~pyroSAR.drivers.ID
        the SAR scene to be processed
    outdir: str
        The directory to write the final files to.
    t_srs: int, str or osr.SpatialReference
        A target geographic reference system in WKT, EPSG, PROJ4 or OPENGIS format.
        See function :func:`spatialist.auxil.crsConvert()` for details.
        Default: `4326 <http://spatialreference.org/ref/epsg/4326/>`_.
    tr: int or float, optional
        The target resolution in meters. Default is 20
    polarizations: list or {'VV', 'HH', 'VH', 'HV', 'all'}, optional
        The polarizations to be processed; can be a string for a single polarization e.g. 'VV' or a list of several
        polarizations e.g. ['VV', 'VH']. Default is 'all'.
    shapefile: str or :py:class:`~spatialist.vector.Vector`, optional
        A vector geometry for subsetting the SAR scene to a test site. Default is None.
    scaling: {'dB', 'db', 'linear'}, optional
        Should the output be in linear or decibel scaling? Default is 'dB'.
    geocoding_type: {'Range-Doppler', 'SAR simulation cross correlation'}, optional
        The type of geocoding applied; can be either 'Range-Doppler' (default) or 'SAR simulation cross correlation'
    removeS1BorderNoise: bool, optional
        Enables removal of S1 GRD border noise (default).
    removeS1ThermalNoise: bool, optional
        Enables removal of S1 thermal noise (default).
    offset: tuple, optional
        A tuple defining offsets for left, right, top and bottom in pixels, e.g. (100, 100, 0, 0); this variable is
        overridden if a shapefile is defined. Default is None.
    externalDEMFile: str or None, optional
        The absolute path to an external DEM file. Default is None.
    externalDEMNoDataValue: int, float or None, optional
        The no data value of the external DEM. If not specified (default) the function will try to read it from the
        specified external DEM.
    externalDEMApplyEGM: bool, optional
        Apply Earth Gravitational Model to external DEM? Default is True.
    terainFlattening: bool
        apply topographic normalization on the data?
    basename_extensions: list of str
        names of additional parameters to append to the basename, e.g. ['orbitNumber_rel']
    test: bool, optional
        If set to True the workflow xml file is only written and not executed. Default is False.
    export_extra: list or None
        a list of image file IDs to be exported to outdir. The following IDs are currently supported:
         - incidenceAngleFromEllipsoid
         - localIncidenceAngle
         - projectedLocalIncidenceAngle
         - DEM
    groupsize: int
        the number of workers executed together in one gpt call
    cleanup: bool
        should all files written to the temporary directory during function execution be deleted after processing?
    gpt_exceptions: dict
        a dictionary to override the configured GPT executable for certain operators;
        each (sub-)workflow containing this operator will be executed with the define executable;
        
         - e.g. ``{'Terrain-Flattening': '/home/user/snap/bin/gpt'}``
    returnWF: bool
        return the full name of the written workflow XML file?
    demResamplingMethod: str
        one of the following:
         - 'NEAREST_NEIGHBOUR'
         - 'BILINEAR_INTERPOLATION'
         - 'CUBIC_CONVOLUTION'
         - 'BISINC_5_POINT_INTERPOLATION'
         - 'BISINC_11_POINT_INTERPOLATION'
         - 'BISINC_21_POINT_INTERPOLATION'
         - 'BICUBIC_INTERPOLATION'
    imgResamplingMethod: str
        the resampling method for geocoding the SAR image; the options are identical to demResamplingMethod
    speckleFilter: str
        one of the following:
         - 'Boxcar'
         - 'Median'
         - 'Frost'
         - 'Gamma Map'
         - 'Refined Lee'
         - 'Lee'
         - 'Lee Sigma'
    refarea: str
        one of the following:
         - 'beta0'
         - 'gamma0'
         - 'sigma0'
    
    Returns
    -------
    str or None
        either the name of the workflow file if `returnWF == True` or None otherwise

    Note
    ----
    If only one polarization is selected and not extra products are defined the results are directly written to GeoTiff.
    Otherwise the results are first written to a folder containing ENVI files and then transformed to GeoTiff files
    (one for each polarization/extra product).
    If GeoTiff would directly be selected as output format for multiple polarizations then a multilayer GeoTiff
    is written by SNAP which is considered an unfavorable format
    
    
    .. figure:: figures/snap_geocode.png
        :scale: 25%
        :align: center
        
        Workflow diagram for function geocode for processing a Sentinel-1 Ground Range
        Detected (GRD) scene to radiometrically terrain corrected (RTC) backscatter.
        An additional Subset node might be inserted in case a vector geometry is provided.

    Examples
    --------
    geocode a Sentinel-1 scene and export the local incidence angle map with it

    >>> from pyroSAR.snap import geocode
    >>> filename = 'S1A_IW_GRDH_1SDV_20180829T170656_20180829T170721_023464_028DE0_F7BD.zip'
    >>> geocode(infile=filename, outdir='outdir', tr=20, scaling='dB',
    >>>         export_extra=['DEM', 'localIncidenceAngle'], t_srs=4326)

    See Also
    --------
    :class:`pyroSAR.drivers.ID`,
    :class:`spatialist.vector.Vector`,
    :func:`spatialist.auxil.crsConvert()`
    """
    
    id = infile if isinstance(infile, pyroSAR.ID) else pyroSAR.identify(infile)
    
    if id.is_processed(outdir):
        print('scene {} already processed'.format(id.outname_base()))
        return
    # print(os.path.basename(id.scene))
    if not os.path.isdir(outdir):
        os.makedirs(outdir)
    ############################################
    # general setup
    
    if id.sensor in ['ASAR', 'ERS1', 'ERS2']:
        formatName = 'ENVISAT'
    elif id.sensor in ['S1A', 'S1B']:
        if id.product == 'SLC':
            raise RuntimeError('Sentinel-1 SLC data is not supported yet')
        formatName = 'SENTINEL-1'
    else:
        raise RuntimeError('sensor not supported (yet)')
    ######################
    # print('- assessing polarization selection')
    if isinstance(polarizations, str):
        if polarizations == 'all':
            polarizations = id.polarizations
        else:
            if polarizations in id.polarizations:
                polarizations = [polarizations]
            else:
                raise RuntimeError('polarization {} does not exists in the source product'.format(polarizations))
    elif isinstance(polarizations, list):
        polarizations = [x for x in polarizations if x in id.polarizations]
    else:
        raise RuntimeError('polarizations must be of type str or list')
    
    format = 'GeoTiff-BigTIFF' if len(polarizations) == 1 and export_extra is None else 'ENVI'
    # print(polarizations)
    # print(format)
    
    bandnames = dict()
    bandnames['int'] = ['Intensity_' + x for x in polarizations]
    bandnames['beta0'] = ['Beta0_' + x for x in polarizations]
    bandnames['gamma0'] = ['Gamma0_' + x for x in polarizations]
    bandnames['sigma0'] = ['Sigma0_' + x for x in polarizations]
    ############################################
    ############################################
    # parse base workflow
    # print('- parsing base workflow')
    workflow = parse_recipe('base')
    ############################################
    # Read node configuration
    # print('-- configuring Read Node')
    read = workflow['Read']
    read.parameters['file'] = id.scene
    read.parameters['formatName'] = formatName
    ############################################
    # Remove-GRD-Border-Noise node configuration
    # print('-- configuring Remove-GRD-Border-Noise Node')
    if id.sensor in ['S1A', 'S1B'] and removeS1BorderNoise:
        bn = parse_node('Remove-GRD-Border-Noise')
        workflow.insert_node(bn, before='Read')
        bn.parameters['selectedPolarisations'] = polarizations
    ############################################
    # ThermalNoiseRemoval node configuration
    # print('-- configuring ThermalNoiseRemoval Node')
    if id.sensor in ['S1A', 'S1B'] and removeS1ThermalNoise:
        tn = parse_node('ThermalNoiseRemoval')
        workflow.insert_node(tn, before='Read')
        tn.parameters['selectedPolarisations'] = polarizations
    ############################################
    # orbit file application node configuration
    # print('-- configuring Apply-Orbit-File Node')
    orbit_lookup = {'ENVISAT': 'DELFT Precise (ENVISAT, ERS1&2) (Auto Download)',
                    'SENTINEL-1': 'Sentinel Precise (Auto Download)'}
    orbitType = orbit_lookup[formatName]
    if formatName == 'ENVISAT' and id.acquisition_mode == 'WSM':
        orbitType = 'DORIS Precise VOR (ENVISAT) (Auto Download)'
    
    orb = workflow['Apply-Orbit-File']
    orb.parameters['orbitType'] = orbitType
    ############################################
    # calibration node configuration
    # print('-- configuring Calibration Node')
    cal = workflow['Calibration']
    cal.parameters['selectedPolarisations'] = polarizations
    cal.parameters['sourceBands'] = bandnames['int']
    if terrainFlattening:
        if refarea != 'gamma0':
            raise RuntimeError('if terrain flattening is applied refarea must be gamma0')
        cal.parameters['outputBetaBand'] = True
    else:
        refarea_options = ['sigma0', 'beta0', 'gamma0']
        if refarea not in refarea_options:
            message = '{0} must be one of the following:\n- {1}'
            raise ValueError(message.format('refarea', '\n- '.join(refarea_options)))
        cal.parameters['output{}Band'.format(refarea[:-1].capitalize())] = True
    last = cal.id
    ############################################
    # terrain flattening node configuration
    # print('-- configuring Terrain-Flattening Node')
    if terrainFlattening:
        tf = parse_node('Terrain-Flattening')
        workflow.insert_node(tf, before=last)
        if id.sensor in ['ERS1', 'ERS2'] or (id.sensor == 'ASAR' and id.acquisition_mode != 'APP'):
            tf.parameters['sourceBands'] = 'Beta0'
        else:
            tf.parameters['sourceBands'] = bandnames['beta0']
        if externalDEMFile is None:
            tf.parameters['reGridMethod'] = True
        else:
            tf.parameters['reGridMethod'] = False
        last = tf.id
    ############################################
    # speckle filtering node configuration
    speckleFilter_options = ['Boxcar',
                             'Median',
                             'Frost',
                             'Gamma Map',
                             'Refined Lee',
                             'Lee',
                             'Lee Sigma']
    
    if speckleFilter:
        message = '{0} must be one of the following:\n- {1}'
        if speckleFilter not in speckleFilter_options:
            raise ValueError(message.format('speckleFilter', '\n- '.join(speckleFilter_options)))
        sf = parse_node('Speckle-Filter')
        workflow.insert_node(sf, before=last)
        sf.parameters['sourceBands'] = bandnames[refarea]
        sf.parameters['filter'] = speckleFilter
        last = sf.id
    ############################################
    # configuration of node sequence for specific geocoding approaches
    # print('-- configuring geocoding approach Nodes')
    if geocoding_type == 'Range-Doppler':
        tc = parse_node('Terrain-Correction')
        workflow.insert_node(tc, before=last)
        tc.parameters['sourceBands'] = bandnames[refarea]
    elif geocoding_type == 'SAR simulation cross correlation':
        sarsim = parse_node('SAR-Simulation')
        workflow.insert_node(sarsim, before=last)
        sarsim.parameters['sourceBands'] = bandnames[refarea]
        
        workflow.insert_node(parse_node('Cross-Correlation'), before='SAR-Simulation')
        
        tc = parse_node('SARSim-Terrain-Correction')
        workflow.insert_node(tc, before='Cross-Correlation')
    else:
        raise RuntimeError('geocode_type not recognized')
    
    ############################################
    # Multilook node configuration
    
    try:
        image_geometry = id.meta['image_geometry']
        incidence = id.meta['incidence']
    except KeyError:
        raise RuntimeError('This function does not yet support sensor {}'.format(id.sensor))
    
    rlks, azlks = multilook_factors(sp_rg=id.spacing[0],
                                    sp_az=id.spacing[1],
                                    tr_rg=tr,
                                    tr_az=tr,
                                    geometry=image_geometry,
                                    incidence=incidence)
    
    if azlks > 1 or rlks > 1:
        workflow.insert_node(parse_node('Multilook'), before='Calibration')
        ml = workflow['Multilook']
        ml.parameters['nAzLooks'] = azlks
        ml.parameters['nRgLooks'] = rlks
        ml.parameters['sourceBands'] = None
        # if cal.parameters['outputBetaBand']:
        #     ml.parameters['sourceBands'] = bandnames['beta0']
        # elif cal.parameters['outputGammaBand']:
        #     ml.parameters['sourceBands'] = bandnames['gamma0']
        # elif cal.parameters['outputSigmaBand']:
        #     ml.parameters['sourceBands'] = bandnames['sigma0']
    ############################################
    # specify spatial resolution and coordinate reference system of the output dataset
    # print('-- configuring CRS')
    tc.parameters['pixelSpacingInMeter'] = tr
    
    try:
        t_srs = crsConvert(t_srs, 'epsg')
    except TypeError:
        raise RuntimeError("format of parameter 't_srs' not recognized")
    
    # the EPSG code 4326 is not supported by SNAP and thus the WKT string has to be defined;
    # in all other cases defining EPSG:{code} will do
    if t_srs == 4326:
        t_srs = 'GEOGCS["WGS84(DD)",' \
                'DATUM["WGS84",' \
                'SPHEROID["WGS84", 6378137.0, 298.257223563]],' \
                'PRIMEM["Greenwich", 0.0],' \
                'UNIT["degree", 0.017453292519943295],' \
                'AXIS["Geodetic longitude", EAST],' \
                'AXIS["Geodetic latitude", NORTH]]'
    else:
        t_srs = 'EPSG:{}'.format(t_srs)
    
    tc.parameters['mapProjection'] = t_srs
    ############################################
    # (optionally) add node for conversion from linear to db scaling
    # print('-- configuring LinearToFromdB Node')
    if scaling not in ['dB', 'db', 'linear']:
        raise RuntimeError('scaling must be  a string of either "dB", "db" or "linear"')
    
    if scaling in ['dB', 'db']:
        lin2db = parse_node('lin2db')
        workflow.insert_node(lin2db, before=tc.id)
        lin2db.parameters['sourceBands'] = bandnames[refarea]
    
    ############################################
    # (optionally) add subset node and add bounding box coordinates of defined shapefile
    # print('-- configuring Subset Node')
    if shapefile:
        # print('--- read')
        shp = shapefile.clone() if isinstance(shapefile, Vector) else Vector(shapefile)
        # reproject the geometry to WGS 84 latlon
        # print('--- reproject')
        shp.reproject(4326)
        ext = shp.extent
        shp.close()
        # add an extra buffer of 0.01 degrees
        buffer = 0.01
        ext['xmin'] -= buffer
        ext['ymin'] -= buffer
        ext['xmax'] += buffer
        ext['ymax'] += buffer
        # print('--- create bbox')
        with bbox(ext, 4326) as bounds:
            # print('--- intersect')
            inter = intersect(id.bbox(), bounds)
            if not inter:
                raise RuntimeError('no bounding box intersection between shapefile and scene')
            # print('--- close intersect')
            inter.close()
            # print('--- get wkt')
            wkt = bounds.convert2wkt()[0]
        
        # print('--- parse XML node')
        subset = parse_node('Subset')
        # print('--- insert node')
        workflow.insert_node(subset, before='Read')
        subset.parameters['region'] = [0, 0, id.samples, id.lines]
        subset.parameters['geoRegion'] = wkt
    ############################################
    # (optionally) configure subset node for pixel offsets
    if offset and not shapefile:
        subset = parse_node('Subset')
        workflow.insert_node(subset, before='Read')
        
        # left, right, top and bottom offset in pixels
        l, r, t, b = offset
        
        subset_values = [l, t, id.samples - l - r, id.lines - t - b]
        subset.parameters['region'] = subset_values
        subset.parameters['geoRegion'] = ''
    ############################################
    # parametrize write node
    # print('-- configuring Write Node')
    # create a suffix for the output file to identify processing steps performed in the workflow
    suffix = workflow.suffix
    
    basename = os.path.join(outdir, id.outname_base(basename_extensions))
    extension = suffix if format == 'ENVI' else polarizations[0] + '_' + suffix
    outname = basename + '_' + extension
    
    write = workflow['Write']
    write.parameters['file'] = outname
    write.parameters['formatName'] = format
    ############################################
    ############################################
    if export_extra is not None:
        options = ['incidenceAngleFromEllipsoid',
                   'localIncidenceAngle',
                   'projectedLocalIncidenceAngle',
                   'DEM']
        write = parse_node('Write')
        workflow.insert_node(write, before=tc.id, resetSuccessorSource=False)
        write.parameters['file'] = outname
        write.parameters['formatName'] = format
        for item in export_extra:
            if item not in options:
                raise RuntimeError("ID '{}' not valid for argument 'export_extra'".format(item))
            key = 'save{}{}'.format(item[0].upper(), item[1:])
            tc.parameters[key] = True
    ############################################
    ############################################
    # select DEM type
    # print('-- configuring DEM')
    dempar = {'externalDEMFile': externalDEMFile,
              'externalDEMApplyEGM': externalDEMApplyEGM}
    if externalDEMFile is not None:
        if os.path.isfile(externalDEMFile):
            if externalDEMNoDataValue is None:
                with Raster(externalDEMFile) as dem:
                    dempar['externalDEMNoDataValue'] = dem.nodata
                if dempar['externalDEMNoDataValue'] is None:
                    raise RuntimeError('Cannot read NoData value from DEM file. '
                                       'Please specify externalDEMNoDataValue')
            else:
                dempar['externalDEMNoDataValue'] = externalDEMNoDataValue
            dempar['reGridMethod'] = False
        else:
            raise RuntimeError('specified externalDEMFile does not exist')
        dempar['demName'] = 'External DEM'
    else:
        # SRTM 1arcsec is only available between -58 and +60 degrees.
        # If the scene exceeds those latitudes SRTM 3arcsec is selected.
        if id.getCorners()['ymax'] > 60 or id.getCorners()['ymin'] < -58:
            dempar['demName'] = 'SRTM 3Sec'
        else:
            dempar['demName'] = 'SRTM 1Sec HGT'
        dempar['externalDEMFile'] = None
        dempar['externalDEMNoDataValue'] = 0
    
    for key, value in dempar.items():
        workflow.set_par(key, value)
    ############################################
    ############################################
    # configure the resampling methods
    
    options = ['NEAREST_NEIGHBOUR',
               'BILINEAR_INTERPOLATION',
               'CUBIC_CONVOLUTION',
               'BISINC_5_POINT_INTERPOLATION',
               'BISINC_11_POINT_INTERPOLATION',
               'BISINC_21_POINT_INTERPOLATION',
               'BICUBIC_INTERPOLATION']
    
    message = '{0} must be one of the following:\n- {1}'
    if demResamplingMethod not in options:
        raise ValueError(message.format('demResamplingMethod', '\n- '.join(options)))
    if imgResamplingMethod not in options:
        raise ValueError(message.format('imgResamplingMethod', '\n- '.join(options)))
    
    workflow.set_par('demResamplingMethod', demResamplingMethod)
    workflow.set_par('imgResamplingMethod', imgResamplingMethod)
    ############################################
    ############################################
    # write workflow to file and optionally execute it
    # print('- writing workflow to file')
    
    workflow.write(outname + '_proc')
    
    # execute the newly written workflow
    if not test:
        try:
            groups = groupbyWorkers(outname + '_proc.xml', groupsize)
            gpt(outname + '_proc.xml', groups=groups, cleanup=cleanup,
                gpt_exceptions=gpt_exceptions)
        except RuntimeError as e:
            if cleanup:
                if os.path.isdir(outname):
                    shutil.rmtree(outname)
                elif os.path.isfile(outname):
                    os.remove(outname)
                os.remove(outname + '_proc.xml')
            with open(outname + '_error.log', 'w') as log:
                log.write(str(e))
    
    if returnWF:
        return outname + '_proc.xml'
コード例 #10
0
def geocode(infile, outdir, t_srs=4326, tr=20, polarizations='all', shapefile=None, scaling='dB',
            geocoding_type='Range-Doppler', removeS1BoderNoise=True, offset=None, externalDEMFile=None,
            externalDEMNoDataValue=None, externalDEMApplyEGM=True, basename_extensions=None, test=False):
    """
    wrapper function for geocoding SAR images using ESA SNAP

    Parameters
    ----------
    infile: str or ~pyroSAR.drivers.ID
        the SAR scene to be processed
    outdir: str
        The directory to write the final files to.
    t_srs: int, str or osr.SpatialReference
        A target geographic reference system in WKT, EPSG, PROJ4 or OPENGIS format.
        See function :func:`spatialist.auxil.crsConvert()` for details.
        Default: `4326 <http://spatialreference.org/ref/epsg/4326/>`_.
    tr: int or float, optional
        The target resolution in meters. Default is 20
    polarizations: list or {'VV', 'HH', 'VH', 'HV', 'all'}, optional
        The polarizations to be processed; can be a string for a single polarization e.g. 'VV' or a list of several
        polarizations e.g. ['VV', 'VH']. Default is 'all'.
    shapefile: str or :py:class:`~spatialist.vector.Vector`, optional
        A vector geometry for subsetting the SAR scene to a test site. Default is None.
    scaling: {'dB', 'db', 'linear'}, optional
        Should the output be in linear or decibel scaling? Default is 'dB'.
    geocoding_type: {'Range-Doppler', 'SAR simulation cross correlation'}, optional
        The type of geocoding applied; can be either 'Range-Doppler' (default) or 'SAR simulation cross correlation'
    removeS1BoderNoise: bool, optional
        Enables removal of S1 GRD border noise (default).
    offset: tuple, optional
        A tuple defining offsets for left, right, top and bottom in pixels, e.g. (100, 100, 0, 0); this variable is
        overridden if a shapefile is defined. Default is None.
    externalDEMFile: str or None, optional
        The absolute path to an external DEM file. Default is None.
    externalDEMNoDataValue: int, float or None, optional
        The no data value of the external DEM. If not specified (default) the function will try to read it from the
        specified external DEM.
    externalDEMApplyEGM: bool, optional
        Apply Earth Gravitational Model to external DEM? Default is True.
    basename_extensions: list of str
        names of additional parameters to append to the basename, e.g. ['orbitNumber_rel']
    test: bool, optional
        If set to True the workflow xml file is only written and not executed. Default is False.

    Note
    ----
    If only one polarization is selected the results are directly written to GeoTiff.
    Otherwise the results are first written to a folder containing ENVI files and then transformed to GeoTiff files
    (one for each polarization).
    If GeoTiff would directly be selected as output format for multiple polarizations then a multilayer GeoTiff
    is written by SNAP which is considered an unfavorable format

    See Also
    --------
    :class:`pyroSAR.drivers.ID`,
    :class:`spatialist.vector.Vector`,
    :func:`spatialist.auxil.crsConvert()`
    """
    
    id = infile if isinstance(infile, pyroSAR.ID) else pyroSAR.identify(infile)
    
    if id.is_processed(outdir):
        print('scene {} already processed'.format(id.outname_base()))
        return
    # print(os.path.basename(id.scene))
    if not os.path.isdir(outdir):
        os.makedirs(outdir)
    ############################################
    # general setup
    
    if id.sensor in ['ASAR', 'ERS1', 'ERS2']:
        formatName = 'ENVISAT'
    elif id.sensor in ['S1A', 'S1B']:
        if id.product == 'SLC':
            raise RuntimeError('Sentinel-1 SLC data is not supported yet')
        formatName = 'SENTINEL-1'
    else:
        raise RuntimeError('sensor not supported (yet)')
    ######################
    # print('- assessing polarization selection')
    if isinstance(polarizations, str):
        if polarizations == 'all':
            polarizations = id.polarizations
        else:
            if polarizations in id.polarizations:
                polarizations = [polarizations]
            else:
                raise RuntimeError('polarization {} does not exists in the source product'.format(polarizations))
    elif isinstance(polarizations, list):
        polarizations = [x for x in polarizations if x in id.polarizations]
    else:
        raise RuntimeError('polarizations must be of type str or list')
    
    format = 'GeoTiff-BigTIFF' if len(polarizations) == 1 else 'ENVI'
    # print(polarizations)
    # print(format)
    
    bands_int = ','.join(['Intensity_' + x for x in polarizations])
    bands_beta = ','.join(['Beta0_' + x for x in polarizations])
    bands_gamma = ','.join(['Gamma0_' + x for x in polarizations])
    ############################################
    ############################################
    # parse base workflow
    # print('- parsing base workflow')
    workflow = parse_recipe('geocode')
    ############################################
    # Read node configuration
    # print('-- configuring Read Node')
    read = workflow.find('.//node[@id="Read"]')
    read.find('.//parameters/file').text = id.scene
    read.find('.//parameters/formatName').text = formatName
    ############################################
    # Remove-GRD-Border-Noise node configuration
    # print('-- configuring Remove-GRD-Border-Noise Node')
    if id.sensor in ['S1A', 'S1B'] and removeS1BoderNoise:
        insert_node(workflow, parse_node('Remove-GRD-Border-Noise'), before='Read')
        bn = workflow.find('.//node[@id="Remove-GRD-Border-Noise"]')
        bn.find('.//parameters/selectedPolarisations').text = ','.join(polarizations)
    ############################################
    # orbit file application node configuration
    # print('-- configuring Apply-Orbit-File Node')
    orbit_lookup = {'ENVISAT': 'DELFT Precise (ENVISAT, ERS1&2) (Auto Download)',
                    'SENTINEL-1': 'Sentinel Precise (Auto Download)'}
    orbitType = orbit_lookup[formatName]
    if formatName == 'ENVISAT' and id.acquisition_mode == 'WSM':
        orbitType = 'DORIS Precise VOR (ENVISAT) (Auto Download)'
    
    orb = workflow.find('.//node[@id="Apply-Orbit-File"]')
    orb.find('.//parameters/orbitType').text = orbitType
    ############################################
    # calibration node configuration
    # print('-- configuring Calibration Node')
    cal = workflow.find('.//node[@id="Calibration"]')
    
    cal.find('.//parameters/selectedPolarisations').text = ','.join(polarizations)
    cal.find('.//parameters/sourceBands').text = bands_int
    ############################################
    # terrain flattening node configuration
    # print('-- configuring Terrain-Flattening Node')
    tf = workflow.find('.//node[@id="Terrain-Flattening"]')
    if id.sensor in ['ERS1', 'ERS2'] or (id.sensor == 'ASAR' and id.acquisition_mode != 'APP'):
        tf.find('.//parameters/sourceBands').text = 'Beta0'
    else:
        tf.find('.//parameters/sourceBands').text = bands_beta
    ############################################
    # configuration of node sequence for specific geocoding approaches
    # print('-- configuring geocoding approach Nodes')
    if geocoding_type == 'Range-Doppler':
        insert_node(workflow, parse_node('Terrain-Correction'), before='Terrain-Flattening')
        tc = workflow.find('.//node[@id="Terrain-Correction"]')
        tc.find('.//parameters/sourceBands').text = bands_gamma
    elif geocoding_type == 'SAR simulation cross correlation':
        insert_node(workflow, parse_node('SAR-Simulation'), before='Terrain-Flattening')
        insert_node(workflow, parse_node('Cross-Correlation'), before='SAR-Simulation')
        insert_node(workflow, parse_node('SARSim-Terrain-Correction'), before='Cross-Correlation')
        tc = workflow.find('.//node[@id="SARSim-Terrain-Correction"]')
        
        sarsim = workflow.find('.//node[@id="SAR-Simulation"]')
        sarsim.find('.//parameters/sourceBands').text = bands_gamma
    else:
        raise RuntimeError('geocode_type not recognized')
    
    ############################################
    # specify spatial resolution and coordinate reference system of the output dataset
    # print('-- configuring CRS')
    tc.find('.//parameters/pixelSpacingInMeter').text = str(tr)
    
    try:
        t_srs = crsConvert(t_srs, 'epsg')
    except TypeError:
        raise RuntimeError("format of parameter 't_srs' not recognized")
    
    # the EPSG code 4326 is not supported by SNAP and thus the WKT string has to be defined;
    # in all other cases defining EPSG:{code} will do
    if t_srs == 4326:
        t_srs = 'GEOGCS["WGS84(DD)",' \
                'DATUM["WGS84",' \
                'SPHEROID["WGS84", 6378137.0, 298.257223563]],' \
                'PRIMEM["Greenwich", 0.0],' \
                'UNIT["degree", 0.017453292519943295],' \
                'AXIS["Geodetic longitude", EAST],' \
                'AXIS["Geodetic latitude", NORTH]]'
    else:
        t_srs = 'EPSG:{}'.format(t_srs)
    
    tc.find('.//parameters/mapProjection').text = t_srs
    ############################################
    # (optionally) add node for conversion from linear to db scaling
    # print('-- configuring LinearToFromdB Node')
    if scaling not in ['dB', 'db', 'linear']:
        raise RuntimeError('scaling must be  a string of either "dB", "db" or "linear"')
    
    if scaling in ['dB', 'db']:
        lin2db = parse_node('lin2db')
        sourceNode = 'Terrain-Correction' if geocoding_type == 'Range-Doppler' else 'SARSim-Terrain-Correction'
        insert_node(workflow, lin2db, before=sourceNode)
        
        lin2db = workflow.find('.//node[@id="LinearToFromdB"]')
        lin2db.find('.//parameters/sourceBands').text = bands_gamma
    
    ############################################
    # (optionally) add subset node and add bounding box coordinates of defined shapefile
    # print('-- configuring Subset Node')
    if shapefile:
        # print('--- read')
        shp = shapefile if isinstance(shapefile, Vector) else Vector(shapefile)
        # reproject the geometry to WGS 84 latlon
        # print('--- reproject')
        shp.reproject(4326)
        ext = shp.extent
        # add an extra buffer of 0.01 degrees
        buffer = 0.01
        ext['xmin'] -= buffer
        ext['ymin'] -= buffer
        ext['xmax'] += buffer
        ext['ymax'] += buffer
        #print('--- create bbox')
        with bbox(ext, shp.srs) as bounds:
            # print('--- intersect')
            print(shapefile.srs)
            inter = intersect(id.bbox(), bounds)
            if not inter:
                raise RuntimeError('no bounding box intersection between shapefile and scene')
            # print('--- close intersect')
            inter.close()
            # print('--- get wkt')
            wkt = bounds.convert2wkt()[0]
        if isinstance(shapefile, str):
            shp.close()
        # print('--- parse XML node')
        subset = parse_node('Subset')
        # print('--- insert node')
        insert_node(workflow, subset, before='Read')
        
        subset = workflow.find('.//node[@id="Subset"]')
        subset.find('.//parameters/region').text = ','.join(map(str, [0, 0, id.samples, id.lines]))
        subset.find('.//parameters/geoRegion').text = wkt
    ############################################
    # (optionally) configure subset node for pixel offsets
    if offset and not shapefile:
        subset = parse_node('Subset')
        insert_node(workflow, subset, before='Read')
        
        # left, right, top and bottom offset in pixels
        l, r, t, b = offset
        
        subset = workflow.find('.//node[@id="Subset"]')
        subset_values = ','.join(map(str, [l, t, id.samples - l - r, id.lines - t - b]))
        subset.find('.//parameters/region').text = subset_values
        subset.find('.//parameters/geoRegion').text = ''
    ############################################
    # parametrize write node
    # print('-- configuring Write Node')
    # create a suffix for the output file to identify processing steps performed in the workflow
    suffix = parse_suffix(workflow)
    
    basename = os.path.join(outdir, id.outname_base(basename_extensions))
    extension = suffix if format == 'ENVI' else polarizations[0] + '_' + suffix
    outname = basename + '_' + extension
    
    write = workflow.find('.//node[@id="Write"]')
    write.find('.//parameters/file').text = outname
    write.find('.//parameters/formatName').text = format
    ############################################
    ############################################
    # select DEM type
    # print('-- configuring DEM')
    if externalDEMFile is not None:
        if os.path.isfile(externalDEMFile):
            if externalDEMNoDataValue is None:
                with Raster(externalDEMFile) as dem:
                    externalDEMNoDataValue = dem.nodata
                if externalDEMNoDataValue is None:
                    raise RuntimeError('Cannot read NoData value from DEM file. '
                                       'Please specify externalDEMNoDataValue')
        else:
            raise RuntimeError('specified externalDEMFile does not exist')
        demname = 'External DEM'
    else:
        # SRTM 1arcsec is only available between -58 and +60 degrees.
        # If the scene exceeds those latitudes SRTM 3arcsec is selected.
        if id.getCorners()['ymax'] > 60 or id.getCorners()['ymin'] < -58:
            demname = 'SRTM 3Sec'
        else:
            demname = 'SRTM 1Sec HGT'
        externalDEMFile = None
        externalDEMNoDataValue = 0
    
    for demName in workflow.findall('.//parameters/demName'):
        demName.text = demname
    for externalDEM in workflow.findall('.//parameters/externalDEMFile'):
        externalDEM.text = externalDEMFile
    for demNodata in workflow.findall('.//parameters/externalDEMNoDataValue'):
        demNodata.text = str(externalDEMNoDataValue)
    for egm in workflow.findall('.//parameters/externalDEMApplyEGM'):
        egm.text = str(externalDEMApplyEGM).lower()
    ############################################
    ############################################
    # write workflow to file and optionally execute it
    # print('- writing workflow to file')
    
    write_recipe(workflow, outname + '_proc')
    
    # execute the newly written workflow
    if not test:
        try:
            gpt(outname + '_proc.xml')
        except RuntimeError:
            os.remove(outname + '_proc.xml')
コード例 #11
0
ファイル: geocode.py プロジェクト: GeoBigData/s1_preprocessor
def main(s1_archive,
         out_path,
         t_srs='utm',
         tr=10,
         polarization='all',
         bbox=None,
         shapefile=None,
         scaling='db',
         geocoding_type='Range-Doppler',
         remove_s1_border_noise=True,
         remove_s1_thermal_noise=False,
         offset_left=None,
         offset_right=None,
         offset_top=None,
         offset_bottom=None,
         external_dem_file=None,
         external_dem_no_data_value=None,
         external_dem_apply_egm=True,
         terrain_flattening=True,
         basename_extension=None,
         test=False,
         export_extra=None,
         group_size=2,
         cleanup=True,
         return_wf=False):

    # identify the scene from input archive
    print("Identifying S1 scene from input archive.")
    scene = pyroSAR.identify(s1_archive)
    print("Success")

    # deal with offsets
    offset = (offset_left, offset_right, offset_top, offset_bottom)
    if set(offset) == set([None]):
        offset = None

    if t_srs.lower() == 'utm':
        # get the bbox
        scene_extent = scene.bbox().extent
        scene_bbox = box(scene_extent['xmin'], scene_extent['ymin'],
                         scene_extent['xmax'], scene_extent['ymax'])
        # get the lat and long from the aoi centroid
        long = scene_bbox.centroid.x
        lat = scene_bbox.centroid.y

        # use the longitude to get the utm zone (3rd element in returned list
        c = utm.from_latlon(lat, long)
        utm_zone = c[2]

        # build the epsg code
        if lat >= 0.0:
            t_srs = int("326%s" % (str(utm_zone)))
        else:
            t_srs = int("327%s" % (str(utm_zone)))
    else:
        # try to convert to an integer -- if it doesn't work, leave as is
        try:

            t_srs = int(t_srs)
        except ValueError:
            pass

    # deal with basename extensions
    if basename_extension is None:
        basename_extensions = None
    else:
        basename_extensions = [basename_extension]

    # deal with export_extra
    if export_extra is not None:
        export_extra = list(map(str, export_extra.split(',')))

    # make the output directory if it doesn't exist
    if os.path.exists(out_path) is False:
        os.mkdir(out_path)

    # if bbox is provided, convert to format expected by pyroSAR
    if bbox is not None:
        bbox = map(float, bbox.split(','))
        shapefile = spatialist.bbox(
            dict(zip(['xmin', 'ymin', 'xmax', 'ymax'], bbox)),
            crs=spatialist.auxil.crsConvert(4326, 'epsg'))

    print("Geocoding image according to input options.")
    geocode(infile=scene,
            outdir=out_path,
            t_srs=t_srs,
            tr=tr,
            polarizations=[polarization],
            shapefile=shapefile,
            scaling=scaling,
            geocoding_type=geocoding_type,
            removeS1BoderNoise=remove_s1_border_noise,
            removeS1ThermalNoise=remove_s1_thermal_noise,
            offset=offset,
            externalDEMFile=external_dem_file,
            externalDEMNoDataValue=external_dem_no_data_value,
            externalDEMApplyEGM=external_dem_apply_egm,
            terrainFlattening=terrain_flattening,
            basename_extensions=basename_extensions,
            test=test,
            export_extra=export_extra,
            groupsize=group_size,
            cleanup=cleanup,
            returnWF=return_wf)
    print("Processing completed successfully.")