function varargout = wflowacc(X,Y,dem,varargin)
% Upslope area (flow accumulation) algorithm for Digital Elevation Models
%
% [flowacc,flowdir,slope,runs] = wflowaccrob(X,Y,dem)
% ... = wflowaccrob(X,Y,dem,'propertyname',propertyvalue,...)
%
% Multiple flowdirection and flowaccumulation algorithm that routes
% through flat terrain (not sinks). Remove sinks with the function imfill
% that requires the image processing toolbox.
%
% Input:
% X,Y coordinate matrices created by meshgrid
% dem digital elevation model same size as X and Y
%
% Properties:
% propertyname propertyvalues
%
% 'type' 'multi' (default): multiple flowdirection (dinf)
% 'single': single flow direction (d8). Flow occurs only
% along the steepest descent
%
% 'exponent' exponent governing the relation between flow
% direction and slope. Default is 1, which means, there
% is a linear relation. You may want to increase the
% exponent when flow direction should rather follow a
% steepest descent (single) flow direction (e.g. 5). This
% option is only effective for multiple flowdirection.
%
% 'mode' 'default': deterministic flow
% 'random': totally random flow to downward neighbors
% 'randomized': deterministic flow with noise
%
% 'W0' W0 is an initiation grid same size as dem and refers
% to the water in each cell before routing through the
% catchment. By default W0 is ones(size(dem)).
%
% 'routeflats' 'yes' (default) or 'no', decides upon routing over
% flats/plateaus.
%
% 'edges' decide on how to handle flow on grid edges.
% 'closed' (default) forces all water to remain on the
% grid, 'open' assumes that edge cells loose the ratio
% r = # of neighbor cells/8
% of water.
%
% Output:
% flowacc flowaccumulation (upslope area) grid
% flowdir flowdirection (sparse matrix)
% slope slope (sparse matrix)
% runs number of loops to route across flats (scalar)
%
%
% Example:
%
% [X,Y,dem] = peaks(100);
% A = wflowacc(X,Y,dem);
% surf(X,Y,dem,log(A))
% shading interp;
% camlight; lighting phong
%
%
% Required m-files:
% ixneighbors
%
% Wolfgang Schwanghart (w.schwanghart@unibas.ch)
% Last Update: 7. January 2009
% Please cite: Schwanghart, W., 2009: Robust flow accumulation. Mathworks
% File-Exchange.
%
if nargin<3;
error('wrong number of input arguments')
else
siz = size(dem);
if any(size(X) ~= size(Y)) || any((size(X) ~= siz));
error('X, Y and dem must have same size')
end
end
% general values
nrc = numel(dem);
nans = isnan(dem);
% check input using PARSEARGS
params.type = {'multi','single'};
params.mode = {'default','randomized','random'};
params.W0 = ones(siz);
params.exponent = 1;
params.routeflats = {'yes','no'};
params.edges = {'closed','open'};
params = parseargs(params,varargin{:});
% *********************************************************************
% normal multiple FLOW DIRECTION calculation
% calculate cell size
% calculate maximum slope and slope direction
% find neighbors of cells
[ic1,icd1] = ixneighbors(dem);
e = (dem(ic1)-dem(icd1))./hypot(X(ic1)-X(icd1),Y(ic1)-Y(icd1));
if nargout > 2;
S = sparse(ic1,icd1,e,nrc,nrc); % slope matrix
end
Ad = sparse(ic1,icd1,1,nrc,nrc); % adjacency matrix
% edge correction when open
switch params.edges
case 'open';
edgecorrection = full(sum(Ad,2)/8);
end
e(e<0) = 0;
% *********************************************************************
% flow direction matrix
M = sparse(ic1,icd1,e,nrc,nrc);
% *********************************************************************
% routing through flats
switch params.routeflats
case 'yes'
flagflats = 1;
case 'no'
flagflats = 0;
end
% A flat or plateau exists when two or more adjacent cells have the same
% elevation. Up to now flow direction indicates for these cells
% no exchange of water.
% The subsequent code first identifies flats and then iteratively removes
% them.
run = 0; % counter
while flagflats == 1;
run = run+1;
% in a digital elevation model only those cells flats can be assigned
% as flats that "do not give" and are -not- located on the edge of the
% dem.
% now check whether one of the surrounding cells is a giver. This is
% done by querying the neighbors of the flats.
ing = find(sum(M,2)==0);
ing(nans(ing)) = [];
a = full(sum(sparse(ing,ing,1,nrc,nrc)*Ad,2)==8);
b = full(Ad*a);
inb_flats = reshape(b<8 & a,siz);
IX_outb_flats = find(b & ~a);
% not too many of IX_outb_flats should be sills. To check whether a
% cell is a sill it
% 1. should be a giver and
% 2. have the same elevation as its neighbor in IX_inb_flats
[ic,icd] = ixneighbors(dem,inb_flats);
i = dem(ic)==dem(icd);
ic = ic(i);
icd = icd(i);
i = ismembc(icd,IX_outb_flats);
if any(i)
ic = ic(i);
icd = icd(i);
% now icd are the indices of the sills and ic are the
% indices of the cells that contribute to the sills
% --> water exchange from ic to icd
% now a new connects matrix is built for sills
M = M+sparse(ic,icd,0.01,nrc,nrc);
flagflats = 1;
else
flagflats = 0;
end
end
% ******************************************************************
% Randomization of amount transferred to another cell
switch params.mode;
case 'random'
M = abs(sprandn(M));
case 'randomized'
% randomize coefficient. The higher, the more random
rc = 0.01;
M = M + (rc * abs(sprandn(M)));
otherwise
end
% ******************************************************************
% single flow direction, flow concentration
switch params.type
case 'single'
[m,IX2] = max(M,[],2);
i = m==0;
IX1 = (1:nrc)';
IX1(i) = [];
IX2(i) = [];
M = sparse(IX1,IX2,1,nrc,nrc);
otherwise
if params.exponent ~= 1;
M = M.^params.exponent;
end
end
% ******************************************************************
% Row standardization of M only necessary when multiple flow dir
switch params.type
case 'multi'
M = spdiags(spfun(@(x) 1./x,sum(M,2)),0,nrc,nrc) * M;
end
% ******************************************************************
% Solve equation according
switch params.edges
case 'open';
flowacc = (speye(nrc,nrc)-spdiags(edgecorrection,0,nrc,nrc)*M')\params.W0(:);
otherwise
flowacc = (speye(nrc,nrc)-M')\params.W0(:);
end
flowacc = reshape(flowacc,siz);
% ******************************************************************
% Create output
varargout{1} = flowacc;
if nargout > 1;
varargout{2} = M;
if nargout > 2;
varargout{3} = S;
if nargout > 3;
varargout{4} = run;
end
end
end
% and this is it...
% :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
% :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
% :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
function X = parseargs(X,varargin)
%PARSEARGS - Parses name-value pairs
%
% Behaves like setfield, but accepts multiple name-value pairs and provides
% some additional features:
% 1) If any field of X is an cell-array of strings, it can only be set to
% one of those strings. If no value is specified for that field, the
% first string is selected.
% 2) Where the field is not empty, its data type cannot be changed
% 3) Where the field contains a scalar, its size cannot be changed.
%
% X = parseargs(X,name1,value1,name2,value2,...)
%
% Intended for use as an argument parser for functions which multiple options.
% Example usage:
%
% function my_function(varargin)
% X.StartValue = 0;
% X.StopOnError = false;
% X.SolverType = {'fixedstep','variablestep'};
% X.OutputFile = 'out.txt';
% X = parseargs(X,varargin{:});
%
% Then call (e.g.):
%
% my_function('OutputFile','out2.txt','SolverType','variablestep');
% The various #ok comments below are to stop MLint complaining about
% inefficient usage. In all cases, the inefficient usage (of error, getfield,
% setfield and find) is used to ensure compatibility with earlier versions
% of MATLAB.
remaining = nargin-1; % number of arguments other than X
count = 1;
fields = fieldnames(X);
modified = zeros(size(fields));
% Take input arguments two at a time until we run out.
while remaining>=2
fieldname = varargin{count};
fieldind = find(strcmp(fieldname,fields));
if ~isempty(fieldind)
oldvalue = getfield(X,fieldname); %#ok
newvalue = varargin{count+1};
if iscell(oldvalue)
% Cell arrays must contain strings, and the new value must be
% a string which appears in the list.
if ~iscellstr(oldvalue)
error(sprintf('All allowed values for "%s" must be strings',fieldname)); %#ok
end
if ~ischar(newvalue)
error(sprintf('New value for "%s" must be a string',fieldname)); %#ok
end
if isempty(find(strcmp(oldvalue,newvalue))) %#ok
error(sprintf('"%s" is not allowed for field "%s"',newvalue,fieldname)); %#ok
end
elseif ~isempty(oldvalue)
% The caller isn't allowed to change the data type of a non-empty property,
% and scalars must remain as scalars.
if ~strcmp(class(oldvalue),class(newvalue))
error(sprintf('Cannot change class of field "%s" from "%s" to "%s"',...
fieldname,class(oldvalue),class(newvalue))); %#ok
elseif numel(oldvalue)==1 & numel(newvalue)~=1 %#ok
error(sprintf('New value for "%s" must be a scalar',fieldname)); %#ok
end
end
X = setfield(X,fieldname,newvalue); %#ok
modified(fieldind) = 1;
else
error(['Not a valid field name: ' fieldname]);
end
remaining = remaining - 2;
count = count + 2;
end
% Check that we had a value for every name.
if remaining~=0
error('Odd number of arguments supplied. Name-value pairs required');
end
% Now find cell arrays which were not modified by the above process, and select
% the first string.
notmodified = find(~modified);
for i=1:length(notmodified)
fieldname = fields{notmodified(i)};
oldvalue = getfield(X,fieldname); %#ok
if iscell(oldvalue)
if ~iscellstr(oldvalue)
error(sprintf('All allowed values for "%s" must be strings',fieldname)); %#ok
elseif isempty(oldvalue)
error(sprintf('Empty cell array not allowed for field "%s"',fieldname)); %#ok
end
X = setfield(X,fieldname,oldvalue{1}); %#ok
end
end