def mark_range(bin_low, bin_high):
    """
    nan out contiguous areas in F which fall within the given range,
    and satisfy the feature area requirement.
    alters F, from above
    """
    in_bin = (dem.F >= bin_low) & (dem.F <= bin_high)

    marks = field.SimpleGrid(F=in_bin, extents=dem.extents)

    # find contiguous areas of this value:
    label, num_features = ndi.label(marks.F)

    labels = field.SimpleGrid(F=label, extents=dem.extents)

    feat_sizes = np.bincount(labels.F.ravel())

    # Good features
    success = False

    for feat_i, feat_size in enumerate(feat_sizes):
        if feat_i == 0:
            continue
        if feat_size > feat_size_thresh:
            success = True
            sel = (labels.F == feat_i)
            # mask[ sel ] = True
            F[sel] = np.nan

    return success
示例#2
0
def lidar_mask():
    mask = np.isfinite(levee_burn().F)

    mask_open = ndimage.binary_dilation(mask, iterations=5)

    lidar_mask = field.SimpleGrid(extents=levee_burn().extents, F=mask_open)
    return lidar_mask
示例#3
0
def lidar_resamp():
    lidar_resamp_fn = 'lidar-levees.tif'

    if not os.path.exists(lidar_resamp_fn):
        # Will compile the lidar data here:
        lidar_levees = field.SimpleGrid(extents=lidar_mask().extents,
                                        F=np.zeros(lidar_mask().F.shape,
                                                   np.float32))
        lidar_levees.F[:, :] = np.nan

        lidar_resamp = lidar_src.to_grid(bounds=lidar_mask().extents,
                                         dx=lidar_mask().dx,
                                         dy=lidar_mask().dy)

        lidar_resamp.F[~lidar_mask().F] = np.nan

        # There are seams, at least 1px wide, between lidar tiles.
        lidar_resamp.fill_by_convolution(iterations=1)

        lidar_resamp.write_gdal('lidar-levees.tif')
    else:
        lidar_resamp = field.GdalGrid('lidar-levees.tif')
    return lidar_resamp
示例#4
0
                                            F.size, len(heap))

    vis_depth, vis_idx = heapq.heappop(heap)
    assert flood_order[vis_idx] == -1

    flood_order[vis_idx] = count
    count += 1

    maybe_push((vis_idx[0] - 1, vis_idx[1]))
    maybe_push((vis_idx[0] + 1, vis_idx[1]))
    maybe_push((vis_idx[0], vis_idx[1] - 1))
    maybe_push((vis_idx[0], vis_idx[1] + 1))

##

flood_order_f = field.SimpleGrid(F=flood_order, extents=dem.extents)

plt.figure(2).clf()
fig, ax = plt.subplots(num=2)
zoom = (570996, 571427, 4155174, 4155536)

# flood_order_f.crop(zoom).plot(ax=ax,interpolation='nearest',cmap='jet')

sub_flood_order_f = flood_order_f.downsample(5)
sub_flood_order_f.F = np.ma.array(sub_flood_order_f.F,
                                  mask=sub_flood_order_f.F < 0)

sub_flood_order_f.plot(ax=ax, interpolation='nearest', cmap='jet')

