Contents
- Parsing and checking parameters
- Checking variables and setting internal parameters
- Computing the slopes
- Setting the (non unique) flow direction over the neighbours
- Routing through flats
- Randomization of amount transferred to another cell
- Estimation of the flow direction given by direction of the maximal
- 'gd8','ed83' or 'ed82': nondispersive single flow direction
- Sparse flow direction representation
- Upslope area
function [M,varargout] = sflow8(dem,varargin)
% SFLOW8 - Upslope area algorithm for Digital Elevation Models using % single flow direction d8 and gd8 % % dir = sflow8(dem); % [dir,slope,upslope] = sflow8(dem, flow, ... % propertyname',propertyvalue,...); % % Single flow direction algorithm (flow occurs only along the steepest % descent) that routes through flat terrain (not sinks). % Remove sinks with the function imfill that requires the image processing % toolbox. % % Input: % dem : digital elevation model same size as X and Y % flow : optional string setting the nature of the estimated flow, it % can'be: % - 'd8' for the estimation of the discrete flow directions given % by 8-connectivity, % - 'gd8' for the estimation of the discrete global non-dispersive % flow directions proposed in [Paik08], % - 'ed82' and 'ed83' for the estimation of the discrete non- % dispersive flow directions of 2nd and 3rd orders also proposed % in [Paik08]; % default: flow='d8'. % % Property [propertyname propertyvalues] % 'dx', 'dy': optional horizontal and vertical pixel center spacing; if % omitted, a value of 1.0 is assumed. % 'mode' : see wflowacc. % 'rflat' : see wflowacc. % % Outputs: % dir : flow direction indices (sparse matrix). % slope : slope values (sparse matrix). % upslope : upslope area. % % % Example from Paik: % dem = [ 93 99 96 95 94; ... % 94 95 97 96 93 ; ... % 91 98 100 97 95; ... % 92 94 96 98 94; ... % 89 91 90 92 93] % % References: % [Paik08] K. Paik: "Global search algorithm for nondispersive flow path % extraction", Journal of Geophysical Research, 113:F04001, 2008. % [Tarb97] D.G. Tarboton: "A new method for the determination of flow % directions and upslope areas in grid digital elevation models", % Water Resources Research, 33(2):309-319, 1997. % [WLD07] J.P. Wilson, C.S. Lam and Y. Deng: "Comparison of the performance % of flow-routing algorithms used in GIS-based hydrologic analysis", % Hydrological Processes, 21:1026-1044, 2007.
Parsing and checking parameters
error(nargchk(1, 14, nargin, 'struct')); error(nargoutchk(1, 3, nargout, 'struct')); % mandatory parameter if ~isnumeric(dem) error('grdsmooth:inputerror','a matrix is required in input'); end p = createParser('SFLOW8'); % create an instance of the inputParser class. % optional parameters p.addOptional('flow', 'd8', @(x)ischar(x) && ... any(strcmpi(x,{'d8','gd8','ed82','ed83'}))); p.addParamValue('mode','default', @(x)ischar(x) && ... any(strcmpi(x,{'default','randomized','random'}))); p.addParamValue('rflat', false, @(x)islogical(x)); p.addParamValue('dx',1,@(x)isscalar(x) && round(x)==x && x>=1); p.addParamValue('dy',1,@(x)isscalar(x) && round(x)==x && x>=1); p.addParamValue('disp',false,@(x)islogical(x)); % parse and validate all input arguments p.parse(varargin{:}); p = getvarParser(p);
Checking variables and setting internal parameters
% general values [NX,NY] = size(dem); nrc = numel(dem); % nans = isnan(dem); [Y,X] = meshgrid(1:NY,1:NX); % if any(size(X) ~= size(Y)) || any((size(X) ~= size(dem))); % error('X, Y and dem must have same size and same size as the dem') % end
Computing the slopes
% find neighbours of cells: for each pixel in the dem, give the index list % of its 8 neighbours in the graph (unless the pixel is on any border of % the image) [ic1,icd1] = ixneighbours(dem); % create the adjacency matrix AD = sparse(ic1,icd1,1,nrc,nrc); % calculate the local slope direction and maximum slope: for each pixel in the % dem, give the slope between this pixel and its 8 neighbours (unless the pixel % is on any border of the image) E = (dem(ic1)-dem(icd1)) ./ ... hypot((X(ic1)-X(icd1))/p.dx,(Y(ic1)-Y(icd1))/p.dy); if nargout >= 2 % slope matrix varargout{1} = sparse(ic1,icd1,E,nrc,nrc); end
Setting the (non unique) flow direction over the neighbours
% reset the null slopes E(E<0) = 0; % initialize the flow direction matrix with all slopes M = sparse(ic1,icd1,E,nrc,nrc);
Routing through flats
% 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. while p.rflat % 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. [r,ic,icd] = routeflat(dem,M,AD); if any(r) % 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 ic = ic(r); icd = icd(r); % a new connects matrix is built for sills M = M+sparse(ic,icd,0.01,nrc,nrc); p.rflat = true; else p.rflat = false; end end
Randomization of amount transferred to another cell
switch p.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
Estimation of the flow direction given by direction of the maximal
slope: LSD - local steepest slope
Ix = (1:nrc)'; % retrieve the maxima values of the slopes and their position (index) [maxS,Idx] = max(M,[],2); % get rid of the null values (if commented, set the variables i below when % defining Md and M2d) ii = (maxS==0); Ix(ii) = []; Idx(ii) = []; % M = sparse(Ix,Idx,1,nrc,nrc); % dummy variables for checking that all pixels in the DEM have been visited visited = zeros(size(Ix));
'gd8','ed83' or 'ed82': nondispersive single flow direction
if any(strcmp(p.flow,{'gd8','ed82','ed83'})) while ~all(visited(:)) % local direction of the 1st LSD [Md,ix,iy,idx,idy] = localdownslope(Ix,Idx,NX,NY); % define the potential second direction pixels % possible choice for the 2nd direction: "the secondary direction is % defined as the steeper direction between clockwise and counterclockwise % directions adjacent to the LSD" A = localsecdir(ix,iy,idx,idy,NX,NY); [m, Idx2 ] = min( dem(A),[], 2 ); % final list of the 2nd LSD indices giving for each pixel (indexed % by Ix) the index of its secondary downslope direction Idx2 = diag(A(:,Idx2)); % Idx2 : 2nd LSD indices giving for each pixel (indexed by Ix) the % index of its secondary downslope direction % extract all potential corridors (independently of the start cell): % "situation in which the flow passes through a % clearly defined corridor with steep lateral slopes: both cells in % clockwise and anticlockwise direciton adjacent to the LSD have % elevation higher than the LSD" (dem value on the 2nd downslope % direction > dem value on the LSD)" c = dem(Idx2) > dem(Ix); % get rid of those pixels as potential steepest slope and let the % flow pass in the corridor anyway (there wont be change for those % pixels) Idx2(c) = Idx(c); % select the start cell as the max of the dem [m,start] = max(dem(Ix).*(~visited)); % and take the first maximal value in the dem we encounter start = start(1); % first found maximum % start : index position of the start cell in the vector Ix storing % the position of the pixels with non null slope % define the seed pixel as the upstream cell 'directing' the flow upstream = start; % update the list of visited pixels visited(start) = 1; while ~isempty(start) && ~isempty(upstream) visited(upstream) = 1; % look at the LSD: if a flat area is to be reached soon, then % stop propagating the flow for this start cell lsdupstream = find(Ix==Idx(upstream),1,'first'); % lsdupstream : index position of the LSD of the upstream cell % in the vector Ix if isempty(lsdupstream) % we will reach a flat area upstream = []; %#ok break; % we leave the LSD as it is end visited(lsdupstream) = 1; if c(upstream) % then we are here in the presence of a corridor % these pixels will be used as new start cells for the % global search: "the LSD is chosen as the flow direction % and its downstream cell becomes a new start cell" start = lsdupstream; upstream = start; %#ok % nothing else is changed, move downwards and go to the next % instance of the loop break; end % end of 'if c(upstream)...' % else proceed... % local direction of the 2nd LSD M2d = localdownslope(Ix,Idx2,NX,NY); % calculate the 'global' slope wrt the considered start cell % over the whole image % sstart = repmat(Ix(start),nrc,1); a = Ix(start); b = [Idx(lsdupstream) ;Idx2(lsdupstream)]; SS = (dem(a)-dem(b)) ./ ... hypot((X(a)-X(b))/p.dx, (Y(a)-Y(b)/p.dy)); % find where: % - "the direction exists": Md(upstream)~=0 (always true), % - "the flow is the same as the determined flow of the upstream % cell": Md(lsdupstream)==Md(upstream), % - "the secondary direction is the same as that of the upstream % cell": M2d(lsdupstream)==M2d(upstream). % it gives the position of the set of pixels whose downslope % direction could possibly be changed % refine by looking for the pixels where "the gradient between % the downstream cell of the secondary direction and the start % cell is steeper than the gradient between the downstream cell % of the primary direction and the start cell": SS(1)<SS(2) if Md(upstream) ~= 0 && ... % existence Md(lsdupstream) == Md(upstream) && ... % unidirectionality M2d(lsdupstream) == M2d(upstream) && ...% accrued deviation SS(1) < SS(2) % gradient condition % update by replacing the final downstream direction by the % secondary direction Md(lsdupstream) = M2d(lsdupstream); Idx(lsdupstream) = Idx2(lsdupstream); % Idx2(ilsdupstream) = Idx2(Idx2(ilsdupstream)); end % end of 'if Md(iupstream)...' % possibly update both the upstream and start cells by moving % them downwards, depending on the rule chosen for estimating % the flow switch p.flow case 'ed82' % move the start cell downwards, 1 order apart from the % cell of consideration start = lsdupstream; % find(Ix==Idx(upstream),1,'first') % consequently move the upstream cell upstream = find(Ix==Idx(lsdupstream),1,'first'); % one % position below because it has been already processed case 'ed83' % move the start cell downwards, 2 orders apart from the % cell of consideration start = find(Ix==Idx(lsdupstream),1,'first'); upstream = start; % at that position, it has not been % processed otherwise % 'gd8' % the start cell is left unchanged, the upstream cell % is moved downwards upstream = lsdupstream; end end % end of 'while ~isemtpy(iupstream)...' end % end of 'while sum(Ix)~=0...' end
Sparse flow direction representation
final sparse matrix giving, for each pixel, the index of the downslope pixel in the matrix
M = sparse(Ix,Idx,1,nrc,nrc); if p.disp displayflow(M,NX,NY); end
Upslope area
if nargout ==3 varargout{2} = upslopearea(dem,M); end
end
function [r,ic,icd] = routeflat(dem,M,AD) % check whether one of the surrounding cells is a giver. This is % done by querying the neighbours of the flats. ing = find(sum(M,2)==0); ing(nans(ing)) = []; a = full(sum(sparse(ing,ing,1,numel(dem),numel(dem))*AD,2)==8); b = full(AD*a); inb_flats = reshape(b<8 & a, size(dem)); 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 neighbour in IX_inb_flats [ic,icd] = ixneighbours(dem,inb_flats); r = (dem(ic)==dem(icd)); ic = ic(r); icd = icd(r); r = ismembc(icd,IX_outb_flats); end
function [Md,ix,iy,idx,idy] = localdownslope(Ix,Idx,NX,NY) % column vector giving for each pixel the downslope direction in [1,9] % - for the pixel grid: [ix,iy] = ind2sub([NX,NY],Ix); % - for the LSD directions: [idx,idy] = ind2sub([NX,NY],Idx); % find the local direction: the following mapping is used: % --------------- ------------- % | NW | N | NE | | 1 | 4 | 7 | % --------------- ------------- % | W | | E | => | 2 | | 8 | % --------------- ------------- % | SW | S | SE | | 3 | 6 | 9 | % --------------- ------------- % matrix giving for each pixel the downslope direction in [1,9] Md = sub2ind([3,3],idx-ix+2,idy-iy+2); % central pixel (5), no direction: Md(Md==0)=5; % Md = zeros(nrc,1); % zeros(NX,NY); % %%in the case the variable ii above has not been set: % %%i = idx-ix+2>0 & idy-iy+2>0; % %%Md(Ix(i)) = sub2ind([3,3],idx(i)-ix(i)+2,idy(i)-iy(i)+2); % % Md = sub2ind([3,3],idx-ix+2,idy-iy+2); % Md(Ix) = sub2ind([3,3],idx-ix+2,idy-iy+2); end
function neidir = localsecdir(ix,iy,idx,idy,NX,NY) % coordinates of the clockwise neighbours of the LSD directions: idx2 = idx + (idx<NX).*(ix>=idx).*(iy>idy) - ... (idx>1).*(ix<=idx).*(iy<idy); idy2 = idy + (idy<NY).*(iy>=idy).*(ix<idx) - ... (idy>1).*(iy<=idy).*(ix>idx); % column vector of corresponding indices: neidir = sub2ind([NX,NY], idx2, idy2); % note that Mclock is indexed by Ix % coordinates of the counterclockwise neighbours of the LSD directions idx2 = idx + (idx<NX).*(ix>=idx).*(iy<idy) - ... (idx>1).*(ix<=idx).*(iy>idy); idy2 = idy + (idy<NY).*(iy>=idy).*(ix>idx) - ... (idy>1).*(iy<=idy).*(ix<idx); % define the potential second direction pixels neidir = cat(2, ... neidir, ... sub2ind([NX NY], idx2, idy2)); end
function Mflow = displayflow (M,x,y) Mflow = zeros(x,y); [uppix,downpix] = find(M); [ix,iy] = ind2sub([x,y],uppix); [idx,idy] = ind2sub([x,y],downpix); Mflow(uppix) = sub2ind([3,3],idx-ix+2,idy-iy+2); disp('flow coding:'); disp(reshape([1:4,0,6:9],3,3)); %disp('flow directions:'); %disp(Mflow); figure, imagesc(Mflow), axis image, colormap jet end
function mask = bordernans(dem) % BORDERNANS - Find all connected NaNs connected to the DEM border. % % mask = bordernans(dem); % % Input: % dem : input DEM image. % % Output: % mask : logical matrix the same size as dem; true values in mask % correspond to border NaN locations. % % Example: % E = magic(5); % E(2:5,1:2) = NaN; % E(3:4,4) = NaN % border_nans(E) nan_mask = isnan(dem); mask = nan_mask & ~imclearborder(nan_mask); end
function A = upslopearea(dem,M) % UPSLOPEAREA - Compute the upslope area for each pixel of an input DEM % given the flow matrix % % A = upslopearea(dem,M); % % Inputs: % dem : input DEM image. % M : (sparse) matrix representing the distribution of flow from pixel % to pixel. % % Output: % A : upslope area for each corresponding pixel of dem. % % Note: % Connected groups of NaN pixels touching the border are treated as % having no contribution to flow. % Right-side vector is normally all ones, reflecting an equal contribution % to water flow originating in each pixel. rhs = ones(numel(dem), 1); % Connected groups of NaN pixels that touch the border do not contribute % to water volume. mask = bordernans(dem); rhs(mask(:)) = 0; A = M \ rhs; A = reshape(A, size(dem)); end