Ejemplo n.º 1
0
    def step(self, dt):
        """Advance one step."""

        div_q, dt_cfl = self.diffusion_upstream_2d()

        dt_use = utils.clip_scalar(np.min([dt_cfl, dt]), 0, self.max_dt)

        self.ice_thick = utils.clip_min(
            self.surface_h + (self.get_mb() + div_q) * dt_use - self.bed_topo,
            0)

        # Next step
        self.t += dt_use
        return dt
Ejemplo n.º 2
0
    def step(self, dt):
        """Advance one step."""

        # Just a check to avoid useless computations
        if dt <= 0:
            raise InvalidParamsError('dt needs to be strictly positive')

        # Guarantee a precise arrival on a specific date if asked
        min_dt = dt if dt < self.min_dt else self.min_dt
        dt = utils.clip_scalar(dt, min_dt, self.max_dt)

        with warnings.catch_warnings():
            warnings.filterwarnings("ignore", category=RuntimeWarning)

            fl = self.fls[0]
            dx = fl.dx_meter

            # Switch to the notation from the MUSCL_1D example
            # This is useful to ensure that the MUSCL-SuperBee code
            # is working as it has been benchmarked many times

            # mass balance
            m_dot = self.get_mb(fl.surface_h, self.yr, fl_id=id(fl))
            # get in the surface elevation
            S = fl.surface_h
            # get the bed
            B = fl.bed_h
            # define Glen's law here
            N = self.glen_n
            # this is the correct Gamma !!
            Gamma = 2. * self.glen_a * (self.rho * G)**N / (N + 2.)
            # time stepping
            c_stab = 0.165

            # define the finite difference indices
            k = np.arange(0, fl.nx)
            kp = np.hstack([np.arange(1, fl.nx), fl.nx - 1])
            kpp = np.hstack([np.arange(2, fl.nx), fl.nx - 1, fl.nx - 1])
            km = np.hstack([0, np.arange(0, fl.nx - 1)])
            kmm = np.hstack([0, 0, np.arange(0, fl.nx - 2)])

            # I'm gonna introduce another level of adaptive time stepping here,
            # which is probably not necessary. However I keep it to be
            # consistent with my benchmarked and tested code.
            # If the OGGM time stepping is correctly working, this loop
            # should never run more than once
            # Fabi: actually no, it is your job to choose the right time step
            # but you can let OGGM decide whether a new time step is needed
            # or not -> to be meliorated one day
            stab_t = 0.
            while stab_t < dt:
                H = S - B

                # MUSCL scheme up. "up" denotes here the k+1/2 flux boundary
                r_up_m = (H[k] - H[km]) / (H[kp] - H[k])  # Eq. 27
                H_up_m = H[k] + 0.5 * self.phi(r_up_m) * (H[kp] - H[k]
                                                          )  # Eq. 23
                # Eq. 27, k+1 is used instead of k
                r_up_p = (H[kp] - H[k]) / (H[kpp] - H[kp])
                # Eq. 24
                H_up_p = H[kp] - 0.5 * self.phi(r_up_p) * (H[kpp] - H[kp])

                # surface slope gradient
                s_grad_up = ((S[kp] - S[k])**2. / dx**2.)**((N - 1.) / 2.)
                # like Eq. 30, now using Eq. 23 instead of Eq. 24
                D_up_m = Gamma * H_up_m**(N + 2.) * s_grad_up
                D_up_p = Gamma * H_up_p**(N + 2.) * s_grad_up  # Eq. 30

                # Eq. 31
                D_up_min = np.minimum(D_up_m, D_up_p)
                # Eq. 32
                D_up_max = np.maximum(D_up_m, D_up_p)
                D_up = np.zeros(fl.nx)

                # Eq. 33
                cond = (S[kp] <= S[k]) & (H_up_m <= H_up_p)
                D_up[cond] = D_up_min[cond]
                cond = (S[kp] <= S[k]) & (H_up_m > H_up_p)
                D_up[cond] = D_up_max[cond]
                cond = (S[kp] > S[k]) & (H_up_m <= H_up_p)
                D_up[cond] = D_up_max[cond]
                cond = (S[kp] > S[k]) & (H_up_m > H_up_p)
                D_up[cond] = D_up_min[cond]

                # MUSCL scheme down. "down" denotes the k-1/2 flux boundary
                r_dn_m = (H[km] - H[kmm]) / (H[k] - H[km])
                H_dn_m = H[km] + 0.5 * self.phi(r_dn_m) * (H[k] - H[km])
                r_dn_p = (H[k] - H[km]) / (H[kp] - H[k])
                H_dn_p = H[k] - 0.5 * self.phi(r_dn_p) * (H[kp] - H[k])

                # calculate the slope gradient
                s_grad_dn = ((S[k] - S[km])**2. / dx**2.)**((N - 1.) / 2.)
                D_dn_m = Gamma * H_dn_m**(N + 2.) * s_grad_dn
                D_dn_p = Gamma * H_dn_p**(N + 2.) * s_grad_dn

                D_dn_min = np.minimum(D_dn_m, D_dn_p)
                D_dn_max = np.maximum(D_dn_m, D_dn_p)
                D_dn = np.zeros(fl.nx)

                cond = (S[k] <= S[km]) & (H_dn_m <= H_dn_p)
                D_dn[cond] = D_dn_min[cond]
                cond = (S[k] <= S[km]) & (H_dn_m > H_dn_p)
                D_dn[cond] = D_dn_max[cond]
                cond = (S[k] > S[km]) & (H_dn_m <= H_dn_p)
                D_dn[cond] = D_dn_max[cond]
                cond = (S[k] > S[km]) & (H_dn_m > H_dn_p)
                D_dn[cond] = D_dn_min[cond]

                # Eq. 37
                dt_stab = c_stab * dx**2. / max(max(abs(D_up)), max(abs(D_dn)))
                dt_use = min(dt_stab, dt - stab_t)
                stab_t = stab_t + dt_use

                # explicit time stepping scheme, Eq. 36
                div_q = (D_up * (S[kp] - S[k]) / dx - D_dn *
                         (S[k] - S[km]) / dx) / dx
                # Eq. 35
                S = S[k] + (m_dot + div_q) * dt_use

                # Eq. 7
                S = np.maximum(S, B)

        # Done with the loop, prepare output
        fl.thick = S - B

        # Next step
        self.t += dt