##
示例#5
0
def add_coamps_fields(model,
                      cache_dir,
                      fields=[('air_temp', 'Tair'), ('rltv_hum', 'RH')],
                      mask_field=None):
    """
    Uses existing times, assumed already hourly, in model.met_ds.

    fields: list of tuples, (coamps_name, suntans_name), e.g.
       [ ('air_temp','Tair'), ... ]
    to grab air_temp from coamps, and save to the Tair field of met_ds.

    mask: stompy.spatial.Field, evaluates to nonzero for areas that should 
    be masked out.
    """
    period_start = model.met_ds.nt.values[0]
    period_stop = model.met_ds.nt.values[-1]

    coamps_fields = [fld[0] for fld in fields]
    src_ds = coamps.coamps_dataset(model.grid.bounds(),
                                   period_start,
                                   period_stop,
                                   cache_dir=cache_dir,
                                   fields=coamps_fields)

    # only include points which are within 3km of the model domain
    g_poly = geometry.Polygon(
        model.grid.boundary_polygon().exterior).buffer(3000)

    fld = field.SimpleGrid(extents=[
        src_ds.x.values[0], src_ds.x.values[-1], src_ds.y.values[0],
        src_ds.y.values[-1]
    ],
                           F=src_ds[coamps_fields[0]].isel(time=0).values)
    logging.info("coamps temp: gridded resolution: %.2f %.2f" %
                 (fld.dx, fld.dy))
    # RH 2019-08-01: some nan values sneak through the other masks. try to
    # masking out any pixels that are nan on this initial time slice.

    time_slc = (src_ds.time.values >= period_start) & (src_ds.time.values <=
                                                       period_stop)
    src_sub_ds = src_ds.isel(time=time_slc)

    # make sure these are aligned
    # somehow there are some 1 second errors in the met times.
    # allow slop up to 10 minutes
    assert len(model.met_ds.nt.values) == len(
        src_sub_ds.time.values
    ), "Mismatch in length of met_ds time and coamps ds time"
    assert np.all(
        np.abs(model.met_ds.nt.values -
               src_sub_ds.time.values) < np.timedelta64(600, 's'))

    X, Y = np.meshgrid(src_ds.x.values, src_ds.y.values)
    XY = np.stack((X, Y), axis=-1)
    base_valid = fld.polygon_mask(g_poly)
    if mask_field is not None:
        # additionally mask by a field passed in
        base_valid = base_valid & (mask_field(XY) == 0.0)

    met_ds = model.met_ds
    for coamps_name, sun_name in fields:
        all_values = src_sub_ds[coamps_name].values
        # additionally restrict to pixels where this variable is finite
        # over all time steps
        valid = base_valid & np.all(np.isfinite(all_values), axis=0)
        values = src_sub_ds[coamps_name].values[:, valid]

        # moved mask code inside the loop in order to catch all nan values
        # even if they are finite at the start, and nan later.
        xcoords = X[valid]
        ycoords = Y[valid]

        for v in [sun_name, 'x_' + sun_name, 'y_' + sun_name, 'z_' + sun_name]:
            if v in met_ds: del met_ds[v]
        met_ds['x_' + sun_name] = ("N" + sun_name), xcoords
        met_ds['y_' + sun_name] = ("N" + sun_name), ycoords
        met_ds['z_' + sun_name] = ("N" +
                                   sun_name), 10.0 * np.ones_like(xcoords)

        # DBG:
        #if not np.all(np.isfinite(values)):
        #    import pdb # each time step, 519 values are nan.
        #    pdb.set_trace()
        assert np.all(np.isfinite(values))
        met_ds[sun_name] = (('nt', 'N' + sun_name), values)
示例#6
0
                        right=0.99,
                        wspace=0.01,
                        bottom=0.02,
                        hspace=0.01,
                        top=0.99)

    fig.savefig(os.path.join(fig_dir, "compare-4-lidar-north_marsh_pond.png"),
                dpi=200)

##

# Show difference between cbec as-built and the 2011 lidar.
cbec_on_2011 = cbec_dem.extract_tile(match=lidar2011)
cbec_on_2011.name = cbec_dem.name

delta = field.SimpleGrid(F=cbec_on_2011.F - lidar2011.F,
                         extents=cbec_on_2011.extents)
delta.name = f"[cbec {version}] - [2011 Lidar]"
delta.smooth_by_convolution(iterations=4)

if 0:
    plt.figure(2).clf()
    fig, axs = plt.subplots(1, 3, num=2)
    fig.set_size_inches([8.5, 4.5], forward=True)

    for ax, dem in zip(axs.ravel(), [cbec_on_2011, delta, lidar2011]):
        img = dem.plot(ax=ax, cmap=sst, vmin=0.5, vmax=3.5)
        ax.axis('tight')
        ax.axis('equal')
        ax.axis(zoom)
        ax.axis('off')
        ax.set_title(dem.name)
        return mark_range(bin_low, bin_high)
    else:
        return test_recursive(bin_low, bin_high)

    # # not right, just stowing some code here.
    # mr=scipy.stats.mode(dem.F[in_bin]) # slow!
    # bin_high=bin_low=mr.mode[0]


# This is pretty slow --
while test_recursive(min_val, max_val):
    pass

##

out = field.SimpleGrid(F=F, extents=dem.extents)

plt.figure(2).clf()
fig, ax = plt.subplots(num=2)
out.plot(ax=ax, interpolation='nearest', vmin=-2, vmax=4)

fig.set_size_inches((6, 8), forward=True)
ax.xaxis.set_visible(0)
ax.yaxis.set_visible(0)
fig.tight_layout()

fig.savefig('remove_flat_ponds-v00.png')
##

out.write_gdal('../sources/usgs_2m_remove_flat_ponds_v00.tif')
示例#8
0
def add_wind_preblended(model, cache_dir, pad=np.timedelta64(3 * 3600, 's')):
    """
    model: A HydroModel instance
    cache_dir: path for caching wind data
    pad: how far before/after the simulation the wind dataset should extend
    
    Add wind data from pre-blended netcdf output
    """
    g = model.grid

    period_start = model.run_start - pad
    period_stop = model.run_stop + pad

    # note that this is a bit different than the sfei data
    #  - already in UTC
    #  - larger footprint, coarser grid
    #  - natural neighbors run on the observed + COAMPS (thinned).
    blended_ds = blended_dataset(period_start, period_stop)

    # Not ready for other years
    assert blended_ds.time.values[
        0] <= period_start, "FIX: pre-blended wind only set up for some of 2017"
    assert blended_ds.time.values[
        -1] >= period_stop, "FIX: pre-blended wind only set up for some of 2017"

    # buffer out the model domain a bit to get a generous footprint
    g_poly = geometry.Polygon(g.boundary_polygon().exterior).buffer(3000)

    # For each of the sources, which points will be included?
    # Add a mask variable for each dataset
    exclude_poly = None
    for src_name, src_ds in [('BLENDED', blended_ds)]:
        fld = field.SimpleGrid(extents=[
            src_ds.x.values[0], src_ds.x.values[-1], src_ds.y.values[0],
            src_ds.y.values[-1]
        ],
                               F=src_ds.wind_u.isel(time=0).values)
        logging.info("%s: gridded resolution: %.2f %.2f" %
                     (src_name, fld.dx, fld.dy))
        mask = fld.polygon_mask(g_poly)
        logging.info("%s: %d of %d samples fall within grid" %
                     (src_name, mask.sum(), mask.size))
        if exclude_poly is not None:
            omit = fld.polygon_mask(exclude_poly)
            mask = mask & (~omit)
            logging.info(
                "%s: %d of %d samples fall within exclusion poly, will use %d"
                % (src_name, omit.sum(), omit.size, mask.sum()))

        src_ds['mask'] = src_ds.wind_u.dims[1:], mask

    #  Trim to the same period
    time_slc = (blended_ds.time.values >=
                period_start) & (blended_ds.time.values <= period_stop)
    blended_sub_ds = blended_ds.isel(time=time_slc)

    times = blended_sub_ds.time.values

    # Now we start to break down the interface with model, as wind is not really
    # ready to go.

    met_ds = model.zero_met(times=times)

    srcs = [blended_sub_ds]
    src_counts = [src.mask.values.sum() for src in srcs]
    n_points = np.sum(src_counts)

    xcoords = []
    ycoords = []
    for src in srcs:
        X, Y = np.meshgrid(src.x.values, src.y.values)
        xcoords.append(X[src.mask.values])
        ycoords.append(Y[src.mask.values])
    xcoords = np.concatenate(xcoords)
    ycoords = np.concatenate(ycoords)

    # Replace placeholder coordinates for wind variables.
    for name in ['Uwind', 'Vwind']:
        del met_ds["x_" + name]
        del met_ds["y_" + name]
        del met_ds["z_" + name]
        del met_ds[name]

        met_ds["x_" + name] = ("N" + name), xcoords
        met_ds["y_" + name] = ("N" + name, ), ycoords
        met_ds["z_" + name] = ("N" + name, ), 10.0 * np.ones_like(xcoords)

    Uwind_t = []
    Vwind_t = []
    for ti in utils.progress(range(len(times)), msg="Compiling wind: %s"):
        Uwind = []
        Vwind = []
        for src in srcs:
            Uwind.append(src.wind_u.isel(time=ti).values[src.mask])
            Vwind.append(src.wind_v.isel(time=ti).values[src.mask])
        Uwind = np.concatenate(Uwind)
        Vwind = np.concatenate(Vwind)
        Uwind_t.append(Uwind)
        Vwind_t.append(Vwind)
    met_ds['Uwind'] = ('nt', "NUwind"), np.stack(Uwind_t)
    met_ds['Vwind'] = ('nt', "NVwind"), np.stack(Vwind_t)

    logging.info("New Met Dataset:")
    logging.info(str(met_ds))
    model.met_ds = met_ds
    if int(model.config['metmodel']) not in [4, 5]:
        logging.warning("While adding wind, noticed metmodel %s" %
                        (model.config['metmodel']))