Ejemplo n.º 3
0
def calibrate_calving_k_single_wconsensus(gdir, calving_data_fn=pygem_prms.calving_data_fullfn):
    """Calibrate calving parameter k with frontal ablation data and export to the given glacier directory
    
    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        where to write the data
    """
    assert os.path.exists(calving_data_fn), 'Error: ' + calving_data_fn + ' does not exist.'
    
    #%%
    
    # Load calving data
    calving_data = pd.read_csv(pygem_prms.calving_data_fullfn)
    
    if gdir.rgi_id in list(calving_data.RGIId):
        
        # Load glacier data
        gidx = np.where(gdir.rgi_id == calving_data.RGIId)[0][0]
        calving_flux_obs_gta = calving_data.loc[gidx,'frontal_ablation_Gta'] 
        calving_flux_obs_km3a = calving_flux_obs_gta * pygem_prms.density_water / pygem_prms.density_ice
        
        fls = gdir.read_pickle('inversion_flowlines')
        # Ice thickness at calving front from consensus ice thickness
        h_calving = fls[-1].consensus_h[-1]
        # Water level (max based on 50 m freeboard)
        th = fls[-1].surface_h[-1]
        vmin, vmax = cfg.PARAMS['free_board_marine_terminating']
        # Constrain water level such that the freeboard is somewhere between 10 and 50 m
        water_level = clip_scalar(0, th - vmax, th - vmin)

        # ----- Optimize calving_k ------
        def objective(calving_k_obj):
            """ Objective function for calving data (mimize difference between model and observation).
            
            Parameters
            ----------
            calving_k : calving parameter
            """
            # Calving flux (km3 yr-1; positive value refers to amount of calving, need to subtract from mb)
            calving_output = calving_flux_from_depth(gdir, water_level=water_level, thick=h_calving, k=calving_k_obj)
            calving_flux_km3a = calving_output['flux']
            # Difference with observation (km3 yr-1)
            calving_flux_dif_abs = abs(calving_flux_km3a - calving_flux_obs_km3a)
            return calving_flux_dif_abs
        
        # Run objective
        calving_k_init = 1
        calving_k_bnds = ((0,50),)
        calving_k_opt_all = minimize(objective, calving_k_init, method=pygem_prms.method_opt,
                                     bounds=calving_k_bnds, 
#                                     options={'ftol':ftol_opt, 'eps':eps_opt}
                                     )
        # Record the optimized parameters
        calving_k_opt = calving_k_opt_all.x[0]
        calving_k = calving_k_opt
        
        # Calving flux (km3 yr-1; positive value refers to amount of calving, need to subtract from mb)
        calving_output = calving_flux_from_depth(gdir, water_level=water_level, thick=h_calving, k=calving_k)
        calving_flux = calving_output['flux']
            
        print('\n', gdir.rgi_id)
        print('  calving_k:', np.round(calving_k_opt,2))
        print('  calving flux (km3 yr-1):', np.round(calving_flux,3))
        print('  calving flux obs (km3 yr-1):', np.round(calving_flux_obs_km3a,3))
        print('  freeboard:', np.round(calving_output['free_board'],1))
        print('  water_level:', np.round(calving_output['water_level'],1))
        print('  h_calving (m):', np.round(h_calving,1))
        print('  w_calving (m):', np.round(fls[-1].widths[-1] * gdir.grid.dx, 1),'\n')
        
        
        # Pickle data
        output_fn = gdir.get_filepath('calving_k_opt')
        with open(output_fn, 'wb') as f:
            pickle.dump(calving_k_opt, f)