示例#9
0
def add_wind_coamps_sfei(model,
                         cache_dir,
                         pad=np.timedelta64(3 * 3600, 's'),
                         coamps_buffer=30e3,
                         air_temp=False):
    """
    model: A HydroModel instance
    cache_dir: path for caching wind data
    pad: how far before/after the simulation the wind dataset should extend
    
    Combine SFEI interpolated winds and COAMPS winds.
    coamps_buffer: coamps samples within this distance of SFEI data are omitted.

    This method does not work so well with SUNTANS.  The available interpolation
    methods (inverse distance and kriging) do not deal well with having two 
    distinct, densely sampled datasets with a gap in between.

    air_temp: if 'coamps', fill in air temperature samples from coamps data.
    """
    g = model.grid

    period_start = model.run_start - pad
    period_stop = model.run_stop + pad

    fields = ['wnd_utru', 'wnd_vtru', 'pres_msl']
    if air_temp == 'coamps':
        # may add sol_rad at some point...
        fields += ['air_temp', 'rltv_hum']
    coamps_ds = coamps.coamps_dataset(g.bounds(),
                                      period_start,
                                      period_stop,
                                      cache_dir=cache_dir,
                                      fields=fields)

    sfei_ds = xr.open_dataset('wind_natneighbor_WY2017.nc')
    # SFEI data is PST
    logging.info(sfei_ds.time.values[0])
    sfei_ds.time.values[:] += np.timedelta64(8 * 3600, 's')
    logging.info(sfei_ds.time.values[0])  # just to be sure it took.
    sfei_ds.time.attrs['timezone'] = 'UTC'
    # Not ready for other years
    assert sfei_ds.time.values[
        0] <= period_start, "FIX: SFEI wind only setup for 2017"
    assert sfei_ds.time.values[
        -1] >= period_stop, "FIX: SFEI wind only setup for 2017"

    # buffer out the model domain a bit to get a generous footprint
    g_poly = geometry.Polygon(g.boundary_polygon().exterior).buffer(3000)

    # For each of the sources, which points will be included?
    # Add a mask variable for each dataset
    exclude_poly = None
    for src_name, src_ds in [('SFEI', sfei_ds), ('COAMPS', coamps_ds)]:
        fld = field.SimpleGrid(extents=[
            src_ds.x.values[0], src_ds.x.values[-1], src_ds.y.values[0],
            src_ds.y.values[-1]
        ],
                               F=src_ds.wind_u.isel(time=0).values)
        logging.info("%s: gridded resolution: %.2f %.2f" %
                     (src_name, fld.dx, fld.dy))
        mask = fld.polygon_mask(g_poly)
        logging.info("%s: %d of %d samples fall within grid" %
                     (src_name, mask.sum(), mask.size))
        if exclude_poly is not None:
            omit = fld.polygon_mask(exclude_poly)
            mask = mask & (~omit)
            logging.info(
                "%s: %d of %d samples fall within exclusion poly, will use %d"
                % (src_name, omit.sum(), omit.size, mask.sum()))

        src_ds['mask'] = src_ds.wind_u.dims[1:], mask

        # Add these points to the exclusion polygon for successive sources
        X, Y = fld.XY()
        xys = np.c_[X[mask], Y[mask]]
        pnts = [geometry.Point(xy[0], xy[1]) for xy in xys]
        poly = cascaded_union([p.buffer(coamps_buffer) for p in pnts])
        if exclude_poly is None:
            exclude_poly = poly
        else:
            exclude_poly = exclude_poly.union(poly)

    #  Trim to the same period
    # SFEI
    time_slc = (sfei_ds.time.values >= period_start) & (sfei_ds.time.values <=
                                                        period_stop)
    sfei_sub_ds = sfei_ds.isel(time=time_slc)

    # COAMPS
    time_slc = (coamps_ds.time.values >= period_start) & (coamps_ds.time.values
                                                          <= period_stop)
    coamps_sub_ds = coamps_ds.isel(time=time_slc)

    # make sure that worked:
    assert np.all(sfei_sub_ds.time.values == coamps_sub_ds.time.values)

    times = sfei_sub_ds.time.values

    # Now we start to break down the interface with model, as wind is not really
    # ready to go.

    met_ds = model.zero_met(times=times)

    srcs = [sfei_sub_ds, coamps_sub_ds]
    src_counts = [src.mask.values.sum() for src in srcs]
    n_points = np.sum(src_counts)

    xcoords = []
    ycoords = []
    for src in srcs:
        X, Y = np.meshgrid(src.x.values, src.y.values)
        xcoords.append(X[src.mask.values])
        ycoords.append(Y[src.mask.values])
    xcoords = np.concatenate(xcoords)
    ycoords = np.concatenate(ycoords)

    # Replace placeholder coordinates for wind variables.
    for name in ['Uwind', 'Vwind']:
        del met_ds["x_" + name]
        del met_ds["y_" + name]
        del met_ds["z_" + name]
        del met_ds[name]

        met_ds["x_" + name] = ("N" + name), xcoords
        met_ds["y_" + name] = ("N" + name, ), ycoords
        met_ds["z_" + name] = ("N" + name, ), 10.0 * np.ones_like(xcoords)

    Uwind_t = []
    Vwind_t = []
    for ti in utils.progress(range(len(times)), msg="Compiling wind: %s"):
        Uwind = []
        Vwind = []
        for src in srcs:
            Uwind.append(src.wind_u.isel(time=ti).values[src.mask])
            Vwind.append(src.wind_v.isel(time=ti).values[src.mask])
        Uwind = np.concatenate(Uwind)
        Vwind = np.concatenate(Vwind)
        Uwind_t.append(Uwind)
        Vwind_t.append(Vwind)
    met_ds['Uwind'] = ('nt', "NUwind"), np.stack(Uwind_t)
    met_ds['Vwind'] = ('nt', "NVwind"), np.stack(Vwind_t)

    logging.info("New Met Dataset:")
    logging.info(str(met_ds))
    model.met_ds = met_ds
    if int(model.config['metmodel']) not in [0, 4]:
        logging.warning("Adding wind, will override metmodel %s => %d" %
                        (model.config['metmodel'], 4))
    model.config['metmodel'] = 4  # wind only
示例#10
0
zoom = (647088.821657404, 647702.7390601198, 4185484.2206095303,
        4185941.6880934904)

plt.figure(10).clf()

fig, ax = plt.subplots(num=10)

dem_at_adcp = dem(np.c_[adcp_ds.x, adcp_ds.y])
delta = dem_at_adcp - adcp_ds.z_bed

scat = ax.scatter(adcp_ds.x, adcp_ds.y, 20, delta, cmap='coolwarm')
scat.set_clim([-0.75, 0.75])

plt.colorbar(scat, ax=ax, orientation='horizontal', label='dem - adcp')

ax.axis('equal')
ax.axis(zoom)

##

# And difference between the ADCP interpolated bathy
dem_delta = field.SimpleGrid(extents=dem_adcp.extents, F=dem_adcp.F - dem.F)

plt.figure(11).clf()
fig, ax = plt.subplots(num=11)
img = dem_delta.plot(cmap='coolwarm', clim=[-1, 1])
plt.colorbar(img, ax=ax, orientation='horizontal', label='dem_adcp - dem_dwr')

ax.axis('equal')
ax.axis(zoom)
示例#11
0
##

total_crop = [6321753, 6325212, 2116503, 2118419]
# that covers the junction
dem = field.GdalGrid("../bathy/dwr/OldRiver_SJ_072020171.tif",
                     geo_bounds=total_crop)
dem.F[dem.F < -1e+10] = np.nan

##