Ejemplo n.º 4
0
def find_inversion_calving(gdir, water_level=None, fixed_water_depth=None):
    """Optimized search for a calving flux compatible with the bed inversion.

    See Recinos et al 2019 for details.

    Parameters
    ----------
    water_level : float
        the water level. It should be zero m a.s.l, but:
        - sometimes the frontal elevation is unrealistically high (or low).
        - lake terminating glaciers
        - other uncertainties
        With this parameter, you can produce more realistic values. The default
        is to infer the water level from PARAMS['free_board_lake_terminating']
        and PARAMS['free_board_marine_terminating']
    fixed_water_depth : float
        fix the water depth to an observed value and let the free board vary
        instead.
    """
    from oggm.core import climate
    from oggm.exceptions import MassBalanceCalibrationError

    if not gdir.is_tidewater or not cfg.PARAMS['use_kcalving_for_inversion']:
        # Do nothing
        return

    # Let's start from a fresh state
    gdir.inversion_calving_rate = 0

    with utils.DisableLogger():
        climate.local_t_star(gdir)
        climate.mu_star_calibration(gdir)
        prepare_for_inversion(gdir)
        v_ref = mass_conservation_inversion(gdir, water_level=water_level)

    # Store for statistics
    gdir.add_to_diagnostics('volume_before_calving', v_ref)

    # Get the relevant variables
    cls = gdir.read_pickle('inversion_input')[-1]
    slope = cls['slope_angle'][-1]
    width = cls['width'][-1]

    # Check that water level is within given bounds
    if water_level is None:
        th = cls['hgt'][-1]
        if gdir.is_lake_terminating:
            water_level = th - cfg.PARAMS['free_board_lake_terminating']
        else:
            vmin, vmax = cfg.PARAMS['free_board_marine_terminating']
            water_level = utils.clip_scalar(0, th - vmax, th - vmin)

    # The functions all have the same shape: they decrease, then increase
    # We seek the absolute minimum first
    def to_minimize(h):
        if fixed_water_depth is not None:
            fl = calving_flux_from_depth(gdir,
                                         thick=h,
                                         water_level=water_level,
                                         water_depth=fixed_water_depth,
                                         fixed_water_depth=True)
        else:
            fl = calving_flux_from_depth(gdir,
                                         water_level=water_level,
                                         water_depth=h)

        flux = fl['flux'] * 1e9 / cfg.SEC_IN_YEAR
        sia_thick = sia_thickness(slope, width, flux)
        return fl['thick'] - sia_thick

    abs_min = optimize.minimize(to_minimize, [1],
                                bounds=((1e-4, 1e4), ),
                                tol=1e-1)
    if not abs_min['success']:
        raise RuntimeError('Could not find the absolute minimum in calving '
                           'flux optimization: {}'.format(abs_min))
    if abs_min['fun'] > 0:
        # This happens, and means that this glacier simply can't calve
        # This is an indicator for physics not matching, often a unrealistic
        # slope of free-board
        df = gdir.read_json('local_mustar')
        out = calving_flux_from_depth(gdir, water_level=water_level)

        odf = dict()
        odf['calving_flux'] = 0
        odf['calving_mu_star'] = df['mu_star_glacierwide']
        odf['calving_law_flux'] = out['flux']
        odf['calving_water_level'] = out['water_level']
        odf['calving_inversion_k'] = out['inversion_calving_k']
        odf['calving_front_slope'] = slope
        odf['calving_front_water_depth'] = out['water_depth']
        odf['calving_front_free_board'] = out['free_board']
        odf['calving_front_thick'] = out['thick']
        odf['calving_front_width'] = out['width']
        for k, v in odf.items():
            gdir.add_to_diagnostics(k, v)
        return

    # OK, we now find the zero between abs min and an arbitrary high front
    abs_min = abs_min['x'][0]
    opt = optimize.brentq(to_minimize, abs_min, 1e4)

    # This is the thick guaranteeing OGGM Flux = Calving Law Flux
    # Let's see if it results in a meaningful mu_star

    # Give the flux to the inversion and recompute
    if fixed_water_depth is not None:
        out = calving_flux_from_depth(gdir,
                                      water_level=water_level,
                                      thick=opt,
                                      water_depth=fixed_water_depth,
                                      fixed_water_depth=True)
        f_calving = out['flux']
    else:
        out = calving_flux_from_depth(gdir,
                                      water_level=water_level,
                                      water_depth=opt)
        f_calving = out['flux']

    gdir.inversion_calving_rate = f_calving

    with utils.DisableLogger():
        # We accept values down to zero before stopping
        cfg.PARAMS['min_mu_star'] = 0
        cfg.PARAMS['clip_mu_star'] = False

        # At this step we might raise a MassBalanceCalibrationError
        try:
            climate.local_t_star(gdir)
            df = gdir.read_json('local_mustar')
        except MassBalanceCalibrationError as e:
            assert 'mu* out of specified bounds' in str(e)
            # When this happens we clip mu* to zero
            cfg.PARAMS['clip_mu_star'] = True
            climate.local_t_star(gdir)
            df = gdir.read_json('local_mustar')

        climate.mu_star_calibration(gdir)
        prepare_for_inversion(gdir)
        mass_conservation_inversion(gdir, water_level=water_level)

    if fixed_water_depth is not None:
        out = calving_flux_from_depth(gdir,
                                      water_level=water_level,
                                      water_depth=fixed_water_depth,
                                      fixed_water_depth=True)
    else:
        out = calving_flux_from_depth(gdir, water_level=water_level)

    fl = gdir.read_pickle('inversion_flowlines')[-1]
    f_calving = (fl.flux[-1] * (gdir.grid.dx**2) * 1e-9 /
                 cfg.PARAMS['ice_density'])

    # Store results
    odf = dict()
    odf['calving_flux'] = f_calving
    odf['calving_mu_star'] = df['mu_star_glacierwide']
    odf['calving_law_flux'] = out['flux']
    odf['calving_water_level'] = out['water_level']
    odf['calving_inversion_k'] = out['inversion_calving_k']
    odf['calving_front_slope'] = slope
    odf['calving_front_water_depth'] = out['water_depth']
    odf['calving_front_free_board'] = out['free_board']
    odf['calving_front_thick'] = out['thick']
    odf['calving_front_width'] = out['width']
    for k, v in odf.items():
        gdir.add_to_diagnostics(k, v)

    # Restore defaults
    with utils.DisableLogger():
        cfg.PARAMS['min_mu_star'] = 1.
        cfg.PARAMS['clip_mu_star'] = False

    return odf