# that's in ft
# lowpass in 2D at 15ft
dx = 20
dem_lp = dem.copy()
dem_lp.smooth_by_convolution(dx)
dem_hp = field.SimpleGrid(extents=dem.extents, F=dem.F - dem_lp.F)
dem_rms = dem_hp.copy()
dem_rms.F = dem_rms.F**2
dem_rms.smooth_by_convolution(dx)
dem_rms.F = np.sqrt(dem_rms.F)

##

plt.figure(1).clf()
fig, axs = plt.subplots(1, 4, sharex=True, sharey=True, num=1)

img1 = dem.plot(vmin=-20, vmax=5, cmap='jet', ax=axs[0])
img2 = dem_lp.plot(vmin=-20, vmax=5, cmap='jet', ax=axs[1])

img3 = dem_hp.plot(vmin=-1, vmax=1, cmap='seismic', ax=axs[2])
img4 = dem_rms.plot(vmin=0.1, vmax=1.0, cmap='plasma_r', ax=axs[3])
示例#12
0
def base_dem(res=2.0):
    F = np.zeros((int(W / res), int(L / res)), np.float64)
    f = field.SimpleGrid(extents=[0, L, 0, W], F=F)
    x, y = f.xy()
    f.F[:, :] = (-y / 10)[:, None]
    return f
示例#13
0
# Out in Grizzly
seed_xy = np.array([583638, 4218191.])

##

z_inundate = dem.copy()
z_inundate.F[:, :] = np.nan

from scipy import ndimage

for thresh in np.arange(0, 4.0, 0.01):
    print(thresh)
    wet = (leveed_dem.F <= thresh).astype(np.int8)
    labels, n_features = ndimage.label(wet)
    labeled = field.SimpleGrid(extents=leveed_dem.extents, F=labels)
    bay_label = labeled(seed_xy)
    sel = (labels == bay_label) & np.isnan(z_inundate.F)
    z_inundate.F[sel] = thresh

z_inundate.write_gdal('inundation_sea_level.tif')

##
plt.figure(2).clf()
fig, axs = plt.subplots(1, 2, num=2)
img = z_inundate.plot(ax=axs[0], cmap=turbo)
img.set_clim(1.5, 2.5)
topo_cmap = scmap.load_gradient('oc-sst.cpt')
img2 = leveed_dem.plot(ax=axs[1], cmap=topo_cmap, vmin=0.0, vmax=3.0)
plt.colorbar(img,
             ax=axs[0],
示例#14
0
    def to_grid(self,nx=None,ny=None,bounds=None,dx=None,dy=None):
        """ render the layers to a SimpleGrid tile.
        """
        # boil the arguments down to dimensions
        if bounds is None:
            xmin,xmax,ymin,ymax = self.bounds()
        else:
            if len(bounds) == 2:
                xmin,ymin = bounds[0]
                xmax,ymax = bounds[1]
            else:
                xmin,xmax,ymin,ymax = bounds
        if nx is None:
            nx=1+int(np.round((xmax-xmin)/dx))
            ny=1+int(np.round((ymax-ymin)/dy))

        # allocate the blank starting canvas
        result_F =np.ones((ny,nx),'f8')
        result_F[:]=-999
        result_data=field.SimpleGrid(extents=bounds,F=result_F)
        result_alpha=result_data.copy()
        result_alpha.F[:]=0.0

        # Which sources to use, and in what order?
        box=geometry.box(bounds[0],bounds[2],bounds[1],bounds[3])

        # Which sources are relevant?
        relevant_srcs=np.nonzero( [ box.intersects(geom)  
                                    for geom in self.sources['geom'] ])[0]
        # omit negative priorities
        relevant_srcs=relevant_srcs[ self.src_priority[relevant_srcs]>=0 ]

        # Starts with lowest, goes to highest
        order = np.argsort(self.src_priority[relevant_srcs])
        ordered_srcs=relevant_srcs[order]

        for src_i in ordered_srcs:
            print self.sources['src_name'][src_i]
            print "   data mode: %s  alpha mode: %s"%(self.data_mode[src_i],
                                                      self.alpha_mode[src_i])

            source=self.load_source(src_i)
            src_data = source.to_grid(bounds=bounds,dx=dx,dy=dy)
            src_alpha= field.SimpleGrid(extents=src_data.extents,
                                        F=np.ones(src_data.F.shape,'f8'))

            if 0: # slower
                src_alpha.mask_outside(self.sources['geom'][src_i],value=0.0)
            else: 
                mask=src_alpha.polygon_mask(self.sources['geom'][src_i])
                src_alpha.F[~mask] = 0.0

            # create an alpha tile. depending on alpha_mode, this may draw on the lower data,
            # the polygon and/or the data tile.
            # modify the data tile according to the data mode - so if the data mode is 
            # overlay, do nothing.  but if it's max, the resulting data tile is the max
            # of itself and the lower data.
            # composite the data tile, using its alpha to blend with lower data.

            # the various operations
            def min():
                """ new data will only decrease values
                """
                valid=result_alpha.F>0
                src_data.F[valid]=np.minimum( src_data.F[valid],result_data.F[valid] )
            def max():
                """ new data will only increase values
                """
                valid=result_alpha.F>0
                src_data.F[valid]=np.maximum( src_data.F[valid],result_data.F[valid] )
            def fill(dist):
                "fill in small missing areas"
                pixels=int(round(float(dist)/dx))
                niters=np.maximum( pixels//3, 2 )
                src_data.fill_by_convolution(iterations=niters)
            def overlay():
                pass 
            # alpha channel operations:
            def valid():
                # updates alpha channel to be zero where source data is missing.
                data_missing=np.isnan(src_data.F)
                src_alpha.F[data_missing]=0.0
            def gaussian(dist):
                "smooth with gaussian filter - this allows spreading beyond original poly!"
                pixels=int(round(float(dist)/dx))
                src_alpha.F=ndimage.gaussian_filter(src_alpha.F,pixels)
            def feather(dist):
                "linear feathering within original poly"
                pixels=int(round(float(dist)/dx))
                Fsoft=ndimage.distance_transform_bf(src_alpha.F)
                src_alpha.F = (Fsoft/pixels).clip(0,1)

            # dangerous! executing code from a shapefile!
            eval(self.data_mode[src_i])
            eval(self.alpha_mode[src_i])

            data_missing=np.isnan(src_data.F)
            src_alpha.F[data_missing]=0.0
            cleaned=src_data.F.copy()
            cleaned[data_missing]=-999 # avoid nan contamination.

            assert np.allclose( result_data.extents, src_data.extents )
            assert np.all( result_data.F.shape==src_data.F.shape )
            # before getting into fancy modes, just stack it all up:
            result_data.F   = result_data.F *(1-src_alpha.F) + cleaned*src_alpha.F
            result_alpha.F  = result_alpha.F*(1-src_alpha.F) + src_alpha.F

        # fudge it a bit, and allow semi-transparent data back out, but 
        # at least nan out the totally transparent stuff.
        result_data.F[ result_alpha.F==0 ] = np.nan
        return result_data
txy00 = dict(name='ADCP RBF cluster=3k med=3',
             f=field.GdalGrid('adcp-rbf-cluster-med3.tif'))
txy01 = dict(name='DEM RBF qtracks', f=field.GdalGrid('tile-rbf-qtracks.tif'))
txy02 = dict(name='DEM RBF cluster', f=field.GdalGrid('tile-rbf-cluster.tif'))
txy03 = dict(name='DEM RBF cluster, 3x3 median',
             f=field.GdalGrid('tile-rbf-cluster-med3.tif'))
zha00 = dict(name='Zhang', f=field.GdalGrid('zhang-dem.tif'))
zbd00 = dict(name='Zhang bidir', f=field.GdalGrid('zhang-dem-bidir.tif'))

candidates = [txy00, txy01, txy02, txy03, zha00, zbd00]
truth = tile

##

# bitmask of samples which are valid in all outputs
mask = field.SimpleGrid(extents=truth.extents, F=np.isfinite(truth.F))

# Get them all on the same grid, interpolating onto the tile
for cand in candidates:
    f = cand['f']

    if (f.extents == truth.extents) and (f.dx == truth.dx) and (f.dy
                                                                == truth.dy):
        cand['fm'] = f
    else:
        print("%s not aligned.  Interpolating" % cand['name'])
        cand['fm'] = f.extract_tile(match=truth, interpolation='bilinear')

    mask.F = mask.F & np.isfinite(cand['fm'].F)

##