Ejemplo n.º 5
0
def gridded_attributes(gdir):
    """Adds attributes to the gridded file, useful for thickness interpolation.

    This could be useful for distributed ice thickness models.
    The raster data are added to the gridded_data file.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        where to write the data
    """

    # Variables
    grids_file = gdir.get_filepath('gridded_data')
    with ncDataset(grids_file) as nc:
        topo_smoothed = nc.variables['topo_smoothed'][:]
        glacier_mask = nc.variables['glacier_mask'][:]

    # Glacier exterior including nunataks
    erode = binary_erosion(glacier_mask)
    glacier_ext = glacier_mask ^ erode
    glacier_ext = np.where(glacier_mask == 1, glacier_ext, 0)

    # Intersects between glaciers
    gdfi = gpd.GeoDataFrame(columns=['geometry'])
    if gdir.has_file('intersects'):
        # read and transform to grid
        gdf = gdir.read_shapefile('intersects')
        salem.transform_geopandas(gdf, gdir.grid, inplace=True)
        gdfi = pd.concat([gdfi, gdf[['geometry']]])

    # Ice divide mask
    # Probably not the fastest way to do this, but it works
    dist = np.array([])
    jj, ii = np.where(glacier_ext)
    for j, i in zip(jj, ii):
        dist = np.append(dist, np.min(gdfi.distance(shpg.Point(i, j))))

    with warnings.catch_warnings():
        warnings.filterwarnings("ignore", category=RuntimeWarning)
        pok = np.where(dist <= 1)
    glacier_ext_intersect = glacier_ext * 0
    glacier_ext_intersect[jj[pok], ii[pok]] = 1

    # Distance from border mask - Scipy does the job
    dx = gdir.grid.dx
    dis_from_border = 1 + glacier_ext_intersect - glacier_ext
    dis_from_border = distance_transform_edt(dis_from_border) * dx

    # Slope
    glen_n = cfg.PARAMS['glen_n']
    sy, sx = np.gradient(topo_smoothed, dx, dx)
    slope = np.arctan(np.sqrt(sy**2 + sx**2))
    slope_factor = utils.clip_scalar(slope,
                                     np.deg2rad(cfg.PARAMS['min_slope']*4),
                                     np.pi/2)
    slope_factor = 1 / slope_factor**(glen_n / (glen_n+2))

    aspect = np.arctan2(-sx, sy)
    aspect[aspect < 0] += 2 * np.pi

    with ncDataset(grids_file, 'a') as nc:

        vn = 'glacier_ext_erosion'
        if vn in nc.variables:
            v = nc.variables[vn]
        else:
            v = nc.createVariable(vn, 'i1', ('y', 'x', ))
        v.units = '-'
        v.long_name = 'Glacier exterior with binary erosion method'
        v[:] = glacier_ext

        vn = 'ice_divides'
        if vn in nc.variables:
            v = nc.variables[vn]
        else:
            v = nc.createVariable(vn, 'i1', ('y', 'x', ))
        v.units = '-'
        v.long_name = 'Glacier ice divides'
        v[:] = glacier_ext_intersect

        vn = 'slope'
        if vn in nc.variables:
            v = nc.variables[vn]
        else:
            v = nc.createVariable(vn, 'f4', ('y', 'x', ))
        v.units = 'rad'
        v.long_name = 'Local slope based on smoothed topography'
        v[:] = slope

        vn = 'aspect'
        if vn in nc.variables:
            v = nc.variables[vn]
        else:
            v = nc.createVariable(vn, 'f4', ('y', 'x', ))
        v.units = 'rad'
        v.long_name = 'Local aspect based on smoothed topography'
        v[:] = aspect

        vn = 'slope_factor'
        if vn in nc.variables:
            v = nc.variables[vn]
        else:
            v = nc.createVariable(vn, 'f4', ('y', 'x', ))
        v.units = '-'
        v.long_name = 'Slope factor as defined in Farinotti et al 2009'
        v[:] = slope_factor

        vn = 'dis_from_border'
        if vn in nc.variables:
            v = nc.variables[vn]
        else:
            v = nc.createVariable(vn, 'f4', ('y', 'x', ))
        v.units = 'm'
        v.long_name = 'Distance from glacier boundaries'
        v[:] = dis_from_border
Ejemplo n.º 6
0
def define_glacier_region(gdir, entity=None):
    """Very first task: define the glacier's local grid.

    Defines the local projection (Transverse Mercator), centered on the
    glacier. There is some options to set the resolution of the local grid.
    It can be adapted depending on the size of the glacier with::

        dx (m) = d1 * AREA (km) + d2 ; clipped to dmax

    or be set to a fixed value. See ``params.cfg`` for setting these options.
    Default values of the adapted mode lead to a resolution of 50 m for
    Hintereisferner, which is approx. 8 km2 large.
    After defining the grid, the topography and the outlines of the glacier
    are transformed into the local projection. The default interpolation for
    the topography is `cubic`.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        where to write the data
    entity : geopandas.GeoSeries
        the glacier geometry to process
    """

    # Make a local glacier map
    proj_params = dict(name='tmerc', lat_0=0., lon_0=gdir.cenlon,
                       k=0.9996, x_0=0, y_0=0, datum='WGS84')
    proj4_str = "+proj={name} +lat_0={lat_0} +lon_0={lon_0} +k={k} " \
                "+x_0={x_0} +y_0={y_0} +datum={datum}".format(**proj_params)
    proj_in = pyproj.Proj("+init=EPSG:4326", preserve_units=True)
    proj_out = pyproj.Proj(proj4_str, preserve_units=True)
    project = partial(transform_proj, proj_in, proj_out)
    # transform geometry to map
    geometry = shapely.ops.transform(project, entity['geometry'])
    geometry = multi_to_poly(geometry, gdir=gdir)
    xx, yy = geometry.exterior.xy

    # Save transformed geometry to disk
    entity = entity.copy()
    entity['geometry'] = geometry
    # Avoid fiona bug: https://github.com/Toblerity/Fiona/issues/365
    for k, s in entity.iteritems():
        if type(s) in [np.int32, np.int64]:
            entity[k] = int(s)
    towrite = gpd.GeoDataFrame(entity).T
    towrite.crs = proj4_str
    # Delete the source before writing
    if 'DEM_SOURCE' in towrite:
        del towrite['DEM_SOURCE']

    # Define glacier area to use
    area = entity['Area']

    # Do we want to use the RGI area or ours?
    if not cfg.PARAMS['use_rgi_area']:
        # Update Area
        area = geometry.area * 1e-6
        entity['Area'] = area
        towrite['Area'] = area

    # Write shapefile
    gdir.write_shapefile(towrite, 'outlines')

    # Also transform the intersects if necessary
    gdf = cfg.PARAMS['intersects_gdf']
    if len(gdf) > 0:
        gdf = gdf.loc[((gdf.RGIId_1 == gdir.rgi_id) |
                       (gdf.RGIId_2 == gdir.rgi_id))]
        if len(gdf) > 0:
            gdf = salem.transform_geopandas(gdf, to_crs=proj_out)
            if hasattr(gdf.crs, 'srs'):
                # salem uses pyproj
                gdf.crs = gdf.crs.srs
            gdir.write_shapefile(gdf, 'intersects')
    else:
        # Sanity check
        if cfg.PARAMS['use_intersects']:
            raise InvalidParamsError('You seem to have forgotten to set the '
                                     'intersects file for this run. OGGM '
                                     'works better with such a file. If you '
                                     'know what your are doing, set '
                                     "cfg.PARAMS['use_intersects'] = False to "
                                     "suppress this error.")

    # 6. choose a spatial resolution with respect to the glacier area
    dxmethod = cfg.PARAMS['grid_dx_method']
    if dxmethod == 'linear':
        dx = np.rint(cfg.PARAMS['d1'] * area + cfg.PARAMS['d2'])
    elif dxmethod == 'square':
        dx = np.rint(cfg.PARAMS['d1'] * np.sqrt(area) + cfg.PARAMS['d2'])
    elif dxmethod == 'fixed':
        dx = np.rint(cfg.PARAMS['fixed_dx'])
    else:
        raise InvalidParamsError('grid_dx_method not supported: {}'
                                 .format(dxmethod))
    # Additional trick for varying dx
    if dxmethod in ['linear', 'square']:
        dx = utils.clip_scalar(dx, cfg.PARAMS['d2'], cfg.PARAMS['dmax'])

    log.debug('(%s) area %.2f km, dx=%.1f', gdir.rgi_id, area, dx)

    # Safety check
    border = cfg.PARAMS['border']
    if border > 1000:
        raise InvalidParamsError("You have set a cfg.PARAMS['border'] value "
                                 "of {}. ".format(cfg.PARAMS['border']) +
                                 'This a very large value, which is '
                                 'currently not supported in OGGM.')

    # For tidewater glaciers we force border to 10
    if gdir.is_tidewater and cfg.PARAMS['clip_tidewater_border']:
        border = 10

    # Corners, incl. a buffer of N pix
    ulx = np.min(xx) - border * dx
    lrx = np.max(xx) + border * dx
    uly = np.max(yy) + border * dx
    lry = np.min(yy) - border * dx
    # n pixels
    nx = np.int((lrx - ulx) / dx)
    ny = np.int((uly - lry) / dx)

    # Back to lon, lat for DEM download/preparation
    tmp_grid = salem.Grid(proj=proj_out, nxny=(nx, ny), x0y0=(ulx, uly),
                          dxdy=(dx, -dx), pixel_ref='corner')
    minlon, maxlon, minlat, maxlat = tmp_grid.extent_in_crs(crs=salem.wgs84)

    # Open DEM
    source = entity.DEM_SOURCE if hasattr(entity, 'DEM_SOURCE') else None
    if not is_dem_source_available(source,
                                   (minlon, maxlon),
                                   (minlat, maxlat)):
        raise InvalidDEMError('Source: {} not available for glacier {}'
                              .format(source, gdir.rgi_id))
    dem_list, dem_source = get_topo_file((minlon, maxlon), (minlat, maxlat),
                                         rgi_region=gdir.rgi_region,
                                         rgi_subregion=gdir.rgi_subregion,
                                         dx_meter=dx,
                                         source=source)
    log.debug('(%s) DEM source: %s', gdir.rgi_id, dem_source)
    log.debug('(%s) N DEM Files: %s', gdir.rgi_id, len(dem_list))

    # Decide how to tag nodata
    def _get_nodata(rio_ds):
        nodata = rio_ds[0].meta.get('nodata', None)
        if nodata is None:
            # badly tagged geotiffs, let's do it ourselves
            nodata = -32767 if source == 'TANDEM' else -9999
        return nodata

    # A glacier area can cover more than one tile:
    if len(dem_list) == 1:
        dem_dss = [rasterio.open(dem_list[0])]  # if one tile, just open it
        dem_data = rasterio.band(dem_dss[0], 1)
        if LooseVersion(rasterio.__version__) >= LooseVersion('1.0'):
            src_transform = dem_dss[0].transform
        else:
            src_transform = dem_dss[0].affine
        nodata = _get_nodata(dem_dss)
    else:
        dem_dss = [rasterio.open(s) for s in dem_list]  # list of rasters
        nodata = _get_nodata(dem_dss)
        dem_data, src_transform = merge_tool(dem_dss, nodata=nodata)  # merge

    # Use Grid properties to create a transform (see rasterio cookbook)
    dst_transform = rasterio.transform.from_origin(
        ulx, uly, dx, dx  # sign change (2nd dx) is done by rasterio.transform
    )

    # Set up profile for writing output
    profile = dem_dss[0].profile
    profile.update({
        'crs': proj4_str,
        'transform': dst_transform,
        'nodata': nodata,
        'width': nx,
        'height': ny
    })

    # Could be extended so that the cfg file takes all Resampling.* methods
    if cfg.PARAMS['topo_interp'] == 'bilinear':
        resampling = Resampling.bilinear
    elif cfg.PARAMS['topo_interp'] == 'cubic':
        resampling = Resampling.cubic
    else:
        raise InvalidParamsError('{} interpolation not understood'
                                 .format(cfg.PARAMS['topo_interp']))

    dem_reproj = gdir.get_filepath('dem')
    profile.pop('blockxsize', None)
    profile.pop('blockysize', None)
    profile.pop('compress', None)
    with rasterio.open(dem_reproj, 'w', **profile) as dest:
        dst_array = np.empty((ny, nx), dtype=dem_dss[0].dtypes[0])
        reproject(
            # Source parameters
            source=dem_data,
            src_crs=dem_dss[0].crs,
            src_transform=src_transform,
            src_nodata=nodata,
            # Destination parameters
            destination=dst_array,
            dst_transform=dst_transform,
            dst_crs=proj4_str,
            dst_nodata=nodata,
            # Configuration
            resampling=resampling)
        dest.write(dst_array, 1)

    for dem_ds in dem_dss:
        dem_ds.close()

    # Glacier grid
    x0y0 = (ulx+dx/2, uly-dx/2)  # To pixel center coordinates
    glacier_grid = salem.Grid(proj=proj_out, nxny=(nx, ny),  dxdy=(dx, -dx),
                              x0y0=x0y0)
    glacier_grid.to_json(gdir.get_filepath('glacier_grid'))

    # Write DEM source info
    gdir.add_to_diagnostics('dem_source', dem_source)
    source_txt = DEM_SOURCE_INFO.get(dem_source, dem_source)
    with open(gdir.get_filepath('dem_source'), 'w') as fw:
        fw.write(source_txt)
        fw.write('\n\n')
        fw.write('# Data files\n\n')
        for fname in dem_list:
            fw.write('{}\n'.format(os.path.basename(fname)))